diff --git a/models/issue.go b/models/issue.go index 81652771ba..228316fc3c 100644 --- a/models/issue.go +++ b/models/issue.go @@ -709,6 +709,22 @@ func (issue *Issue) ChangeTitle(doer *User, oldTitle string) (err error) { 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 func AddDeletePRBranchComment(doer *User, repo *Repository, issueID int64, branchName string) error { issue, err := getIssueByID(x, issueID) diff --git a/modules/notification/base/notifier.go b/modules/notification/base/notifier.go index 428f9a9544..5cd2b4c060 100644 --- a/modules/notification/base/notifier.go +++ b/modules/notification/base/notifier.go @@ -28,6 +28,7 @@ type Notifier interface { NotifyIssueChangeContent(doer *models.User, issue *models.Issue, oldContent string) NotifyIssueClearLabels(doer *models.User, issue *models.Issue) 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, addedLabels []*models.Label, removedLabels []*models.Label) diff --git a/modules/notification/base/null.go b/modules/notification/base/null.go index b2ce0742b6..15d06ec856 100644 --- a/modules/notification/base/null.go +++ b/modules/notification/base/null.go @@ -102,6 +102,10 @@ func (*NullNotifier) NotifyIssueClearLabels(doer *models.User, issue *models.Iss 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 func (*NullNotifier) NotifyIssueChangeLabels(doer *models.User, issue *models.Issue, addedLabels []*models.Label, removedLabels []*models.Label) { diff --git a/modules/notification/indexer/indexer.go b/modules/notification/indexer/indexer.go index f292d7339b..6e848e6318 100644 --- a/modules/notification/indexer/indexer.go +++ b/modules/notification/indexer/indexer.go @@ -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) { issue_indexer.UpdateIssueIndexer(issue) } + +func (r *indexerNotifier) NotifyIssueChangeRef(doer *models.User, issue *models.Issue, oldRef string) { + issue_indexer.UpdateIssueIndexer(issue) +} diff --git a/modules/notification/notification.go b/modules/notification/notification.go index d17b13b9e5..57f1e7c16d 100644 --- a/modules/notification/notification.go +++ b/modules/notification/notification.go @@ -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 func NotifyIssueChangeLabels(doer *models.User, issue *models.Issue, addedLabels []*models.Label, removedLabels []*models.Label) { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 9dd49b06c8..a7b917ae1e 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -760,6 +760,7 @@ code = Code code.desc = Access source code, files, commits and branches. branch = Branch tree = Tree +clear_ref = `Clear current reference` filter_branch_and_tag = Filter branch or tag branches = Branches tags = Tags diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 4c745ed5d7..4fc83719fd 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -1244,7 +1244,7 @@ func ViewIssue(ctx *context.Context) { ctx.Data["Participants"] = participants ctx.Data["NumParticipants"] = len(participants) 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["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID) 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 func UpdateIssueContent(ctx *context.Context) { issue := GetActionIssue(ctx) diff --git a/routers/routes/routes.go b/routers/routes/routes.go index bdb82db6f5..779e8614b3 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -733,6 +733,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/title", repo.UpdateIssueTitle) m.Post("/content", repo.UpdateIssueContent) m.Post("/watch", repo.IssueWatch) + m.Post("/ref", repo.UpdateIssueRef) m.Group("/dependency", func() { m.Post("/add", repo.AddDependency) m.Post("/delete", repo.RemoveDependency) diff --git a/services/issue/issue.go b/services/issue/issue.go index 64d69119b7..0f90a2bcd0 100644 --- a/services/issue/issue.go +++ b/services/issue/issue.go @@ -42,6 +42,20 @@ func ChangeTitle(issue *models.Issue, doer *models.User, title string) (err erro 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) // Deleting is done the GitHub way (quote from their api documentation): // https://developer.github.com/v3/issues/#edit-an-issue diff --git a/templates/repo/issue/branch_selector_field.tmpl b/templates/repo/issue/branch_selector_field.tmpl index 4f80c13e52..69d99b3441 100644 --- a/templates/repo/issue/branch_selector_field.tmpl +++ b/templates/repo/issue/branch_selector_field.tmpl @@ -1,5 +1,10 @@ {{if and (not .Issue.IsPull) (not .PageIsComparePull)}} + +
+