Skip to content

Commit 61db834

Browse files
saitholunny
authored andcommitted
Change target branch for pull request (#6488)
* Adds functionality to change target branch of created pull requests Signed-off-by: Mario Lubenka <[email protected]> * Use const instead of var in JavaScript additions Signed-off-by: Mario Lubenka <[email protected]> * Check if branches are equal and if PR already exists before changing target branch Signed-off-by: Mario Lubenka <[email protected]> * Make sure to check all commits Signed-off-by: Mario Lubenka <[email protected]> * Print error messages for user as error flash message Signed-off-by: Mario Lubenka <[email protected]> * Disallow changing target branch of closed or merged pull requests Signed-off-by: Mario Lubenka <[email protected]> * Resolve conflicts after merge of upstream/master Signed-off-by: Mario Lubenka <[email protected]> * Change order of branch select fields Signed-off-by: Mario Lubenka <[email protected]> * Removes duplicate check Signed-off-by: Mario Lubenka <[email protected]> * Use ctx.Tr for translations Signed-off-by: Mario Lubenka <[email protected]> * Recompile JS Signed-off-by: Mario Lubenka <[email protected]> * Use correct translation namespace Signed-off-by: Mario Lubenka <[email protected]> * Remove redundant if condition Signed-off-by: Mario Lubenka <[email protected]> * Moves most change branch logic into pull service Signed-off-by: Mario Lubenka <[email protected]> * Completes comment Signed-off-by: Mario Lubenka <[email protected]> * Add Ref to ChangesPayload for logging changed target branches instead of creating a new struct Signed-off-by: Mario Lubenka <[email protected]> * Revert changes to go.mod Signed-off-by: Mario Lubenka <[email protected]> * Directly use createComment method Signed-off-by: Mario Lubenka <[email protected]> * Return 404 if pull request is not found. Move written check up Signed-off-by: Mario Lubenka <[email protected]> * Remove variable declaration Signed-off-by: Mario Lubenka <[email protected]> * Return client errors on change pull request target errors Signed-off-by: Mario Lubenka <[email protected]> * Return error in commit.HasPreviousCommit Signed-off-by: Mario Lubenka <[email protected]> * Adds blank line Signed-off-by: Mario Lubenka <[email protected]> * Test patch before persisting new target branch Signed-off-by: Mario Lubenka <[email protected]> * Update patch before testing (not working) Signed-off-by: Mario Lubenka <[email protected]> * Removes patch calls when changeing pull request target Signed-off-by: Mario Lubenka <[email protected]> * Removes unneeded check for base name Signed-off-by: Mario Lubenka <[email protected]> * Moves ChangeTargetBranch completely to pull service. Update patch status. Signed-off-by: Mario Lubenka <[email protected]> * Set webhook mode after errors were validated Signed-off-by: Mario Lubenka <[email protected]> * Update PR in one transaction Signed-off-by: Mario Lubenka <[email protected]> * Move logic for check if head is equal with branch to pull model Signed-off-by: Mario Lubenka <[email protected]> * Adds missing comment and simplify return Signed-off-by: Mario Lubenka <[email protected]> * Adjust CreateComment method call Signed-off-by: Mario Lubenka <[email protected]>
1 parent 59d6401 commit 61db834

File tree

19 files changed

+461
-19
lines changed

19 files changed

+461
-19
lines changed

models/error.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,22 @@ func (err ErrBranchNameConflict) Error() string {
953953
return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName)
954954
}
955955

