55package models
66
77import (
8+ "bufio"
9+ "bytes"
810"fmt"
911"io/ioutil"
1012"os"
@@ -328,6 +330,34 @@ func (pr *PullRequest) CheckUserAllowedToMerge(doer *User) (err error) {
328330return nil
329331}
330332
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+
331361// Merge merges pull request to base repository.
332362// FIXME: add repoWorkingPull make sure two merges does not happen at same time.
333363func (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
371401var stderr string
372402if _ , stderr , err = process .GetManager ().ExecTimeout (5 * time .Minute ,
373403fmt .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 {
375405return fmt .Errorf ("git clone: %s" , stderr )
376406}
377407
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"
384409
385410// 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+ }
386428if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
387429fmt .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 {
389431return fmt .Errorf ("git remote add [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
390432}
391433
392- // Merge commits.
434+ // Fetch head branch
393435if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
394436fmt .Sprintf ("PullRequest.Merge (git fetch): %s" , tmpBasePath ),
395- "git" , "fetch" , "head_repo" ); err != nil {
437+ "git" , "fetch" , remoteRepoName ); err != nil {
396438return fmt .Errorf ("git fetch [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
397439}
398440
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.
399469switch mergeStyle {
400470case MergeStyleMerge :
401471if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
402472fmt .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 {
404474return fmt .Errorf ("git merge --no-ff --no-commit [%s]: %v - %s" , tmpBasePath , err , stderr )
405475}
406476
@@ -415,7 +485,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
415485// Checkout head branch
416486if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
417487fmt .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 {
419489return fmt .Errorf ("git checkout: %s" , stderr )
420490}
421491// Rebase before merging
@@ -433,14 +503,14 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
433503// Merge fast forward
434504if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
435505fmt .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 {
437507return fmt .Errorf ("git merge --ff-only [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
438508}
439509case MergeStyleRebaseMerge :
440510// Checkout head branch
441511if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
442512fmt .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 {
444514return fmt .Errorf ("git checkout: %s" , stderr )
445515}
446516// Rebase before merging
@@ -458,7 +528,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
458528// Prepare merge with commit
459529if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
460530fmt .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 {
462532return fmt .Errorf ("git merge --no-ff [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
463533}
464534
@@ -475,7 +545,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
475545// Merge with squash
476546if _ , stderr , err = process .GetManager ().ExecDir (- 1 , tmpBasePath ,
477547fmt .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 {
479549return fmt .Errorf ("git merge --squash [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
480550}
481551sig := pr .Issue .Poster .NewGitSig ()
0 commit comments