Skip to content

Commit

Permalink
Auto merge pull requests when all checks succeeded via WebUI (#19648)
Browse files Browse the repository at this point in the history
Add WebUI part of Auto merge feature

close #19621

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: delvh <dev.lh@web.de>
  • Loading branch information
3 people authored Jun 11, 2022
1 parent ce3dd04 commit a9cc9c0
Show file tree
Hide file tree
Showing 10 changed files with 300 additions and 137 deletions.
29 changes: 13 additions & 16 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1568,14 +1568,7 @@ pulls.squash_merge_pull_request = Create squash commit
pulls.merge_manually = Manually merged
pulls.merge_commit_id = The merge commit ID
pulls.require_signed_wont_sign = The branch requires signed commits but this merge will not be signed
pulls.merge_pull_request_now = Merge Pull Request Now
pulls.rebase_merge_pull_request_now = Rebase and Merge Now
pulls.rebase_merge_commit_pull_request_now = Rebase and Merge Now (--no-ff)
pulls.squash_merge_pull_request_now = Squash and Merge Now
pulls.merge_pull_request_on_status_success = Merge Pull Request When All Checks Succeed
pulls.rebase_merge_pull_request_on_status_success = Rebase and Merge When All Checks Succeed
pulls.rebase_merge_commit_pull_request_on_status_success = Rebase and Merge (--no-ff) When All Checks Succeed
pulls.squash_merge_pull_request_on_status_success = Squash and Merge When All Checks Succeed

pulls.invalid_merge_option = You cannot use this merge option for this pull request.
pulls.merge_conflict = Merge Failed: There was a conflict whilst merging. Hint: Try a different strategy
pulls.merge_conflict_summary = Error Message
Expand Down Expand Up @@ -1606,14 +1599,18 @@ pulls.reopened_at = `reopened this pull request <a id="%[1]s" href="#%[1]s">%[2]
pulls.merge_instruction_hint = `You can also view <a class="show-instruction">command line instructions</a>.`
pulls.merge_instruction_step1_desc = From your project repository, check out a new branch and test the changes.
pulls.merge_instruction_step2_desc = Merge the changes and update on Gitea.
pulls.merge_on_status_success = The pull request was scheduled to merge when all checks succeed.
pulls.merge_on_status_success_already_scheduled = This pull request is already scheduled to merge when all checks succeed.
pulls.pr_has_pending_merge_on_success = %[1]s scheduled this pull request to auto merge when all checks succeed %[2]s.
pulls.merge_pull_on_success_cancel = Cancel auto merge
pulls.pull_request_not_scheduled = This pull request is not scheduled to auto merge.
pulls.pull_request_schedule_canceled = The auto merge was canceled for this pull request.
pulls.pull_request_scheduled_auto_merge = `scheduled this pull request to auto merge when all checks succeed %[1]s`
pulls.pull_request_canceled_scheduled_auto_merge = `canceled auto merging this pull request when all checks succeed %[1]s`

pulls.auto_merge_button_when_succeed = (When checks succeed)
pulls.auto_merge_when_succeed = Auto merge when all checks succeed
pulls.auto_merge_newly_scheduled = The pull request was scheduled to merge when all checks succeed.
pulls.auto_merge_has_pending_schedule = %[1]s scheduled this pull request to auto merge when all checks succeed %[2]s.

pulls.auto_merge_cancel_schedule = Cancel auto merge
pulls.auto_merge_not_scheduled = This pull request is not scheduled to auto merge.
pulls.auto_merge_canceled_schedule = The auto merge was canceled for this pull request.

pulls.auto_merge_newly_scheduled_comment = `scheduled this pull request to auto merge when all checks succeed %[1]s`
pulls.auto_merge_canceled_schedule_comment = `canceled auto merging this pull request when all checks succeed %[1]s`

milestones.new = New Milestone
milestones.open_tab = %d Open
Expand Down
38 changes: 38 additions & 0 deletions routers/web/repo/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
access_model "code.gitea.io/gitea/models/perm/access"
pull_model "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"
Expand All @@ -36,6 +37,7 @@ import (
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/routers/utils"
asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/automerge"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/gitdiff"
pull_service "code.gitea.io/gitea/services/pull"
Expand Down Expand Up @@ -966,6 +968,22 @@ func MergePullRequest(ctx *context.Context) {
message += "\n\n" + form.MergeMessageField
}

if form.MergeWhenChecksSucceed {
// delete all scheduled auto merges
_ = pull_model.DeleteScheduledAutoMerge(ctx, pr.ID)
// schedule auto merge
scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message)
if err != nil {
ctx.ServerError("ScheduleAutoMerge", err)
return
} else if scheduled {
// nothing more to do ...
ctx.Flash.Success(ctx.Tr("repo.pulls.auto_merge_newly_scheduled"))
ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, pr.Index))
return
}
}

if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message); err != nil {
if models.IsErrInvalidMergeStyle(err) {
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
Expand Down Expand Up @@ -1070,6 +1088,26 @@ func MergePullRequest(ctx *context.Context) {
ctx.Redirect(issue.Link())
}

// CancelAutoMergePullRequest cancels a scheduled pr
func CancelAutoMergePullRequest(ctx *context.Context) {
issue := checkPullInfo(ctx)
if ctx.Written() {
return
}

if err := automerge.RemoveScheduledAutoMerge(ctx, ctx.Doer, issue.PullRequest); err != nil {
if db.IsErrNotExist(err) {
ctx.Flash.Error(ctx.Tr("repo.pulls.auto_merge_not_scheduled"))
ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, issue.Index))
return
}
ctx.ServerError("RemoveScheduledAutoMerge", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.pulls.auto_merge_canceled_schedule"))
ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, issue.Index))
}

func stopTimerIfAvailable(user *user_model.User, issue *models.Issue) error {
if models.StopwatchExists(user.ID, issue.ID) {
if err := models.CreateOrStopIssueStopwatch(user, issue); err != nil {
Expand Down
1 change: 1 addition & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,7 @@ func RegisterRoutes(m *web.Route) {
m.Get(".patch", repo.DownloadPullPatch)
m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
m.Post("/merge", context.RepoMustNotBeArchived(), bindIgnErr(forms.MergePullRequestForm{}), repo.MergePullRequest)
m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest)
m.Post("/update", repo.UpdatePullRequest)
m.Post("/set_allow_maintainer_edit", bindIgnErr(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits)
m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
Expand Down
4 changes: 2 additions & 2 deletions templates/repo/issue/view_content/comments.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -843,8 +843,8 @@
<span class="badge">{{svg "octicon-git-merge" 16}}</span>
<span class="text grey">
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
{{if eq .Type 34}}{{$.i18n.Tr "repo.pulls.pull_request_scheduled_auto_merge" $createdStr | Safe}}
{{else}}{{$.i18n.Tr "repo.pulls.pull_request_canceled_scheduled_auto_merge" $createdStr | Safe}}{{end}}
{{if eq .Type 34}}{{$.i18n.Tr "repo.pulls.auto_merge_newly_scheduled_comment" $createdStr | Safe}}
{{else}}{{$.i18n.Tr "repo.pulls.auto_merge_canceled_schedule_comment" $createdStr | Safe}}{{end}}
</span>
</div>
{{end}}
Expand Down
198 changes: 101 additions & 97 deletions templates/repo/issue/view_content/pull.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,14 @@
{{$.i18n.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason) }}
</div>
{{end}}

{{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}}
{{if and (or $.IsRepoAdmin (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}

{{/* admin can merge without checks, writer can merge when checkes succeed */}}
{{$canMergeNow := and (or $.IsRepoAdmin (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
{{/* admin and writer both can make an auto merge schedule */}}

{{if $canMergeNow}}
{{if $notAllOverridableChecksOk}}
<div class="item">
<i class="icon icon-octicon">{{svg "octicon-dot-fill"}}</i>
Expand All @@ -277,7 +283,6 @@
{{end}}
{{end}}

{{$canAutoMerge = true}}
{{if (gt .Issue.PullRequest.CommitsBehind 0)}}
<div class="ui divider"></div>
<div class="item item-section">
Expand Down Expand Up @@ -317,112 +322,111 @@
</div>
{{end}}

{{if and (or $.IsRepoAdmin (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
{{if .AllowMerge}}
{{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}}
{{$approvers := .Issue.PullRequest.GetApprovers}}
{{if or $prUnit.PullRequestsConfig.AllowMerge $prUnit.PullRequestsConfig.AllowRebase $prUnit.PullRequestsConfig.AllowRebaseMerge $prUnit.PullRequestsConfig.AllowSquash}}
{{if .AllowMerge}} {{/* user is allowed to merge */}}
{{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}}
{{$approvers := .Issue.PullRequest.GetApprovers}}
{{if or $prUnit.PullRequestsConfig.AllowMerge $prUnit.PullRequestsConfig.AllowRebase $prUnit.PullRequestsConfig.AllowRebaseMerge $prUnit.PullRequestsConfig.AllowSquash}}
{{$hasPendingPullRequestMergeTip := ""}}
{{if .HasPendingPullRequestMerge}}
{{$createdPRMergeStr := TimeSinceUnix .PendingPullRequestMerge.CreatedUnix $.i18n.Lang}}
{{$hasPendingPullRequestMergeTip = $.i18n.Tr "repo.pulls.auto_merge_has_pending_schedule" .PendingPullRequestMerge.Doer.Name $createdPRMergeStr}}
{{end}}
<div class="ui divider"></div>
<script>
<!-- /* eslint-disable */ -->
(() => {
const defaultMergeTitle = {{.DefaultMergeMessage}};
const defaultSquashMergeTitle = {{.DefaultSquashMergeMessage}};
const defaultMergeMessage = 'Reviewed-on: ' + {{$.Issue.HTMLURL}} + '\n' + {{$approvers}};
const mergeForm = {
'baseLink': {{.Link}},
'textCancel': {{$.i18n.Tr "cancel"}},
'textDeleteBranch': {{$.i18n.Tr "repo.branch.delete" .HeadTarget}},
'textAutoMergeButtonWhenSucceed': {{$.i18n.Tr "repo.pulls.auto_merge_button_when_succeed"}},
'textAutoMergeWhenSucceed': {{$.i18n.Tr "repo.pulls.auto_merge_when_succeed"}},
'textAutoMergeCancelSchedule': {{$.i18n.Tr "repo.pulls.auto_merge_cancel_schedule"}},
<div class="ui divider"></div>
'canMergeNow': {{$canMergeNow}},
'allOverridableChecksOk': {{not $notAllOverridableChecksOk}},
'pullHeadCommitID': {{.PullHeadCommitID}},
'isPullBranchDeletable': {{.IsPullBranchDeletable}},
'defaultDeleteBranchAfterMerge': {{$prUnit.PullRequestsConfig.DefaultDeleteBranchAfterMerge}},
'mergeMessageFieldPlaceHolder': {{$.i18n.Tr "repo.editor.commit_message_desc"}},
<script>
<!-- /* eslint-disable */ -->
(() => {
const defaultMergeTitle = {{.DefaultMergeMessage}};
const defaultSquashMergeTitle = {{.DefaultSquashMergeMessage}};
const defaultMergeMessage = 'Reviewed-on: ' + {{$.Issue.HTMLURL}} + '\n' + {{$approvers}};
const mergeForm = {
'baseLink': {{.Link}},
'textCancel': {{$.i18n.Tr "cancel"}},
'textDeleteBranch': {{$.i18n.Tr "repo.branch.delete" .HeadTarget}},
'hasPendingPullRequestMerge': {{.HasPendingPullRequestMerge}},
'hasPendingPullRequestMergeTip': {{$hasPendingPullRequestMergeTip}},
};
'allOverridableChecksOk': {{not $notAllOverridableChecksOk}},
'pullHeadCommitID': {{.PullHeadCommitID}},
'isPullBranchDeletable': {{.IsPullBranchDeletable}},
'defaultDeleteBranchAfterMerge': {{$prUnit.PullRequestsConfig.DefaultDeleteBranchAfterMerge}},
'mergeMessageFieldPlaceHolder': {{$.i18n.Tr "repo.editor.commit_message_desc"}},
};
mergeForm['mergeStyles'] = [
{
'name': 'merge',
'allowed': {{$prUnit.PullRequestsConfig.AllowMerge}},
'textDoMerge': {{$.i18n.Tr "repo.pulls.merge_pull_request"}},
'mergeTitleFieldText': defaultMergeTitle,
'mergeMessageFieldText': defaultMergeMessage,
},
{
'name': 'rebase',
'allowed': {{$prUnit.PullRequestsConfig.AllowRebase}},
'textDoMerge': {{$.i18n.Tr "repo.pulls.rebase_merge_pull_request"}},
'hideMergeMessageTexts': true,
},
{
'name': 'rebase-merge',
'allowed': {{$prUnit.PullRequestsConfig.AllowRebaseMerge}},
'textDoMerge': {{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}},
'mergeTitleFieldText': defaultMergeTitle,
'mergeMessageFieldText': defaultMergeMessage,
},
{
'name': 'squash',
'allowed': {{$prUnit.PullRequestsConfig.AllowSquash}},
'textDoMerge': {{$.i18n.Tr "repo.pulls.squash_merge_pull_request"}},
'mergeTitleFieldText': defaultSquashMergeTitle,
'mergeMessageFieldText': defaultMergeMessage,
},
{
'name': 'manually-merged',
'allowed': {{and $prUnit.PullRequestsConfig.AllowManualMerge $.IsRepoAdmin}},
'textDoMerge': {{$.i18n.Tr "repo.pulls.merge_manually"}},
'hideMergeMessageTexts': true,
}
];
window.config.pageData.pullRequestMergeForm = mergeForm;
})();
</script>
const generalHideAutoMerge = mergeForm.canMergeNow && mergeForm.allOverridableChecksOk; // if this PR can be merged now, then hide the auto merge
mergeForm['mergeStyles'] = [
{
'name': 'merge',
'allowed': {{$prUnit.PullRequestsConfig.AllowMerge}},
'textDoMerge': {{$.i18n.Tr "repo.pulls.merge_pull_request"}},
'mergeTitleFieldText': defaultMergeTitle,
'mergeMessageFieldText': defaultMergeMessage,
'hideAutoMerge': generalHideAutoMerge,
},
{
'name': 'rebase',
'allowed': {{$prUnit.PullRequestsConfig.AllowRebase}},
'textDoMerge': {{$.i18n.Tr "repo.pulls.rebase_merge_pull_request"}},
'hideMergeMessageTexts': true,
'hideAutoMerge': generalHideAutoMerge,
},
{
'name': 'rebase-merge',
'allowed': {{$prUnit.PullRequestsConfig.AllowRebaseMerge}},
'textDoMerge': {{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}},
'mergeTitleFieldText': defaultMergeTitle,
'mergeMessageFieldText': defaultMergeMessage,
'hideAutoMerge': generalHideAutoMerge,
},
{
'name': 'squash',
'allowed': {{$prUnit.PullRequestsConfig.AllowSquash}},
'textDoMerge': {{$.i18n.Tr "repo.pulls.squash_merge_pull_request"}},
'mergeTitleFieldText': defaultSquashMergeTitle,
'mergeMessageFieldText': defaultMergeMessage,
'hideAutoMerge': generalHideAutoMerge,
},
{
'name': 'manually-merged',
'allowed': {{and $prUnit.PullRequestsConfig.AllowManualMerge $.IsRepoAdmin}},
'textDoMerge': {{$.i18n.Tr "repo.pulls.merge_manually"}},
'hideMergeMessageTexts': true,
'hideAutoMerge': true,
}
];
window.config.pageData.pullRequestMergeForm = mergeForm;
})();
</script>

<div id="pull-request-merge-form"></div>
<div id="pull-request-merge-form"></div>

{{if .ShowMergeInstructions}}
<div class="instruct-toggle mt-3"> {{$.i18n.Tr "repo.pulls.merge_instruction_hint" | Safe}} </div>
<div class="instruct-content" style="display:none">
<div class="ui divider"></div>
<div><h3 class="di">{{$.i18n.Tr "step1"}} </h3>{{$.i18n.Tr "repo.pulls.merge_instruction_step1_desc"}}</div>
<div class="ui secondary segment">
{{if eq .Issue.PullRequest.Flow 0}}
<div>git checkout -b {{if ne .Issue.PullRequest.HeadRepo.ID .Issue.PullRequest.BaseRepo.ID}}{{.Issue.PullRequest.HeadRepo.OwnerName}}-{{end}}{{.Issue.PullRequest.HeadBranch}} {{.Issue.PullRequest.BaseBranch}}</div>
<div>git pull {{if ne .Issue.PullRequest.HeadRepo.ID .Issue.PullRequest.BaseRepo.ID}}{{.Issue.PullRequest.HeadRepo.HTMLURL}}{{else}}origin{{end}} {{.Issue.PullRequest.HeadBranch}}</div>
{{else}}
<div>git fetch origin {{.Issue.PullRequest.GetGitRefName}}:{{.Issue.PullRequest.HeadBranch}}</div>
{{end}}
</div>
<div><h3 class="di">{{$.i18n.Tr "step2"}} </h3>{{$.i18n.Tr "repo.pulls.merge_instruction_step2_desc"}}</div>
<div class="ui secondary segment">
<div>git checkout {{.Issue.PullRequest.BaseBranch}}</div>
<div>git merge --no-ff {{if ne .Issue.PullRequest.HeadRepo.ID .Issue.PullRequest.BaseRepo.ID}}{{.Issue.PullRequest.HeadRepo.OwnerName}}-{{end}}{{.Issue.PullRequest.HeadBranch}}</div>
<div>git push origin {{.Issue.PullRequest.BaseBranch}}</div>
</div>
</div>
{{end}}
{{else}}
<div class="ui divider"></div>
<div class="item text red">
{{svg "octicon-x"}}
{{$.i18n.Tr "repo.pulls.no_merge_desc"}}
</div>
<div class="item">
{{svg "octicon-info"}}
{{$.i18n.Tr "repo.pulls.no_merge_helper"}}
</div>
{{if .ShowMergeInstructions}}
{{template "repo/issue/view_content/pull_merge_instruction" (dict "i18n" .i18n "Issue" .Issue)}}
{{end}}
{{else}}
{{/* no merge style was set in repo setting: not or ($prUnit.PullRequestsConfig.AllowMerge ...) */}}
<div class="ui divider"></div>
<div class="item text red">
{{svg "octicon-x"}}
{{$.i18n.Tr "repo.pulls.no_merge_desc"}}
</div>
<div class="item">
{{svg "octicon-info"}}
{{$.i18n.Tr "repo.pulls.no_merge_access"}}
{{$.i18n.Tr "repo.pulls.no_merge_helper"}}
</div>
{{end}}
{{end}}
{{end}} {{/* end if the repo was set to use any merge style */}}
{{else}}
{{/* user is not allowed to merge */}}
<div class="ui divider"></div>
<div class="item">
{{svg "octicon-info"}}
{{$.i18n.Tr "repo.pulls.no_merge_access"}}
</div>
{{end}} {{/* end if user is allowed to merge or not */}}
{{else}}
{{/* Merge conflict without specific file. Suggest manual merge, only if all reviews and status checks OK. */}}
{{if .IsBlockedByApprovals}}
Expand Down
19 changes: 19 additions & 0 deletions templates/repo/issue/view_content/pull_merge_instruction.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div class="instruct-toggle mt-3"> {{$.i18n.Tr "repo.pulls.merge_instruction_hint" | Safe}} </div>
<div class="instruct-content" style="display:none">
<div class="ui divider"></div>
<div><h3 class="di">{{$.i18n.Tr "step1"}} </h3>{{$.i18n.Tr "repo.pulls.merge_instruction_step1_desc"}}</div>
<div class="ui secondary segment">
{{if eq $.Issue.PullRequest.Flow 0}}
<div>git checkout -b {{if ne $.Issue.PullRequest.HeadRepo.ID $.Issue.PullRequest.BaseRepo.ID}}{{$.Issue.PullRequest.HeadRepo.OwnerName}}-{{end}}{{$.Issue.PullRequest.HeadBranch}} {{$.Issue.PullRequest.BaseBranch}}</div>
<div>git pull {{if ne $.Issue.PullRequest.HeadRepo.ID $.Issue.PullRequest.BaseRepo.ID}}{{$.Issue.PullRequest.HeadRepo.HTMLURL}}{{else}}origin{{end}} {{$.Issue.PullRequest.HeadBranch}}</div>
{{else}}
<div>git fetch origin {{$.Issue.PullRequest.GetGitRefName}}:{{$.Issue.PullRequest.HeadBranch}}</div>
{{end}}
</div>
<div><h3 class="di">{{$.i18n.Tr "step2"}} </h3>{{$.i18n.Tr "repo.pulls.merge_instruction_step2_desc"}}</div>
<div class="ui secondary segment">
<div>git checkout {{$.Issue.PullRequest.BaseBranch}}</div>
<div>git merge --no-ff {{if ne $.Issue.PullRequest.HeadRepo.ID $.Issue.PullRequest.BaseRepo.ID}}{{$.Issue.PullRequest.HeadRepo.OwnerName}}-{{end}}{{$.Issue.PullRequest.HeadBranch}}</div>
<div>git push origin {{$.Issue.PullRequest.BaseBranch}}</div>
</div>
</div>
Loading

0 comments on commit a9cc9c0

Please sign in to comment.