diff --git a/docs/content/doc/usage/push-options.en-us.md b/docs/content/doc/usage/push-options.en-us.md index 8d7de19609c83..fb30a9d5a3088 100644 --- a/docs/content/doc/usage/push-options.en-us.md +++ b/docs/content/doc/usage/push-options.en-us.md @@ -31,3 +31,15 @@ Example of changing a repository's visibility to public: ```shell git push -o repo.private=false -u origin master ``` + +- `pulls.merged` (pull index0 [, pull index1]*) - manually merge comfirmed pulls + + notify service that these pull requst has been manually merged by this push + event. then service will mark these pulls as manually merged if manually merge check pass. + if push manually merged pulls without this push option, they maybe will become to empty pulls. + + Example: + + ```shell + git push -o pulls.merged=1,2,3 -u origin master + ``` diff --git a/models/issues/pull.go b/models/issues/pull.go index 3f8b0bc7acb6c..20571de46bf06 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -198,6 +198,9 @@ type PullRequest struct { isHeadRepoLoaded bool `xorm:"-"` Flow PullRequestFlow `xorm:"NOT NULL DEFAULT 0"` + + ManuallyMergeConfirmed bool `xorm:"NOT NULL DEFAULT false"` + ManuallyMergeConfirmedVersion int64 `xorm:"NOT NULL DEFAULT 0"` } func init() { @@ -872,3 +875,29 @@ func MergeBlockedByOfficialReviewRequests(ctx context.Context, protectBranch *gi func MergeBlockedByOutdatedBranch(protectBranch *git_model.ProtectedBranch, pr *PullRequest) bool { return protectBranch.BlockOnOutdatedBranch && pr.CommitsBehind > 0 } + +// ManuallyMergePullConfirm confirm manually merge pulls by repo id and indexes +func ManuallyMergePullConfirmByIndexes(ctx context.Context, repoID int64, baseBranchs []string, indexes []int64) error { + _, err := db.GetEngine(ctx).Where(builder.And( + builder.Eq{"base_repo_id": repoID}, + builder.Eq{"has_merged": false}, + builder.In("`index`", indexes), + builder.In("`base_branch`", baseBranchs), + )). + Incr("manually_merge_confirmed_version"). + SetExpr("manually_merge_confirmed", true).Update(new(PullRequest)) + + return err +} + +// ResetManuallyMergePullConfirm reset manually merge confirmed flag +func (pr *PullRequest) ResetManuallyMergePullConfirm(ctx context.Context) error { + _, err := db.GetEngine(ctx). + Where(builder.And( + builder.Eq{"`id`": pr.ID}, + builder.Eq{"manually_merge_confirmed_version": pr.ManuallyMergeConfirmedVersion}, + )). + Incr("manually_merge_confirmed_version"). + SetExpr("manually_merge_confirmed", false).Update(new(PullRequest)) + return err +} diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 15600f057cfb1..82dc0a30a09be 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -455,6 +455,8 @@ var migrations = []Migration{ NewMigration("Add scope for access_token", v1_19.AddScopeForAccessTokens), // v240 -> v241 NewMigration("Add actions tables", v1_19.AddActionsTables), + // v241 -> v242 + NewMigration("Add manually_merge_pull_confirmed to pull_request table", v1_19.AddManuallyMergePullConfirmedToPullRequest), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_19/v241.go b/models/migrations/v1_19/v241.go new file mode 100644 index 0000000000000..a994165243f6d --- /dev/null +++ b/models/migrations/v1_19/v241.go @@ -0,0 +1,19 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_19 //nolint + +import ( + "xorm.io/xorm" +) + +func AddManuallyMergePullConfirmedToPullRequest(x *xorm.Engine) error { + type PullRequest struct { + ID int64 `xorm:"pk autoincr"` + + ManuallyMergeConfirmed bool `xorm:"NOT NULL DEFAULT false"` + ManuallyMergeConfirmedVersion int64 `xorm:"NOT NULL DEFAULT 0"` + } + + return x.Sync(new(PullRequest)) +} diff --git a/modules/private/hook.go b/modules/private/hook.go index 9533eaae5966a..6c3f7940cceb7 100644 --- a/modules/private/hook.go +++ b/modules/private/hook.go @@ -10,6 +10,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "time" "code.gitea.io/gitea/modules/json" @@ -31,6 +32,7 @@ type GitPushOptions map[string]string const ( GitPushOptionRepoPrivate = "repo.private" GitPushOptionRepoTemplate = "repo.template" + GitPushOptionMergePulls = "pulls.merged" ) // Bool checks for a key in the map and parses as a boolean @@ -43,6 +45,26 @@ func (g GitPushOptions) Bool(key string, def bool) bool { return def } +// Int64Array checks for a key in the map and parses as a int64 array +func (g GitPushOptions) Int64Array(key string) []int64 { + if val, ok := g[key]; ok { + parts := strings.SplitN(val, ",", 10) + result := make([]int64, 0, len(parts)) + for _, part := range parts { + v, err := strconv.ParseInt(part, 10, 64) + if err != nil { + break + } + + result = append(result, v) + } + + return result + } + + return []int64{} +} + // HookOptions represents the options for the Hook calls type HookOptions struct { OldCommitIDs []string diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index 75de47bdc4b26..bd2fdb78efa41 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -110,6 +110,29 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err), }) + return + } + + // handle manually merge pulls confirm + mergePullIndexes := opts.GitPushOptions.Int64Array(private.GitPushOptionMergePulls) + if len(mergePullIndexes) > 0 { + baseBranchs := make([]string, 0, len(opts.OldCommitIDs)) + for i := range opts.OldCommitIDs { + refFullName := opts.RefFullNames[i] + newCommitID := opts.NewCommitIDs[i] + + if newCommitID != git.EmptySHA && strings.HasPrefix(refFullName, git.BranchPrefix) { + baseBranchs = append(baseBranchs, strings.TrimPrefix(refFullName, git.BranchPrefix)) + } + } + + if err := issues_model.ManuallyMergePullConfirmByIndexes(ctx, repo.ID, baseBranchs, mergePullIndexes); err != nil { + log.Error("Failed to manually merge pull confirm: %s/%s Error: %v", ownerName, repoName, err) + ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ + Err: fmt.Sprintf("Failed to manually merge pull confirm: %s/%s Error: %v", ownerName, repoName, err), + }) + return + } } } diff --git a/services/pull/check.go b/services/pull/check.go index 481491c73bb09..8ca559806384d 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -224,6 +224,15 @@ func getMergeCommit(ctx context.Context, pr *issues_model.PullRequest) (*git.Com return commit, nil } +func resetManuallyMergePullConfirm(ctx context.Context, pr *issues_model.PullRequest) { + if pr.ManuallyMergeConfirmed { + pr.ManuallyMergeConfirmed = false + if err := pr.ResetManuallyMergePullConfirm(ctx); err != nil { + log.Error("PullRequest[%d]: reset manually_merge_pull_confirmed failed: %v", pr.ID, err) + } + } +} + // manuallyMerged checks if a pull request got manually merged // When a pull request got manually merged mark the pull request as merged func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool { @@ -232,14 +241,16 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool { return false } - if unit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests); err == nil { - config := unit.PullRequestsConfig() - if !config.AutodetectManualMerge { + if !pr.ManuallyMergeConfirmed { + if unit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests); err == nil { + config := unit.PullRequestsConfig() + if !config.AutodetectManualMerge { + return false + } + } else { + log.Error("%-v BaseRepo.GetUnit(unit.TypePullRequests): %v", pr, err) return false } - } else { - log.Error("%-v BaseRepo.GetUnit(unit.TypePullRequests): %v", pr, err) - return false } commit, err := getMergeCommit(ctx, pr) @@ -250,6 +261,7 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool { if commit == nil { // no merge commit found + resetManuallyMergePullConfirm(ctx, pr) return false } @@ -275,6 +287,7 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool { log.Error("%-v setMerged : %v", pr, err) return false } else if !merged { + resetManuallyMergePullConfirm(ctx, pr) return false } diff --git a/templates/repo/issue/view_content/pull_merge_instruction.tmpl b/templates/repo/issue/view_content/pull_merge_instruction.tmpl index 816f25cbcf901..ea44170759058 100644 --- a/templates/repo/issue/view_content/pull_merge_instruction.tmpl +++ b/templates/repo/issue/view_content/pull_merge_instruction.tmpl @@ -14,6 +14,6 @@