@ -5,6 +5,8 @@
package models
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"os"
@ -328,6 +330,34 @@ func (pr *PullRequest) CheckUserAllowedToMerge(doer *User) (err error) {
return nil
}
func getDiffTree ( repoPath , baseBranch , headBranch string ) ( string , error ) {
getDiffTreeFromBranch := func ( repoPath , baseBranch , headBranch string ) ( string , error ) {
var stdout , stderr string
// Compute the diff-tree for sparse-checkout
// The branch argument must be enclosed with double-quotes ("") in case it contains slashes (e.g "feature/test")
stdout , stderr , err := process . GetManager ( ) . ExecDir ( - 1 , repoPath ,
fmt . Sprintf ( "PullRequest.Merge (git diff-tree): %s" , repoPath ) ,
"git" , "diff-tree" , "--no-commit-id" , "--name-only" , "-r" , "--root" , baseBranch , headBranch )
if err != nil {
return "" , fmt . Errorf ( "git diff-tree [%s base:%s head:%s]: %s" , repoPath , baseBranch , headBranch , stderr )
}
return stdout , nil
}
list , err := getDiffTreeFromBranch ( repoPath , baseBranch , headBranch )
if err != nil {
return "" , err
}
// Prefixing '/' for each entry, otherwise all files with the same name in subdirectories would be matched.
out := bytes . Buffer { }
scanner := bufio . NewScanner ( strings . NewReader ( list ) )
for scanner . Scan ( ) {
fmt . Fprintf ( & out , "/%s\n" , scanner . Text ( ) )
}
return out . String ( ) , nil
}
// Merge merges pull request to base repository.
// FIXME: add repoWorkingPull make sure two merges does not happen at same time.
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
var stderr string
if _ , stderr , err = process . GetManager ( ) . ExecTimeout ( 5 * time . Minute ,
fmt . Sprintf ( "PullRequest.Merge (git clone): %s" , tmpBasePath ) ,
"git" , "clone" , baseGitRepo . Path , tmpBasePath ) ; err != nil {
"git" , "clone" , "-s" , "--no-checkout" , "-b" , pr . BaseBranch , baseGitRepo . Path , tmpBasePath ) ; err != nil {
return fmt . Errorf ( "git clone: %s" , stderr )
}
// Check out base branch.
if _ , stderr , err = process . GetManager ( ) . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git checkout): %s" , tmpBasePath ) ,
"git" , "checkout" , pr . BaseBranch ) ; err != nil {
return fmt . Errorf ( "git checkout: %s" , stderr )
}
remoteRepoName := "head_repo"
// Add head repo remote.
addCacheRepo := func ( staging , cache string ) error {
p := filepath . Join ( staging , ".git" , "objects" , "info" , "alternates" )
f , err := os . OpenFile ( p , os . O_APPEND | os . O_WRONLY , 0600 )
if err != nil {
return err
}
defer f . Close ( )
data := filepath . Join ( cache , "objects" )
if _ , err := fmt . Fprintln ( f , data ) ; err != nil {
return err
}
return nil
}
if err := addCacheRepo ( tmpBasePath , headRepoPath ) ; err != nil {
return fmt . Errorf ( "addCacheRepo [%s -> %s]: %v" , headRepoPath , tmpBasePath , err )
}
if _ , stderr , err = process . GetManager ( ) . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git remote add): %s" , tmpBasePath ) ,
"git" , "remote" , "add" , "head_repo" , headRepoPath ) ; err != nil {
"git" , "remote" , "add" , remoteRepoName , headRepoPath ) ; err != nil {
return fmt . Errorf ( "git remote add [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
}
// Merge commits.
// Fetch head branch
if _ , stderr , err = process . GetManager ( ) . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git fetch): %s" , tmpBasePath ) ,
"git" , "fetch" , "head_repo" ) ; err != nil {
"git" , "fetch" , remoteRepoName ) ; err != nil {
return fmt . Errorf ( "git fetch [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
}
trackingBranch := path . Join ( remoteRepoName , pr . HeadBranch )
stagingBranch := fmt . Sprintf ( "%s_%s" , remoteRepoName , pr . HeadBranch )
// Enable sparse-checkout
sparseCheckoutList , err := getDiffTree ( tmpBasePath , pr . BaseBranch , trackingBranch )
if err != nil {
return fmt . Errorf ( "getDiffTree: %v" , err )
}
sparseCheckoutListPath := filepath . Join ( tmpBasePath , ".git" , "info" , "sparse-checkout" )
if err := ioutil . WriteFile ( sparseCheckoutListPath , [ ] byte ( sparseCheckoutList ) , 0600 ) ; err != nil {
return fmt . Errorf ( "Writing sparse-checkout file to %s: %v" , sparseCheckoutListPath , err )
}
if _ , stderr , err = process . GetManager ( ) . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git config): %s" , tmpBasePath ) ,
"git" , "config" , "--local" , "core.sparseCheckout" , "true" ) ; err != nil {
return fmt . Errorf ( "git config [core.sparsecheckout -> true]: %v" , stderr )
}
// Read base branch index
if _ , stderr , err = process . GetManager ( ) . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git read-tree): %s" , tmpBasePath ) ,
"git" , "read-tree" , "HEAD" ) ; err != nil {
return fmt . Errorf ( "git read-tree HEAD: %s" , stderr )
}
// Merge commits.
switch mergeStyle {
case MergeStyleMerge :
if _ , stderr , err = process . GetManager ( ) . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git merge --no-ff --no-commit): %s" , tmpBasePath ) ,
"git" , "merge" , "--no-ff" , "--no-commit" , "head_repo/" + pr . HeadBranch ) ; err != nil {
"git" , "merge" , "--no-ff" , "--no-commit" , tracking Branch) ; err != nil {
return fmt . Errorf ( "git merge --no-ff --no-commit [%s]: %v - %s" , tmpBasePath , err , stderr )
}
@ -415,7 +485,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
// Checkout head branch
if _ , stderr , err = process . GetManager ( ) . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git checkout): %s" , tmpBasePath ) ,
"git" , "checkout" , "-b" , "head_repo_" + pr . HeadBranch , "head_repo/" + pr . Head Branch) ; err != nil {
"git" , "checkout" , "-b" , stagingBranch , tracking Branch) ; err != nil {
return fmt . Errorf ( "git checkout: %s" , stderr )
}
// Rebase before merging
@ -433,14 +503,14 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
// Merge fast forward
if _ , stderr , err = process . GetManager ( ) . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git rebase): %s" , tmpBasePath ) ,
"git" , "merge" , "--ff-only" , "-q" , "head_repo_" + pr . Head Branch) ; err != nil {
"git" , "merge" , "--ff-only" , "-q" , staging Branch) ; err != nil {
return fmt . Errorf ( "git merge --ff-only [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
}
case MergeStyleRebaseMerge :
// Checkout head branch
if _ , stderr , err = process . GetManager ( ) . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git checkout): %s" , tmpBasePath ) ,
"git" , "checkout" , "-b" , "head_repo_" + pr . HeadBranch , "head_repo/" + pr . Head Branch) ; err != nil {
"git" , "checkout" , "-b" , stagingBranch , tracking Branch) ; err != nil {
return fmt . Errorf ( "git checkout: %s" , stderr )
}
// Rebase before merging
@ -458,7 +528,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
// Prepare merge with commit
if _ , stderr , err = process . GetManager ( ) . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git merge): %s" , tmpBasePath ) ,
"git" , "merge" , "--no-ff" , "--no-commit" , "-q" , "head_repo_" + pr . Head Branch) ; err != nil {
"git" , "merge" , "--no-ff" , "--no-commit" , "-q" , staging Branch) ; err != nil {
return fmt . Errorf ( "git merge --no-ff [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
}
@ -475,7 +545,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
// Merge with squash
if _ , stderr , err = process . GetManager ( ) . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git squash): %s" , tmpBasePath ) ,
"git" , "merge" , "-q" , "--squash" , "head_repo/" + pr . Head Branch) ; err != nil {
"git" , "merge" , "-q" , "--squash" , tracking Branch) ; err != nil {
return fmt . Errorf ( "git merge --squash [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
}
sig := pr . Issue . Poster . NewGitSig ( )