summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
author6543 <6543@obermui.de>2022-06-11 16:44:20 +0200
committerGitHub <noreply@github.com>2022-06-11 16:44:20 +0200
commita9cc9c0f7afacb2e51cfd1885d392f56dca0c7f5 (patch)
tree97c5ec44aa74ac8718d3760d47008f788d8e7d6d
parentce3dd04c63a048fe791ed864c2023fd37b09e427 (diff)
downloadgitea-a9cc9c0f7afacb2e51cfd1885d392f56dca0c7f5.tar.gz
gitea-a9cc9c0f7afacb2e51cfd1885d392f56dca0c7f5.zip
Auto merge pull requests when all checks succeeded via WebUI (#19648)
Add WebUI part of Auto merge feature close #19621 Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: delvh <dev.lh@web.de>
-rw-r--r--options/locale/locale_en-US.ini29
-rw-r--r--routers/web/repo/pull.go38
-rw-r--r--routers/web/web.go1
-rw-r--r--templates/repo/issue/view_content/comments.tmpl4
-rw-r--r--templates/repo/issue/view_content/pull.tmpl198
-rw-r--r--templates/repo/issue/view_content/pull_merge_instruction.tmpl19
-rw-r--r--web_src/js/components/PullRequestMergeForm.vue134
-rw-r--r--web_src/js/svg.js2
-rw-r--r--web_src/less/_base.less8
-rw-r--r--web_src/less/_repository.less4
10 files changed, 300 insertions, 137 deletions
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index c4ad714717..b1c3247315 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -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
@@ -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
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index 8df4ccc607..d698f1c49a 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -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"
@@ -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"
@@ -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"))
@@ -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 {
diff --git a/routers/web/web.go b/routers/web/web.go
index bf4c4662af..88a446d067 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -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)
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl
index 235f4c8fc2..0258a9f969 100644
--- a/templates/repo/issue/view_content/comments.tmpl
+++ b/templates/repo/issue/view_content/comments.tmpl
@@ -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}}
diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl
index c764138fa0..d2282f07f6 100644
--- a/templates/repo/issue/view_content/pull.tmpl
+++ b/templates/repo/issue/view_content/pull.tmpl
@@ -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>
@@ -277,7 +283,6 @@
{{end}}
{{end}}
- {{$canAutoMerge = true}}
{{if (gt .Issue.PullRequest.CommitsBehind 0)}}
<div class="ui divider"></div>
<div class="item item-section">
@@ -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}}
diff --git a/templates/repo/issue/view_content/pull_merge_instruction.tmpl b/templates/repo/issue/view_content/pull_merge_instruction.tmpl
new file mode 100644
index 0000000000..0ed70860f3
--- /dev/null
+++ b/templates/repo/issue/view_content/pull_merge_instruction.tmpl
@@ -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>
diff --git a/web_src/js/components/PullRequestMergeForm.vue b/web_src/js/components/PullRequestMergeForm.vue
index 40398a65cb..75fbceb800 100644
--- a/web_src/js/components/PullRequestMergeForm.vue
+++ b/web_src/js/components/PullRequestMergeForm.vue
@@ -1,9 +1,23 @@
<template>
+ <!--
+ if this component is shown, either the user is admin (can do merge without checks), or they is a writer who has the permission to do merge
+ if the user is a writer and can't do merge now (canMergeNow==false), then only show the Auto Merge for them
+ How to test the UI manually:
+ * Method 1: manually set some variables in pull.tmpl, eg: {{$notAllOverridableChecksOk = true}} {{$canMergeNow = false}}
+ * Method 2: make a protected branch, then set state=pending/success :
+ curl -X POST ${root_url}/api/v1/repos/${owner}/${repo}/statuses/${sha} \
+ -H "accept: application/json" -H "authorization: Basic $base64_auth" -H "Content-Type: application/json" \
+ -d '{"context": "test/context", "description": "description", "state": "${state}", "target_url": "http://localhost"}'
+ -->
<div>
+ <!-- eslint-disable -->
+ <div v-if="mergeForm.hasPendingPullRequestMerge" v-html="mergeForm.hasPendingPullRequestMergeTip" class="ui info message"></div>
+
<div class="ui form" v-if="showActionForm">
<form :action="mergeForm.baseLink+'/merge'" method="post">
<input type="hidden" name="_csrf" :value="csrfToken">
<input type="hidden" name="head_commit_id" v-model="mergeForm.pullHeadCommitID">
+ <input type="hidden" name="merge_when_checks_succeed" v-model="autoMergeWhenSucceed">
<template v-if="!mergeStyleDetail.hideMergeMessageTexts">
<div class="field">
@@ -14,39 +28,72 @@
</div>
</template>
- <button class="ui button" :class="[mergeForm.allOverridableChecksOk?'green':'red']" type="submit" name="do" :value="mergeStyle">
+ <button class="ui button" :class="mergeButtonStyleClass" type="submit" name="do" :value="mergeStyle">
{{ mergeStyleDetail.textDoMerge }}
+ <template v-if="autoMergeWhenSucceed">
+ {{ mergeForm.textAutoMergeButtonWhenSucceed }}
+ </template>
</button>
<button class="ui button merge-cancel" @click="toggleActionForm(false)">
{{ mergeForm.textCancel }}
</button>
- <div class="ui checkbox ml-2" v-if="mergeForm.isPullBranchDeletable">
+ <div class="ui checkbox ml-2" v-if="mergeForm.isPullBranchDeletable && !autoMergeWhenSucceed">
<input name="delete_branch_after_merge" type="checkbox" v-model="deleteBranchAfterMerge" id="delete-branch-after-merge">
<label for="delete-branch-after-merge">{{ mergeForm.textDeleteBranch }}</label>
</div>
</form>
</div>
- <template v-if="!showActionForm">
- <div class="ui buttons merge-button" :class="[mergeForm.allOverridableChecksOk?'green':'red']" @click="toggleActionForm(true)">
+ <div v-if="!showActionForm" class="df">
+ <!-- the merge button -->
+ <div class="ui buttons merge-button" :class="mergeButtonStyleClass" @click="toggleActionForm(true)" >
<button class="ui button">
<svg-icon name="octicon-git-merge"/>
- <span class="button-text">{{ mergeStyleDetail.textDoMerge }}</span>
+ <span class="button-text">
+ {{ mergeStyleDetail.textDoMerge }}
+ <template v-if="autoMergeWhenSucceed">
+ {{ mergeForm.textAutoMergeButtonWhenSucceed }}
+ </template>
+ </span>
</button>
<div class="ui dropdown icon button no-text" @click.stop="showMergeStyleMenu = !showMergeStyleMenu" v-if="mergeStyleAllowedCount>1">
<svg-icon name="octicon-triangle-down" :size="14"/>
<div class="menu" :class="{'show':showMergeStyleMenu}">
<template v-for="msd in mergeForm.mergeStyles">
- <div class="item" v-if="msd.allowed" :key="msd.name" @click.stop="mergeStyle=msd.name">
- {{ msd.textDoMerge }}
+ <!-- if can merge now, show one action "merge now", and an action "auto merge when succeed" -->
+ <div class="item" v-if="msd.allowed && mergeForm.canMergeNow" :key="msd.name" @click.stop="switchMergeStyle(msd.name)">
+ <div class="action-text">
+ {{ msd.textDoMerge }}
+ </div>
+ <div v-if="!msd.hideAutoMerge" class="auto-merge-small" @click.stop="switchMergeStyle(msd.name, true)">
+ <svg-icon name="octicon-clock" :size="14"/>
+ <div class="auto-merge-tip">
+ {{ mergeForm.textAutoMergeWhenSucceed }}
+ </div>
+ </div>
+ </div>
+
+ <!-- if can NOT merge now, only show one action "auto merge when succeed" -->
+ <div class="item" v-if="msd.allowed && !mergeForm.canMergeNow && !msd.hideAutoMerge" :key="msd.name" @click.stop="switchMergeStyle(msd.name, true)">
+ <div class="action-text">
+ {{ msd.textDoMerge }} {{ mergeForm.textAutoMergeButtonWhenSucceed }}
+ </div>
</div>
</template>
</div>
</div>
</div>
- </template>
+
+ <!-- the cancel auto merge button -->
+ <form v-if="mergeForm.hasPendingPullRequestMerge" :action="mergeForm.baseLink+'/cancel_auto_merge'" method="post" class="ml-4">
+ <input type="hidden" name="_csrf" :value="csrfToken">
+ <button class="ui button">
+ {{ mergeForm.textAutoMergeCancelSchedule }}
+ </button>
+ </form>
+ </div>
</div>
</template>
@@ -68,6 +115,7 @@ export default {
mergeTitleFieldValue: '',
mergeMessageFieldValue: '',
deleteBranchAfterMerge: false,
+ autoMergeWhenSucceed: false,
mergeStyle: '',
mergeStyleDetail: { // dummy only, these values will come from one of the mergeForm.mergeStyles
@@ -82,6 +130,13 @@ export default {
showActionForm: false,
}),
+ computed: {
+ mergeButtonStyleClass() {
+ if (this.mergeForm.allOverridableChecksOk) return 'green';
+ return this.autoMergeWhenSucceed ? 'blue' : 'red';
+ }
+ },
+
watch: {
mergeStyle(val) {
this.mergeStyleDetail = this.mergeForm.mergeStyles.find((e) => e.name === val);
@@ -90,7 +145,7 @@ export default {
created() {
this.mergeStyleAllowedCount = this.mergeForm.mergeStyles.reduce((v, msd) => v + (msd.allowed ? 1 : 0), 0);
- this.mergeStyle = this.mergeForm.mergeStyles.find((e) => e.allowed)?.name;
+ this.switchMergeStyle(this.mergeForm.mergeStyles.find((e) => e.allowed)?.name, !this.mergeForm.canMergeNow);
},
mounted() {
@@ -111,7 +166,11 @@ export default {
this.deleteBranchAfterMerge = this.mergeForm.defaultDeleteBranchAfterMerge;
this.mergeTitleFieldValue = this.mergeStyleDetail.mergeTitleFieldText;
this.mergeMessageFieldValue = this.mergeStyleDetail.mergeMessageFieldText;
- }
+ },
+ switchMergeStyle(name, autoMerge = false) {
+ this.mergeStyle = name;
+ this.autoMergeWhenSucceed = autoMerge;
+ },
},
};
</script>
@@ -124,4 +183,59 @@ export default {
.ui.checkbox label {
cursor: pointer;
}
+
+/* make the dropdown list left-aligned */
+.ui.merge-button {
+ position: relative;
+}
+.ui.merge-button .ui.dropdown {
+ position: static;
+}
+.ui.merge-button > .ui.dropdown:last-child > .menu:not(.left) {
+ left: 0;
+ right: auto;
+}
+.ui.merge-button .ui.dropdown .menu > .item {
+ display: flex;
+ align-items: stretch;
+ padding: 0 !important; /* polluted by semantic.css: .ui.dropdown .menu > .item { !important } */
+}
+
+/* merge style list item */
+.action-text {
+ padding: 0.8rem;
+ flex: 1
+}
+
+.auto-merge-small {
+ width: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+}
+.auto-merge-small .auto-merge-tip {
+ display: none;
+ left: 38px;
+ top: -1px;
+ bottom: -1px;
+ position: absolute;
+ align-items: center;
+ color: var(--color-info-text);
+ background-color: var(--color-info-bg);
+ border: 1px solid var(--color-info-border);
+ border-left: none;
+ padding-right: 1rem;
+}
+
+.auto-merge-small:hover {
+ color: var(--color-info-text);
+ background-color: var(--color-info-bg);
+ border: 1px solid var(--color-info-border);
+}
+
+.auto-merge-small:hover .auto-merge-tip {
+ display: flex;
+}
+
</style>
diff --git a/web_src/js/svg.js b/web_src/js/svg.js
index 926f0a5d05..9c39852c30 100644
--- a/web_src/js/svg.js
+++ b/web_src/js/svg.js
@@ -1,6 +1,7 @@
import octiconChevronDown from '../../public/img/svg/octicon-chevron-down.svg';
import octiconChevronRight from '../../public/img/svg/octicon-chevron-right.svg';
import octiconCopy from '../../public/img/svg/octicon-copy.svg';
+import octiconClock from '../../public/img/svg/octicon-clock.svg';
import octiconGitMerge from '../../public/img/svg/octicon-git-merge.svg';
import octiconGitPullRequest from '../../public/img/svg/octicon-git-pull-request.svg';
import octiconIssueClosed from '../../public/img/svg/octicon-issue-closed.svg';
@@ -23,6 +24,7 @@ export const svgs = {
'octicon-chevron-down': octiconChevronDown,
'octicon-chevron-right': octiconChevronRight,
'octicon-copy': octiconCopy,
+ 'octicon-clock': octiconClock,
'octicon-git-merge': octiconGitMerge,
'octicon-git-pull-request': octiconGitPullRequest,
'octicon-issue-closed': octiconIssueClosed,
diff --git a/web_src/less/_base.less b/web_src/less/_base.less
index c029cb9485..4d7f69e3b3 100644
--- a/web_src/less/_base.less
+++ b/web_src/less/_base.less
@@ -2003,14 +2003,6 @@ table th[data-sortt-desc] {
margin-right: 0 !important;
}
-/* limit width of all direct dropdown menu children */
-/* https://github.com/go-gitea/gitea/pull/10835 */
-.dropdown:not(.selection) > .menu:not(.review-box) > *:not(.header) {
- max-width: 300px;
- overflow-x: hidden;
- text-overflow: ellipsis;
-}
-
.ui.dropdown .menu .item {
border-radius: 0;
}
diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less
index d73cb90330..37a5017fbd 100644
--- a/web_src/less/_repository.less
+++ b/web_src/less/_repository.less
@@ -1055,10 +1055,6 @@
.merge-section {
background-color: var(--color-box-body);
- .item {
- padding: .25rem 0;
- }
-
.item-section {
display: flex;
align-items: center;