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:
Vedran 2020-09-08 18:29:51 +02:00 committed by GitHub
parent 0ed8d268ad
commit e204398754
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 107 additions and 9 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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) {

View File

@ -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)
}

View File

@ -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) {

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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,11 +32,17 @@
</div> </div>
</div> </div>
<div id="branch-list" class="scrolling menu reference-list-menu"> <div id="branch-list" class="scrolling menu reference-list-menu">
{{if .Issue.Ref}}
<div class="item text small" data-id="" data-id-selector="#ref_selector"><strong><a href="#">{{$.i18n.Tr "repo.clear_ref"}}</a></strong></div>
{{end}}
{{range .Branches}} {{range .Branches}}
<div class="item" data-id="refs/heads/{{.}}" data-name="{{.}}" data-id-selector="#ref_selector">{{.}}</div> <div class="item" data-id="refs/heads/{{.}}" data-name="{{.}}" data-id-selector="#ref_selector">{{.}}</div>
{{end}} {{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">
{{if .Issue.Ref}}
<div class="item text small" data-id="" data-id-selector="#ref_selector"><strong><a href="#">{{.i18n.Tr "repo.clear_ref"}}</a></strong></div>
{{end}}
{{range .Tags}} {{range .Tags}}
<div class="item" data-id="refs/tags/{{.}}" data-name="tags/{{.}}" data-id-selector="#ref_selector">{{.}}</div> <div class="item" data-id="refs/tags/{{.}}" data-name="tags/{{.}}" data-id-selector="#ref_selector">{{.}}</div>
{{end}} {{end}}

View File

@ -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');