feat: show progress of issues and PRs migrations (#12738)

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 <fogti@noreply.codeberg.org>
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
This commit is contained in:
Gusted 2026-05-28 00:49:07 +02:00 committed by Gusted
parent 68858a9de2
commit d25f7ae70d
6 changed files with 136 additions and 2 deletions

View file

@ -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

View file

@ -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",

View file

@ -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...)
}

View file

@ -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], "")

View file

@ -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
}

View file

@ -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)
}