@ -11,7 +11,6 @@ import (
"fmt"
"fmt"
"io/ioutil"
"io/ioutil"
"os"
"os"
"path"
"path/filepath"
"path/filepath"
"strings"
"strings"
@ -22,6 +21,8 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/timeutil"
"github.com/mcuadros/go-version"
)
)
// Merge merges pull request to base repository.
// Merge merges pull request to base repository.
@ -66,20 +67,17 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
headRepoPath := models . RepoPath ( pr . HeadUserName , pr . HeadRepo . Name )
headRepoPath := models . RepoPath ( pr . HeadUserName , pr . HeadRepo . Name )
if err := git . Clone ( baseGitRepo . Path , tmpBasePath , git . CloneRepoOptions {
if err := git . InitRepository ( tmpBasePath , false ) ; err != nil {
Shared : true ,
return fmt . Errorf ( "git init: %v" , err )
NoCheckout : true ,
Branch : pr . BaseBranch ,
} ) ; err != nil {
return fmt . Errorf ( "git clone: %v" , err )
}
}
remoteRepoName := "head_repo"
remoteRepoName := "head_repo"
baseBranch := "base"
// Add head repo remote.
// Add head repo remote.
addCacheRepo := func ( staging , cache string ) error {
addCacheRepo := func ( staging , cache string ) error {
p := filepath . Join ( staging , ".git" , "objects" , "info" , "alternates" )
p := filepath . Join ( staging , ".git" , "objects" , "info" , "alternates" )
f , err := os . OpenFile ( p , os . O_APPEND | os . O_WRONLY , 0600 )
f , err := os . OpenFile ( p , os . O_APPEND | os . O_CREATE | os . O_ WRONLY , 0600 )
if err != nil {
if err != nil {
return err
return err
}
}
@ -91,25 +89,41 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
return nil
return nil
}
}
if err := addCacheRepo ( tmpBasePath , headRepo Path) ; err != nil {
if err := addCacheRepo ( tmpBasePath , baseGitRepo . Path ) ; err != nil {
return fmt . Errorf ( "addCacheRepo [%s -> %s]: %v" , headRepoPath , tmpBasePath , err )
return fmt . Errorf ( "addCacheRepo [%s -> %s]: %v" , headRepoPath , tmpBasePath , err )
}
}
var errbuf strings . Builder
var errbuf strings . Builder
if err := git . NewCommand ( "remote" , "add" , "-t" , pr . BaseBranch , "-m" , pr . BaseBranch , "origin" , baseGitRepo . Path ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
return fmt . Errorf ( "git remote add [%s -> %s]: %s" , baseGitRepo . Path , tmpBasePath , errbuf . String ( ) )
}
if err := git . NewCommand ( "fetch" , "origin" , "--no-tags" , pr . BaseBranch + ":" + baseBranch , pr . BaseBranch + ":original_" + baseBranch ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
return fmt . Errorf ( "git fetch [%s -> %s]: %s" , headRepoPath , tmpBasePath , errbuf . String ( ) )
}
if err := git . NewCommand ( "symbolic-ref" , "HEAD" , git . BranchPrefix + baseBranch ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
return fmt . Errorf ( "git symbolic-ref HEAD base [%s]: %s" , tmpBasePath , errbuf . String ( ) )
}
if err := addCacheRepo ( tmpBasePath , headRepoPath ) ; err != nil {
return fmt . Errorf ( "addCacheRepo [%s -> %s]: %v" , headRepoPath , tmpBasePath , err )
}
if err := git . NewCommand ( "remote" , "add" , remoteRepoName , headRepoPath ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
if err := git . NewCommand ( "remote" , "add" , remoteRepoName , headRepoPath ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
return fmt . Errorf ( "git remote add [%s -> %s]: %s" , headRepoPath , tmpBasePath , errbuf . String ( ) )
return fmt . Errorf ( "git remote add [%s -> %s]: %s" , headRepoPath , tmpBasePath , errbuf . String ( ) )
}
}
trackingBranch := "tracking"
// Fetch head branch
// Fetch head branch
if err := git . NewCommand ( "fetch" , remoteRepoName , fmt . Sprintf ( "%s:refs/remotes/%s/%s" , pr . HeadBranch , remoteRepoName , pr . HeadBranch ) ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
if err := git . NewCommand ( "fetch" , "--no-tags" , remoteRepoName , pr . HeadBranch + ":" + trackingBranch ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
return fmt . Errorf ( "git fetch [%s -> %s]: %s" , headRepoPath , tmpBasePath , errbuf . String ( ) )
return fmt . Errorf ( "git fetch [%s -> %s]: %s" , headRepoPath , tmpBasePath , errbuf . String ( ) )
}
}
trackingBranch := path . Join ( remoteRepoName , pr . HeadBranch )
stagingBranch := "staging"
stagingBranch := fmt . Sprintf ( "%s_%s" , remoteRepoName , pr . HeadBranch )
// Enable sparse-checkout
// Enable sparse-checkout
sparseCheckoutList , err := getDiffTree ( tmpBasePath , pr . BaseBranch , trackingBranch )
sparseCheckoutList , err := getDiffTree ( tmpBasePath , b aseBranch, trackingBranch )
if err != nil {
if err != nil {
return fmt . Errorf ( "getDiffTree: %v" , err )
return fmt . Errorf ( "getDiffTree: %v" , err )
}
}
@ -123,21 +137,37 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
return fmt . Errorf ( "Writing sparse-checkout file to %s: %v" , sparseCheckoutListPath , err )
return fmt . Errorf ( "Writing sparse-checkout file to %s: %v" , sparseCheckoutListPath , err )
}
}
gitConfigCommand := func ( ) func ( ) * git . Command {
binVersion , err := git . BinVersion ( )
if err != nil {
log . Fatal ( "Error retrieving git version: %v" , err )
}
if version . Compare ( binVersion , "1.8.0" , ">=" ) {
return func ( ) * git . Command {
return git . NewCommand ( "config" , "--local" )
}
}
return func ( ) * git . Command {
return git . NewCommand ( "config" )
}
} ( )
// Switch off LFS process (set required, clean and smudge here also)
// Switch off LFS process (set required, clean and smudge here also)
if err := git . NewCommand ( "config" , "--local" , "filter.lfs.process" , "" ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
if err := gitConfigCommand ( ) . AddArguments ( "filter.lfs.process" , "" ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
return fmt . Errorf ( "git config [filter.lfs.process -> <> ]: %v" , errbuf . String ( ) )
return fmt . Errorf ( "git config [filter.lfs.process -> <> ]: %v" , errbuf . String ( ) )
}
}
if err := git . NewCommand ( "config" , "--local" , "filter.lfs.required" , "false" ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
if err := gitConfigCommand ( ) . AddArguments ( "filter.lfs.required" , "false" ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
return fmt . Errorf ( "git config [filter.lfs.required -> <false> ]: %v" , errbuf . String ( ) )
return fmt . Errorf ( "git config [filter.lfs.required -> <false> ]: %v" , errbuf . String ( ) )
}
}
if err := git . NewCommand ( "config" , "--local" , "filter.lfs.clean" , "" ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
if err := gitConfigCommand ( ) . AddArguments ( "filter.lfs.clean" , "" ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
return fmt . Errorf ( "git config [filter.lfs.clean -> <> ]: %v" , errbuf . String ( ) )
return fmt . Errorf ( "git config [filter.lfs.clean -> <> ]: %v" , errbuf . String ( ) )
}
}
if err := git . NewCommand ( "config" , "--local" , "filter.lfs.smudge" , "" ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
if err := gitConfigCommand ( ) . AddArguments ( "filter.lfs.smudge" , "" ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
return fmt . Errorf ( "git config [filter.lfs.smudge -> <> ]: %v" , errbuf . String ( ) )
return fmt . Errorf ( "git config [filter.lfs.smudge -> <> ]: %v" , errbuf . String ( ) )
}
}
if err := git . NewCommand ( "config" , "--local" , "core.sparseCheckout" , "true" ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
if err := gitConfigCommand ( ) . AddArguments ( "core.sparseCheckout" , "true" ) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
return fmt . Errorf ( "git config [core.sparsecheckout -> true]: %v" , errbuf . String ( ) )
return fmt . Errorf ( "git config [core.sparsecheckout -> true]: %v" , errbuf . String ( ) )
}
}
@ -163,11 +193,11 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
return fmt . Errorf ( "git checkout: %s" , errbuf . String ( ) )
return fmt . Errorf ( "git checkout: %s" , errbuf . String ( ) )
}
}
// Rebase before merging
// Rebase before merging
if err := git . NewCommand ( "rebase" , "-q" , pr . B aseBranch) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
if err := git . NewCommand ( "rebase" , "-q" , b aseBranch) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
return fmt . Errorf ( "git rebase [%s -> %s]: %s" , headRepoPath , tmpBasePath , errbuf . String ( ) )
return fmt . Errorf ( "git rebase [%s -> %s]: %s" , headRepoPath , tmpBasePath , errbuf . String ( ) )
}
}
// Checkout base branch again
// Checkout base branch again
if err := git . NewCommand ( "checkout" , pr . B aseBranch) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
if err := git . NewCommand ( "checkout" , b aseBranch) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
return fmt . Errorf ( "git checkout: %s" , errbuf . String ( ) )
return fmt . Errorf ( "git checkout: %s" , errbuf . String ( ) )
}
}
// Merge fast forward
// Merge fast forward
@ -180,11 +210,11 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
return fmt . Errorf ( "git checkout: %s" , errbuf . String ( ) )
return fmt . Errorf ( "git checkout: %s" , errbuf . String ( ) )
}
}
// Rebase before merging
// Rebase before merging
if err := git . NewCommand ( "rebase" , "-q" , pr . B aseBranch) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
if err := git . NewCommand ( "rebase" , "-q" , b aseBranch) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
return fmt . Errorf ( "git rebase [%s -> %s]: %s" , headRepoPath , tmpBasePath , errbuf . String ( ) )
return fmt . Errorf ( "git rebase [%s -> %s]: %s" , headRepoPath , tmpBasePath , errbuf . String ( ) )
}
}
// Checkout base branch again
// Checkout base branch again
if err := git . NewCommand ( "checkout" , pr . B aseBranch) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
if err := git . NewCommand ( "checkout" , b aseBranch) . RunInDirPipeline ( tmpBasePath , nil , & errbuf ) ; err != nil {
return fmt . Errorf ( "git checkout: %s" , errbuf . String ( ) )
return fmt . Errorf ( "git checkout: %s" , errbuf . String ( ) )
}
}
// Prepare merge with commit
// Prepare merge with commit
@ -216,7 +246,7 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
if err != nil {
if err != nil {
return fmt . Errorf ( "Failed to get full commit id for HEAD: %v" , err )
return fmt . Errorf ( "Failed to get full commit id for HEAD: %v" , err )
}
}
mergeBaseSHA , err := git . GetFullCommitID ( tmpBasePath , "origin/" + pr . B aseBranch )
mergeBaseSHA , err := git . GetFullCommitID ( tmpBasePath , "original_" + b aseBranch )
if err != nil {
if err != nil {
return fmt . Errorf ( "Failed to get full commit id for origin/%s: %v" , pr . BaseBranch , err )
return fmt . Errorf ( "Failed to get full commit id for origin/%s: %v" , pr . BaseBranch , err )
}
}
@ -249,7 +279,7 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
)
)
// Push back to upstream.
// Push back to upstream.
if err := git . NewCommand ( "push" , "origin" , pr . BaseBranch ) . RunInDirTimeoutEnvPipeline ( env , - 1 , tmpBasePath , nil , & errbuf ) ; err != nil {
if err := git . NewCommand ( "push" , "origin" , baseBranch + ":" + pr . BaseBranch ) . RunInDirTimeoutEnvPipeline ( env , - 1 , tmpBasePath , nil , & errbuf ) ; err != nil {
return fmt . Errorf ( "git push: %s" , errbuf . String ( ) )
return fmt . Errorf ( "git push: %s" , errbuf . String ( ) )
}
}