From 4415797c896aed012b41ba7443670029d378f0ee Mon Sep 17 00:00:00 2001 From: pomidorry Date: Wed, 6 May 2026 16:45:51 +0300 Subject: [PATCH 01/17] add last pull mirror sync success timestamp --- models/migrations/migrations.go | 1 + models/migrations/v1_27/v332.go | 27 ++++++++++++++ models/repo/repo.go | 1 + models/repo/update.go | 9 +++++ modules/structs/repo.go | 4 ++- services/convert/repository.go | 3 +- services/convert/repository_test.go | 47 +++++++++++++++++++++++++ services/mirror/mirror_pull.go | 4 +++ templates/swagger/v1_json.tmpl | 5 +++ templates/swagger/v1_openapi3_json.tmpl | 5 +++ 10 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 models/migrations/v1_27/v332.go create mode 100644 services/convert/repository_test.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index c3a8f08b5d..c3f68d2449 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -409,6 +409,7 @@ func prepareMigrationTasks() []*migration { // Gitea 1.26.0 ends at migration ID number 330 (database version 331) newMigration(331, "Add ActionRunAttempt model and related action fields", v1_27.AddActionRunAttemptModel), + newMigration(332, "Add last_pull_sync_success_unix to repository", v1_27.AddLastPullSyncSuccessUnixToRepository), } return preparedMigrations } diff --git a/models/migrations/v1_27/v332.go b/models/migrations/v1_27/v332.go new file mode 100644 index 0000000000..4fba94665a --- /dev/null +++ b/models/migrations/v1_27/v332.go @@ -0,0 +1,27 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_27 + +import ( + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +type repositoryWithLastPullSyncSuccessUnix struct { + LastPullSyncSuccessUnix int64 `xorm:"INDEX"` +} + +func (repositoryWithLastPullSyncSuccessUnix) TableName() string { + return "repository" +} + +func AddLastPullSyncSuccessUnixToRepository(x *xorm.Engine) error { + if err := x.Sync(new(repositoryWithLastPullSyncSuccessUnix)); err != nil { + return err + } + + _, err := x.Exec("UPDATE repository SET last_pull_sync_success_unix = ?", int64(timeutil.TimeStampNow())) + return err +} diff --git a/models/repo/repo.go b/models/repo/repo.go index 7814bb4876..ed8758dd57 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -219,6 +219,7 @@ type Repository struct { CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT 0"` + LastPullSyncSuccessUnix timeutil.TimeStamp `xorm:"INDEX"` } func init() { diff --git a/models/repo/update.go b/models/repo/update.go index bf560cf695..d8ac0222d2 100644 --- a/models/repo/update.go +++ b/models/repo/update.go @@ -9,6 +9,7 @@ import ( "time" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" ) @@ -33,6 +34,14 @@ func UpdateRepositoryUpdatedTime(ctx context.Context, repoID int64, updateTime t return err } +// UpdateRepositoryLastPullSyncSuccess updates a repository's last successful pull mirror sync time. +func UpdateRepositoryLastPullSyncSuccess(ctx context.Context, repoID int64, syncTime timeutil.TimeStamp) error { + _, err := db.GetEngine(ctx).ID(repoID).Cols("last_pull_sync_success_unix").NoAutoTime().Update(&Repository{ + LastPullSyncSuccessUnix: syncTime, + }) + return err +} + // UpdateRepositoryColsWithAutoTime updates repository's columns and the timestamp fields automatically func UpdateRepositoryColsWithAutoTime(ctx context.Context, repo *Repository, colName string, moreColNames ...string) error { _, err := db.GetEngine(ctx).ID(repo.ID).Cols(append([]string{colName}, moreColNames...)...).Update(repo) diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 0c3a0ab44e..8d16c80020 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -121,6 +121,9 @@ type Repository struct { DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit"` AvatarURL string `json:"avatar_url"` Internal bool `json:"internal"` + // swagger:strfmt date-time + LastPullSyncSuccess time.Time `json:"last_pull_sync_success"` + Licenses []string `json:"licenses"` MirrorInterval string `json:"mirror_interval"` // ObjectFormatName of the underlying git repository ObjectFormatName ObjectFormatName `json:"object_format_name"` @@ -128,7 +131,6 @@ type Repository struct { MirrorUpdated time.Time `json:"mirror_updated"` RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"` Topics []string `json:"topics"` - Licenses []string `json:"licenses"` } // CreateRepoOption options when creating repository diff --git a/services/convert/repository.go b/services/convert/repository.go index 503f6bb2a3..509681e1bb 100644 --- a/services/convert/repository.go +++ b/services/convert/repository.go @@ -247,12 +247,13 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR DefaultTargetBranch: defaultTargetBranch, AvatarURL: repo.AvatarLink(ctx), Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate, + LastPullSyncSuccess: repo.LastPullSyncSuccessUnix.AsTime(), + Licenses: util.SliceNilAsEmpty(repoLicenses.StringList()), MirrorInterval: mirrorInterval, MirrorUpdated: mirrorUpdated, RepoTransfer: transfer, Topics: util.SliceNilAsEmpty(repo.Topics), ObjectFormatName: api.ObjectFormatName(repo.ObjectFormatName), - Licenses: util.SliceNilAsEmpty(repoLicenses.StringList()), } } diff --git a/services/convert/repository_test.go b/services/convert/repository_test.go new file mode 100644 index 0000000000..18bf3863c7 --- /dev/null +++ b/services/convert/repository_test.go @@ -0,0 +1,47 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package convert + +import ( + "testing" + "time" + + "code.gitea.io/gitea/models/db" + access_model "code.gitea.io/gitea/models/perm/access" + perm_model "code.gitea.io/gitea/models/perm" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/timeutil" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestToRepoIncludesLastPullSyncSuccess(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + ctx := t.Context() + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + repo.IsMirror = true + require.NoError(t, repo_model.DeleteMirrorByRepoID(ctx, repo.ID)) + repo.LastPullSyncSuccessUnix = timeutil.TimeStamp(1714000000) + require.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_mirror", "last_pull_sync_success_unix")) + + mirror := &repo_model.Mirror{ + RepoID: repo.ID, + Interval: time.Hour, + } + require.NoError(t, db.Insert(ctx, mirror)) + + mirror.UpdatedUnix = timeutil.TimeStamp(1715000000) + mirror.NextUpdateUnix = timeutil.TimeStamp(1716000000) + require.NoError(t, repo_model.UpdateMirror(ctx, mirror)) + + apiRepo := ToRepo(ctx, repo, access_model.Permission{AccessMode: perm_model.AccessModeRead}) + require.NotNil(t, apiRepo) + + assert.Equal(t, mirror.UpdatedUnix.AsTime(), apiRepo.MirrorUpdated) + assert.Equal(t, repo.LastPullSyncSuccessUnix.AsTime(), apiRepo.LastPullSyncSuccess) + assert.NotEqual(t, apiRepo.MirrorUpdated, apiRepo.LastPullSyncSuccess) +} diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index 9ce35f9eab..05958e215d 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -419,6 +419,10 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { log.Error("SyncMirrors [repo: %-v]: unable to add repo to license updater queue: %v", m.Repo, err) return false } + if err = repo_model.UpdateRepositoryLastPullSyncSuccess(ctx, m.Repo.ID, m.UpdatedUnix); err != nil { + log.Error("SyncMirrors [repo: %-v]: failed to update repository last_pull_sync_success_unix: %v", m.Repo, err) + return false + } log.Trace("SyncMirrors [repo: %-v]: Successfully updated", m.Repo) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 26d45940f2..03fc6d7a46 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -29020,6 +29020,11 @@ "type": "string", "x-go-name": "LanguagesURL" }, + "last_pull_sync_success": { + "type": "string", + "format": "date-time", + "x-go-name": "LastPullSyncSuccess" + }, "licenses": { "type": "array", "items": { diff --git a/templates/swagger/v1_openapi3_json.tmpl b/templates/swagger/v1_openapi3_json.tmpl index 33adff75e0..f43b01e1da 100644 --- a/templates/swagger/v1_openapi3_json.tmpl +++ b/templates/swagger/v1_openapi3_json.tmpl @@ -9273,6 +9273,11 @@ "type": "string", "x-go-name": "LanguagesURL" }, + "last_pull_sync_success": { + "format": "date-time", + "type": "string", + "x-go-name": "LastPullSyncSuccess" + }, "licenses": { "items": { "type": "string" From cfc8a32d9f6889d5f4d98b3c556ccc9b3dba0332 Mon Sep 17 00:00:00 2001 From: pomidorry Date: Wed, 6 May 2026 16:52:47 +0300 Subject: [PATCH 02/17] tests --- services/convert/repository_test.go | 47 --------------------------- tests/integration/mirror_pull_test.go | 15 +++++++++ 2 files changed, 15 insertions(+), 47 deletions(-) delete mode 100644 services/convert/repository_test.go diff --git a/services/convert/repository_test.go b/services/convert/repository_test.go deleted file mode 100644 index 18bf3863c7..0000000000 --- a/services/convert/repository_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package convert - -import ( - "testing" - "time" - - "code.gitea.io/gitea/models/db" - access_model "code.gitea.io/gitea/models/perm/access" - perm_model "code.gitea.io/gitea/models/perm" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unittest" - "code.gitea.io/gitea/modules/timeutil" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestToRepoIncludesLastPullSyncSuccess(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - ctx := t.Context() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - repo.IsMirror = true - require.NoError(t, repo_model.DeleteMirrorByRepoID(ctx, repo.ID)) - repo.LastPullSyncSuccessUnix = timeutil.TimeStamp(1714000000) - require.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_mirror", "last_pull_sync_success_unix")) - - mirror := &repo_model.Mirror{ - RepoID: repo.ID, - Interval: time.Hour, - } - require.NoError(t, db.Insert(ctx, mirror)) - - mirror.UpdatedUnix = timeutil.TimeStamp(1715000000) - mirror.NextUpdateUnix = timeutil.TimeStamp(1716000000) - require.NoError(t, repo_model.UpdateMirror(ctx, mirror)) - - apiRepo := ToRepo(ctx, repo, access_model.Permission{AccessMode: perm_model.AccessModeRead}) - require.NotNil(t, apiRepo) - - assert.Equal(t, mirror.UpdatedUnix.AsTime(), apiRepo.MirrorUpdated) - assert.Equal(t, repo.LastPullSyncSuccessUnix.AsTime(), apiRepo.LastPullSyncSuccess) - assert.NotEqual(t, apiRepo.MirrorUpdated, apiRepo.LastPullSyncSuccess) -} diff --git a/tests/integration/mirror_pull_test.go b/tests/integration/mirror_pull_test.go index 7902dc10cb..97e1b70485 100644 --- a/tests/integration/mirror_pull_test.go +++ b/tests/integration/mirror_pull_test.go @@ -93,6 +93,10 @@ func TestMirrorPull(t *testing.T) { ok := mirror_service.SyncPullMirror(ctx, mirrorRepo.ID) assert.True(t, ok) + mirror := unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) + mirrorRepo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: mirrorRepo.ID}) + assert.Equal(t, mirror.UpdatedUnix, mirrorRepo.LastPullSyncSuccessUnix) + // actually there is a tag in the source repo, so after "sync", that tag will also come into the mirror initCount++ @@ -110,4 +114,15 @@ func TestMirrorPull(t *testing.T) { count, err = db.Count[repo_model.Release](t.Context(), findOptions) assert.NoError(t, err) assert.Equal(t, initCount, count) + + mirrorRepo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: mirrorRepo.ID}) + lastPullSyncSuccess := mirrorRepo.LastPullSyncSuccessUnix + mirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) + assert.NoError(t, mirror_service.UpdateAddress(ctx, mirror, repoPath+"-missing")) + + ok = mirror_service.SyncPullMirror(ctx, mirrorRepo.ID) + assert.False(t, ok) + + mirrorRepo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: mirrorRepo.ID}) + assert.Equal(t, lastPullSyncSuccess, mirrorRepo.LastPullSyncSuccessUnix) } From 1c2f67bab40c02ca06986b25709f3720a7c19e61 Mon Sep 17 00:00:00 2001 From: pomidorry Date: Wed, 6 May 2026 16:59:21 +0300 Subject: [PATCH 03/17] linter fix --- models/repo/repo.go | 6 +++--- modules/structs/repo.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/models/repo/repo.go b/models/repo/repo.go index ed8758dd57..fc37b1ff9f 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -216,9 +216,9 @@ type Repository struct { // Avatar: ID(10-20)-md5(32) - must fit into 64 symbols Avatar string `xorm:"VARCHAR(64)"` - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` - ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT 0"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT 0"` LastPullSyncSuccessUnix timeutil.TimeStamp `xorm:"INDEX"` } diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 8d16c80020..ba949ae7f9 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -124,7 +124,7 @@ type Repository struct { // swagger:strfmt date-time LastPullSyncSuccess time.Time `json:"last_pull_sync_success"` Licenses []string `json:"licenses"` - MirrorInterval string `json:"mirror_interval"` + MirrorInterval string `json:"mirror_interval"` // ObjectFormatName of the underlying git repository ObjectFormatName ObjectFormatName `json:"object_format_name"` // swagger:strfmt date-time From de678aca0651529fd684fcfefad04c6a87f66acc Mon Sep 17 00:00:00 2001 From: pomidorry <106489913+Pomidorry@users.noreply.github.com> Date: Wed, 6 May 2026 22:03:01 +0300 Subject: [PATCH 04/17] Reorder fields Signed-off-by: pomidorry <106489913+Pomidorry@users.noreply.github.com> --- modules/structs/repo.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/structs/repo.go b/modules/structs/repo.go index ba949ae7f9..9979e5a5db 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -121,16 +121,16 @@ type Repository struct { DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit"` AvatarURL string `json:"avatar_url"` Internal bool `json:"internal"` - // swagger:strfmt date-time - LastPullSyncSuccess time.Time `json:"last_pull_sync_success"` - Licenses []string `json:"licenses"` MirrorInterval string `json:"mirror_interval"` // ObjectFormatName of the underlying git repository ObjectFormatName ObjectFormatName `json:"object_format_name"` // swagger:strfmt date-time MirrorUpdated time.Time `json:"mirror_updated"` + // swagger:strfmt date-time + LastPullSyncSuccess time.Time `json:"last_pull_sync_success"` RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"` Topics []string `json:"topics"` + Licenses []string `json:"licenses"` } // CreateRepoOption options when creating repository From 202a5644c8648a9d258849e95076da57f51db8f2 Mon Sep 17 00:00:00 2001 From: pomidorry Date: Wed, 6 May 2026 22:06:48 +0300 Subject: [PATCH 05/17] reorder --- modules/structs/repo.go | 4 ++-- services/convert/repository.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 9979e5a5db..b8f164b455 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -121,7 +121,7 @@ type Repository struct { DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit"` AvatarURL string `json:"avatar_url"` Internal bool `json:"internal"` - MirrorInterval string `json:"mirror_interval"` + MirrorInterval string `json:"mirror_interval"` // ObjectFormatName of the underlying git repository ObjectFormatName ObjectFormatName `json:"object_format_name"` // swagger:strfmt date-time @@ -130,7 +130,7 @@ type Repository struct { LastPullSyncSuccess time.Time `json:"last_pull_sync_success"` RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"` Topics []string `json:"topics"` - Licenses []string `json:"licenses"` + Licenses []string `json:"licenses"` } // CreateRepoOption options when creating repository diff --git a/services/convert/repository.go b/services/convert/repository.go index 509681e1bb..cbb27477ea 100644 --- a/services/convert/repository.go +++ b/services/convert/repository.go @@ -248,12 +248,12 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR AvatarURL: repo.AvatarLink(ctx), Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate, LastPullSyncSuccess: repo.LastPullSyncSuccessUnix.AsTime(), - Licenses: util.SliceNilAsEmpty(repoLicenses.StringList()), MirrorInterval: mirrorInterval, MirrorUpdated: mirrorUpdated, RepoTransfer: transfer, Topics: util.SliceNilAsEmpty(repo.Topics), ObjectFormatName: api.ObjectFormatName(repo.ObjectFormatName), + Licenses: util.SliceNilAsEmpty(repoLicenses.StringList()), } } From f95b359c30996681276c50f56390cb9243bec540 Mon Sep 17 00:00:00 2001 From: pomidorry Date: Wed, 6 May 2026 22:14:42 +0300 Subject: [PATCH 06/17] linter fix --- modules/structs/repo.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/structs/repo.go b/modules/structs/repo.go index b8f164b455..29c49146e9 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -125,12 +125,12 @@ type Repository struct { // ObjectFormatName of the underlying git repository ObjectFormatName ObjectFormatName `json:"object_format_name"` // swagger:strfmt date-time - MirrorUpdated time.Time `json:"mirror_updated"` + MirrorUpdated time.Time `json:"mirror_updated"` // swagger:strfmt date-time - LastPullSyncSuccess time.Time `json:"last_pull_sync_success"` - RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"` - Topics []string `json:"topics"` - Licenses []string `json:"licenses"` + LastPullSyncSuccess time.Time `json:"last_pull_sync_success"` + RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"` + Topics []string `json:"topics"` + Licenses []string `json:"licenses"` } // CreateRepoOption options when creating repository From b88d7a8469f3f476184adea2c6fbd0047fe5c2ad Mon Sep 17 00:00:00 2001 From: pomidorry Date: Wed, 6 May 2026 22:21:47 +0300 Subject: [PATCH 07/17] last_pull_sync_success -> mirror entity --- models/migrations/migrations.go | 2 +- models/migrations/v1_27/v332.go | 12 ++++++------ models/repo/mirror.go | 12 ++++++++++-- models/repo/repo.go | 7 +++---- models/repo/update.go | 9 --------- services/convert/repository.go | 4 +++- services/mirror/mirror_pull.go | 4 ++-- tests/integration/mirror_pull_test.go | 11 +++++------ 8 files changed, 30 insertions(+), 31 deletions(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index c3f68d2449..e44630253c 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -409,7 +409,7 @@ func prepareMigrationTasks() []*migration { // Gitea 1.26.0 ends at migration ID number 330 (database version 331) newMigration(331, "Add ActionRunAttempt model and related action fields", v1_27.AddActionRunAttemptModel), - newMigration(332, "Add last_pull_sync_success_unix to repository", v1_27.AddLastPullSyncSuccessUnixToRepository), + newMigration(332, "Add last_pull_sync_success_unix to mirror", v1_27.AddLastPullSyncSuccessUnixToMirror), } return preparedMigrations } diff --git a/models/migrations/v1_27/v332.go b/models/migrations/v1_27/v332.go index 4fba94665a..bf290f4fdf 100644 --- a/models/migrations/v1_27/v332.go +++ b/models/migrations/v1_27/v332.go @@ -9,19 +9,19 @@ import ( "xorm.io/xorm" ) -type repositoryWithLastPullSyncSuccessUnix struct { +type mirrorWithLastPullSyncSuccessUnix struct { LastPullSyncSuccessUnix int64 `xorm:"INDEX"` } -func (repositoryWithLastPullSyncSuccessUnix) TableName() string { - return "repository" +func (mirrorWithLastPullSyncSuccessUnix) TableName() string { + return "mirror" } -func AddLastPullSyncSuccessUnixToRepository(x *xorm.Engine) error { - if err := x.Sync(new(repositoryWithLastPullSyncSuccessUnix)); err != nil { +func AddLastPullSyncSuccessUnixToMirror(x *xorm.Engine) error { + if err := x.Sync(new(mirrorWithLastPullSyncSuccessUnix)); err != nil { return err } - _, err := x.Exec("UPDATE repository SET last_pull_sync_success_unix = ?", int64(timeutil.TimeStampNow())) + _, err := x.Exec("UPDATE mirror SET last_pull_sync_success_unix = ?", int64(timeutil.TimeStampNow())) return err } diff --git a/models/repo/mirror.go b/models/repo/mirror.go index be7b785612..7d27cf9707 100644 --- a/models/repo/mirror.go +++ b/models/repo/mirror.go @@ -25,8 +25,9 @@ type Mirror struct { Interval time.Duration EnablePrune bool `xorm:"NOT NULL DEFAULT true"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX"` - NextUpdateUnix timeutil.TimeStamp `xorm:"INDEX"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX"` + NextUpdateUnix timeutil.TimeStamp `xorm:"INDEX"` + LastPullSyncSuccessUnix timeutil.TimeStamp `xorm:"INDEX"` LFS bool `xorm:"lfs_enabled NOT NULL DEFAULT false"` LFSEndpoint string `xorm:"lfs_endpoint TEXT"` @@ -98,6 +99,13 @@ func TouchMirror(ctx context.Context, m *Mirror) error { return err } +// UpdateMirrorLastPullSyncSuccess updates the mirror's last successful pull mirror sync time. +func UpdateMirrorLastPullSyncSuccess(ctx context.Context, m *Mirror, syncTime timeutil.TimeStamp) error { + m.LastPullSyncSuccessUnix = syncTime + _, err := db.GetEngine(ctx).ID(m.ID).Cols("last_pull_sync_success_unix").NoAutoTime().Update(m) + return err +} + // DeleteMirrorByRepoID deletes a mirror by repoID func DeleteMirrorByRepoID(ctx context.Context, repoID int64) error { _, err := db.GetEngine(ctx).Delete(&Mirror{RepoID: repoID}) diff --git a/models/repo/repo.go b/models/repo/repo.go index fc37b1ff9f..7814bb4876 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -216,10 +216,9 @@ type Repository struct { // Avatar: ID(10-20)-md5(32) - must fit into 64 symbols Avatar string `xorm:"VARCHAR(64)"` - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` - ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT 0"` - LastPullSyncSuccessUnix timeutil.TimeStamp `xorm:"INDEX"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT 0"` } func init() { diff --git a/models/repo/update.go b/models/repo/update.go index d8ac0222d2..bf560cf695 100644 --- a/models/repo/update.go +++ b/models/repo/update.go @@ -9,7 +9,6 @@ import ( "time" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" ) @@ -34,14 +33,6 @@ func UpdateRepositoryUpdatedTime(ctx context.Context, repoID int64, updateTime t return err } -// UpdateRepositoryLastPullSyncSuccess updates a repository's last successful pull mirror sync time. -func UpdateRepositoryLastPullSyncSuccess(ctx context.Context, repoID int64, syncTime timeutil.TimeStamp) error { - _, err := db.GetEngine(ctx).ID(repoID).Cols("last_pull_sync_success_unix").NoAutoTime().Update(&Repository{ - LastPullSyncSuccessUnix: syncTime, - }) - return err -} - // UpdateRepositoryColsWithAutoTime updates repository's columns and the timestamp fields automatically func UpdateRepositoryColsWithAutoTime(ctx context.Context, repo *Repository, colName string, moreColNames ...string) error { _, err := db.GetEngine(ctx).ID(repo.ID).Cols(append([]string{colName}, moreColNames...)...).Update(repo) diff --git a/services/convert/repository.go b/services/convert/repository.go index cbb27477ea..eafb78d674 100644 --- a/services/convert/repository.go +++ b/services/convert/repository.go @@ -152,11 +152,13 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR mirrorInterval := "" var mirrorUpdated time.Time + var lastPullSyncSuccess time.Time if repo.IsMirror { pullMirror, err := repo_model.GetMirrorByRepoID(ctx, repo.ID) if err == nil { mirrorInterval = pullMirror.Interval.String() mirrorUpdated = pullMirror.UpdatedUnix.AsTime() + lastPullSyncSuccess = pullMirror.LastPullSyncSuccessUnix.AsTime() } } @@ -247,7 +249,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR DefaultTargetBranch: defaultTargetBranch, AvatarURL: repo.AvatarLink(ctx), Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate, - LastPullSyncSuccess: repo.LastPullSyncSuccessUnix.AsTime(), + LastPullSyncSuccess: lastPullSyncSuccess, MirrorInterval: mirrorInterval, MirrorUpdated: mirrorUpdated, RepoTransfer: transfer, diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index 05958e215d..de72f8daab 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -419,8 +419,8 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { log.Error("SyncMirrors [repo: %-v]: unable to add repo to license updater queue: %v", m.Repo, err) return false } - if err = repo_model.UpdateRepositoryLastPullSyncSuccess(ctx, m.Repo.ID, m.UpdatedUnix); err != nil { - log.Error("SyncMirrors [repo: %-v]: failed to update repository last_pull_sync_success_unix: %v", m.Repo, err) + if err = repo_model.UpdateMirrorLastPullSyncSuccess(ctx, m, m.UpdatedUnix); err != nil { + log.Error("SyncMirrors [repo: %-v]: failed to update mirror last_pull_sync_success_unix: %v", m.Repo, err) return false } diff --git a/tests/integration/mirror_pull_test.go b/tests/integration/mirror_pull_test.go index 97e1b70485..d9d798f2c1 100644 --- a/tests/integration/mirror_pull_test.go +++ b/tests/integration/mirror_pull_test.go @@ -94,8 +94,7 @@ func TestMirrorPull(t *testing.T) { assert.True(t, ok) mirror := unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) - mirrorRepo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: mirrorRepo.ID}) - assert.Equal(t, mirror.UpdatedUnix, mirrorRepo.LastPullSyncSuccessUnix) + assert.Equal(t, mirror.UpdatedUnix, mirror.LastPullSyncSuccessUnix) // actually there is a tag in the source repo, so after "sync", that tag will also come into the mirror initCount++ @@ -115,14 +114,14 @@ func TestMirrorPull(t *testing.T) { assert.NoError(t, err) assert.Equal(t, initCount, count) - mirrorRepo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: mirrorRepo.ID}) - lastPullSyncSuccess := mirrorRepo.LastPullSyncSuccessUnix + mirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) + lastPullSyncSuccess := mirror.LastPullSyncSuccessUnix mirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) assert.NoError(t, mirror_service.UpdateAddress(ctx, mirror, repoPath+"-missing")) ok = mirror_service.SyncPullMirror(ctx, mirrorRepo.ID) assert.False(t, ok) - mirrorRepo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: mirrorRepo.ID}) - assert.Equal(t, lastPullSyncSuccess, mirrorRepo.LastPullSyncSuccessUnix) + mirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) + assert.Equal(t, lastPullSyncSuccess, mirror.LastPullSyncSuccessUnix) } From 8177745b133d76fa9d77680333e77fe84df4643e Mon Sep 17 00:00:00 2001 From: pomidorry Date: Wed, 6 May 2026 22:56:02 +0300 Subject: [PATCH 08/17] last_pull_sync and time to zero --- models/migrations/migrations.go | 2 +- models/migrations/v1_27/v332.go | 20 +++++++------------- models/repo/mirror.go | 14 +++++++------- modules/structs/repo.go | 10 +++++----- services/convert/repository.go | 6 +++--- services/mirror/mirror_pull.go | 4 ++-- templates/swagger/v1_json.tmpl | 4 ++-- templates/swagger/v1_openapi3_json.tmpl | 4 ++-- tests/integration/mirror_pull_test.go | 6 +++--- 9 files changed, 32 insertions(+), 38 deletions(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index e44630253c..4a7f27e938 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -409,7 +409,7 @@ func prepareMigrationTasks() []*migration { // Gitea 1.26.0 ends at migration ID number 330 (database version 331) newMigration(331, "Add ActionRunAttempt model and related action fields", v1_27.AddActionRunAttemptModel), - newMigration(332, "Add last_pull_sync_success_unix to mirror", v1_27.AddLastPullSyncSuccessUnixToMirror), + newMigration(332, "Add last_mirror_sync_unix to mirror", v1_27.AddLastMirrorSyncUnixToMirror), } return preparedMigrations } diff --git a/models/migrations/v1_27/v332.go b/models/migrations/v1_27/v332.go index bf290f4fdf..7840d26690 100644 --- a/models/migrations/v1_27/v332.go +++ b/models/migrations/v1_27/v332.go @@ -3,25 +3,19 @@ package v1_27 -import ( - "code.gitea.io/gitea/modules/timeutil" +import "xorm.io/xorm" - "xorm.io/xorm" -) - -type mirrorWithLastPullSyncSuccessUnix struct { - LastPullSyncSuccessUnix int64 `xorm:"INDEX"` +type mirrorWithLastMirrorSyncUnix struct { + LastMirrorSyncUnix int64 `xorm:"INDEX"` } -func (mirrorWithLastPullSyncSuccessUnix) TableName() string { +func (mirrorWithLastMirrorSyncUnix) TableName() string { return "mirror" } -func AddLastPullSyncSuccessUnixToMirror(x *xorm.Engine) error { - if err := x.Sync(new(mirrorWithLastPullSyncSuccessUnix)); err != nil { +func AddLastMirrorSyncUnixToMirror(x *xorm.Engine) error { + if err := x.Sync(new(mirrorWithLastMirrorSyncUnix)); err != nil { return err } - - _, err := x.Exec("UPDATE mirror SET last_pull_sync_success_unix = ?", int64(timeutil.TimeStampNow())) - return err + return nil } diff --git a/models/repo/mirror.go b/models/repo/mirror.go index 7d27cf9707..7a509851e5 100644 --- a/models/repo/mirror.go +++ b/models/repo/mirror.go @@ -25,9 +25,9 @@ type Mirror struct { Interval time.Duration EnablePrune bool `xorm:"NOT NULL DEFAULT true"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX"` - NextUpdateUnix timeutil.TimeStamp `xorm:"INDEX"` - LastPullSyncSuccessUnix timeutil.TimeStamp `xorm:"INDEX"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX"` + NextUpdateUnix timeutil.TimeStamp `xorm:"INDEX"` + LastMirrorSyncUnix timeutil.TimeStamp `xorm:"INDEX"` LFS bool `xorm:"lfs_enabled NOT NULL DEFAULT false"` LFSEndpoint string `xorm:"lfs_endpoint TEXT"` @@ -99,10 +99,10 @@ func TouchMirror(ctx context.Context, m *Mirror) error { return err } -// UpdateMirrorLastPullSyncSuccess updates the mirror's last successful pull mirror sync time. -func UpdateMirrorLastPullSyncSuccess(ctx context.Context, m *Mirror, syncTime timeutil.TimeStamp) error { - m.LastPullSyncSuccessUnix = syncTime - _, err := db.GetEngine(ctx).ID(m.ID).Cols("last_pull_sync_success_unix").NoAutoTime().Update(m) +// UpdateMirrorLastSyncTime updates the mirror's last successful sync time. +func UpdateMirrorLastSyncTime(ctx context.Context, m *Mirror, syncTime timeutil.TimeStamp) error { + m.LastMirrorSyncUnix = syncTime + _, err := db.GetEngine(ctx).ID(m.ID).Cols("last_mirror_sync_unix").NoAutoTime().Update(m) return err } diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 29c49146e9..ab01060ab1 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -125,12 +125,12 @@ type Repository struct { // ObjectFormatName of the underlying git repository ObjectFormatName ObjectFormatName `json:"object_format_name"` // swagger:strfmt date-time - MirrorUpdated time.Time `json:"mirror_updated"` + MirrorUpdated time.Time `json:"mirror_updated"` // swagger:strfmt date-time - LastPullSyncSuccess time.Time `json:"last_pull_sync_success"` - RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"` - Topics []string `json:"topics"` - Licenses []string `json:"licenses"` + LastMirrorSync time.Time `json:"last_mirror_sync"` + RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"` + Topics []string `json:"topics"` + Licenses []string `json:"licenses"` } // CreateRepoOption options when creating repository diff --git a/services/convert/repository.go b/services/convert/repository.go index eafb78d674..1749b7410b 100644 --- a/services/convert/repository.go +++ b/services/convert/repository.go @@ -152,13 +152,13 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR mirrorInterval := "" var mirrorUpdated time.Time - var lastPullSyncSuccess time.Time + var lastMirrorSync time.Time if repo.IsMirror { pullMirror, err := repo_model.GetMirrorByRepoID(ctx, repo.ID) if err == nil { mirrorInterval = pullMirror.Interval.String() mirrorUpdated = pullMirror.UpdatedUnix.AsTime() - lastPullSyncSuccess = pullMirror.LastPullSyncSuccessUnix.AsTime() + lastMirrorSync = pullMirror.LastMirrorSyncUnix.AsTime() } } @@ -249,7 +249,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR DefaultTargetBranch: defaultTargetBranch, AvatarURL: repo.AvatarLink(ctx), Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate, - LastPullSyncSuccess: lastPullSyncSuccess, + LastMirrorSync: lastMirrorSync, MirrorInterval: mirrorInterval, MirrorUpdated: mirrorUpdated, RepoTransfer: transfer, diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index de72f8daab..757e807468 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -419,8 +419,8 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { log.Error("SyncMirrors [repo: %-v]: unable to add repo to license updater queue: %v", m.Repo, err) return false } - if err = repo_model.UpdateMirrorLastPullSyncSuccess(ctx, m, m.UpdatedUnix); err != nil { - log.Error("SyncMirrors [repo: %-v]: failed to update mirror last_pull_sync_success_unix: %v", m.Repo, err) + if err = repo_model.UpdateMirrorLastSyncTime(ctx, m, m.UpdatedUnix); err != nil { + log.Error("SyncMirrors [repo: %-v]: failed to update mirror last_mirror_sync_unix: %v", m.Repo, err) return false } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 03fc6d7a46..e80f95c9db 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -29020,10 +29020,10 @@ "type": "string", "x-go-name": "LanguagesURL" }, - "last_pull_sync_success": { + "last_mirror_sync": { "type": "string", "format": "date-time", - "x-go-name": "LastPullSyncSuccess" + "x-go-name": "LastMirrorSync" }, "licenses": { "type": "array", diff --git a/templates/swagger/v1_openapi3_json.tmpl b/templates/swagger/v1_openapi3_json.tmpl index f43b01e1da..640804922f 100644 --- a/templates/swagger/v1_openapi3_json.tmpl +++ b/templates/swagger/v1_openapi3_json.tmpl @@ -9273,10 +9273,10 @@ "type": "string", "x-go-name": "LanguagesURL" }, - "last_pull_sync_success": { + "last_mirror_sync": { "format": "date-time", "type": "string", - "x-go-name": "LastPullSyncSuccess" + "x-go-name": "LastMirrorSync" }, "licenses": { "items": { diff --git a/tests/integration/mirror_pull_test.go b/tests/integration/mirror_pull_test.go index d9d798f2c1..1e3afb70d6 100644 --- a/tests/integration/mirror_pull_test.go +++ b/tests/integration/mirror_pull_test.go @@ -94,7 +94,7 @@ func TestMirrorPull(t *testing.T) { assert.True(t, ok) mirror := unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) - assert.Equal(t, mirror.UpdatedUnix, mirror.LastPullSyncSuccessUnix) + assert.Equal(t, mirror.UpdatedUnix, mirror.LastMirrorSyncUnix) // actually there is a tag in the source repo, so after "sync", that tag will also come into the mirror initCount++ @@ -115,7 +115,7 @@ func TestMirrorPull(t *testing.T) { assert.Equal(t, initCount, count) mirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) - lastPullSyncSuccess := mirror.LastPullSyncSuccessUnix + lastMirrorSync := mirror.LastMirrorSyncUnix mirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) assert.NoError(t, mirror_service.UpdateAddress(ctx, mirror, repoPath+"-missing")) @@ -123,5 +123,5 @@ func TestMirrorPull(t *testing.T) { assert.False(t, ok) mirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) - assert.Equal(t, lastPullSyncSuccess, mirror.LastPullSyncSuccessUnix) + assert.Equal(t, lastMirrorSync, mirror.LastMirrorSyncUnix) } From a9fa7c93c20a251a4eb3667fae4a02f2eb50bf20 Mon Sep 17 00:00:00 2001 From: pomidorry Date: Wed, 6 May 2026 23:06:53 +0300 Subject: [PATCH 09/17] last_sync --- models/migrations/migrations.go | 2 +- models/migrations/v1_27/v332.go | 13 +++++-------- models/repo/mirror.go | 10 +++++----- modules/structs/repo.go | 2 +- services/convert/repository.go | 6 +++--- services/mirror/mirror_pull.go | 2 +- templates/swagger/v1_json.tmpl | 4 ++-- templates/swagger/v1_openapi3_json.tmpl | 4 ++-- tests/integration/mirror_pull_test.go | 6 +++--- 9 files changed, 23 insertions(+), 26 deletions(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 4a7f27e938..c1d247bc41 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -409,7 +409,7 @@ func prepareMigrationTasks() []*migration { // Gitea 1.26.0 ends at migration ID number 330 (database version 331) newMigration(331, "Add ActionRunAttempt model and related action fields", v1_27.AddActionRunAttemptModel), - newMigration(332, "Add last_mirror_sync_unix to mirror", v1_27.AddLastMirrorSyncUnixToMirror), + newMigration(332, "Add last_sync_unix to mirror", v1_27.AddLastSyncUnixToMirror), } return preparedMigrations } diff --git a/models/migrations/v1_27/v332.go b/models/migrations/v1_27/v332.go index 7840d26690..ec9320e8f6 100644 --- a/models/migrations/v1_27/v332.go +++ b/models/migrations/v1_27/v332.go @@ -5,17 +5,14 @@ package v1_27 import "xorm.io/xorm" -type mirrorWithLastMirrorSyncUnix struct { - LastMirrorSyncUnix int64 `xorm:"INDEX"` +type mirrorWithLastSyncUnix struct { + LastSyncUnix int64 `xorm:"last_sync_unix INDEX"` } -func (mirrorWithLastMirrorSyncUnix) TableName() string { +func (mirrorWithLastSyncUnix) TableName() string { return "mirror" } -func AddLastMirrorSyncUnixToMirror(x *xorm.Engine) error { - if err := x.Sync(new(mirrorWithLastMirrorSyncUnix)); err != nil { - return err - } - return nil +func AddLastSyncUnixToMirror(x *xorm.Engine) error { + return x.Sync(new(mirrorWithLastSyncUnix)) } diff --git a/models/repo/mirror.go b/models/repo/mirror.go index 7a509851e5..9bf4fedd31 100644 --- a/models/repo/mirror.go +++ b/models/repo/mirror.go @@ -25,9 +25,9 @@ type Mirror struct { Interval time.Duration EnablePrune bool `xorm:"NOT NULL DEFAULT true"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX"` - NextUpdateUnix timeutil.TimeStamp `xorm:"INDEX"` - LastMirrorSyncUnix timeutil.TimeStamp `xorm:"INDEX"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX"` + NextUpdateUnix timeutil.TimeStamp `xorm:"INDEX"` + LastSyncUnix timeutil.TimeStamp `xorm:"last_sync_unix INDEX"` LFS bool `xorm:"lfs_enabled NOT NULL DEFAULT false"` LFSEndpoint string `xorm:"lfs_endpoint TEXT"` @@ -101,8 +101,8 @@ func TouchMirror(ctx context.Context, m *Mirror) error { // UpdateMirrorLastSyncTime updates the mirror's last successful sync time. func UpdateMirrorLastSyncTime(ctx context.Context, m *Mirror, syncTime timeutil.TimeStamp) error { - m.LastMirrorSyncUnix = syncTime - _, err := db.GetEngine(ctx).ID(m.ID).Cols("last_mirror_sync_unix").NoAutoTime().Update(m) + m.LastSyncUnix = syncTime + _, err := db.GetEngine(ctx).ID(m.ID).Cols("last_sync_unix").NoAutoTime().Update(m) return err } diff --git a/modules/structs/repo.go b/modules/structs/repo.go index ab01060ab1..626c35f9e4 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -127,7 +127,7 @@ type Repository struct { // swagger:strfmt date-time MirrorUpdated time.Time `json:"mirror_updated"` // swagger:strfmt date-time - LastMirrorSync time.Time `json:"last_mirror_sync"` + LastSync time.Time `json:"last_sync"` RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"` Topics []string `json:"topics"` Licenses []string `json:"licenses"` diff --git a/services/convert/repository.go b/services/convert/repository.go index 1749b7410b..1976a78e1b 100644 --- a/services/convert/repository.go +++ b/services/convert/repository.go @@ -152,13 +152,13 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR mirrorInterval := "" var mirrorUpdated time.Time - var lastMirrorSync time.Time + var lastSync time.Time if repo.IsMirror { pullMirror, err := repo_model.GetMirrorByRepoID(ctx, repo.ID) if err == nil { mirrorInterval = pullMirror.Interval.String() mirrorUpdated = pullMirror.UpdatedUnix.AsTime() - lastMirrorSync = pullMirror.LastMirrorSyncUnix.AsTime() + lastSync = pullMirror.LastSyncUnix.AsTime() } } @@ -249,7 +249,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR DefaultTargetBranch: defaultTargetBranch, AvatarURL: repo.AvatarLink(ctx), Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate, - LastMirrorSync: lastMirrorSync, + LastSync: lastSync, MirrorInterval: mirrorInterval, MirrorUpdated: mirrorUpdated, RepoTransfer: transfer, diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index 757e807468..76f2c4b683 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -420,7 +420,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { return false } if err = repo_model.UpdateMirrorLastSyncTime(ctx, m, m.UpdatedUnix); err != nil { - log.Error("SyncMirrors [repo: %-v]: failed to update mirror last_mirror_sync_unix: %v", m.Repo, err) + log.Error("SyncMirrors [repo: %-v]: failed to update mirror last_sync_unix: %v", m.Repo, err) return false } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index e80f95c9db..697999c0d1 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -29020,10 +29020,10 @@ "type": "string", "x-go-name": "LanguagesURL" }, - "last_mirror_sync": { + "last_sync": { "type": "string", "format": "date-time", - "x-go-name": "LastMirrorSync" + "x-go-name": "LastSync" }, "licenses": { "type": "array", diff --git a/templates/swagger/v1_openapi3_json.tmpl b/templates/swagger/v1_openapi3_json.tmpl index 640804922f..bacb1f8821 100644 --- a/templates/swagger/v1_openapi3_json.tmpl +++ b/templates/swagger/v1_openapi3_json.tmpl @@ -9273,10 +9273,10 @@ "type": "string", "x-go-name": "LanguagesURL" }, - "last_mirror_sync": { + "last_sync": { "format": "date-time", "type": "string", - "x-go-name": "LastMirrorSync" + "x-go-name": "LastSync" }, "licenses": { "items": { diff --git a/tests/integration/mirror_pull_test.go b/tests/integration/mirror_pull_test.go index 1e3afb70d6..85d44d492b 100644 --- a/tests/integration/mirror_pull_test.go +++ b/tests/integration/mirror_pull_test.go @@ -94,7 +94,7 @@ func TestMirrorPull(t *testing.T) { assert.True(t, ok) mirror := unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) - assert.Equal(t, mirror.UpdatedUnix, mirror.LastMirrorSyncUnix) + assert.Equal(t, mirror.UpdatedUnix, mirror.LastSyncUnix) // actually there is a tag in the source repo, so after "sync", that tag will also come into the mirror initCount++ @@ -115,7 +115,7 @@ func TestMirrorPull(t *testing.T) { assert.Equal(t, initCount, count) mirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) - lastMirrorSync := mirror.LastMirrorSyncUnix + lastMirrorSync := mirror.LastSyncUnix mirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) assert.NoError(t, mirror_service.UpdateAddress(ctx, mirror, repoPath+"-missing")) @@ -123,5 +123,5 @@ func TestMirrorPull(t *testing.T) { assert.False(t, ok) mirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) - assert.Equal(t, lastMirrorSync, mirror.LastMirrorSyncUnix) + assert.Equal(t, lastMirrorSync, mirror.LastSyncUnix) } From ce53599ba79d1659176b95845b69f8bfd9594726 Mon Sep 17 00:00:00 2001 From: pomidorry Date: Wed, 6 May 2026 23:11:51 +0300 Subject: [PATCH 10/17] fix linters --- modules/structs/repo.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 626c35f9e4..44486cd9cd 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -125,12 +125,12 @@ type Repository struct { // ObjectFormatName of the underlying git repository ObjectFormatName ObjectFormatName `json:"object_format_name"` // swagger:strfmt date-time - MirrorUpdated time.Time `json:"mirror_updated"` + MirrorUpdated time.Time `json:"mirror_updated"` // swagger:strfmt date-time - LastSync time.Time `json:"last_sync"` - RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"` - Topics []string `json:"topics"` - Licenses []string `json:"licenses"` + LastSync time.Time `json:"last_sync"` + RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"` + Topics []string `json:"topics"` + Licenses []string `json:"licenses"` } // CreateRepoOption options when creating repository From f64d8bf3648b879a06d6ce565ce0c6e4e8a455d5 Mon Sep 17 00:00:00 2001 From: pomidorry Date: Fri, 8 May 2026 15:22:31 +0300 Subject: [PATCH 11/17] decouple last sync time from license update queue --- services/mirror/mirror_pull.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index 76f2c4b683..e26387ebab 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -412,16 +412,16 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { } } - // Update License + if err = repo_model.UpdateMirrorLastSyncTime(ctx, m, m.UpdatedUnix); err != nil { + log.Error("SyncMirrors [repo: %-v]: failed to update mirror last_sync_unix: %v", m.Repo, err) + return false + } + + // Update license metadata after a successful mirror sync. if err = repo_service.AddRepoToLicenseUpdaterQueue(&repo_service.LicenseUpdaterOptions{ RepoID: m.Repo.ID, }); err != nil { log.Error("SyncMirrors [repo: %-v]: unable to add repo to license updater queue: %v", m.Repo, err) - return false - } - if err = repo_model.UpdateMirrorLastSyncTime(ctx, m, m.UpdatedUnix); err != nil { - log.Error("SyncMirrors [repo: %-v]: failed to update mirror last_sync_unix: %v", m.Repo, err) - return false } log.Trace("SyncMirrors [repo: %-v]: Successfully updated", m.Repo) From e3d566171564b91d0a4c9cc3df75095a7749cc61 Mon Sep 17 00:00:00 2001 From: pomidorry Date: Fri, 8 May 2026 15:24:11 +0300 Subject: [PATCH 12/17] test simplification --- tests/integration/mirror_pull_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/mirror_pull_test.go b/tests/integration/mirror_pull_test.go index 85d44d492b..0e81aa3d5e 100644 --- a/tests/integration/mirror_pull_test.go +++ b/tests/integration/mirror_pull_test.go @@ -116,7 +116,6 @@ func TestMirrorPull(t *testing.T) { mirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) lastMirrorSync := mirror.LastSyncUnix - mirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) assert.NoError(t, mirror_service.UpdateAddress(ctx, mirror, repoPath+"-missing")) ok = mirror_service.SyncPullMirror(ctx, mirrorRepo.ID) From 903c19b77f52ec13850c20c822eea2c7c35e415e Mon Sep 17 00:00:00 2001 From: pomidorry Date: Fri, 8 May 2026 21:06:27 +0300 Subject: [PATCH 13/17] Sync -> SyncWithOption --- models/migrations/v1_27/v332.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/models/migrations/v1_27/v332.go b/models/migrations/v1_27/v332.go index ec9320e8f6..8cf087d56e 100644 --- a/models/migrations/v1_27/v332.go +++ b/models/migrations/v1_27/v332.go @@ -14,5 +14,8 @@ func (mirrorWithLastSyncUnix) TableName() string { } func AddLastSyncUnixToMirror(x *xorm.Engine) error { - return x.Sync(new(mirrorWithLastSyncUnix)) + _, err := x.SyncWithOptions(xorm.SyncOptions{ + IgnoreDropIndices: true, + }, new(mirrorWithLastSyncUnix)) + return err } From 03faefc4d116323f510a58fe86cedf2a5819a839 Mon Sep 17 00:00:00 2001 From: pomidorry Date: Fri, 8 May 2026 21:07:03 +0300 Subject: [PATCH 14/17] removed extra part --- models/repo/mirror.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/repo/mirror.go b/models/repo/mirror.go index 9bf4fedd31..1069943d40 100644 --- a/models/repo/mirror.go +++ b/models/repo/mirror.go @@ -27,7 +27,7 @@ type Mirror struct { UpdatedUnix timeutil.TimeStamp `xorm:"INDEX"` NextUpdateUnix timeutil.TimeStamp `xorm:"INDEX"` - LastSyncUnix timeutil.TimeStamp `xorm:"last_sync_unix INDEX"` + LastSyncUnix timeutil.TimeStamp `xorm:"INDEX"` LFS bool `xorm:"lfs_enabled NOT NULL DEFAULT false"` LFSEndpoint string `xorm:"lfs_endpoint TEXT"` From d765cb2aecdd57cf0f116ef1b522d0a66c772e1f Mon Sep 17 00:00:00 2001 From: pomidorry Date: Fri, 8 May 2026 21:16:54 +0300 Subject: [PATCH 15/17] naming --- modules/structs/repo.go | 2 +- services/convert/repository.go | 2 +- templates/swagger/v1_json.tmpl | 10 +++++----- templates/swagger/v1_openapi3_json.tmpl | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 44486cd9cd..c33aea8a8d 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -127,7 +127,7 @@ type Repository struct { // swagger:strfmt date-time MirrorUpdated time.Time `json:"mirror_updated"` // swagger:strfmt date-time - LastSync time.Time `json:"last_sync"` + MirrorLastSyncAt time.Time `json:"mirror_last_sync_at"` RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"` Topics []string `json:"topics"` Licenses []string `json:"licenses"` diff --git a/services/convert/repository.go b/services/convert/repository.go index 1976a78e1b..ec010ee375 100644 --- a/services/convert/repository.go +++ b/services/convert/repository.go @@ -249,7 +249,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR DefaultTargetBranch: defaultTargetBranch, AvatarURL: repo.AvatarLink(ctx), Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate, - LastSync: lastSync, + MirrorLastSyncAt: lastSync, MirrorInterval: mirrorInterval, MirrorUpdated: mirrorUpdated, RepoTransfer: transfer, diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 697999c0d1..8b21b53dc6 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -29020,11 +29020,6 @@ "type": "string", "x-go-name": "LanguagesURL" }, - "last_sync": { - "type": "string", - "format": "date-time", - "x-go-name": "LastSync" - }, "licenses": { "type": "array", "items": { @@ -29044,6 +29039,11 @@ "type": "string", "x-go-name": "MirrorInterval" }, + "mirror_last_sync_at": { + "type": "string", + "format": "date-time", + "x-go-name": "MirrorLastSyncAt" + }, "mirror_updated": { "type": "string", "format": "date-time", diff --git a/templates/swagger/v1_openapi3_json.tmpl b/templates/swagger/v1_openapi3_json.tmpl index bacb1f8821..ec04a0a1ee 100644 --- a/templates/swagger/v1_openapi3_json.tmpl +++ b/templates/swagger/v1_openapi3_json.tmpl @@ -9273,11 +9273,6 @@ "type": "string", "x-go-name": "LanguagesURL" }, - "last_sync": { - "format": "date-time", - "type": "string", - "x-go-name": "LastSync" - }, "licenses": { "items": { "type": "string" @@ -9297,6 +9292,11 @@ "type": "string", "x-go-name": "MirrorInterval" }, + "mirror_last_sync_at": { + "format": "date-time", + "type": "string", + "x-go-name": "MirrorLastSyncAt" + }, "mirror_updated": { "format": "date-time", "type": "string", From e8800f42840b4968cf9de08da3c4b04c9746047b Mon Sep 17 00:00:00 2001 From: pomidorry Date: Fri, 8 May 2026 21:19:26 +0300 Subject: [PATCH 16/17] make last_sync_unix update non-fatal --- services/mirror/mirror_pull.go | 1 - 1 file changed, 1 deletion(-) diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index e26387ebab..ccdee55d0e 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -414,7 +414,6 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { if err = repo_model.UpdateMirrorLastSyncTime(ctx, m, m.UpdatedUnix); err != nil { log.Error("SyncMirrors [repo: %-v]: failed to update mirror last_sync_unix: %v", m.Repo, err) - return false } // Update license metadata after a successful mirror sync. From 98d4ea4b24bc00eeb49c594f2de56343e4a0af51 Mon Sep 17 00:00:00 2001 From: pomidorry Date: Fri, 8 May 2026 21:38:37 +0300 Subject: [PATCH 17/17] linter fix --- modules/structs/repo.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/structs/repo.go b/modules/structs/repo.go index c33aea8a8d..5626f7e14b 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -127,10 +127,10 @@ type Repository struct { // swagger:strfmt date-time MirrorUpdated time.Time `json:"mirror_updated"` // swagger:strfmt date-time - MirrorLastSyncAt time.Time `json:"mirror_last_sync_at"` - RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"` - Topics []string `json:"topics"` - Licenses []string `json:"licenses"` + MirrorLastSyncAt time.Time `json:"mirror_last_sync_at"` + RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"` + Topics []string `json:"topics"` + Licenses []string `json:"licenses"` } // CreateRepoOption options when creating repository