Skip to content

Commit bf647ce

Browse files
Bwkolunny
authored andcommitted
Check for manual merging of a pull request (#719)
When an open pull request got manually merged mark the pull request as merged
1 parent 17c5e12 commit bf647ce

File tree

1 file changed

+143
-17
lines changed

1 file changed

+143
-17
lines changed

models/pull.go

Lines changed: 143 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package models
66

77
import (
88
"fmt"
9+
"io/ioutil"
910
"os"
1011
"path"
1112
"path/filepath"
@@ -20,6 +21,7 @@ import (
2021
"code.gitea.io/gitea/modules/setting"
2122
"code.gitea.io/gitea/modules/sync"
2223
api "code.gitea.io/sdk/gitea"
24+
2325
"github.com/Unknwon/com"
2426
"github.com/go-xorm/xorm"
2527
)
@@ -43,6 +45,7 @@ const (
4345
PullRequestStatusConflict PullRequestStatus = iota
4446
PullRequestStatusChecking
4547
PullRequestStatusMergeable
48+
PullRequestStatusManuallyMerged
4649
)
4750

4851
// PullRequest represents relation between pull request and repositories.
@@ -255,16 +258,6 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
255258
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false)
256259
}()
257260

258-
sess := x.NewSession()
259-
defer sessionRelease(sess)
260-
if err = sess.Begin(); err != nil {
261-
return err
262-
}
263-
264-
if err = pr.Issue.changeStatus(sess, doer, pr.Issue.Repo, true); err != nil {
265-
return fmt.Errorf("Issue.changeStatus: %v", err)
266-
}
267-
268261
headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
269262
headGitRepo, err := git.OpenRepository(headRepoPath)
270263
if err != nil {
@@ -334,15 +327,12 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
334327
return fmt.Errorf("GetBranchCommit: %v", err)
335328
}
336329

337-
pr.HasMerged = true
338330
pr.Merged = time.Now()
331+
pr.Merger = doer
339332
pr.MergerID = doer.ID
340-
if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil {
341-
return fmt.Errorf("update pull request: %v", err)
342-
}
343333

