5
5
package models
6
6
7
7
import (
8
+ "bufio"
9
+ "bytes"
8
10
"fmt"
9
11
"io/ioutil"
10
12
"os"
@@ -328,6 +330,34 @@ func (pr *PullRequest) CheckUserAllowedToMerge(doer *User) (err error) {
328
330
return nil
329
331
}
330
332
333
+ func getDiffTree (repoPath , baseBranch , headBranch string ) (string , error ) {
334
+ getDiffTreeFromBranch := func (repoPath , baseBranch , headBranch string ) (string , error ) {
335
+ var stdout , stderr string
336
+ // Compute the diff-tree for sparse-checkout
337
+ // The branch argument must be enclosed with double-quotes ("") in case it contains slashes (e.g "feature/test")
338
+ stdout , stderr , err := process .GetManager ().ExecDir (- 1 , repoPath ,
339
+ fmt .Sprintf ("PullRequest.Merge (git diff-tree): %s" , repoPath ),
340
+ "git" , "diff-tree" , "--no-commit-id" , "--name-only" , "-r" , "--root" , baseBranch , headBranch )
341
+ if err != nil {
342
+ return "" , fmt .Errorf ("git diff-tree [%s base:%s head:%s]: %s" , repoPath , baseBranch , headBranch , stderr )
343
+ }
344
+ return stdout , nil
345
+ }
346
+
347
+ list , err := getDiffTreeFromBranch (repoPath , baseBranch , headBranch )
348
+ if err != nil {
349
+ return "" , err
350
+ }
351
+
352
+ // Prefixing '/' for each entry, otherwise all files with the same name in subdirectories would be matched.
353
+ out := bytes.Buffer {}
354
+ scanner := bufio .NewScanner (strings .NewReader (list ))
355
+ for scanner .Scan () {
356
+ fmt .Fprintf (& out , "/%s\n " , scanner .Text ())
357
+ }
358
+ return out .String (), nil
359
+ }
360
+
331
361
// Merge merges pull request to base repository.
332
362
// FIXME: add repoWorkingPull make sure two merges does not happen at same time.
333
363
func (pr * PullRequest ) Merge (doer * User , baseGitRepo * git.Repository , mergeStyle MergeStyle , message string ) (err error ) {
@@ -371,36 +401,76 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
371
401
var stderr string
372
402
if _ , stderr , err = process .GetManager ().ExecTimeout (5 * time .Minute ,
373
403
fmt .Sprintf ("PullRequest.Merge (git clone): %s" , tmpBasePath ),
374
- "git" , "clone" , baseGitRepo .Path , tmpBasePath ); err != nil {
404
+ "git" , "clone" , "-s" , "--no-checkout" , "-b" , pr . BaseBranch , baseGitRepo .Path , tmpBasePath ); err != nil {
375
405
return fmt .Errorf ("git clone: %s" , stderr )
376
406
}
377
407
378
- // Check out base branch.
379
- if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
380
- fmt .Sprintf ("PullRequest.Merge (git checkout): %s" , tmpBasePath ),
381
- "git" , "checkout" , pr .BaseBranch ); err != nil {
382
- return fmt .Errorf ("git checkout: %s" , stderr )
383
- }
408
+ remoteRepoName := "head_repo"
384
409
385
410
// Add head repo remote.
411
+ addCacheRepo := func (staging , cache string ) error {
412
+ p := filepath .Join (staging , ".git" , "objects" , "info" , "alternates" )
413
+ f , err := os .OpenFile (p , os .O_APPEND | os .O_WRONLY , 0600 )
414
+ if err != nil {
415
+ return err
416
+ }
417
+ defer f .Close ()
418
+ data := filepath .Join (cache , "objects" )
419
+ if _ , err := fmt .Fprintln (f , data ); err != nil {
420
+ return err
421
+ }
422
+ return nil
423
+ }
424
+
425
+ if err := addCacheRepo (tmpBasePath , headRepoPath ); err != nil {
426
+ return fmt .Errorf ("addCacheRepo [%s -> %s]: %v" , headRepoPath , tmpBasePath , err )
427
+ }
386
428
if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
387
429
fmt .Sprintf ("PullRequest.Merge (git remote add): %s" , tmpBasePath ),
388
- "git" , "remote" , "add" , "head_repo" , headRepoPath ); err != nil {
430
+ "git" , "remote" , "add" , remoteRepoName , headRepoPath ); err != nil {
389
431
return fmt .Errorf ("git remote add [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
390
432
}
391
433
392
- // Merge commits.
434
+ // Fetch head branch
393
435
if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
394
436
fmt .Sprintf ("PullRequest.Merge (git fetch): %s" , tmpBasePath ),
395
- "git" , "fetch" , "head_repo" ); err != nil {
437
+ "git" , "fetch" , remoteRepoName ); err != nil {
396
438
return fmt .Errorf ("git fetch [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
397
439
}
398
440
441
+ trackingBranch := path .Join (remoteRepoName , pr .HeadBranch )
442
+ stagingBranch := fmt .Sprintf ("%s_%s" , remoteRepoName , pr .HeadBranch )
443
+
444
+ // Enable sparse-checkout
445
+ sparseCheckoutList , err := getDiffTree (tmpBasePath , pr .BaseBranch , trackingBranch )
446
+ if err != nil {
447
+ return fmt .Errorf ("getDiffTree: %v" , err )
448
+ }
449
+
450
+ sparseCheckoutListPath := filepath .Join (tmpBasePath , ".git" , "info" , "sparse-checkout" )
451
+ if err := ioutil .WriteFile (sparseCheckoutListPath , []byte (sparseCheckoutList ), 0600 ); err != nil {
452
+ return fmt .Errorf ("Writing sparse-checkout file to %s: %v" , sparseCheckoutListPath , err )
453
+ }
454
+
455
+ if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
456
+ fmt .Sprintf ("PullRequest.Merge (git config): %s" , tmpBasePath ),
457
+ "git" , "config" , "--local" , "core.sparseCheckout" , "true" ); err != nil {
458
+ return fmt .Errorf ("git config [core.sparsecheckout -> true]: %v" , stderr )
459
+ }
460
+
461
+ // Read base branch index
462
+ if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
463
+ fmt .Sprintf ("PullRequest.Merge (git read-tree): %s" , tmpBasePath ),
464
+ "git" , "read-tree" , "HEAD" ); err != nil {
465
+ return fmt .Errorf ("git read-tree HEAD: %s" , stderr )
466
+ }
467
+
468
+ // Merge commits.
399
469
switch mergeStyle {
400
470
case MergeStyleMerge :
401
471
if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
402
472
fmt .Sprintf ("PullRequest.Merge (git merge --no-ff --no-commit): %s" , tmpBasePath ),
403
- "git" , "merge" , "--no-ff" , "--no-commit" , "head_repo/" + pr . HeadBranch ); err != nil {
473
+ "git" , "merge" , "--no-ff" , "--no-commit" , trackingBranch ); err != nil {
404
474
return fmt .Errorf ("git merge --no-ff --no-commit [%s]: %v - %s" , tmpBasePath , err , stderr )
405
475
}
406
476
@@ -415,7 +485,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
415
485
// Checkout head branch
416
486
if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
417
487
fmt .Sprintf ("PullRequest.Merge (git checkout): %s" , tmpBasePath ),
418
- "git" , "checkout" , "-b" , "head_repo_" + pr . HeadBranch , "head_repo/" + pr . HeadBranch ); err != nil {
488
+ "git" , "checkout" , "-b" , stagingBranch , trackingBranch ); err != nil {
419
489
return fmt .Errorf ("git checkout: %s" , stderr )
420
490
}
421
491
// Rebase before merging
@@ -433,14 +503,14 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
433
503
// Merge fast forward
434
504
if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
435
505
fmt .Sprintf ("PullRequest.Merge (git rebase): %s" , tmpBasePath ),
436
- "git" , "merge" , "--ff-only" , "-q" , "head_repo_" + pr . HeadBranch ); err != nil {
506
+ "git" , "merge" , "--ff-only" , "-q" , stagingBranch ); err != nil {
437
507
return fmt .Errorf ("git merge --ff-only [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
438
508
}
439
509
case MergeStyleRebaseMerge :
440
510
// Checkout head branch
441
511
if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
442
512
fmt .Sprintf ("PullRequest.Merge (git checkout): %s" , tmpBasePath ),
443
- "git" , "checkout" , "-b" , "head_repo_" + pr . HeadBranch , "head_repo/" + pr . HeadBranch ); err != nil {
513
+ "git" , "checkout" , "-b" , stagingBranch , trackingBranch ); err != nil {
444
514
return fmt .Errorf ("git checkout: %s" , stderr )
445
515
}
446
516
// Rebase before merging
@@ -458,7 +528,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
458
528
// Prepare merge with commit
459
529
if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
460
530
fmt .Sprintf ("PullRequest.Merge (git merge): %s" , tmpBasePath ),
461
- "git" , "merge" , "--no-ff" , "--no-commit" , "-q" , "head_repo_" + pr . HeadBranch ); err != nil {
531
+ "git" , "merge" , "--no-ff" , "--no-commit" , "-q" , stagingBranch ); err != nil {
462
532
return fmt .Errorf ("git merge --no-ff [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
463
533
}
464
534
@@ -475,7 +545,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
475
545
// Merge with squash
476
546
if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
477
547
fmt .Sprintf ("PullRequest.Merge (git squash): %s" , tmpBasePath ),
478
- "git" , "merge" , "-q" , "--squash" , "head_repo/" + pr . HeadBranch ); err != nil {
548
+ "git" , "merge" , "-q" , "--squash" , trackingBranch ); err != nil {
479
549
return fmt .Errorf ("git merge --squash [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
480
550
}
481
551
sig := pr .Issue .Poster .NewGitSig ()
0 commit comments