From d25f7ae70da05e6a796eb72a2f0bc9feca9867c0 Mon Sep 17 00:00:00 2001 From: Gusted Date: Thu, 28 May 2026 00:49:07 +0200 Subject: [PATCH] feat: show progress of issues and PRs migrations (#12738) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are by the far the longest time spent on during a migration. Indicate the progress of how many issues and PRs were migrated so far. Don't overwhelm the messenger, so they are only updated once a batch is migrated. Which is "slow" enough to see it's not stuck and still doing work. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12738 Reviewed-by: Ellen Εμίλια Άννα Zscheile Reviewed-by: 0ko <0ko@noreply.codeberg.org> --- modules/migration/uploader.go | 1 + options/locale_next/locale_en-US.json | 8 ++ routers/web/user/task.go | 8 ++ services/migrations/lint-locale-usage/llu.go | 4 +- services/migrations/migrate.go | 7 ++ services/migrations/migrate_test.go | 110 +++++++++++++++++++ 6 files changed, 136 insertions(+), 2 deletions(-) diff --git a/modules/migration/uploader.go b/modules/migration/uploader.go index ff642aa4fa..90aa71b9c7 100644 --- a/modules/migration/uploader.go +++ b/modules/migration/uploader.go @@ -1,3 +1,4 @@ +// Copyright 2026 The Forgejo Authors. All rights reserved. // Copyright 2019 The Gitea Authors. All rights reserved. // Copyright 2018 Jonas Franz. All rights reserved. // SPDX-License-Identifier: MIT diff --git a/options/locale_next/locale_en-US.json b/options/locale_next/locale_en-US.json index 061f2c4a25..c7b4e26990 100644 --- a/options/locale_next/locale_en-US.json +++ b/options/locale_next/locale_en-US.json @@ -119,7 +119,15 @@ "migrate.in_progress.labels": "Migrating labels", "migrate.in_progress.releases": "Migrating releases", "migrate.in_progress.issues": "Migrating issues", + "migrate.in_progress.issues.progress": { + "one": "Migrated %[1]d issue", + "other": "Migrated %[1]d issues" + }, "migrate.in_progress.pulls": "Migrating pull requests", + "migrate.in_progress.pulls.progress": { + "one": "Migrated %[1]d pull request", + "other": "Migrated %[1]d pull requests" + }, "migrate.cancel.title": "Cancel migration", "migrate.cancel.confirmation": "Do you want to cancel this migration?", "repo.issue_indexer.title": "Issue Indexer", diff --git a/routers/web/user/task.go b/routers/web/user/task.go index 4059139552..3ff5bb4993 100644 --- a/routers/web/user/task.go +++ b/routers/web/user/task.go @@ -39,6 +39,14 @@ func TaskStatus(ctx *context.Context) { Args: []any{task.Message}, } } + + // Convert float64 to integers. Currently no usage of floats. + for i := range translatableMessage.Args { + if arg, ok := translatableMessage.Args[i].(float64); ok { + translatableMessage.Args[i] = int64(arg) + } + } + message = ctx.Locale.TrString(translatableMessage.Format, translatableMessage.Args...) } diff --git a/services/migrations/lint-locale-usage/llu.go b/services/migrations/lint-locale-usage/llu.go index e0ed031cbe..c6eccfc2d4 100644 --- a/services/migrations/lint-locale-usage/llu.go +++ b/services/migrations/lint-locale-usage/llu.go @@ -35,8 +35,8 @@ func HandleMessengerInFunc(handler llu.Handler, fset *token.FileSet, n2 *ast.Fun if ret, ok := call.Fun.(*ast.Ident); !(ok && messenger.Contains(ret.Name)) { return true } - if len(call.Args) != 1 { - handler.OnWarning(fset, call.Lparen, "unexpected invocation of base.Messenger (expected exactly 1 argument)") + if len(call.Args) == 0 { + handler.OnWarning(fset, call.Lparen, "unexpected invocation of base.Messenger (expected at least one argument)") return true } handler.HandleGoTrArgument(fset, call.Args[0], "") diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go index 6746a8c915..92a959600c 100644 --- a/services/migrations/migrate.go +++ b/services/migrations/migrate.go @@ -344,6 +344,7 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base log.Trace("migrating issues and comments") messenger("migrate.in_progress.issues") issueBatchSize := uploader.MaxBatchInsertSize("issue") + var processedIssuesLen int for i := 1; ; i++ { issues, isEnd, err := downloader.GetIssues(i, issueBatchSize) @@ -389,6 +390,8 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base } } + processedIssuesLen += len(issues) + messenger("migrate.in_progress.issues.progress", processedIssuesLen) if isEnd { break } @@ -399,6 +402,8 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base log.Trace("migrating pull requests and comments") messenger("migrate.in_progress.pulls") prBatchSize := uploader.MaxBatchInsertSize("pullrequest") + var processedPrsLen int + for i := 1; ; i++ { prs, isEnd, err := downloader.GetPullRequests(i, prBatchSize) if err != nil { @@ -471,6 +476,8 @@ func migrateRepository(_ context.Context, doer *user_model.User, downloader base } } + processedPrsLen += len(prs) + messenger("migrate.in_progress.pulls.progress", processedPrsLen) if isEnd { break } diff --git a/services/migrations/migrate_test.go b/services/migrations/migrate_test.go index 804d01df7a..001da836dd 100644 --- a/services/migrations/migrate_test.go +++ b/services/migrations/migrate_test.go @@ -1,3 +1,4 @@ +// Copyright 2026 The Forgejo Authors. All rights reserved. // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT @@ -10,8 +11,10 @@ import ( "forgejo.org/models/unittest" user_model "forgejo.org/models/user" + "forgejo.org/modules/migration" "forgejo.org/modules/setting" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -128,3 +131,110 @@ func TestURLAllowedSSH(t *testing.T) { require.NoError(t, IsPushMirrorURLAllowed(sshURL, user)) }) } + +var _ migration.Uploader = nullUploader{} + +type nullUploader struct{} + +func (nullUploader) MaxBatchInsertSize(string) int { + return 1 +} + +func (nullUploader) CreateRepo(*migration.Repository, migration.MigrateOptions) error { + return nil +} + +func (nullUploader) CreateTopics(...string) error { + return nil +} + +func (nullUploader) CreateMilestones(...*migration.Milestone) error { + return nil +} + +func (nullUploader) CreateReleases(...*migration.Release) error { + return nil +} + +func (nullUploader) SyncTags() error { + return nil +} + +func (nullUploader) CreateLabels(...*migration.Label) error { + return nil +} + +func (nullUploader) CreateIssues(...*migration.Issue) error { + return nil +} + +func (nullUploader) CreateComments(...*migration.Comment) error { + return nil +} + +func (nullUploader) CreatePullRequests(...*migration.PullRequest) error { + return nil +} + +func (nullUploader) CreateReviews(...*migration.Review) error { + return nil +} + +func (nullUploader) Rollback() error { + return nil +} + +func (nullUploader) Finish() error { + return nil +} + +func (nullUploader) Close() {} + +type testingDownloader struct { + migration.NullDownloader +} + +func (testingDownloader) GetRepoInfo() (*migration.Repository, error) { + return &migration.Repository{ + CloneURL: "https://codeberg.org/forgejo-contrib/delightful-forgejo.git", + }, nil +} + +func (testingDownloader) GetIssues(page, _ int) ([]*migration.Issue, bool, error) { + return make([]*migration.Issue, 1), page == 2, nil +} + +func (testingDownloader) GetPullRequests(page, _ int) ([]*migration.PullRequest, bool, error) { + return make([]*migration.PullRequest, 1), page == 3, nil +} + +func TestMigrateRepository(t *testing.T) { + messages := []struct { + key string + args []any + }{ + {key: "migrate.in_progress.git"}, + {key: "migrate.in_progress.topics"}, + {key: "migrate.in_progress.issues"}, + {key: "migrate.in_progress.issues.progress", args: []any{1}}, + {key: "migrate.in_progress.issues.progress", args: []any{2}}, + {key: "migrate.in_progress.pulls"}, + {key: "migrate.in_progress.pulls.progress", args: []any{1}}, + {key: "migrate.in_progress.pulls.progress", args: []any{2}}, + {key: "migrate.in_progress.pulls.progress", args: []any{3}}, + } + messenger := func(key string, args ...any) { + if assert.NotEmpty(t, messages, key) { + assert.Equal(t, messages[0].key, key) + assert.Equal(t, messages[0].args, args) + messages = messages[1:] + } + } + + require.NoError(t, migrateRepository(nil, nil, testingDownloader{}, nullUploader{}, migration.MigrateOptions{ + PullRequests: true, + Issues: true, + }, messenger)) + + assert.Empty(t, messages) +}