344-
if err = sess.Commit(); err != nil {
345-
return fmt.Errorf("Commit: %v", err)
334+
if err = pr.setMerged(); err != nil {
335+
log.Error(4, "setMerged [%d]: %v", pr.ID, err)
346336
}
347337

348338
if err = MergePullRequestAction(doer, pr.Issue.Repo, pr.Issue); err != nil {
@@ -398,6 +388,138 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
398388
return nil
399389
}
400390

391+
// setMerged sets a pull request to merged and closes the corresponding issue
392+
func (pr *PullRequest) setMerged() (err error) {
393+
if pr.HasMerged {
394+
return fmt.Errorf("PullRequest[%d] already merged", pr.Index)
395+
}
396+
if pr.MergedCommitID == "" || pr.Merged.IsZero() || pr.Merger == nil {
397+
return fmt.Errorf("Unable to merge PullRequest[%d], some required fields are empty", pr.Index)
398+
}
399+
400+
pr.HasMerged = true
401+
402+
sess := x.NewSession()
403+
defer sessionRelease(sess)
404+
if err = sess.Begin(); err != nil {
405+
return err
406+
}
407+
408+
if err = pr.LoadIssue(); err != nil {
409+
return err
410+
}
411+
412+
if pr.Issue.Repo.Owner == nil {
413+
if err = pr.Issue.Repo.GetOwner(); err != nil {
414+
return err
415+
}
416+
}
417+
418+
if err = pr.Issue.changeStatus(sess, pr.Merger, pr.Issue.Repo, true); err != nil {
419+
return fmt.Errorf("Issue.changeStatus: %v", err)
420+
}
421+
if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil {
422+
return fmt.Errorf("update pull request: %v", err)
423+
}
424+
425+
if err = sess.Commit(); err != nil {
426+
return fmt.Errorf("Commit: %v", err)
427+
}
428+
return nil
429+
}
430+
431+
// manuallyMerged checks if a pull request got manually merged
432+
// When a pull request got manually merged mark the pull request as merged
433+
func (pr *PullRequest) manuallyMerged() bool {
434+
commit, err := pr.getMergeCommit()
435+
if err != nil {
436+
log.Error(4, "PullRequest[%d].getMergeCommit: %v", pr.ID, err)
437+
return false
438+
}
439+
if commit != nil {
440+
pr.MergedCommitID = commit.ID.String()
441+
pr.Merged = commit.Author.When
442+
pr.Status = PullRequestStatusManuallyMerged
443+
merger, _ := GetUserByEmail(commit.Author.Email)
444+
445+
// When the commit author is unknown set the BaseRepo owner as merger
446+
if merger == nil {
447+
if pr.BaseRepo.Owner == nil {
448+
if err = pr.BaseRepo.getOwner(x); err != nil {
449+
log.Error(4, "BaseRepo.getOwner[%d]: %v", pr.ID, err)
450+
return false
451+
}
452+
}
453+
merger = pr.BaseRepo.Owner
454+
}
455+
pr.Merger = merger
456+
pr.MergerID = merger.ID
457+
458+
if err = pr.setMerged(); err != nil {
459+
log.Error(4, "PullRequest[%d].setMerged : %v", pr.ID, err)
460+
return false
461+
}
462+
log.Info("manuallyMerged[%d]: Marked as manually merged into %s/%s by commit id: %s", pr.ID, pr.BaseRepo.Name, pr.BaseBranch, commit.ID.String())
463+
return true
464+
}
465+
return false
466+
}
467+
468+
// getMergeCommit checks if a pull request got merged
469+
// Returns the git.Commit of the pull request if merged
470+
func (pr *PullRequest) getMergeCommit() (*git.Commit, error) {
471+
if pr.BaseRepo == nil {
472+
var err error
473+
pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
474+
if err != nil {
475+
return nil, fmt.Errorf("GetRepositoryByID: %v", err)
476+
}
477+
}
478+
479+
indexTmpPath := filepath.Join(os.TempDir(), "gitea-"+pr.BaseRepo.Name+"-"+strconv.Itoa(time.Now().Nanosecond()))
480+
defer os.Remove(indexTmpPath)
481+
482+
headFile := fmt.Sprintf("refs/pull/%d/head", pr.Index)
483+
484+
// Check if a pull request is merged into BaseBranch
485+
_, stderr, err := process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("isMerged (git merge-base --is-ancestor): %d", pr.BaseRepo.ID),
486+
[]string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()},
487+
"git", "merge-base", "--is-ancestor", headFile, pr.BaseBranch)
488+
489+
if err != nil {
490+
// Errors are signaled by a non-zero status that is not 1
491+
if err.Error() == "exit status 1" {
492+
return nil, nil
493+
}
494+
return nil, fmt.Errorf("git merge-base --is-ancestor: %v %v", stderr, err)
495+
}
496+
497+
// We can ignore this error since we only get here when there's a valid commit in headFile
498+
commitID, _ := ioutil.ReadFile(pr.BaseRepo.RepoPath() + "/" + headFile)
499+
cmd := string(commitID)[:40] + ".." + pr.BaseBranch
500+
501+
// Get the commit from BaseBranch where the pull request got merged
502+
mergeCommit, stderr, err := process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("isMerged (git rev-list --ancestry-path --merges --reverse): %d", pr.BaseRepo.ID),
503+
[]string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()},
504+
"git", "rev-list", "--ancestry-path", "--merges", "--reverse", cmd)
505+
506+
if err != nil {
507+
return nil, fmt.Errorf("git rev-list --ancestry-path --merges --reverse: %v %v", stderr, err)
508+
}
509+
510+
gitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
511+
if err != nil {
512+
return nil, fmt.Errorf("OpenRepository: %v", err)
513+
}
514+
515+
commit, err := gitRepo.GetCommit(mergeCommit[:40])
516+
if err != nil {
517+
return nil, fmt.Errorf("GetCommit: %v", err)
518+
}
519+
520+
return commit, nil
521+
}
522+
401523
// patchConflicts is a list of conflict description from Git.
402524
var patchConflicts = []string{
403525
"patch does not apply",
@@ -937,7 +1059,9 @@ func TestPullRequests() {
9371059
log.Error(3, "GetBaseRepo: %v", err)
9381060
return nil
9391061
}
940-
1062+
if pr.manuallyMerged() {
1063+
return nil
1064+
}
9411065
if err := pr.testPatch(); err != nil {
9421066
log.Error(3, "testPatch: %v", err)
9431067
return nil
@@ -960,6 +1084,8 @@ func TestPullRequests() {
9601084
if err != nil {
9611085
log.Error(4, "GetPullRequestByID[%s]: %v", prID, err)
9621086
continue
1087+
} else if pr.manuallyMerged() {
1088+
continue
9631089
} else if err = pr.testPatch(); err != nil {
9641090
log.Error(4, "testPatch[%d]: %v", pr.ID, err)
9651091
continue

0 commit comments

Comments
 (0)