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>
This commit is contained in:
parent
ce3dd04c63
commit
a9cc9c0f7a
10 changed files with 300 additions and 137 deletions
|
@ -1568,14 +1568,7 @@ pulls.squash_merge_pull_request = Create squash commit
|
||||||
pulls.merge_manually = Manually merged
|
pulls.merge_manually = Manually merged
|
||||||
pulls.merge_commit_id = The merge commit ID
|
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.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.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 = Merge Failed: There was a conflict whilst merging. Hint: Try a different strategy
|
||||||
pulls.merge_conflict_summary = Error Message
|
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_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_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_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.auto_merge_button_when_succeed = (When 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.auto_merge_when_succeed = Auto merge when all checks succeed
|
||||||
pulls.merge_pull_on_success_cancel = Cancel auto merge
|
pulls.auto_merge_newly_scheduled = The pull request was scheduled to merge when all checks succeed.
|
||||||
pulls.pull_request_not_scheduled = This pull request is not scheduled to auto merge.
|
pulls.auto_merge_has_pending_schedule = %[1]s scheduled this pull request to auto merge when all checks succeed %[2]s.
|
||||||
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.auto_merge_cancel_schedule = Cancel auto merge
|
||||||
pulls.pull_request_canceled_scheduled_auto_merge = `canceled auto merging this pull request when all checks succeed %[1]s`
|
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.new = New Milestone
|
||||||
milestones.open_tab = %d Open
|
milestones.open_tab = %d Open
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
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"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -36,6 +37,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
"code.gitea.io/gitea/routers/utils"
|
"code.gitea.io/gitea/routers/utils"
|
||||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
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/forms"
|
||||||
"code.gitea.io/gitea/services/gitdiff"
|
"code.gitea.io/gitea/services/gitdiff"
|
||||||
pull_service "code.gitea.io/gitea/services/pull"
|
pull_service "code.gitea.io/gitea/services/pull"
|
||||||
|
@ -966,6 +968,22 @@ func MergePullRequest(ctx *context.Context) {
|
||||||
message += "\n\n" + form.MergeMessageField
|
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 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) {
|
if models.IsErrInvalidMergeStyle(err) {
|
||||||
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
|
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
|
||||||
|
@ -1070,6 +1088,26 @@ func MergePullRequest(ctx *context.Context) {
|
||||||
ctx.Redirect(issue.Link())
|
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 {
|
func stopTimerIfAvailable(user *user_model.User, issue *models.Issue) error {
|
||||||
if models.StopwatchExists(user.ID, issue.ID) {
|
if models.StopwatchExists(user.ID, issue.ID) {
|
||||||
if err := models.CreateOrStopIssueStopwatch(user, issue); err != nil {
|
if err := models.CreateOrStopIssueStopwatch(user, issue); err != nil {
|
||||||
|
|
|
@ -1127,6 +1127,7 @@ func RegisterRoutes(m *web.Route) {
|
||||||
m.Get(".patch", repo.DownloadPullPatch)
|
m.Get(".patch", repo.DownloadPullPatch)
|
||||||
m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
|
m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
|
||||||
m.Post("/merge", context.RepoMustNotBeArchived(), bindIgnErr(forms.MergePullRequestForm{}), repo.MergePullRequest)
|
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("/update", repo.UpdatePullRequest)
|
||||||
m.Post("/set_allow_maintainer_edit", bindIgnErr(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits)
|
m.Post("/set_allow_maintainer_edit", bindIgnErr(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits)
|
||||||
m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
|
m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
|
||||||
|
|
|
@ -843,8 +843,8 @@
|
||||||
<span class="badge">{{svg "octicon-git-merge" 16}}</span>
|
<span class="badge">{{svg "octicon-git-merge" 16}}</span>
|
||||||
<span class="text grey">
|
<span class="text grey">
|
||||||
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
|
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
|
||||||
{{if eq .Type 34}}{{$.i18n.Tr "repo.pulls.pull_request_scheduled_auto_merge" $createdStr | Safe}}
|
{{if eq .Type 34}}{{$.i18n.Tr "repo.pulls.auto_merge_newly_scheduled_comment" $createdStr | Safe}}
|
||||||
{{else}}{{$.i18n.Tr "repo.pulls.pull_request_canceled_scheduled_auto_merge" $createdStr | Safe}}{{end}}
|
{{else}}{{$.i18n.Tr "repo.pulls.auto_merge_canceled_schedule_comment" $createdStr | Safe}}{{end}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -251,8 +251,14 @@
|
||||||
{{$.i18n.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason) }}
|
{{$.i18n.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason) }}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}}
|
{{$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}}
|
{{if $notAllOverridableChecksOk}}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<i class="icon icon-octicon">{{svg "octicon-dot-fill"}}</i>
|
<i class="icon icon-octicon">{{svg "octicon-dot-fill"}}</i>
|
||||||
|
@ -277,7 +283,6 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{$canAutoMerge = true}}
|
|
||||||
{{if (gt .Issue.PullRequest.CommitsBehind 0)}}
|
{{if (gt .Issue.PullRequest.CommitsBehind 0)}}
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div class="item item-section">
|
<div class="item item-section">
|
||||||
|
@ -317,14 +322,16 @@
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if and (or $.IsRepoAdmin (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
|
{{if .AllowMerge}} {{/* user is allowed to merge */}}
|
||||||
{{if .AllowMerge}}
|
|
||||||
{{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}}
|
{{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}}
|
||||||
{{$approvers := .Issue.PullRequest.GetApprovers}}
|
{{$approvers := .Issue.PullRequest.GetApprovers}}
|
||||||
{{if or $prUnit.PullRequestsConfig.AllowMerge $prUnit.PullRequestsConfig.AllowRebase $prUnit.PullRequestsConfig.AllowRebaseMerge $prUnit.PullRequestsConfig.AllowSquash}}
|
{{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>
|
<div class="ui divider"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
<!-- /* eslint-disable */ -->
|
<!-- /* eslint-disable */ -->
|
||||||
(() => {
|
(() => {
|
||||||
|
@ -335,13 +342,22 @@
|
||||||
'baseLink': {{.Link}},
|
'baseLink': {{.Link}},
|
||||||
'textCancel': {{$.i18n.Tr "cancel"}},
|
'textCancel': {{$.i18n.Tr "cancel"}},
|
||||||
'textDeleteBranch': {{$.i18n.Tr "repo.branch.delete" .HeadTarget}},
|
'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"}},
|
||||||
|
|
||||||
|
'canMergeNow': {{$canMergeNow}},
|
||||||
'allOverridableChecksOk': {{not $notAllOverridableChecksOk}},
|
'allOverridableChecksOk': {{not $notAllOverridableChecksOk}},
|
||||||
'pullHeadCommitID': {{.PullHeadCommitID}},
|
'pullHeadCommitID': {{.PullHeadCommitID}},
|
||||||
'isPullBranchDeletable': {{.IsPullBranchDeletable}},
|
'isPullBranchDeletable': {{.IsPullBranchDeletable}},
|
||||||
'defaultDeleteBranchAfterMerge': {{$prUnit.PullRequestsConfig.DefaultDeleteBranchAfterMerge}},
|
'defaultDeleteBranchAfterMerge': {{$prUnit.PullRequestsConfig.DefaultDeleteBranchAfterMerge}},
|
||||||
'mergeMessageFieldPlaceHolder': {{$.i18n.Tr "repo.editor.commit_message_desc"}},
|
'mergeMessageFieldPlaceHolder': {{$.i18n.Tr "repo.editor.commit_message_desc"}},
|
||||||
|
|
||||||
|
'hasPendingPullRequestMerge': {{.HasPendingPullRequestMerge}},
|
||||||
|
'hasPendingPullRequestMergeTip': {{$hasPendingPullRequestMergeTip}},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const generalHideAutoMerge = mergeForm.canMergeNow && mergeForm.allOverridableChecksOk; // if this PR can be merged now, then hide the auto merge
|
||||||
mergeForm['mergeStyles'] = [
|
mergeForm['mergeStyles'] = [
|
||||||
{
|
{
|
||||||
'name': 'merge',
|
'name': 'merge',
|
||||||
|
@ -349,12 +365,14 @@
|
||||||
'textDoMerge': {{$.i18n.Tr "repo.pulls.merge_pull_request"}},
|
'textDoMerge': {{$.i18n.Tr "repo.pulls.merge_pull_request"}},
|
||||||
'mergeTitleFieldText': defaultMergeTitle,
|
'mergeTitleFieldText': defaultMergeTitle,
|
||||||
'mergeMessageFieldText': defaultMergeMessage,
|
'mergeMessageFieldText': defaultMergeMessage,
|
||||||
|
'hideAutoMerge': generalHideAutoMerge,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'rebase',
|
'name': 'rebase',
|
||||||
'allowed': {{$prUnit.PullRequestsConfig.AllowRebase}},
|
'allowed': {{$prUnit.PullRequestsConfig.AllowRebase}},
|
||||||
'textDoMerge': {{$.i18n.Tr "repo.pulls.rebase_merge_pull_request"}},
|
'textDoMerge': {{$.i18n.Tr "repo.pulls.rebase_merge_pull_request"}},
|
||||||
'hideMergeMessageTexts': true,
|
'hideMergeMessageTexts': true,
|
||||||
|
'hideAutoMerge': generalHideAutoMerge,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'rebase-merge',
|
'name': 'rebase-merge',
|
||||||
|
@ -362,6 +380,7 @@
|
||||||
'textDoMerge': {{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}},
|
'textDoMerge': {{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}},
|
||||||
'mergeTitleFieldText': defaultMergeTitle,
|
'mergeTitleFieldText': defaultMergeTitle,
|
||||||
'mergeMessageFieldText': defaultMergeMessage,
|
'mergeMessageFieldText': defaultMergeMessage,
|
||||||
|
'hideAutoMerge': generalHideAutoMerge,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'squash',
|
'name': 'squash',
|
||||||
|
@ -369,12 +388,14 @@
|
||||||
'textDoMerge': {{$.i18n.Tr "repo.pulls.squash_merge_pull_request"}},
|
'textDoMerge': {{$.i18n.Tr "repo.pulls.squash_merge_pull_request"}},
|
||||||
'mergeTitleFieldText': defaultSquashMergeTitle,
|
'mergeTitleFieldText': defaultSquashMergeTitle,
|
||||||
'mergeMessageFieldText': defaultMergeMessage,
|
'mergeMessageFieldText': defaultMergeMessage,
|
||||||
|
'hideAutoMerge': generalHideAutoMerge,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'manually-merged',
|
'name': 'manually-merged',
|
||||||
'allowed': {{and $prUnit.PullRequestsConfig.AllowManualMerge $.IsRepoAdmin}},
|
'allowed': {{and $prUnit.PullRequestsConfig.AllowManualMerge $.IsRepoAdmin}},
|
||||||
'textDoMerge': {{$.i18n.Tr "repo.pulls.merge_manually"}},
|
'textDoMerge': {{$.i18n.Tr "repo.pulls.merge_manually"}},
|
||||||
'hideMergeMessageTexts': true,
|
'hideMergeMessageTexts': true,
|
||||||
|
'hideAutoMerge': true,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
window.config.pageData.pullRequestMergeForm = mergeForm;
|
window.config.pageData.pullRequestMergeForm = mergeForm;
|
||||||
|
@ -384,27 +405,10 @@
|
||||||
<div id="pull-request-merge-form"></div>
|
<div id="pull-request-merge-form"></div>
|
||||||
|
|
||||||
{{if .ShowMergeInstructions}}
|
{{if .ShowMergeInstructions}}
|
||||||
<div class="instruct-toggle mt-3"> {{$.i18n.Tr "repo.pulls.merge_instruction_hint" | Safe}} </div>
|
{{template "repo/issue/view_content/pull_merge_instruction" (dict "i18n" .i18n "Issue" .Issue)}}
|
||||||
<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}}
|
{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
{{/* no merge style was set in repo setting: not or ($prUnit.PullRequestsConfig.AllowMerge ...) */}}
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div class="item text red">
|
<div class="item text red">
|
||||||
{{svg "octicon-x"}}
|
{{svg "octicon-x"}}
|
||||||
|
@ -414,15 +418,15 @@
|
||||||
{{svg "octicon-info"}}
|
{{svg "octicon-info"}}
|
||||||
{{$.i18n.Tr "repo.pulls.no_merge_helper"}}
|
{{$.i18n.Tr "repo.pulls.no_merge_helper"}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}} {{/* end if the repo was set to use any merge style */}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
{{/* user is not allowed to merge */}}
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
{{svg "octicon-info"}}
|
{{svg "octicon-info"}}
|
||||||
{{$.i18n.Tr "repo.pulls.no_merge_access"}}
|
{{$.i18n.Tr "repo.pulls.no_merge_access"}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}} {{/* end if user is allowed to merge or not */}}
|
||||||
{{end}}
|
|
||||||
{{else}}
|
{{else}}
|
||||||
{{/* Merge conflict without specific file. Suggest manual merge, only if all reviews and status checks OK. */}}
|
{{/* Merge conflict without specific file. Suggest manual merge, only if all reviews and status checks OK. */}}
|
||||||
{{if .IsBlockedByApprovals}}
|
{{if .IsBlockedByApprovals}}
|
||||||
|
|
|
@ -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>
|
|
@ -1,9 +1,23 @@
|
||||||
<template>
|
<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>
|
<div>
|
||||||
|
<!-- eslint-disable -->
|
||||||
|
<div v-if="mergeForm.hasPendingPullRequestMerge" v-html="mergeForm.hasPendingPullRequestMergeTip" class="ui info message"></div>
|
||||||
|
|
||||||
<div class="ui form" v-if="showActionForm">
|
<div class="ui form" v-if="showActionForm">
|
||||||
<form :action="mergeForm.baseLink+'/merge'" method="post">
|
<form :action="mergeForm.baseLink+'/merge'" method="post">
|
||||||
<input type="hidden" name="_csrf" :value="csrfToken">
|
<input type="hidden" name="_csrf" :value="csrfToken">
|
||||||
<input type="hidden" name="head_commit_id" v-model="mergeForm.pullHeadCommitID">
|
<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">
|
<template v-if="!mergeStyleDetail.hideMergeMessageTexts">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -14,39 +28,72 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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 }}
|
{{ mergeStyleDetail.textDoMerge }}
|
||||||
|
<template v-if="autoMergeWhenSucceed">
|
||||||
|
{{ mergeForm.textAutoMergeButtonWhenSucceed }}
|
||||||
|
</template>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="ui button merge-cancel" @click="toggleActionForm(false)">
|
<button class="ui button merge-cancel" @click="toggleActionForm(false)">
|
||||||
{{ mergeForm.textCancel }}
|
{{ mergeForm.textCancel }}
|
||||||
</button>
|
</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">
|
<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>
|
<label for="delete-branch-after-merge">{{ mergeForm.textDeleteBranch }}</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="!showActionForm">
|
<div v-if="!showActionForm" class="df">
|
||||||
<div class="ui buttons merge-button" :class="[mergeForm.allOverridableChecksOk?'green':'red']" @click="toggleActionForm(true)">
|
<!-- the merge button -->
|
||||||
|
<div class="ui buttons merge-button" :class="mergeButtonStyleClass" @click="toggleActionForm(true)" >
|
||||||
<button class="ui button">
|
<button class="ui button">
|
||||||
<svg-icon name="octicon-git-merge"/>
|
<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>
|
</button>
|
||||||
<div class="ui dropdown icon button no-text" @click.stop="showMergeStyleMenu = !showMergeStyleMenu" v-if="mergeStyleAllowedCount>1">
|
<div class="ui dropdown icon button no-text" @click.stop="showMergeStyleMenu = !showMergeStyleMenu" v-if="mergeStyleAllowedCount>1">
|
||||||
<svg-icon name="octicon-triangle-down" :size="14"/>
|
<svg-icon name="octicon-triangle-down" :size="14"/>
|
||||||
<div class="menu" :class="{'show':showMergeStyleMenu}">
|
<div class="menu" :class="{'show':showMergeStyleMenu}">
|
||||||
<template v-for="msd in mergeForm.mergeStyles">
|
<template v-for="msd in mergeForm.mergeStyles">
|
||||||
<div class="item" v-if="msd.allowed" :key="msd.name" @click.stop="mergeStyle=msd.name">
|
<!-- 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 }}
|
{{ msd.textDoMerge }}
|
||||||
</div>
|
</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>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -68,6 +115,7 @@ export default {
|
||||||
mergeTitleFieldValue: '',
|
mergeTitleFieldValue: '',
|
||||||
mergeMessageFieldValue: '',
|
mergeMessageFieldValue: '',
|
||||||
deleteBranchAfterMerge: false,
|
deleteBranchAfterMerge: false,
|
||||||
|
autoMergeWhenSucceed: false,
|
||||||
|
|
||||||
mergeStyle: '',
|
mergeStyle: '',
|
||||||
mergeStyleDetail: { // dummy only, these values will come from one of the mergeForm.mergeStyles
|
mergeStyleDetail: { // dummy only, these values will come from one of the mergeForm.mergeStyles
|
||||||
|
@ -82,6 +130,13 @@ export default {
|
||||||
showActionForm: false,
|
showActionForm: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
mergeButtonStyleClass() {
|
||||||
|
if (this.mergeForm.allOverridableChecksOk) return 'green';
|
||||||
|
return this.autoMergeWhenSucceed ? 'blue' : 'red';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
mergeStyle(val) {
|
mergeStyle(val) {
|
||||||
this.mergeStyleDetail = this.mergeForm.mergeStyles.find((e) => e.name === val);
|
this.mergeStyleDetail = this.mergeForm.mergeStyles.find((e) => e.name === val);
|
||||||
|
@ -90,7 +145,7 @@ export default {
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.mergeStyleAllowedCount = this.mergeForm.mergeStyles.reduce((v, msd) => v + (msd.allowed ? 1 : 0), 0);
|
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() {
|
mounted() {
|
||||||
|
@ -111,7 +166,11 @@ export default {
|
||||||
this.deleteBranchAfterMerge = this.mergeForm.defaultDeleteBranchAfterMerge;
|
this.deleteBranchAfterMerge = this.mergeForm.defaultDeleteBranchAfterMerge;
|
||||||
this.mergeTitleFieldValue = this.mergeStyleDetail.mergeTitleFieldText;
|
this.mergeTitleFieldValue = this.mergeStyleDetail.mergeTitleFieldText;
|
||||||
this.mergeMessageFieldValue = this.mergeStyleDetail.mergeMessageFieldText;
|
this.mergeMessageFieldValue = this.mergeStyleDetail.mergeMessageFieldText;
|
||||||
}
|
},
|
||||||
|
switchMergeStyle(name, autoMerge = false) {
|
||||||
|
this.mergeStyle = name;
|
||||||
|
this.autoMergeWhenSucceed = autoMerge;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -124,4 +183,59 @@ export default {
|
||||||
.ui.checkbox label {
|
.ui.checkbox label {
|
||||||
cursor: pointer;
|
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>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import octiconChevronDown from '../../public/img/svg/octicon-chevron-down.svg';
|
import octiconChevronDown from '../../public/img/svg/octicon-chevron-down.svg';
|
||||||
import octiconChevronRight from '../../public/img/svg/octicon-chevron-right.svg';
|
import octiconChevronRight from '../../public/img/svg/octicon-chevron-right.svg';
|
||||||
import octiconCopy from '../../public/img/svg/octicon-copy.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 octiconGitMerge from '../../public/img/svg/octicon-git-merge.svg';
|
||||||
import octiconGitPullRequest from '../../public/img/svg/octicon-git-pull-request.svg';
|
import octiconGitPullRequest from '../../public/img/svg/octicon-git-pull-request.svg';
|
||||||
import octiconIssueClosed from '../../public/img/svg/octicon-issue-closed.svg';
|
import octiconIssueClosed from '../../public/img/svg/octicon-issue-closed.svg';
|
||||||
|
@ -23,6 +24,7 @@ export const svgs = {
|
||||||
'octicon-chevron-down': octiconChevronDown,
|
'octicon-chevron-down': octiconChevronDown,
|
||||||
'octicon-chevron-right': octiconChevronRight,
|
'octicon-chevron-right': octiconChevronRight,
|
||||||
'octicon-copy': octiconCopy,
|
'octicon-copy': octiconCopy,
|
||||||
|
'octicon-clock': octiconClock,
|
||||||
'octicon-git-merge': octiconGitMerge,
|
'octicon-git-merge': octiconGitMerge,
|
||||||
'octicon-git-pull-request': octiconGitPullRequest,
|
'octicon-git-pull-request': octiconGitPullRequest,
|
||||||
'octicon-issue-closed': octiconIssueClosed,
|
'octicon-issue-closed': octiconIssueClosed,
|
||||||
|
|
|
@ -2003,14 +2003,6 @@ table th[data-sortt-desc] {
|
||||||
margin-right: 0 !important;
|
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 {
|
.ui.dropdown .menu .item {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1055,10 +1055,6 @@
|
||||||
.merge-section {
|
.merge-section {
|
||||||
background-color: var(--color-box-body);
|
background-color: var(--color-box-body);
|
||||||
|
|
||||||
.item {
|
|
||||||
padding: .25rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-section {
|
.item-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
Loading…
Reference in a new issue