mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Change/remove a branch of an open issue (#9080)
* Add field with isIssueWriter to front end * Make branch field editable * Switch frontend to form and POST from javascript * Add /issue/id/ref endpoint to routes * Use UpdateIssueTitle model to change ref in backend * Removed crossreference check and adding comments on branch change * Use ref returned from POST to update the field * Prevent calling loadRepo from models/ * Branch/tag refreshed without page reload * Remove filter for empty branch name * Add clear option to tag list as well * Delete button translation and coloring * Fix for not showing selected branch name in new issue * Check that branch is not being changed on a PR * Change logic * Notification when changing issue ref * Fix for renamed permission parameter * Fix for failing build * Apply suggestions from code review Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: Gitea <gitea@fake.local> Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
This commit is contained in:
		@@ -709,6 +709,22 @@ func (issue *Issue) ChangeTitle(doer *User, oldTitle string) (err error) {
 | 
				
			|||||||
	return sess.Commit()
 | 
						return sess.Commit()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ChangeRef changes the branch of this issue, as the given user.
 | 
				
			||||||
 | 
					func (issue *Issue) ChangeRef(doer *User, oldRef string) (err error) {
 | 
				
			||||||
 | 
						sess := x.NewSession()
 | 
				
			||||||
 | 
						defer sess.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = sess.Begin(); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = updateIssueCols(sess, issue, "ref"); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("updateIssueCols: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sess.Commit()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AddDeletePRBranchComment adds delete branch comment for pull request issue
 | 
					// AddDeletePRBranchComment adds delete branch comment for pull request issue
 | 
				
			||||||
func AddDeletePRBranchComment(doer *User, repo *Repository, issueID int64, branchName string) error {
 | 
					func AddDeletePRBranchComment(doer *User, repo *Repository, issueID int64, branchName string) error {
 | 
				
			||||||
	issue, err := getIssueByID(x, issueID)
 | 
						issue, err := getIssueByID(x, issueID)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,6 +28,7 @@ type Notifier interface {
 | 
				
			|||||||
	NotifyIssueChangeContent(doer *models.User, issue *models.Issue, oldContent string)
 | 
						NotifyIssueChangeContent(doer *models.User, issue *models.Issue, oldContent string)
 | 
				
			||||||
	NotifyIssueClearLabels(doer *models.User, issue *models.Issue)
 | 
						NotifyIssueClearLabels(doer *models.User, issue *models.Issue)
 | 
				
			||||||
	NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle string)
 | 
						NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle string)
 | 
				
			||||||
 | 
						NotifyIssueChangeRef(doer *models.User, issue *models.Issue, oldRef string)
 | 
				
			||||||
	NotifyIssueChangeLabels(doer *models.User, issue *models.Issue,
 | 
						NotifyIssueChangeLabels(doer *models.User, issue *models.Issue,
 | 
				
			||||||
		addedLabels []*models.Label, removedLabels []*models.Label)
 | 
							addedLabels []*models.Label, removedLabels []*models.Label)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -102,6 +102,10 @@ func (*NullNotifier) NotifyIssueClearLabels(doer *models.User, issue *models.Iss
 | 
				
			|||||||
func (*NullNotifier) NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle string) {
 | 
					func (*NullNotifier) NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle string) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NotifyIssueChangeRef places a place holder function
 | 
				
			||||||
 | 
					func (*NullNotifier) NotifyIssueChangeRef(doer *models.User, issue *models.Issue, oldTitle string) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NotifyIssueChangeLabels places a place holder function
 | 
					// NotifyIssueChangeLabels places a place holder function
 | 
				
			||||||
func (*NullNotifier) NotifyIssueChangeLabels(doer *models.User, issue *models.Issue,
 | 
					func (*NullNotifier) NotifyIssueChangeLabels(doer *models.User, issue *models.Issue,
 | 
				
			||||||
	addedLabels []*models.Label, removedLabels []*models.Label) {
 | 
						addedLabels []*models.Label, removedLabels []*models.Label) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -148,3 +148,7 @@ func (r *indexerNotifier) NotifyIssueChangeContent(doer *models.User, issue *mod
 | 
				
			|||||||
func (r *indexerNotifier) NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle string) {
 | 
					func (r *indexerNotifier) NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle string) {
 | 
				
			||||||
	issue_indexer.UpdateIssueIndexer(issue)
 | 
						issue_indexer.UpdateIssueIndexer(issue)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *indexerNotifier) NotifyIssueChangeRef(doer *models.User, issue *models.Issue, oldRef string) {
 | 
				
			||||||
 | 
						issue_indexer.UpdateIssueIndexer(issue)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -178,6 +178,13 @@ func NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle str
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NotifyIssueChangeRef notifies change reference to notifiers
 | 
				
			||||||
 | 
					func NotifyIssueChangeRef(doer *models.User, issue *models.Issue, oldRef string) {
 | 
				
			||||||
 | 
						for _, notifier := range notifiers {
 | 
				
			||||||
 | 
							notifier.NotifyIssueChangeRef(doer, issue, oldRef)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NotifyIssueChangeLabels notifies change labels to notifiers
 | 
					// NotifyIssueChangeLabels notifies change labels to notifiers
 | 
				
			||||||
func NotifyIssueChangeLabels(doer *models.User, issue *models.Issue,
 | 
					func NotifyIssueChangeLabels(doer *models.User, issue *models.Issue,
 | 
				
			||||||
	addedLabels []*models.Label, removedLabels []*models.Label) {
 | 
						addedLabels []*models.Label, removedLabels []*models.Label) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -760,6 +760,7 @@ code = Code
 | 
				
			|||||||
code.desc = Access source code, files, commits and branches.
 | 
					code.desc = Access source code, files, commits and branches.
 | 
				
			||||||
branch = Branch
 | 
					branch = Branch
 | 
				
			||||||
tree = Tree
 | 
					tree = Tree
 | 
				
			||||||
 | 
					clear_ref = `Clear current reference`
 | 
				
			||||||
filter_branch_and_tag = Filter branch or tag
 | 
					filter_branch_and_tag = Filter branch or tag
 | 
				
			||||||
branches = Branches
 | 
					branches = Branches
 | 
				
			||||||
tags = Tags
 | 
					tags = Tags
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1244,7 +1244,7 @@ func ViewIssue(ctx *context.Context) {
 | 
				
			|||||||
	ctx.Data["Participants"] = participants
 | 
						ctx.Data["Participants"] = participants
 | 
				
			||||||
	ctx.Data["NumParticipants"] = len(participants)
 | 
						ctx.Data["NumParticipants"] = len(participants)
 | 
				
			||||||
	ctx.Data["Issue"] = issue
 | 
						ctx.Data["Issue"] = issue
 | 
				
			||||||
	ctx.Data["ReadOnly"] = true
 | 
						ctx.Data["ReadOnly"] = false
 | 
				
			||||||
	ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + ctx.Data["Link"].(string)
 | 
						ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + ctx.Data["Link"].(string)
 | 
				
			||||||
	ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID)
 | 
						ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID)
 | 
				
			||||||
	ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
 | 
						ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
 | 
				
			||||||
@@ -1344,6 +1344,30 @@ func UpdateIssueTitle(ctx *context.Context) {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateIssueRef change issue's ref (branch)
 | 
				
			||||||
 | 
					func UpdateIssueRef(ctx *context.Context) {
 | 
				
			||||||
 | 
						issue := GetActionIssue(ctx)
 | 
				
			||||||
 | 
						if ctx.Written() {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !ctx.IsSigned || (!issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) || issue.IsPull {
 | 
				
			||||||
 | 
							ctx.Error(403)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ref := ctx.QueryTrim("ref")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := issue_service.ChangeIssueRef(issue, ctx.User, ref); err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("ChangeRef", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.JSON(200, map[string]interface{}{
 | 
				
			||||||
 | 
							"ref": ref,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateIssueContent change issue's content
 | 
					// UpdateIssueContent change issue's content
 | 
				
			||||||
func UpdateIssueContent(ctx *context.Context) {
 | 
					func UpdateIssueContent(ctx *context.Context) {
 | 
				
			||||||
	issue := GetActionIssue(ctx)
 | 
						issue := GetActionIssue(ctx)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -733,6 +733,7 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
				
			|||||||
				m.Post("/title", repo.UpdateIssueTitle)
 | 
									m.Post("/title", repo.UpdateIssueTitle)
 | 
				
			||||||
				m.Post("/content", repo.UpdateIssueContent)
 | 
									m.Post("/content", repo.UpdateIssueContent)
 | 
				
			||||||
				m.Post("/watch", repo.IssueWatch)
 | 
									m.Post("/watch", repo.IssueWatch)
 | 
				
			||||||
 | 
									m.Post("/ref", repo.UpdateIssueRef)
 | 
				
			||||||
				m.Group("/dependency", func() {
 | 
									m.Group("/dependency", func() {
 | 
				
			||||||
					m.Post("/add", repo.AddDependency)
 | 
										m.Post("/add", repo.AddDependency)
 | 
				
			||||||
					m.Post("/delete", repo.RemoveDependency)
 | 
										m.Post("/delete", repo.RemoveDependency)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,6 +42,20 @@ func ChangeTitle(issue *models.Issue, doer *models.User, title string) (err erro
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ChangeIssueRef changes the branch of this issue, as the given user.
 | 
				
			||||||
 | 
					func ChangeIssueRef(issue *models.Issue, doer *models.User, ref string) error {
 | 
				
			||||||
 | 
						oldRef := issue.Ref
 | 
				
			||||||
 | 
						issue.Ref = ref
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := issue.ChangeRef(doer, oldRef); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						notification.NotifyIssueChangeRef(doer, issue, oldRef)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateAssignees is a helper function to add or delete one or multiple issue assignee(s)
 | 
					// UpdateAssignees is a helper function to add or delete one or multiple issue assignee(s)
 | 
				
			||||||
// Deleting is done the GitHub way (quote from their api documentation):
 | 
					// Deleting is done the GitHub way (quote from their api documentation):
 | 
				
			||||||
// https://developer.github.com/v3/issues/#edit-an-issue
 | 
					// https://developer.github.com/v3/issues/#edit-an-issue
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,10 @@
 | 
				
			|||||||
{{if and (not .Issue.IsPull) (not .PageIsComparePull)}}
 | 
					{{if and (not .Issue.IsPull) (not .PageIsComparePull)}}
 | 
				
			||||||
<input id="ref_selector" name="ref" type="hidden" value="{{.Issue.Ref}}">
 | 
					<input id="ref_selector" name="ref" type="hidden" value="{{.Issue.Ref}}">
 | 
				
			||||||
 | 
					<input id="editing_mode" name="edit_mode" type="hidden" value="{{(or .IsIssueWriter .HasIssuesOrPullsWritePermission)}}">
 | 
				
			||||||
 | 
					<form method="POST" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/ref" id="update_issueref_form">
 | 
				
			||||||
 | 
						{{$.CsrfTokenHtml}}
 | 
				
			||||||
 | 
					</form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="ui {{if .ReadOnly}}disabled{{end}} floating filter select-branch dropdown" data-no-results="{{.i18n.Tr "repo.pulls.no_results"}}">
 | 
					<div class="ui {{if .ReadOnly}}disabled{{end}} floating filter select-branch dropdown" data-no-results="{{.i18n.Tr "repo.pulls.no_results"}}">
 | 
				
			||||||
	<div class="ui basic small button">
 | 
						<div class="ui basic small button">
 | 
				
			||||||
		<span class="text branch-name">{{if .Issue.Ref}}{{$.RefEndName}}{{else}}{{.i18n.Tr "repo.issues.no_ref"}}{{end}}</span>
 | 
							<span class="text branch-name">{{if .Issue.Ref}}{{$.RefEndName}}{{else}}{{.i18n.Tr "repo.issues.no_ref"}}{{end}}</span>
 | 
				
			||||||
@@ -27,14 +32,20 @@
 | 
				
			|||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div id="branch-list" class="scrolling menu reference-list-menu">
 | 
							<div id="branch-list" class="scrolling menu reference-list-menu">
 | 
				
			||||||
		{{range .Branches}}
 | 
								{{if .Issue.Ref}}
 | 
				
			||||||
			<div class="item" data-id="refs/heads/{{.}}" data-name="{{.}}" data-id-selector="#ref_selector">{{.}}</div>
 | 
									<div class="item text small" data-id="" data-id-selector="#ref_selector"><strong><a href="#">{{$.i18n.Tr "repo.clear_ref"}}</a></strong></div>
 | 
				
			||||||
		{{end}}
 | 
								{{end}}	
 | 
				
			||||||
 | 
								{{range .Branches}}
 | 
				
			||||||
 | 
									<div class="item" data-id="refs/heads/{{.}}" data-name="{{.}}" data-id-selector="#ref_selector">{{.}}</div>
 | 
				
			||||||
 | 
								{{end}}
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div id="tag-list" class="scrolling menu reference-list-menu" style="display: none">
 | 
							<div id="tag-list" class="scrolling menu reference-list-menu" style="display: none">
 | 
				
			||||||
		{{range .Tags}}
 | 
								{{if .Issue.Ref}}
 | 
				
			||||||
			<div class="item" data-id="refs/tags/{{.}}" data-name="tags/{{.}}" data-id-selector="#ref_selector">{{.}}</div>
 | 
									<div class="item text small" data-id="" data-id-selector="#ref_selector"><strong><a href="#">{{.i18n.Tr "repo.clear_ref"}}</a></strong></div>
 | 
				
			||||||
		{{end}}
 | 
								{{end}}	
 | 
				
			||||||
 | 
								{{range .Tags}}
 | 
				
			||||||
 | 
									<div class="item" data-id="refs/tags/{{.}}" data-name="tags/{{.}}" data-id-selector="#ref_selector">{{.}}</div>
 | 
				
			||||||
 | 
								{{end}}
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -112,8 +112,23 @@ function initBranchSelector() {
 | 
				
			|||||||
  const $selectBranch = $('.ui.select-branch');
 | 
					  const $selectBranch = $('.ui.select-branch');
 | 
				
			||||||
  const $branchMenu = $selectBranch.find('.reference-list-menu');
 | 
					  const $branchMenu = $selectBranch.find('.reference-list-menu');
 | 
				
			||||||
  $branchMenu.find('.item:not(.no-select)').click(function () {
 | 
					  $branchMenu.find('.item:not(.no-select)').click(function () {
 | 
				
			||||||
    $($(this).data('id-selector')).val($(this).data('id'));
 | 
					    const selectedValue = $(this).data('id');
 | 
				
			||||||
    $selectBranch.find('.ui .branch-name').text($(this).data('name'));
 | 
					    const editMode = $('#editing_mode').val();
 | 
				
			||||||
 | 
					    $($(this).data('id-selector')).val(selectedValue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (editMode === 'true') {
 | 
				
			||||||
 | 
					      const form = $('#update_issueref_form');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      $.post(form.attr('action'), {
 | 
				
			||||||
 | 
					        _csrf: csrf,
 | 
				
			||||||
 | 
					        ref: selectedValue
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      () => {
 | 
				
			||||||
 | 
					        window.location.reload();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } else if (editMode === '') {
 | 
				
			||||||
 | 
					      $selectBranch.find('.ui .branch-name').text(selectedValue);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  $selectBranch.find('.reference.column').on('click', function () {
 | 
					  $selectBranch.find('.reference.column').on('click', function () {
 | 
				
			||||||
    $selectBranch.find('.scrolling.reference-list-menu').css('display', 'none');
 | 
					    $selectBranch.find('.scrolling.reference-list-menu').css('display', 'none');
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user