956+
// ErrBranchesEqual represents an error that branch name conflicts with other branch.
957+
type ErrBranchesEqual struct {
958+
BaseBranchName string
959+
HeadBranchName string
960+
}
961+
962+
// IsErrBranchesEqual checks if an error is an ErrBranchesEqual.
963+
func IsErrBranchesEqual(err error) bool {
964+
_, ok := err.(ErrBranchesEqual)
965+
return ok
966+
}
967+
968+
func (err ErrBranchesEqual) Error() string {
969+
return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName)
970+
}
971+
956972
// ErrNotAllowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it.
957973
type ErrNotAllowedToMerge struct {
958974
Reason string
@@ -1090,6 +1106,23 @@ func (err ErrIssueNotExist) Error() string {
10901106
return fmt.Sprintf("issue does not exist [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index)
10911107
}
10921108

1109+
// ErrIssueIsClosed represents a "IssueIsClosed" kind of error.
1110+
type ErrIssueIsClosed struct {
1111+
ID int64
1112+
RepoID int64
1113+
Index int64
1114+
}
1115+
1116+
// IsErrIssueIsClosed checks if an error is a ErrIssueNotExist.
1117+
func IsErrIssueIsClosed(err error) bool {
1118+
_, ok := err.(ErrIssueIsClosed)
1119+
return ok
1120+
}
1121+
1122+
func (err ErrIssueIsClosed) Error() string {
1123+
return fmt.Sprintf("issue is closed [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index)
1124+
}
1125+
10931126
// ErrIssueLabelTemplateLoad represents a "ErrIssueLabelTemplateLoad" kind of error.
10941127
type ErrIssueLabelTemplateLoad struct {
10951128
TemplateFile string
@@ -1326,6 +1359,28 @@ func (err ErrRebaseConflicts) Error() string {
13261359
return fmt.Sprintf("Rebase Error: %v: Whilst Rebasing: %s\n%s\n%s", err.Err, err.CommitSHA, err.StdErr, err.StdOut)
13271360
}
13281361

1362+
// ErrPullRequestHasMerged represents a "PullRequestHasMerged"-error
1363+
type ErrPullRequestHasMerged struct {
1364+
ID int64
1365+
IssueID int64
1366+
HeadRepoID int64
1367+
BaseRepoID int64
1368+
HeadBranch string
1369+
BaseBranch string
1370+
}
1371+
1372+
// IsErrPullRequestHasMerged checks if an error is a ErrPullRequestHasMerged.
1373+
func IsErrPullRequestHasMerged(err error) bool {
1374+
_, ok := err.(ErrPullRequestHasMerged)
1375+
return ok
1376+
}
1377+
1378+
// Error does pretty-printing :D
1379+
func (err ErrPullRequestHasMerged) Error() string {
1380+
return fmt.Sprintf("pull request has merged [id: %d, issue_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]",
1381+
err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch)
1382+
}
1383+
13291384
// _________ __
13301385
// \_ ___ \ ____ _____ _____ ____ _____/ |_
13311386
// / \ \/ / _ \ / \ / \_/ __ \ / \ __\

models/issue_comment.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ const (
8282
CommentTypeLock
8383
// Unlocks a previously locked issue
8484
CommentTypeUnlock
85+
// Change pull request's target branch
86+
CommentTypeChangeTargetBranch
8587
)
8688

8789
// CommentTag defines comment tag type
@@ -116,6 +118,8 @@ type Comment struct {
116118
Assignee *User `xorm:"-"`
117119
OldTitle string
118120
NewTitle string
121+
OldRef string
122+
NewRef string
119123
DependentIssueID int64
120124
DependentIssue *Issue `xorm:"-"`
121125

@@ -517,6 +521,8 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
517521
Content: opts.Content,
518522
OldTitle: opts.OldTitle,
519523
NewTitle: opts.NewTitle,
524+
OldRef: opts.OldRef,
525+
NewRef: opts.NewRef,
520526
DependentIssueID: opts.DependentIssueID,
521527
TreePath: opts.TreePath,
522528
ReviewID: opts.ReviewID,
@@ -673,6 +679,8 @@ type CreateCommentOptions struct {
673679
RemovedAssignee bool
674680
OldTitle string
675681
NewTitle string
682+
OldRef string
683+
NewRef string
676684
CommitID int64
677685
CommitSHA string
678686
Patch string

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ var migrations = []Migration{
280280
NewMigration("update branch protection for can push and whitelist enable", addBranchProtectionCanPushAndEnableWhitelist),
281281
// v112 -> v113
282282
NewMigration("remove release attachments which repository deleted", removeAttachmentMissedRepo),
283+
// v113 -> v114
284+
NewMigration("new feature: change target branch of pull requests", featureChangeTargetBranch),
283285
}
284286

285287
// Migrate database to current version

models/migrations/v113.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"fmt"
9+
10+
"xorm.io/xorm"
11+
)
12+
13+
func featureChangeTargetBranch(x *xorm.Engine) error {
14+
type Comment struct {
15+
OldRef string
16+
NewRef string
17+
}
18+
19+
if err := x.Sync2(new(Comment)); err != nil {
20+
return fmt.Errorf("Sync2: %v", err)
21+
}
22+
return nil
23+
}

models/pull.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,3 +664,32 @@ func (pr *PullRequest) GetWorkInProgressPrefix() string {
664664
}
665665
return ""
666666
}
667+
668+
// IsHeadEqualWithBranch returns if the commits of branchName are available in pull request head
669+
func (pr *PullRequest) IsHeadEqualWithBranch(branchName string) (bool, error) {
670+
var err error
671+
if err = pr.GetBaseRepo(); err != nil {
672+
return false, err
673+
}
674+
baseGitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
675+
if err != nil {
676+
return false, err
677+
}
678+
baseCommit, err := baseGitRepo.GetBranchCommit(branchName)
679+
if err != nil {
680+
return false, err
681+
}
682+
683+
if err = pr.GetHeadRepo(); err != nil {
684+
return false, err
685+
}
686+
headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
687+
if err != nil {
688+
return false, err
689+
}
690+
headCommit, err := headGitRepo.GetBranchCommit(pr.HeadBranch)
691+
if err != nil {
692+
return false, err
693+
}
694+
return baseCommit.HasPreviousCommit(headCommit.ID)
695+
}

modules/git/commit.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,27 @@ func (c *Commit) CommitsBefore() (*list.List, error) {
306306
return c.repo.getCommitsBefore(c.ID)
307307
}
308308

309+
// HasPreviousCommit returns true if a given commitHash is contained in commit's parents
310+
func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) {
311+
for i := 0; i < c.ParentCount(); i++ {
312+
commit, err := c.Parent(i)
313+
if err != nil {
314+
return false, err
315+
}
316+
if commit.ID == commitHash {
317+
return true, nil
318+
}
319+
commitInParentCommit, err := commit.HasPreviousCommit(commitHash)
320+
if err != nil {
321+
return false, err
322+
}
323+
if commitInParentCommit {
324+
return true, nil
325+
}
326+
}
327+
return false, nil
328+
}
329+
309330
// CommitsBeforeLimit returns num commits before current revision
310331
func (c *Commit) CommitsBeforeLimit(num int) (*list.List, error) {
311332
return c.repo.getCommitsBeforeLimit(c.ID, num)

modules/notification/base/notifier.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type Notifier interface {
3434
NotifyMergePullRequest(*models.PullRequest, *models.User, *git.Repository)
3535
NotifyPullRequestSynchronized(doer *models.User, pr *models.PullRequest)
3636
NotifyPullRequestReview(*models.PullRequest, *models.Review, *models.Comment)
37+
NotifyPullRequestChangeTargetBranch(doer *models.User, pr *models.PullRequest, oldBranch string)
3738

3839
NotifyCreateIssueComment(*models.User, *models.Repository,
3940
*models.Issue, *models.Comment)

modules/notification/base/null.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ func (*NullNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *models
5050
func (*NullNotifier) NotifyPullRequestSynchronized(doer *models.User, pr *models.PullRequest) {
5151
}
5252

53+
// NotifyPullRequestChangeTargetBranch places a place holder function
54+
func (*NullNotifier) NotifyPullRequestChangeTargetBranch(doer *models.User, pr *models.PullRequest, oldBranch string) {
55+
}
56+
5357
// NotifyUpdateComment places a place holder function
5458
func (*NullNotifier) NotifyUpdateComment(doer *models.User, c *models.Comment, oldContent string) {
5559
}

modules/notification/notification.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ func NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comm
8787
}
8888
}
8989

90+
// NotifyPullRequestChangeTargetBranch notifies when a pull request's target branch was changed
91+
func NotifyPullRequestChangeTargetBranch(doer *models.User, pr *models.PullRequest, oldBranch string) {
92+
for _, notifier := range notifiers {
93+
notifier.NotifyPullRequestChangeTargetBranch(doer, pr, oldBranch)
94+
}
95+
}
96+
9097
// NotifyUpdateComment notifies update comment to notifiers
9198
func NotifyUpdateComment(doer *models.User, c *models.Comment, oldContent string) {
9299
for _, notifier := range notifiers {

modules/notification/webhook/webhook.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,37 @@ func (*webhookNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *mod
559559
}
560560
}
561561

562+
func (m *webhookNotifier) NotifyPullRequestChangeTargetBranch(doer *models.User, pr *models.PullRequest, oldBranch string) {
563+
issue := pr.Issue
564+
if !issue.IsPull {
565+
return
566+
}
567+
var err error
568+
569+
if err = issue.LoadPullRequest(); err != nil {
570+
log.Error("LoadPullRequest failed: %v", err)
571+
return
572+
}
573+
issue.PullRequest.Issue = issue
574+
mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
575+
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
576+
Action: api.HookIssueEdited,
577+
Index: issue.Index,
578+
Changes: &api.ChangesPayload{
579+
Ref: &api.ChangesFromPayload{
580+
From: oldBranch,
581+
},
582+
},
583+
PullRequest: issue.PullRequest.APIFormat(),
584+
Repository: issue.Repo.APIFormat(mode),
585+
Sender: doer.APIFormat(),
586+
})
587+
588+
if err != nil {
589+
log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
590+
}
591+
}
592+
562593
func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment) {
563594
var reviewHookType models.HookEventType
564595

modules/structs/hook.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,10 +398,11 @@ type ChangesFromPayload struct {
398398
From string `json:"from"`
399399
}
400400

401-
// ChangesPayload FIXME
401+
// ChangesPayload represents the payload information of issue change
402402
type ChangesPayload struct {
403403
Title *ChangesFromPayload `json:"title,omitempty"`
404404
Body *ChangesFromPayload `json:"body,omitempty"`
405+
Ref *ChangesFromPayload `json:"ref,omitempty"`
405406
}
406407

407408
// __________ .__ .__ __________ __

options/locale/locale_en-US.ini

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1033,15 +1033,17 @@ pulls.no_results = No results found.
10331033
pulls.nothing_to_compare = These branches are equal. There is no need to create a pull request.
10341034
pulls.has_pull_request = `A pull request between these branches already exists: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
10351035
pulls.create = Create Pull Request
1036-
pulls.title_desc = wants to merge %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code>
1036+
pulls.title_desc = wants to merge %[1]d commits from <code>%[2]s</code> into <code id="branch_target">%[3]s</code>
10371037
pulls.merged_title_desc = merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s
1038+
pulls.change_target_branch_at = `changed target branch from <b>%s</b> to <b>%s</b> %s`
10381039
pulls.tab_conversation = Conversation
10391040
pulls.tab_commits = Commits
10401041
pulls.tab_files = Files Changed
10411042
pulls.reopen_to_merge = Please reopen this pull request to perform a merge.
10421043
pulls.cant_reopen_deleted_branch = This pull request cannot be reopened because the branch was deleted.
10431044
pulls.merged = Merged
10441045
pulls.merged_as = The pull request has been merged as <a rel="nofollow" class="ui sha" href="%[1]s"><code>%[2]s</code></a>.
1046+
pulls.is_closed = The pull request has been closed.
10451047
pulls.has_merged = The pull request has been merged.
10461048
pulls.title_wip_desc = `<a href="#">Start the title with <strong>%s</strong></a> to prevent the pull request from being merged accidentally.`
10471049
pulls.cannot_merge_work_in_progress = This pull request is marked as a work in progress. Remove the <strong>%s</strong> prefix from the title when it's ready

routers/repo/issue.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,19 @@ func commentTag(repo *models.Repository, poster *models.User, issue *models.Issu
605605
return models.CommentTagNone, nil
606606
}
607607

608+
func getBranchData(ctx *context.Context, issue *models.Issue) {
609+
ctx.Data["BaseBranch"] = nil
610+
ctx.Data["HeadBranch"] = nil
611+
ctx.Data["HeadUserName"] = nil
612+
ctx.Data["BaseName"] = ctx.Repo.Repository.OwnerName
613+
if issue.IsPull {
614+
pull := issue.PullRequest
615+
ctx.Data["BaseBranch"] = pull.BaseBranch
616+
ctx.Data["HeadBranch"] = pull.HeadBranch
617+
ctx.Data["HeadUserName"] = pull.MustHeadUserName()
618+
}
619+
}
620+
608621
// ViewIssue render issue view page
609622
func ViewIssue(ctx *context.Context) {
610623
if ctx.Params(":type") == "issues" {
@@ -885,6 +898,7 @@ func ViewIssue(ctx *context.Context) {
885898
}
886899
}
887900

901+
getBranchData(ctx, issue)
888902
if issue.IsPull {
889903
pull := issue.PullRequest
890904
pull.Issue = issue

0 commit comments

Comments
 (0)