@@ -6,6 +6,7 @@ package models
6
6
7
7
import (
8
8
"fmt"
9
+ "io/ioutil"
9
10
"os"
10
11
"path"
11
12
"path/filepath"
@@ -20,6 +21,7 @@ import (
20
21
"code.gitea.io/gitea/modules/setting"
21
22
"code.gitea.io/gitea/modules/sync"
22
23
api "code.gitea.io/sdk/gitea"
24
+
23
25
"github.com/Unknwon/com"
24
26
"github.com/go-xorm/xorm"
25
27
)
@@ -43,6 +45,7 @@ const (
43
45
PullRequestStatusConflict PullRequestStatus = iota
44
46
PullRequestStatusChecking
45
47
PullRequestStatusMergeable
48
+ PullRequestStatusManuallyMerged
46
49
)
47
50
48
51
// PullRequest represents relation between pull request and repositories.
@@ -255,16 +258,6 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
255
258
go AddTestPullRequestTask (doer , pr .BaseRepo .ID , pr .BaseBranch , false )
256
259
}()
257
260
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
-
268
261
headRepoPath := RepoPath (pr .HeadUserName , pr .HeadRepo .Name )
269
262
headGitRepo , err := git .OpenRepository (headRepoPath )
270
263
if err != nil {
@@ -334,15 +327,12 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
334
327
return fmt .Errorf ("GetBranchCommit: %v" , err )
335
328
}
336
329
337
- pr .HasMerged = true
338
330
pr .Merged = time .Now ()
331
+ pr .Merger = doer
339
332
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
- }
343
333
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 )
346
336
}
347
337
348
338
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
398
388
return nil
399
389
}
400
390
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
+
401
523
// patchConflicts is a list of conflict description from Git.
402
524
var patchConflicts = []string {
403
525
"patch does not apply" ,
@@ -937,7 +1059,9 @@ func TestPullRequests() {
937
1059
log .Error (3 , "GetBaseRepo: %v" , err )
938
1060
return nil
939
1061
}
940
-
1062
+ if pr .manuallyMerged () {
1063
+ return nil
1064
+ }
941
1065
if err := pr .testPatch (); err != nil {
942
1066
log .Error (3 , "testPatch: %v" , err )
943
1067
return nil
@@ -960,6 +1084,8 @@ func TestPullRequests() {
960
1084
if err != nil {
961
1085
log .Error (4 , "GetPullRequestByID[%s]: %v" , prID , err )
962
1086
continue
1087
+ } else if pr .manuallyMerged () {
1088
+ continue
963
1089
} else if err = pr .testPatch (); err != nil {
964
1090
log .Error (4 , "testPatch[%d]: %v" , pr .ID , err )
965
1091
continue
0 commit comments