Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package semaphore for openSUSE:Factory checked in at 2026-03-23 17:14:28 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/semaphore (Old) and /work/SRC/openSUSE:Factory/.semaphore.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "semaphore" Mon Mar 23 17:14:28 2026 rev:36 rq:1341962 version:2.17.28 Changes: -------- --- /work/SRC/openSUSE:Factory/semaphore/semaphore.changes 2026-03-20 21:26:01.885037437 +0100 +++ /work/SRC/openSUSE:Factory/.semaphore.new.8177/semaphore.changes 2026-03-23 17:16:18.533012600 +0100 @@ -1,0 +2,23 @@ +Mon Mar 23 06:22:01 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 2.17.28: + This release improves runner-to-task tracking and fixes task + status handling in the runner update flow. + A new runner_id field was added to tasks in the database, which + makes it possible to persist the association between a task and + the runner executing it. This improves internal task tracking and + lays the groundwork for more reliable runner state handling. + This release also fixes a bug in the runner update logic where + the current task state was checked using the wrong field. In + api/runners/runners.go, the status check was corrected from + job.Status.IsFinished() to tsk.Task.Status.IsFinished(), ensuring + the system evaluates the actual stored task state before applying + runner updates. This makes task state transitions more accurate + during runner execution. + * What's changed + - Added runner_id to the task record in the database for better + runner-to-task association. (#3712) + - Fixed runner task status validation to use the actual task + status field instead of the job status field. + +------------------------------------------------------------------- Old: ---- semaphore-2.17.27.obscpio web-2.17.27.tar.gz New: ---- semaphore-2.17.28.obscpio web-2.17.28.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ semaphore.spec ++++++ --- /var/tmp/diff_new_pack.dVMDRL/_old 2026-03-23 17:16:22.729187118 +0100 +++ /var/tmp/diff_new_pack.dVMDRL/_new 2026-03-23 17:16:22.733187284 +0100 @@ -17,7 +17,7 @@ Name: semaphore -Version: 2.17.27 +Version: 2.17.28 Release: 0 Summary: Modern UI for Ansible License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.dVMDRL/_old 2026-03-23 17:16:22.833191443 +0100 +++ /var/tmp/diff_new_pack.dVMDRL/_new 2026-03-23 17:16:22.837191610 +0100 @@ -3,8 +3,8 @@ <param name="url">https://github.com/ansible-semaphore/semaphore</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v2.17.27</param> - <param name="match-tag">v2.17.27</param> + <param name="revision">v2.17.28</param> + <param name="match-tag">v2.17.28</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.dVMDRL/_old 2026-03-23 17:16:22.913194771 +0100 +++ /var/tmp/diff_new_pack.dVMDRL/_new 2026-03-23 17:16:22.921195103 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/ansible-semaphore/semaphore</param> - <param name="changesrevision">9c5ed3b01d0266cc61f43dfca99bf23fa6f78252</param></service></servicedata> + <param name="changesrevision">0d961bf3d0106d7f1fd7bf674e824740ee98801e</param></service></servicedata> (No newline at EOF) ++++++ semaphore-2.17.27.obscpio -> semaphore-2.17.28.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/semaphore-2.17.27/api/projects/tasks.go new/semaphore-2.17.28/api/projects/tasks.go --- old/semaphore-2.17.27/api/projects/tasks.go 2026-03-19 17:07:32.000000000 +0100 +++ new/semaphore-2.17.28/api/projects/tasks.go 2026-03-22 14:56:57.000000000 +0100 @@ -365,7 +365,12 @@ editor := helpers.GetFromContext(r, "user").(*db.User) project := helpers.GetFromContext(r, "project").(db.Project) - activeTask := taskPool(r).GetTask(targetTask.ID) + activeTask, err := taskPool(r).GetTask(targetTask.ID) + + if err != nil { + helpers.WriteError(w, err) + return + } if activeTask != nil { // can't delete task in queue or running @@ -380,7 +385,7 @@ return } - err := helpers.Store(r).DeleteTaskWithOutputs(project.ID, targetTask.ID) + err = helpers.Store(r).DeleteTaskWithOutputs(project.ID, targetTask.ID) if err != nil { util.LogErrorF(err, log.Fields{"error": "Bad request. Cannot delete task from database"}) w.WriteHeader(http.StatusBadRequest) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/semaphore-2.17.27/api/runners/runners.go new/semaphore-2.17.28/api/runners/runners.go --- old/semaphore-2.17.27/api/runners/runners.go 2026-03-19 17:07:32.000000000 +0100 +++ new/semaphore-2.17.28/api/runners/runners.go 2026-03-22 14:56:57.000000000 +0100 @@ -258,9 +258,14 @@ } for _, job := range body.Jobs { - tsk := taskPool.GetTask(job.ID) + tsk, err := taskPool.GetTask(job.ID) - if tsk == nil { + if err != nil { + log.WithError(err).WithFields(log.Fields{ + "task_id": job.ID, + "runner_id": runner.ID, + "context": "runner", + }).Warn("runner progress: task not in local pool and could not be loaded from database") continue } @@ -278,10 +283,12 @@ return } - tsk.SetStatus(job.Status) + if !tsk.Task.Status.IsFinished() { + tsk.SetStatus(job.Status) - if job.Commit != nil { - tsk.SetCommit(job.Commit.Hash, job.Commit.Message) + if job.Commit != nil { + tsk.SetCommit(job.Commit.Hash, job.Commit.Message) + } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/semaphore-2.17.27/cli/cmd/root.go new/semaphore-2.17.28/cli/cmd/root.go --- old/semaphore-2.17.27/cli/cmd/root.go 2026-03-19 17:07:32.000000000 +0100 +++ new/semaphore-2.17.28/cli/cmd/root.go 2026-03-22 14:56:57.000000000 +0100 @@ -6,6 +6,7 @@ "net/url" "os" "strings" + "time" "github.com/gorilla/handlers" "github.com/semaphoreui/semaphore/api" @@ -137,6 +138,20 @@ schedulePool.SetDeduplicator(dedup) } + // Each process holds its own in-memory cron table. Schedule CRUD handlers only + // call Refresh on the node that served the HTTP request, so other HA nodes + // would keep stale jobs until restart. Reload from the shared DB on an interval. + if util.HAEnabled() { + const haSchedulePoolSyncInterval = 60 * time.Second + go func() { + ticker := time.NewTicker(haSchedulePoolSyncInterval) + defer ticker.Stop() + for range ticker.C { + schedulePool.Refresh() + } + }() + } + if orphanCleaner := proHA.NewOrphanCleaner(store); orphanCleaner != nil { orphanCleaner.Start() defer orphanCleaner.Stop() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/semaphore-2.17.27/db/Migration.go new/semaphore-2.17.28/db/Migration.go --- old/semaphore-2.17.27/db/Migration.go 2026-03-19 17:07:32.000000000 +0100 +++ new/semaphore-2.17.28/db/Migration.go 2026-03-22 14:56:57.000000000 +0100 @@ -120,6 +120,7 @@ {Version: "2.17.1"}, {Version: "2.17.2"}, {Version: "2.17.15"}, + {Version: "2.17.16"}, } return append(initScripts, commonScripts...) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/semaphore-2.17.27/db/Store.go new/semaphore-2.17.28/db/Store.go --- old/semaphore-2.17.27/db/Store.go 2026-03-19 17:07:32.000000000 +0100 +++ new/semaphore-2.17.28/db/Store.go 2026-03-22 14:56:57.000000000 +0100 @@ -399,6 +399,7 @@ GetTemplateTasks(projectID int, templateID int, params RetrieveQueryParams) ([]TaskWithTpl, error) GetProjectTasks(projectID int, params RetrieveQueryParams) ([]TaskWithTpl, error) GetTask(projectID int, taskID int) (Task, error) + GetTaskByID(taskID int) (Task, error) DeleteTaskWithOutputs(projectID int, taskID int) error GetTaskOutputs(projectID int, taskID int, params RetrieveQueryParams) ([]TaskOutput, error) CreateTaskOutput(output TaskOutput) (TaskOutput, error) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/semaphore-2.17.27/db/Task.go new/semaphore-2.17.28/db/Task.go --- old/semaphore-2.17.27/db/Task.go 2026-03-19 17:07:32.000000000 +0100 +++ new/semaphore-2.17.28/db/Task.go 2026-03-22 14:56:57.000000000 +0100 @@ -54,6 +54,9 @@ UserID *int `db:"user_id" json:"user_id,omitempty"` IntegrationID *int `db:"integration_id" json:"integration_id,omitempty"` ScheduleID *int `db:"schedule_id" json:"schedule_id,omitempty"` + // RunnerID is set while a task is assigned to a remote runner (cleared when the task finishes). + // Used so runner progress API can authorize updates on any HA node. + RunnerID *int `db:"runner_id" json:"-"` Created time.Time `db:"created" json:"created"` Start *time.Time `db:"start" json:"start,omitempty"` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/semaphore-2.17.27/db/bolt/task.go new/semaphore-2.17.28/db/bolt/task.go --- old/semaphore-2.17.27/db/bolt/task.go 2026-03-19 17:07:32.000000000 +0100 +++ new/semaphore-2.17.28/db/bolt/task.go 2026-03-22 14:56:57.000000000 +0100 @@ -229,6 +229,11 @@ return } +func (d *BoltDb) GetTaskByID(taskID int) (task db.Task, err error) { + err = d.getObject(0, db.TaskProps, intObjectID(taskID), &task) + return +} + func (d *BoltDb) GetTemplateTasks(projectID int, templateID int, params db.RetrieveQueryParams) ([]db.TaskWithTpl, error) { return d.getTasks(projectID, &templateID, params) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/semaphore-2.17.27/db/sql/migrations/v2.17.16.err.sql new/semaphore-2.17.28/db/sql/migrations/v2.17.16.err.sql --- old/semaphore-2.17.27/db/sql/migrations/v2.17.16.err.sql 1970-01-01 01:00:00.000000000 +0100 +++ new/semaphore-2.17.28/db/sql/migrations/v2.17.16.err.sql 2026-03-22 14:56:57.000000000 +0100 @@ -0,0 +1 @@ +alter table `task` drop column `runner_id`; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/semaphore-2.17.27/db/sql/migrations/v2.17.16.sql new/semaphore-2.17.28/db/sql/migrations/v2.17.16.sql --- old/semaphore-2.17.27/db/sql/migrations/v2.17.16.sql 1970-01-01 01:00:00.000000000 +0100 +++ new/semaphore-2.17.28/db/sql/migrations/v2.17.16.sql 2026-03-22 14:56:57.000000000 +0100 @@ -0,0 +1 @@ +alter table `task` add `runner_id` int null references `runner`(`id`) on delete set null; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/semaphore-2.17.27/db/sql/task.go new/semaphore-2.17.28/db/sql/task.go --- old/semaphore-2.17.27/db/sql/task.go 2026-03-19 17:07:32.000000000 +0100 +++ new/semaphore-2.17.28/db/sql/task.go 2026-03-22 14:56:57.000000000 +0100 @@ -196,19 +196,21 @@ if task.CommitHash != nil { _, err = d.exec( - "update task set status=?, start=?, `end`=?, commit_hash=?, commit_message=? where id=?", + "update task set status=?, start=?, `end`=?, commit_hash=?, commit_message=?, runner_id=? where id=?", task.Status, task.Start, task.End, task.CommitHash, task.CommitMessage, + task.RunnerID, task.ID) } else { _, err = d.exec( - "update task set status=?, start=?, `end`=? where id=?", + "update task set status=?, start=?, `end`=?, runner_id=? where id=?", task.Status, task.Start, task.End, + task.RunnerID, task.ID) } @@ -321,6 +323,11 @@ return } +func (d *SqlDb) GetTaskByID(taskID int) (task db.Task, err error) { + err = d.selectOne(&task, d.PrepareQuery("select * from task where id=?"), taskID) + return +} + func (d *SqlDb) GetTemplateTasks(projectID int, templateID int, params db.RetrieveQueryParams) (tasks []db.TaskWithTpl, err error) { err = d.getTasks(projectID, &templateID, nil, params, &tasks) return diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/semaphore-2.17.27/pkg/task_logger/task_logger.go new/semaphore-2.17.28/pkg/task_logger/task_logger.go --- old/semaphore-2.17.27/pkg/task_logger/task_logger.go 2026-03-19 17:07:32.000000000 +0100 +++ new/semaphore-2.17.28/pkg/task_logger/task_logger.go 2026-03-22 14:56:57.000000000 +0100 @@ -100,6 +100,30 @@ return s == TaskStoppedStatus || s == TaskSuccessStatus || s == TaskFailStatus } +// TaskStatusProgressRank orders statuses for comparing how far execution has progressed +// (higher = further). Used in HA / remote-runner paths to avoid regressing status when +// merging DB state with stale runtime-store snapshots. +func TaskStatusProgressRank(s TaskStatus) int { + switch s { + case TaskWaitingStatus: + return 10 + case TaskStartingStatus: + return 20 + case TaskWaitingConfirmation: + return 25 + case TaskConfirmed, TaskRejected: + return 26 + case TaskRunningStatus: + return 40 + case TaskStoppingStatus: + return 50 + case TaskStoppedStatus, TaskSuccessStatus, TaskFailStatus: + return 100 + default: + return 0 + } +} + type StatusListener func(status TaskStatus) type LogListener func(new time.Time, msg string) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/semaphore-2.17.27/services/tasks/RemoteJob.go new/semaphore-2.17.28/services/tasks/RemoteJob.go --- old/semaphore-2.17.27/services/tasks/RemoteJob.go 2026-03-19 17:07:32.000000000 +0100 +++ new/semaphore-2.17.28/services/tasks/RemoteJob.go 2026-03-22 14:56:57.000000000 +0100 @@ -93,7 +93,11 @@ func (t *RemoteJob) Run(username string, incomingVersion *string, alias string) (err error) { - tsk := t.taskPool.GetTask(t.Task.ID) + tsk, err := t.taskPool.GetTask(t.Task.ID) + + if err != nil { + return + } if tsk == nil { return fmt.Errorf("task not found") @@ -151,6 +155,15 @@ } tsk.RunnerID = runner.ID + tsk.Task.RunnerID = &runner.ID + db.StoreSession(t.taskPool.store, "remote job assign runner", func() { + err = t.taskPool.store.UpdateTask(tsk.Task) + }) + + if err != nil { + return + } + if t.taskPool != nil && t.taskPool.state != nil { t.taskPool.state.UpdateRuntimeFields(tsk) } @@ -166,13 +179,36 @@ } time.Sleep(1_000_000_000) - tsk = t.taskPool.GetTask(t.Task.ID) + tsk, err = t.taskPool.GetTask(t.Task.ID) + + if err != nil { + return + } if tsk == nil { err = fmt.Errorf("task %d not found", t.Task.ID) return } + if util.HAEnabled() { + var row db.Task + var rowErr error + db.StoreSession(t.taskPool.store, "remote job status sync", func() { + row, rowErr = t.taskPool.store.GetTask(tsk.Task.ProjectID, t.Task.ID) + }) + if rowErr == nil { + // Never regress (e.g. running → starting) if the DB read is briefly stale. + if task_logger.TaskStatusProgressRank(row.Status) >= task_logger.TaskStatusProgressRank(tsk.Task.Status) { + tsk.Task.Status = row.Status + tsk.Task.Start = row.Start + tsk.Task.End = row.End + } + if row.RunnerID != nil { + tsk.Task.RunnerID = row.RunnerID + } + } + } + if tsk.Task.Status == task_logger.TaskSuccessStatus || tsk.Task.Status == task_logger.TaskStoppedStatus || tsk.Task.Status == task_logger.TaskFailStatus { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/semaphore-2.17.27/services/tasks/TaskPool.go new/semaphore-2.17.28/services/tasks/TaskPool.go --- old/semaphore-2.17.27/services/tasks/TaskPool.go 2026-03-19 17:07:32.000000000 +0100 +++ new/semaphore-2.17.28/services/tasks/TaskPool.go 2026-03-22 14:56:57.000000000 +0100 @@ -132,7 +132,7 @@ return p.state.RunningRange() } -func (p *TaskPool) GetTask(id int) (task *TaskRunner) { +func (p *TaskPool) GetTask(id int) (task *TaskRunner, err error) { for _, t := range p.state.QueueRange() { if t.Task.ID == id { task = t @@ -149,6 +149,12 @@ } } + if util.HAEnabled() { + if task == nil { + task, err = p.HydrateTaskRunnerFromDB(id) + } + } + return } @@ -170,12 +176,11 @@ case task := <-p.register: // new task created by API or schedule db.StoreSession(p.store, "new task", func() { - //p.Queue = append(p.Queue, task) - msg := "Task " + task.Template.Name + " added to queue" - task.Log(msg) + task.Log("Task " + task.Template.Name + " added to queue") log.WithFields(log.Fields{ - "task_id": task.Task.ID, - }).Info(msg) + "task_id": task.Task.ID, + "task_name": task.Template.Name, + }).Info("Task added to queue") task.saveStatus() }) p.queueEvents <- PoolEvent{EventTypeNew, task} @@ -188,7 +193,7 @@ } func getTaskName(t *TaskRunner) string { - return t.Template.Name + " " + strconv.Itoa(t.Task.ID) + return t.Template.Name + " (" + strconv.Itoa(t.Task.ID) + ")" } func (p *TaskPool) handleQueue() { @@ -340,10 +345,18 @@ } func runTask(task *TaskRunner, p *TaskPool) { - log.Info("Set resource locker with TaskRunner " + getTaskName(task)) + log.WithFields(log.Fields{ + "context": "task_pool", + "task_id": task.Task.ID, + "task_name": task.Template.Name, + }).Info("Set resource locker") p.onTaskRun(task) - log.Info("Task " + getTaskName(task) + " started") + log.WithFields(log.Fields{ + "context": "task_pool", + "task_id": task.Task.ID, + "task_name": task.Template.Name, + }).Info("Task started") go func() { time.Sleep(1 * time.Second) task.run() @@ -367,6 +380,15 @@ } } +func applyDBPersistedTaskSnapshot(dst *db.Task, src db.Task) { + dst.Status = src.Status + dst.Start = src.Start + dst.End = src.End + dst.RunnerID = src.RunnerID + dst.CommitHash = src.CommitHash + dst.CommitMessage = src.CommitMessage +} + // hydrateTaskRunner builds a TaskRunner for an existing task from DB without starting it func (p *TaskPool) hydrateTaskRunner(taskID int, projectID int) (*TaskRunner, error) { task, err := p.store.GetTask(projectID, taskID) @@ -381,6 +403,9 @@ if p.state != nil { p.state.LoadRuntimeFields(tr) } + // Persisted row from DB must win over runtime-store fields: Redis may still hold a + // snapshot from enqueue time (e.g. status "starting") after the runner updated the DB. + //applyDBPersistedTaskSnapshot(&tr.Task, task) // set appropriate job handler for consistency (not run) var job Job if util.Config.UseRemoteRunner || tr.Template.RunnerTag != nil || tr.Inventory.RunnerTag != nil { @@ -407,6 +432,23 @@ return tr, nil } +// HydrateTaskRunnerFromDB loads a task row by ID and builds a TaskRunner for API-side updates +// (e.g. runner progress on an HA node that did not enqueue the task). +func (p *TaskPool) HydrateTaskRunnerFromDB(taskID int) (*TaskRunner, error) { + row, err := p.store.GetTaskByID(taskID) + if err != nil { + return nil, err + } + tr, err := p.hydrateTaskRunner(taskID, row.ProjectID) + if err != nil { + return nil, err + } + if row.RunnerID != nil { + tr.RunnerID = *row.RunnerID + } + return tr, nil +} + func (p *TaskPool) blocks(t *TaskRunner) bool { if util.Config.MaxParallelTasks > 0 && p.state.RunningCount() >= util.Config.MaxParallelTasks { @@ -443,7 +485,11 @@ } func (p *TaskPool) ConfirmTask(targetTask db.Task) error { - tsk := p.GetTask(targetTask.ID) + tsk, err := p.GetTask(targetTask.ID) + + if err != nil { + return err + } if tsk == nil { // task not active, but exists in database return fmt.Errorf("task is not active") @@ -455,7 +501,11 @@ } func (p *TaskPool) RejectTask(targetTask db.Task) error { - tsk := p.GetTask(targetTask.ID) + tsk, err := p.GetTask(targetTask.ID) + + if err != nil { + return err + } if tsk == nil { // task not active, but exists in database return fmt.Errorf("task is not active") @@ -467,7 +517,11 @@ } func (p *TaskPool) StopTask(targetTask db.Task, forceStop bool) error { - tsk := p.GetTask(targetTask.ID) + tsk, err := p.GetTask(targetTask.ID) + if err != nil { + return err + } + if tsk == nil { // task not active, but exists in database tsk = NewTaskRunner(targetTask, p, "", p.keyInstallationService) @@ -551,7 +605,16 @@ for _, twt := range tasks { // if task is managed locally (queued/running), it was handled above - if p.GetTask(twt.Task.ID) != nil { + tsk, taskErr := p.GetTask(twt.ID) + if taskErr != nil { + log.WithError(err).WithFields(log.Fields{ + "task_id": twt.ID, + "context": "task_pool", + }).Warn("can't get task") + + continue + } + if tsk != nil { continue } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/semaphore-2.17.27/services/tasks/TaskRunner_logging.go new/semaphore-2.17.28/services/tasks/TaskRunner_logging.go --- old/semaphore-2.17.27/services/tasks/TaskRunner_logging.go 2026-03-19 17:07:32.000000000 +0100 +++ new/semaphore-2.17.28/services/tasks/TaskRunner_logging.go 2026-03-22 14:56:57.000000000 +0100 @@ -133,6 +133,12 @@ for _, l := range t.statusListeners { l(status) } + + log.WithFields(log.Fields{ + "task_id": t.Task.ID, + "context": "task_logger", + "status": status, + }).Info("Task status updated") } func (t *TaskRunner) panicOnError(err error, msg string) { ++++++ semaphore.obsinfo ++++++ --- /var/tmp/diff_new_pack.dVMDRL/_old 2026-03-23 17:16:24.849275292 +0100 +++ /var/tmp/diff_new_pack.dVMDRL/_new 2026-03-23 17:16:24.857275624 +0100 @@ -1,5 +1,5 @@ name: semaphore -version: 2.17.27 -mtime: 1773936452 -commit: 9c5ed3b01d0266cc61f43dfca99bf23fa6f78252 +version: 2.17.28 +mtime: 1774187817 +commit: 0d961bf3d0106d7f1fd7bf674e824740ee98801e ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/semaphore/vendor.tar.gz /work/SRC/openSUSE:Factory/.semaphore.new.8177/vendor.tar.gz differ: char 151, line 2 ++++++ web-2.17.27.tar.gz -> web-2.17.28.tar.gz ++++++ /work/SRC/openSUSE:Factory/semaphore/web-2.17.27.tar.gz /work/SRC/openSUSE:Factory/.semaphore.new.8177/web-2.17.28.tar.gz differ: char 30, line 1
