2019-12-13 17:21:06 -05:00
// Copyright 2019 The Gitea Authors.
// All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2019-12-13 17:21:06 -05:00
package pull
import (
2022-01-19 18:26:57 -05:00
"context"
2019-12-13 17:21:06 -05:00
"fmt"
"os"
"path/filepath"
"strings"
2023-06-29 06:03:20 -04:00
git_model "code.gitea.io/gitea/models/git"
2022-06-13 05:37:59 -04:00
issues_model "code.gitea.io/gitea/models/issues"
2021-12-09 20:27:50 -05:00
repo_model "code.gitea.io/gitea/models/repo"
2019-12-13 17:21:06 -05:00
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
2022-05-08 12:46:32 -04:00
repo_module "code.gitea.io/gitea/modules/repository"
2019-12-13 17:21:06 -05:00
)
2023-03-07 15:07:35 -05:00
// Temporary repos created here use standard branch names to help simplify
// merging code
const (
baseBranch = "base" // equivalent to pr.BaseBranch
trackingBranch = "tracking" // equivalent to pr.HeadBranch
stagingBranch = "staging" // this is used for a working branch
)
type prContext struct {
context . Context
tmpBasePath string
pr * issues_model . PullRequest
outbuf * strings . Builder // we keep these around to help reduce needless buffer recreation,
errbuf * strings . Builder // any use should be preceded by a Reset and preferably after use
}
func ( ctx * prContext ) RunOpts ( ) * git . RunOpts {
ctx . outbuf . Reset ( )
ctx . errbuf . Reset ( )
return & git . RunOpts {
Dir : ctx . tmpBasePath ,
Stdout : ctx . outbuf ,
Stderr : ctx . errbuf ,
}
}
// createTemporaryRepoForPR creates a temporary repo with "base" for pr.BaseBranch and "tracking" for pr.HeadBranch
2020-04-01 15:03:08 -04:00
// it also create a second base branch called "original_base"
2023-03-07 15:07:35 -05:00
func createTemporaryRepoForPR ( ctx context . Context , pr * issues_model . PullRequest ) ( prCtx * prContext , cancel context . CancelFunc , err error ) {
2022-11-19 03:12:33 -05:00
if err := pr . LoadHeadRepo ( ctx ) ; err != nil {
2023-03-07 15:07:35 -05:00
log . Error ( "%-v LoadHeadRepo: %v" , pr , err )
return nil , nil , fmt . Errorf ( "%v LoadHeadRepo: %w" , pr , err )
2019-12-13 17:21:06 -05:00
} else if pr . HeadRepo == nil {
2023-03-07 15:07:35 -05:00
log . Error ( "%-v HeadRepo %d does not exist" , pr , pr . HeadRepoID )
return nil , nil , & repo_model . ErrRepoNotExist {
2019-12-13 17:21:06 -05:00
ID : pr . HeadRepoID ,
}
2022-11-19 03:12:33 -05:00
} else if err := pr . LoadBaseRepo ( ctx ) ; err != nil {
2023-03-07 15:07:35 -05:00
log . Error ( "%-v LoadBaseRepo: %v" , pr , err )
return nil , nil , fmt . Errorf ( "%v LoadBaseRepo: %w" , pr , err )
2019-12-13 17:21:06 -05:00
} else if pr . BaseRepo == nil {
2023-03-07 15:07:35 -05:00
log . Error ( "%-v BaseRepo %d does not exist" , pr , pr . BaseRepoID )
return nil , nil , & repo_model . ErrRepoNotExist {
2019-12-13 17:21:06 -05:00
ID : pr . BaseRepoID ,
}
2023-02-18 07:11:03 -05:00
} else if err := pr . HeadRepo . LoadOwner ( ctx ) ; err != nil {
2023-03-07 15:07:35 -05:00
log . Error ( "%-v HeadRepo.LoadOwner: %v" , pr , err )
return nil , nil , fmt . Errorf ( "%v HeadRepo.LoadOwner: %w" , pr , err )
2023-02-18 07:11:03 -05:00
} else if err := pr . BaseRepo . LoadOwner ( ctx ) ; err != nil {
2023-03-07 15:07:35 -05:00
log . Error ( "%-v BaseRepo.LoadOwner: %v" , pr , err )
return nil , nil , fmt . Errorf ( "%v BaseRepo.LoadOwner: %w" , pr , err )
2019-12-13 17:21:06 -05:00
}
// Clone base repo.
2022-05-08 12:46:32 -04:00
tmpBasePath , err := repo_module . CreateTemporaryPath ( "pull" )
2019-12-13 17:21:06 -05:00
if err != nil {
2023-03-07 15:07:35 -05:00
log . Error ( "CreateTemporaryPath[%-v]: %v" , pr , err )
return nil , nil , err
}
prCtx = & prContext {
Context : ctx ,
tmpBasePath : tmpBasePath ,
pr : pr ,
outbuf : & strings . Builder { } ,
errbuf : & strings . Builder { } ,
}
cancel = func ( ) {
if err := repo_module . RemoveTemporaryPath ( tmpBasePath ) ; err != nil {
log . Error ( "Error whilst removing removing temporary repo for %-v: %v" , pr , err )
}
2019-12-13 17:21:06 -05:00
}
baseRepoPath := pr . BaseRepo . RepoPath ( )
headRepoPath := pr . HeadRepo . RepoPath ( )
2023-12-13 16:02:00 -05:00
objectFormat , err := git . GetObjectFormatOfRepo ( ctx , baseRepoPath )
if err != nil {
log . Error ( "Unable to fetch ObjectFormat of repository %s: %v" , baseRepoPath , err )
cancel ( )
return nil , nil , err
}
2019-12-13 17:21:06 -05:00
2023-12-13 16:02:00 -05:00
if err := git . InitRepository ( ctx , tmpBasePath , false , objectFormat ) ; err != nil {
2023-03-07 15:07:35 -05:00
log . Error ( "Unable to init tmpBasePath for %-v: %v" , pr , err )
cancel ( )
return nil , nil , err
2019-12-13 17:21:06 -05:00
}
remoteRepoName := "head_repo"
baseBranch := "base"
2023-03-01 14:19:04 -05:00
fetchArgs := git . TrustedCmdArgs { "--no-tags" }
if git . CheckGitVersionAtLeast ( "2.25.0" ) == nil {
// Writing the commit graph can be slow and is not needed here
fetchArgs = append ( fetchArgs , "--no-write-commit-graph" )
}
2023-03-07 15:07:35 -05:00
// addCacheRepo adds git alternatives for the cacheRepoPath in the repoPath
addCacheRepo := func ( repoPath , cacheRepoPath string ) error {
p := filepath . Join ( repoPath , ".git" , "objects" , "info" , "alternates" )
2022-01-20 12:46:10 -05:00
f , err := os . OpenFile ( p , os . O_APPEND | os . O_CREATE | os . O_WRONLY , 0 o600 )
2019-12-13 17:21:06 -05:00
if err != nil {
2023-03-07 15:07:35 -05:00
log . Error ( "Could not create .git/objects/info/alternates file in %s: %v" , repoPath , err )
2019-12-13 17:21:06 -05:00
return err
}
defer f . Close ( )
2023-03-07 15:07:35 -05:00
data := filepath . Join ( cacheRepoPath , "objects" )
2019-12-13 17:21:06 -05:00
if _ , err := fmt . Fprintln ( f , data ) ; err != nil {
2023-03-07 15:07:35 -05:00
log . Error ( "Could not write to .git/objects/info/alternates file in %s: %v" , repoPath , err )
2019-12-13 17:21:06 -05:00
return err
}
return nil
}
2023-03-07 15:07:35 -05:00
// Add head repo remote.
2019-12-13 17:21:06 -05:00
if err := addCacheRepo ( tmpBasePath , baseRepoPath ) ; err != nil {
2023-03-07 15:07:35 -05:00
log . Error ( "%-v Unable to add base repository to temporary repo [%s -> %s]: %v" , pr , pr . BaseRepo . FullName ( ) , tmpBasePath , err )
cancel ( )
return nil , nil , fmt . Errorf ( "Unable to add base repository to temporary repo [%s -> tmpBasePath]: %w" , pr . BaseRepo . FullName ( ) , err )
2019-12-13 17:21:06 -05:00
}
2022-10-23 10:44:45 -04:00
if err := git . NewCommand ( ctx , "remote" , "add" , "-t" ) . AddDynamicArguments ( pr . BaseBranch ) . AddArguments ( "-m" ) . AddDynamicArguments ( pr . BaseBranch ) . AddDynamicArguments ( "origin" , baseRepoPath ) .
2023-03-07 15:07:35 -05:00
Run ( prCtx . RunOpts ( ) ) ; err != nil {
log . Error ( "%-v Unable to add base repository as origin [%s -> %s]: %v\n%s\n%s" , pr , pr . BaseRepo . FullName ( ) , tmpBasePath , err , prCtx . outbuf . String ( ) , prCtx . errbuf . String ( ) )
cancel ( )
return nil , nil , fmt . Errorf ( "Unable to add base repository as origin [%s -> tmpBasePath]: %w\n%s\n%s" , pr . BaseRepo . FullName ( ) , err , prCtx . outbuf . String ( ) , prCtx . errbuf . String ( ) )
2019-12-13 17:21:06 -05:00
}
2023-03-01 14:19:04 -05:00
if err := git . NewCommand ( ctx , "fetch" , "origin" ) . AddArguments ( fetchArgs ... ) . AddDashesAndList ( pr . BaseBranch + ":" + baseBranch , pr . BaseBranch + ":original_" + baseBranch ) .
2023-03-07 15:07:35 -05:00
Run ( prCtx . RunOpts ( ) ) ; err != nil {
log . Error ( "%-v Unable to fetch origin base branch [%s:%s -> base, original_base in %s]: %v:\n%s\n%s" , pr , pr . BaseRepo . FullName ( ) , pr . BaseBranch , tmpBasePath , err , prCtx . outbuf . String ( ) , prCtx . errbuf . String ( ) )
cancel ( )
return nil , nil , fmt . Errorf ( "Unable to fetch origin base branch [%s:%s -> base, original_base in tmpBasePath]: %w\n%s\n%s" , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , prCtx . outbuf . String ( ) , prCtx . errbuf . String ( ) )
2019-12-13 17:21:06 -05:00
}
2022-10-23 10:44:45 -04:00
if err := git . NewCommand ( ctx , "symbolic-ref" ) . AddDynamicArguments ( "HEAD" , git . BranchPrefix + baseBranch ) .
2023-03-07 15:07:35 -05:00
Run ( prCtx . RunOpts ( ) ) ; err != nil {
log . Error ( "%-v Unable to set HEAD as base branch in [%s]: %v\n%s\n%s" , pr , tmpBasePath , err , prCtx . outbuf . String ( ) , prCtx . errbuf . String ( ) )
cancel ( )
return nil , nil , fmt . Errorf ( "Unable to set HEAD as base branch in tmpBasePath: %w\n%s\n%s" , err , prCtx . outbuf . String ( ) , prCtx . errbuf . String ( ) )
2019-12-13 17:21:06 -05:00
}
if err := addCacheRepo ( tmpBasePath , headRepoPath ) ; err != nil {
2023-03-07 15:07:35 -05:00
log . Error ( "%-v Unable to add head repository to temporary repo [%s -> %s]: %v" , pr , pr . HeadRepo . FullName ( ) , tmpBasePath , err )
cancel ( )
return nil , nil , fmt . Errorf ( "Unable to add head base repository to temporary repo [%s -> tmpBasePath]: %w" , pr . HeadRepo . FullName ( ) , err )
2019-12-13 17:21:06 -05:00
}
2022-10-23 10:44:45 -04:00
if err := git . NewCommand ( ctx , "remote" , "add" ) . AddDynamicArguments ( remoteRepoName , headRepoPath ) .
2023-03-07 15:07:35 -05:00
Run ( prCtx . RunOpts ( ) ) ; err != nil {
log . Error ( "%-v Unable to add head repository as head_repo [%s -> %s]: %v\n%s\n%s" , pr , pr . HeadRepo . FullName ( ) , tmpBasePath , err , prCtx . outbuf . String ( ) , prCtx . errbuf . String ( ) )
cancel ( )
return nil , nil , fmt . Errorf ( "Unable to add head repository as head_repo [%s -> tmpBasePath]: %w\n%s\n%s" , pr . HeadRepo . FullName ( ) , err , prCtx . outbuf . String ( ) , prCtx . errbuf . String ( ) )
2019-12-13 17:21:06 -05:00
}
trackingBranch := "tracking"
// Fetch head branch
2021-07-28 05:42:56 -04:00
var headBranch string
2022-06-13 05:37:59 -04:00
if pr . Flow == issues_model . PullRequestFlowGithub {
2021-07-28 05:42:56 -04:00
headBranch = git . BranchPrefix + pr . HeadBranch
2023-12-13 16:02:00 -05:00
} else if len ( pr . HeadCommitID ) == objectFormat . FullLength ( ) { // for not created pull request
2021-07-28 05:42:56 -04:00
headBranch = pr . HeadCommitID
} else {
headBranch = pr . GetGitRefName ( )
}
2023-03-01 14:19:04 -05:00
if err := git . NewCommand ( ctx , "fetch" ) . AddArguments ( fetchArgs ... ) . AddDynamicArguments ( remoteRepoName , headBranch + ":" + trackingBranch ) .
2023-03-07 15:07:35 -05:00
Run ( prCtx . RunOpts ( ) ) ; err != nil {
cancel ( )
2022-01-19 18:26:57 -05:00
if ! git . IsBranchExist ( ctx , pr . HeadRepo . RepoPath ( ) , pr . HeadBranch ) {
2023-06-29 06:03:20 -04:00
return nil , nil , git_model . ErrBranchNotExist {
2021-07-12 19:26:25 -04:00
BranchName : pr . HeadBranch ,
}
}
2023-03-07 15:07:35 -05:00
log . Error ( "%-v Unable to fetch head_repo head branch [%s:%s -> tracking in %s]: %v:\n%s\n%s" , pr , pr . HeadRepo . FullName ( ) , pr . HeadBranch , tmpBasePath , err , prCtx . outbuf . String ( ) , prCtx . errbuf . String ( ) )
return nil , nil , fmt . Errorf ( "Unable to fetch head_repo head branch [%s:%s -> tracking in tmpBasePath]: %w\n%s\n%s" , pr . HeadRepo . FullName ( ) , headBranch , err , prCtx . outbuf . String ( ) , prCtx . errbuf . String ( ) )
2019-12-13 17:21:06 -05:00
}
2023-03-07 15:07:35 -05:00
prCtx . outbuf . Reset ( )
prCtx . errbuf . Reset ( )
2019-12-13 17:21:06 -05:00
2023-03-07 15:07:35 -05:00
return prCtx , cancel , nil
2019-12-13 17:21:06 -05:00
}