From 08682212ab631572bc680251ceb4daa7bb4e7b9d Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Tue, 8 Jul 2025 00:37:29 +0000 Subject: [PATCH 1/6] [skip ci] Updated translations via Crowdin --- options/locale/locale_ga-IE.ini | 1 + options/locale/locale_pt-PT.ini | 1 + 2 files changed, 2 insertions(+) diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini index 49ce14aded..965cce0c68 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -2355,6 +2355,7 @@ settings.payload_url=URL spriocdhírithe settings.http_method=Modh HTTP settings.content_type=Cineál Ábhar POST settings.secret=Rúnda +settings.webhook_secret_desc=Más féidir le freastalaí an webhook rún a úsáid, is féidir leat lámhleabhar an webhook a leanúint agus rún a líonadh isteach anseo. settings.slack_username=Ainm úsáideora settings.slack_icon_url=URL deilbhín settings.slack_color=Dath diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 2ebea36af9..f10bef0687 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -2353,6 +2353,7 @@ settings.payload_url=URL de destino settings.http_method=Método HTTP settings.content_type=Tipo de conteúdo POST settings.secret=Segredo +settings.webhook_secret_desc=Se o servidor de automatismos web suportar a utilização de segredos, você pode seguir o manual do automatismo web e preencher um segredo aqui. settings.slack_username=Nome de utilizador settings.slack_icon_url=URL do ícone settings.slack_color=Cor From 3763c2ae28ebfa539cefb7ce0830cf5ee6c6f1df Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 8 Jul 2025 16:59:31 +0800 Subject: [PATCH 2/6] Refactor time tracker UI (#34983) Although we decided to "reduce the button amount" on the side bar, not only one user reported that the "time tracker dropdown" is not easy to use. So the best we can do at the moment is: move the buttons to the sidebar again. Fix #34979 --- templates/devtest/fomantic-modal.tmpl | 16 ++-- templates/devtest/gitea-ui.tmpl | 14 ++-- .../issue/sidebar/stopwatch_timetracker.tmpl | 77 ++++++++----------- web_src/css/modules/button.css | 2 +- web_src/js/index-domready.ts | 2 + web_src/js/standalone/devtest.ts | 1 + 6 files changed, 54 insertions(+), 58 deletions(-) diff --git a/templates/devtest/fomantic-modal.tmpl b/templates/devtest/fomantic-modal.tmpl index 838c6893a4..8e769790b2 100644 --- a/templates/devtest/fomantic-modal.tmpl +++ b/templates/devtest/fomantic-modal.tmpl @@ -2,13 +2,15 @@
{{template "base/alert" .}} - -
diff --git a/templates/repo/issue/sidebar/stopwatch_timetracker.tmpl b/templates/repo/issue/sidebar/stopwatch_timetracker.tmpl index ab8600b068..6168b06e17 100644 --- a/templates/repo/issue/sidebar/stopwatch_timetracker.tmpl +++ b/templates/repo/issue/sidebar/stopwatch_timetracker.tmpl @@ -2,35 +2,28 @@ {{if and .CanUseTimetracker (not .Repository.IsArchived)}}
- {{end}} {{if .WorkingUsers}} -
+
{{ctx.Locale.Tr "repo.issues.time_spent_from_all_authors" ($.Issue.TotalTrackedTime | Sec2Hour)}} -
- {{range $user, $trackedtime := .WorkingUsers}} -
- - {{ctx.AvatarUtils.Avatar $user}} - -
- {{template "shared/user/authorlink" $user}} -
- {{$trackedtime|Sec2Hour}} -
-
+
+
+ {{range $user, $trackedtime := .WorkingUsers}} +
+ {{template "shared/user/avatarlink" dict "user" $user}} +
+ {{template "shared/user/authorlink" $user}} +
{{$trackedtime|Sec2Hour}}
- {{end}} -
+
+ {{end}}
{{end}} {{end}} diff --git a/web_src/css/modules/button.css b/web_src/css/modules/button.css index b105bb5de2..8e3309474b 100644 --- a/web_src/css/modules/button.css +++ b/web_src/css/modules/button.css @@ -366,8 +366,8 @@ It needs some tricks to tweak the left/right borders with active state */ .ui.buttons .button { border-right: none; - flex: 1 0 auto; border-radius: 0; + flex-shrink: 0; margin: 0; } .ui.buttons .button:first-child { diff --git a/web_src/js/index-domready.ts b/web_src/js/index-domready.ts index 4d7ab98db0..ca18d1e828 100644 --- a/web_src/js/index-domready.ts +++ b/web_src/js/index-domready.ts @@ -175,3 +175,5 @@ const initDur = performance.now() - initStartTime; if (initDur > 500) { console.error(`slow init functions took ${initDur.toFixed(3)}ms`); } + +document.dispatchEvent(new CustomEvent('gitea:index-ready')); diff --git a/web_src/js/standalone/devtest.ts b/web_src/js/standalone/devtest.ts index e6baf6c9ce..faa38dc467 100644 --- a/web_src/js/standalone/devtest.ts +++ b/web_src/js/standalone/devtest.ts @@ -11,4 +11,5 @@ function initDevtestToast() { } } +// NOTICE: keep in mind that this file is not in "index.js", they do not share the same module system. initDevtestToast(); From 4e10adc871363ccd099867563a06a55bb50b296a Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 8 Jul 2025 22:51:16 +0800 Subject: [PATCH 3/6] Start automerge check again after the conflict check and the schedule (#34989) Fix #34988 Co-authored-by: posativ --- models/pull/automerge.go | 7 ++- services/automerge/automerge.go | 55 ++++++----------------- services/automerge/notify.go | 3 +- services/automergequeue/automergequeue.go | 49 ++++++++++++++++++++ services/pull/check.go | 17 +++++-- services/pull/check_test.go | 52 +++++++++++++++++++-- tests/integration/git_general_test.go | 47 ++++++++++++++----- tests/integration/pull_merge_test.go | 25 ++++++++--- 8 files changed, 187 insertions(+), 68 deletions(-) create mode 100644 services/automergequeue/automergequeue.go diff --git a/models/pull/automerge.go b/models/pull/automerge.go index 3cafacc3a4..7f940a9849 100644 --- a/models/pull/automerge.go +++ b/models/pull/automerge.go @@ -5,12 +5,14 @@ package pull import ( "context" + "errors" "fmt" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" ) // AutoMerge represents a pull request scheduled for merging when checks succeed @@ -76,7 +78,10 @@ func GetScheduledMergeByPullID(ctx context.Context, pullID int64) (bool, *AutoMe return false, nil, err } - doer, err := user_model.GetUserByID(ctx, scheduledPRM.DoerID) + doer, err := user_model.GetPossibleUserByID(ctx, scheduledPRM.DoerID) + if errors.Is(err, util.ErrNotExist) { + doer, err = user_model.NewGhostUser(), nil + } if err != nil { return false, nil, err } diff --git a/services/automerge/automerge.go b/services/automerge/automerge.go index 0520a097d3..1a37d70e5a 100644 --- a/services/automerge/automerge.go +++ b/services/automerge/automerge.go @@ -22,23 +22,21 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/queue" + "code.gitea.io/gitea/services/automergequeue" notify_service "code.gitea.io/gitea/services/notify" pull_service "code.gitea.io/gitea/services/pull" repo_service "code.gitea.io/gitea/services/repository" ) -// prAutoMergeQueue represents a queue to handle update pull request tests -var prAutoMergeQueue *queue.WorkerPoolQueue[string] - // Init runs the task queue to that handles auto merges func Init() error { notify_service.RegisterNotifier(NewNotifier()) - prAutoMergeQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "pr_auto_merge", handler) - if prAutoMergeQueue == nil { + automergequeue.AutoMergeQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "pr_auto_merge", handler) + if automergequeue.AutoMergeQueue == nil { return errors.New("unable to create pr_auto_merge queue") } - go graceful.GetManager().RunWithCancel(prAutoMergeQueue) + go graceful.GetManager().RunWithCancel(automergequeue.AutoMergeQueue) return nil } @@ -56,24 +54,23 @@ func handler(items ...string) []string { return nil } -func addToQueue(pr *issues_model.PullRequest, sha string) { - log.Trace("Adding pullID: %d to the pull requests patch checking queue with sha %s", pr.ID, sha) - if err := prAutoMergeQueue.Push(fmt.Sprintf("%d_%s", pr.ID, sha)); err != nil { - log.Error("Error adding pullID: %d to the pull requests patch checking queue %v", pr.ID, err) - } -} - // ScheduleAutoMerge if schedule is false and no error, pull can be merged directly func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pull *issues_model.PullRequest, style repo_model.MergeStyle, message string, deleteBranchAfterMerge bool) (scheduled bool, err error) { err = db.WithTx(ctx, func(ctx context.Context) error { if err := pull_model.ScheduleAutoMerge(ctx, doer, pull.ID, style, message, deleteBranchAfterMerge); err != nil { return err } - scheduled = true - _, err = issues_model.CreateAutoMergeComment(ctx, issues_model.CommentTypePRScheduledToAutoMerge, pull, doer) return err }) + // Old code made "scheduled" to be true after "ScheduleAutoMerge", but it's not right: + // If the transaction rolls back, then the pull request is not scheduled to auto merge. + // So we should only set "scheduled" to true if there is no error. + scheduled = err == nil + if scheduled { + log.Trace("Pull request [%d] scheduled for auto merge with style [%s] and message [%s]", pull.ID, style, message) + automergequeue.StartPRCheckAndAutoMerge(ctx, pull) + } return scheduled, err } @@ -99,38 +96,12 @@ func StartPRCheckAndAutoMergeBySHA(ctx context.Context, sha string, repo *repo_m } for _, pr := range pulls { - addToQueue(pr, sha) + automergequeue.AddToQueue(pr, sha) } return nil } -// StartPRCheckAndAutoMerge start an automerge check and auto merge task for a pull request -func StartPRCheckAndAutoMerge(ctx context.Context, pull *issues_model.PullRequest) { - if pull == nil || pull.HasMerged || !pull.CanAutoMerge() { - return - } - - if err := pull.LoadBaseRepo(ctx); err != nil { - log.Error("LoadBaseRepo: %v", err) - return - } - - gitRepo, err := gitrepo.OpenRepository(ctx, pull.BaseRepo) - if err != nil { - log.Error("OpenRepository: %v", err) - return - } - defer gitRepo.Close() - commitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName()) - if err != nil { - log.Error("GetRefCommitID: %v", err) - return - } - - addToQueue(pull, commitID) -} - func getPullRequestsByHeadSHA(ctx context.Context, sha string, repo *repo_model.Repository, filter func(*issues_model.PullRequest) bool) (map[int64]*issues_model.PullRequest, error) { gitRepo, err := gitrepo.OpenRepository(ctx, repo) if err != nil { diff --git a/services/automerge/notify.go b/services/automerge/notify.go index b6bbca333b..8a1bb5fc90 100644 --- a/services/automerge/notify.go +++ b/services/automerge/notify.go @@ -12,6 +12,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/services/automergequeue" notify_service "code.gitea.io/gitea/services/notify" ) @@ -45,7 +46,7 @@ func (n *automergeNotifier) PullReviewDismiss(ctx context.Context, doer *user_mo return } // as reviews could have blocked a pending automerge let's recheck - StartPRCheckAndAutoMerge(ctx, review.Issue.PullRequest) + automergequeue.StartPRCheckAndAutoMerge(ctx, review.Issue.PullRequest) } func (n *automergeNotifier) CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus) { diff --git a/services/automergequeue/automergequeue.go b/services/automergequeue/automergequeue.go new file mode 100644 index 0000000000..cdf257e6c8 --- /dev/null +++ b/services/automergequeue/automergequeue.go @@ -0,0 +1,49 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package automergequeue + +import ( + "context" + "fmt" + + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/queue" +) + +var AutoMergeQueue *queue.WorkerPoolQueue[string] + +var AddToQueue = func(pr *issues_model.PullRequest, sha string) { + log.Trace("Adding pullID: %d to the pull requests patch checking queue with sha %s", pr.ID, sha) + if err := AutoMergeQueue.Push(fmt.Sprintf("%d_%s", pr.ID, sha)); err != nil { + log.Error("Error adding pullID: %d to the pull requests patch checking queue %v", pr.ID, err) + } +} + +// StartPRCheckAndAutoMerge start an automerge check and auto merge task for a pull request +func StartPRCheckAndAutoMerge(ctx context.Context, pull *issues_model.PullRequest) { + if pull == nil || pull.HasMerged || !pull.CanAutoMerge() { + return + } + + if err := pull.LoadBaseRepo(ctx); err != nil { + log.Error("LoadBaseRepo: %v", err) + return + } + + gitRepo, err := gitrepo.OpenRepository(ctx, pull.BaseRepo) + if err != nil { + log.Error("OpenRepository: %v", err) + return + } + defer gitRepo.Close() + commitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName()) + if err != nil { + log.Error("GetRefCommitID: %v", err) + return + } + + AddToQueue(pull, commitID) +} diff --git a/services/pull/check.go b/services/pull/check.go index 5d8990aa00..bf6c5fa1c4 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -1,5 +1,4 @@ -// Copyright 2019 The Gitea Authors. -// All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package pull @@ -16,6 +15,7 @@ import ( git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" access_model "code.gitea.io/gitea/models/perm/access" + "code.gitea.io/gitea/models/pull" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -29,6 +29,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" asymkey_service "code.gitea.io/gitea/services/asymkey" + "code.gitea.io/gitea/services/automergequeue" notify_service "code.gitea.io/gitea/services/notify" ) @@ -238,7 +239,7 @@ func isSignedIfRequired(ctx context.Context, pr *issues_model.PullRequest, doer // markPullRequestAsMergeable checks if pull request is possible to leaving checking status, // and set to be either conflict or mergeable. func markPullRequestAsMergeable(ctx context.Context, pr *issues_model.PullRequest) { - // If status has not been changed to conflict by testPullRequestTmpRepoBranchMergeable then we are mergeable + // If the status has not been changed to conflict by testPullRequestTmpRepoBranchMergeable then we are mergeable if pr.Status == issues_model.PullRequestStatusChecking { pr.Status = issues_model.PullRequestStatusMergeable } @@ -257,6 +258,16 @@ func markPullRequestAsMergeable(ctx context.Context, pr *issues_model.PullReques if err := pr.UpdateColsIfNotMerged(ctx, "merge_base", "status", "conflicted_files", "changed_protected_files"); err != nil { log.Error("Update[%-v]: %v", pr, err) } + + // if there is a scheduled merge for this pull request, start the auto merge check (again) + exist, _, err := pull.GetScheduledMergeByPullID(ctx, pr.ID) + if err != nil { + log.Error("GetScheduledMergeByPullID[%-v]: %v", pr, err) + return + } else if !exist { + return + } + automergequeue.StartPRCheckAndAutoMerge(ctx, pr) } // getMergeCommit checks if a pull request has been merged diff --git a/services/pull/check_test.go b/services/pull/check_test.go index fa3a676ef1..eb66615dcf 100644 --- a/services/pull/check_test.go +++ b/services/pull/check_test.go @@ -1,5 +1,4 @@ -// Copyright 2019 The Gitea Authors. -// All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package pull @@ -11,11 +10,18 @@ import ( "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/pull" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/services/automergequeue" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestPullRequest_AddToTaskQueue(t *testing.T) { @@ -63,6 +69,46 @@ func TestPullRequest_AddToTaskQueue(t *testing.T) { pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) assert.Equal(t, issues_model.PullRequestStatusChecking, pr.Status) - prPatchCheckerQueue.ShutdownWait(5 * time.Second) + prPatchCheckerQueue.ShutdownWait(time.Second) prPatchCheckerQueue = nil } + +func TestMarkPullRequestAsMergeable(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + prPatchCheckerQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "pr_patch_checker", func(items ...string) []string { return nil }) + go prPatchCheckerQueue.Run() + defer func() { + prPatchCheckerQueue.ShutdownWait(time.Second) + prPatchCheckerQueue = nil + }() + + addToQueueShaChan := make(chan string, 1) + defer test.MockVariableValue(&automergequeue.AddToQueue, func(pr *issues_model.PullRequest, sha string) { + addToQueueShaChan <- sha + })() + ctx := t.Context() + _, _ = db.GetEngine(ctx).ID(2).Update(&issues_model.PullRequest{Status: issues_model.PullRequestStatusChecking}) + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) + require.False(t, pr.HasMerged) + require.Equal(t, issues_model.PullRequestStatusChecking, pr.Status) + + err := pull.ScheduleAutoMerge(ctx, &user_model.User{ID: 99999}, pr.ID, repo_model.MergeStyleMerge, "test msg", true) + require.NoError(t, err) + + exist, scheduleMerge, err := pull.GetScheduledMergeByPullID(ctx, pr.ID) + require.NoError(t, err) + assert.True(t, exist) + assert.True(t, scheduleMerge.Doer.IsGhost()) + + markPullRequestAsMergeable(ctx, pr) + pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) + require.Equal(t, issues_model.PullRequestStatusMergeable, pr.Status) + + select { + case sha := <-addToQueueShaChan: + assert.Equal(t, "985f0301dba5e7b34be866819cd15ad3d8f508ee", sha) // ref: refs/pull/3/head + case <-time.After(1 * time.Second): + assert.FailNow(t, "Timeout: nothing was added to automergequeue") + } +} diff --git a/tests/integration/git_general_test.go b/tests/integration/git_general_test.go index 3b0f9589d2..e72b7b4ff1 100644 --- a/tests/integration/git_general_test.go +++ b/tests/integration/git_general_test.go @@ -16,6 +16,7 @@ import ( "path/filepath" "slices" "strconv" + "strings" "testing" "time" @@ -489,40 +490,60 @@ func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *tes } func doProtectBranch(ctx APITestContext, branch, userToWhitelistPush, userToWhitelistForcePush, unprotectedFilePatterns, protectedFilePatterns string) func(t *testing.T) { + return doProtectBranchExt(ctx, branch, doProtectBranchOptions{ + UserToWhitelistPush: userToWhitelistPush, + UserToWhitelistForcePush: userToWhitelistForcePush, + UnprotectedFilePatterns: unprotectedFilePatterns, + ProtectedFilePatterns: protectedFilePatterns, + }) +} + +type doProtectBranchOptions struct { + UserToWhitelistPush, UserToWhitelistForcePush, UnprotectedFilePatterns, ProtectedFilePatterns string + + StatusCheckPatterns []string +} + +func doProtectBranchExt(ctx APITestContext, ruleName string, opts doProtectBranchOptions) func(t *testing.T) { // We are going to just use the owner to set the protection. return func(t *testing.T) { csrf := GetUserCSRFToken(t, ctx.Session) formData := map[string]string{ "_csrf": csrf, - "rule_name": branch, - "unprotected_file_patterns": unprotectedFilePatterns, - "protected_file_patterns": protectedFilePatterns, + "rule_name": ruleName, + "unprotected_file_patterns": opts.UnprotectedFilePatterns, + "protected_file_patterns": opts.ProtectedFilePatterns, } - if userToWhitelistPush != "" { - user, err := user_model.GetUserByName(db.DefaultContext, userToWhitelistPush) + if opts.UserToWhitelistPush != "" { + user, err := user_model.GetUserByName(db.DefaultContext, opts.UserToWhitelistPush) assert.NoError(t, err) formData["whitelist_users"] = strconv.FormatInt(user.ID, 10) formData["enable_push"] = "whitelist" formData["enable_whitelist"] = "on" } - if userToWhitelistForcePush != "" { - user, err := user_model.GetUserByName(db.DefaultContext, userToWhitelistForcePush) + if opts.UserToWhitelistForcePush != "" { + user, err := user_model.GetUserByName(db.DefaultContext, opts.UserToWhitelistForcePush) assert.NoError(t, err) formData["force_push_allowlist_users"] = strconv.FormatInt(user.ID, 10) formData["enable_force_push"] = "whitelist" formData["enable_force_push_allowlist"] = "on" } + if len(opts.StatusCheckPatterns) > 0 { + formData["enable_status_check"] = "on" + formData["status_check_contexts"] = strings.Join(opts.StatusCheckPatterns, "\n") + } + // Send the request to update branch protection settings req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), formData) ctx.Session.MakeRequest(t, req, http.StatusSeeOther) - // Check if master branch has been locked successfully + // Check if the "master" branch has been locked successfully flashMsg := ctx.Session.GetCookieFlashMessage() - assert.Equal(t, `Branch protection for rule "`+branch+`" has been updated.`, flashMsg.SuccessMsg) + assert.Equal(t, `Branch protection for rule "`+ruleName+`" has been updated.`, flashMsg.SuccessMsg) } } @@ -688,6 +709,10 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository) + // automerge will merge immediately if the PR is mergeable and there is no "status check" because no status check also means "all checks passed" + // so we must set up a status check to test the auto merge feature + doProtectBranchExt(ctx, "protected", doProtectBranchOptions{StatusCheckPatterns: []string{"*"}})(t) + t.Run("CheckoutProtected", doGitCheckoutBranch(dstPath, "protected")) t.Run("PullProtected", doGitPull(dstPath, "origin", "protected")) t.Run("GenerateCommit", func(t *testing.T) { @@ -728,13 +753,13 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { // Cancel not existing auto merge ctx.ExpectedCode = http.StatusNotFound - t.Run("CancelAutoMergePR", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) + t.Run("CancelAutoMergePRNotExist", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) // Add auto merge request ctx.ExpectedCode = http.StatusCreated t.Run("AutoMergePR", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) - // Can not create schedule twice + // Cannot create schedule twice ctx.ExpectedCode = http.StatusConflict t.Run("AutoMergePRTwice", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index 73b4c22070..897a78cef4 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -35,6 +35,7 @@ import ( "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/services/automerge" + "code.gitea.io/gitea/services/automergequeue" pull_service "code.gitea.io/gitea/services/pull" repo_service "code.gitea.io/gitea/services/repository" commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus" @@ -727,7 +728,7 @@ func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) { // add protected branch for commit status csrf := GetUserCSRFToken(t, session) - // Change master branch to protected + // Change the "master" branch to "protected" req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{ "_csrf": csrf, "rule_name": "master", @@ -737,10 +738,22 @@ func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) { }) session.MakeRequest(t, req, http.StatusSeeOther) + oldAutoMergeAddToQueue := automergequeue.AddToQueue + addToQueueShaChan := make(chan string, 1) + automergequeue.AddToQueue = func(pr *issues_model.PullRequest, sha string) { + addToQueueShaChan <- sha + } // first time insert automerge record, return true scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test", false) assert.NoError(t, err) assert.True(t, scheduled) + // and the pr should be added to automergequeue, in case it is already "mergeable" + select { + case <-addToQueueShaChan: + case <-time.After(time.Second): + assert.FailNow(t, "Timeout: nothing was added to automergequeue") + } + automergequeue.AddToQueue = oldAutoMergeAddToQueue // second time insert automerge record, return false because it does exist scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test", false) @@ -775,13 +788,11 @@ func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) { }) assert.NoError(t, err) - time.Sleep(2 * time.Second) - - // realod pr again - pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID}) - assert.True(t, pr.HasMerged) + assert.Eventually(t, func() bool { + pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID}) + return pr.HasMerged + }, 2*time.Second, 100*time.Millisecond) assert.NotEmpty(t, pr.MergedCommitID) - unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{PullID: pr.ID}) }) } From d3d357a4a4bfdd3a2ebd81021e5d62b1cf5cd59d Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 8 Jul 2025 23:44:14 +0200 Subject: [PATCH 4/6] Tweak placement of diff file menu (#34999) Small tweak for better visual placement. Before: Screenshot 2025-07-08 at 18 16 51 After: Screenshot 2025-07-08 at 18 16 34 Placement matches the "..." button above. --- web_src/js/features/repo-diff.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/web_src/js/features/repo-diff.ts b/web_src/js/features/repo-diff.ts index ad1da5c2fa..bde7ec0324 100644 --- a/web_src/js/features/repo-diff.ts +++ b/web_src/js/features/repo-diff.ts @@ -138,7 +138,14 @@ function initDiffHeaderPopup() { btn.setAttribute('data-header-popup-initialized', ''); const popup = btn.nextElementSibling; if (!popup?.matches('.tippy-target')) throw new Error('Popup element not found'); - createTippy(btn, {content: popup, theme: 'menu', placement: 'bottom', trigger: 'click', interactive: true, hideOnClick: true}); + createTippy(btn, { + content: popup, + theme: 'menu', + placement: 'bottom-end', + trigger: 'click', + interactive: true, + hideOnClick: true, + }); } } From f1b78f3cdd5ae23fd679c9c1a85f4f1e85e92857 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 9 Jul 2025 06:49:30 +0800 Subject: [PATCH 5/6] Fix bug when displaying git user avatar in commits list (#35003) A quick fix for #34991 `ValidateCommitsWithEmails` will create a fake user for a git commit user with a related Gitea user. The UI should not display a link for such users. --- templates/repo/commits_list.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/commits_list.tmpl b/templates/repo/commits_list.tmpl index 959f2a9398..9dae6594b9 100644 --- a/templates/repo/commits_list.tmpl +++ b/templates/repo/commits_list.tmpl @@ -16,7 +16,7 @@
{{$userName := .Author.Name}} - {{if .User}} + {{if and .User (gt .User.ID 0)}} /* User with id == 0 is a fake user from git author */ {{if and .User.FullName DefaultShowFullName}} {{$userName = .User.FullName}} {{end}} From 2cc33686104da45b426eb4bb4892468f612ac404 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Wed, 9 Jul 2025 00:38:25 +0000 Subject: [PATCH 6/6] [skip ci] Updated translations via Crowdin --- options/locale/locale_zh-CN.ini | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 2a97941d6b..f2a3d29bec 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -1400,12 +1400,12 @@ editor.revert=将 %s 还原到: editor.failed_to_commit=提交更改失败。 editor.failed_to_commit_summary=错误信息: -editor.fork_create=派生仓库发起请求变更 -editor.fork_create_description=您不能直接编辑此仓库。您可以从此仓库派生,进行编辑并创建一个拉取请求。 -editor.fork_edit_description=您不能直接编辑此仓库。 更改将写入您的派生仓库 %s,以便您可以创建一个拉取请求。 -editor.fork_not_editable=你已经派生了这个仓库,但是你的分叉是不可编辑的。 +editor.fork_create=派生仓库以请求变更 +editor.fork_create_description=您不能直接编辑此仓库。您可以派生此仓库,进行编辑并创建一个合并请求。 +editor.fork_edit_description=您不能直接编辑此仓库。 更改将写入您的派生仓库 %s,以便您可以创建一个合并请求。 +editor.fork_not_editable=您已经派生了此仓库,但您的派生是不可编辑的。 editor.fork_failed_to_push_branch=推送分支 %s 到仓库失败。 -editor.fork_branch_exists=分支 "%s" 已存在于您的派生仓库中,请选择一个新的分支名称。 +editor.fork_branch_exists=分支「%s」已存在于您的派生仓库中,请选择一个新的分支名称。 commits.desc=浏览代码修改历史 commits.commits=次代码提交 @@ -2171,8 +2171,8 @@ settings.hooks=Web 钩子 settings.githooks=管理 Git 钩子 settings.basic_settings=基本设置 settings.mirror_settings=镜像设置 -settings.mirror_settings.docs=设置您的仓库以自动同步另一个仓库的提交、标签和分支。 -settings.mirror_settings.docs.disabled_pull_mirror.instructions=设置您的项目以自动将提交、标签和分支推送到另一个仓库。您的站点管理员已禁用了拉取镜像。 +settings.mirror_settings.docs=将您的仓库设置为自动同步另一个仓库的提交、标签和分支。 +settings.mirror_settings.docs.disabled_pull_mirror.instructions=将您的项目设置为自动将提交、标签和分支推送到另一个仓库。您的站点管理员已禁用了拉取镜像。 settings.mirror_settings.docs.disabled_push_mirror.instructions=将您的项目设置为自动从一个仓库拉取提交、标签和分支。 settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=现在,这只能在「迁移外部仓库」菜单中完成。欲了解更多信息,请参考: settings.mirror_settings.docs.disabled_push_mirror.info=您的站点管理员已禁用推送镜像。 @@ -2335,6 +2335,8 @@ settings.hooks_desc=当 Gitea 事件发生时,Web 钩子自动发出 HTTP POST settings.webhook_deletion=删除 Web 钩子 settings.webhook_deletion_desc=删除 Web 钩子将删除其设置和历史记录。继续? settings.webhook_deletion_success=Web 钩子删除成功! +settings.webhook.test_delivery=测试推送事件 +settings.webhook.test_delivery_desc=用假推送事件测试这个 Web 钩子。 settings.webhook.test_delivery_desc_disabled=要用假事件测试这个 Web钩子,请激活它。 settings.webhook.request=请求内容 settings.webhook.response=响应内容 @@ -2354,6 +2356,7 @@ settings.payload_url=目标 URL settings.http_method=HTTP 方法 settings.content_type=POST 内容类型 settings.secret=密钥 +settings.webhook_secret_desc=如果 Webhook 服务器支持使用密钥,您可以按照 Webhook 的手册在此处填写一个密钥。 settings.slack_username=服务名称 settings.slack_icon_url=图标 URL settings.slack_color=颜色 @@ -2768,6 +2771,8 @@ branch.new_branch_from=基于「%s」创建新分支 branch.renamed=分支 %s 已重命名为 %s。 branch.rename_default_or_protected_branch_error=只有管理员能重命名默认分支和受保护的分支。 branch.rename_protected_branch_failed=此分支受到 glob 语法规则的保护。 +branch.commits_divergence_from=提交分歧:落后 %[3]s %[1]d 个提交,领先 %[2]d 个提交 +branch.commits_no_divergence=与分支 %[1]s 相同 tag.create_tag=创建标签 %s tag.create_tag_operation=创建标签 @@ -2781,6 +2786,7 @@ topic.done=保存 topic.count_prompt=您最多选择25个主题 topic.format_prompt=主题必须以字母或数字开头,可以包含连字符 ('-') 和句点 ('.'),长度不得超过35个字符。字符必须为小写。 +find_file.follow_symlink=跟随此符号链接的指向位置 find_file.go_to_file=转到文件 find_file.no_matching=没有找到匹配的文件