mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 06:24:11 +01:00 
			
		
		
		
	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
					
				@ -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
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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}}
 | 
			
		||||
 | 
			
		||||
@ -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}}
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
  <!--
 | 
			
		||||
  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>
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1055,10 +1055,6 @@
 | 
			
		||||
        .merge-section {
 | 
			
		||||
          background-color: var(--color-box-body);
 | 
			
		||||
 | 
			
		||||
          .item {
 | 
			
		||||
            padding: .25rem 0;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .item-section {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            align-items: center;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user