2019-12-13 23:21:06 +01:00
// Copyright 2019 The Gitea Authors.
// All rights reserved.
2022-11-27 19:20:29 +01:00
// SPDX-License-Identifier: MIT
2019-12-13 23:21:06 +01:00
package pull
import (
2022-01-20 00:26:57 +01:00
"context"
2019-12-13 23:21:06 +01:00
"fmt"
"os"
"path/filepath"
"strings"
"code.gitea.io/gitea/models"
2022-06-13 11:37:59 +02:00
issues_model "code.gitea.io/gitea/models/issues"
2021-12-10 02:27:50 +01:00
repo_model "code.gitea.io/gitea/models/repo"
2019-12-13 23:21:06 +01:00
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
2022-05-08 18:46:32 +02:00
repo_module "code.gitea.io/gitea/modules/repository"
2019-12-13 23:21:06 +01:00
)
2020-04-01 21:03:08 +02:00
// createTemporaryRepo creates a temporary repo with "base" for pr.BaseBranch and "tracking" for pr.HeadBranch
// it also create a second base branch called "original_base"
2022-06-13 11:37:59 +02:00
func createTemporaryRepo ( ctx context . Context , pr * issues_model . PullRequest ) ( string , error ) {
2022-11-19 09:12:33 +01:00
if err := pr . LoadHeadRepo ( ctx ) ; err != nil {
2020-03-02 23:31:55 +01:00
log . Error ( "LoadHeadRepo: %v" , err )
2022-10-24 21:29:17 +02:00
return "" , fmt . Errorf ( "LoadHeadRepo: %w" , err )
2019-12-13 23:21:06 +01:00
} else if pr . HeadRepo == nil {
log . Error ( "Pr %d HeadRepo %d does not exist" , pr . ID , pr . HeadRepoID )
2021-12-10 02:27:50 +01:00
return "" , & repo_model . ErrRepoNotExist {
2019-12-13 23:21:06 +01:00
ID : pr . HeadRepoID ,
}
2022-11-19 09:12:33 +01:00
} else if err := pr . LoadBaseRepo ( ctx ) ; err != nil {
2020-03-02 23:31:55 +01:00
log . Error ( "LoadBaseRepo: %v" , err )
2022-10-24 21:29:17 +02:00
return "" , fmt . Errorf ( "LoadBaseRepo: %w" , err )
2019-12-13 23:21:06 +01:00
} else if pr . BaseRepo == nil {
log . Error ( "Pr %d BaseRepo %d does not exist" , pr . ID , pr . BaseRepoID )
2021-12-10 02:27:50 +01:00
return "" , & repo_model . ErrRepoNotExist {
2019-12-13 23:21:06 +01:00
ID : pr . BaseRepoID ,
}
2023-02-18 13:11:03 +01:00
} else if err := pr . HeadRepo . LoadOwner ( ctx ) ; err != nil {
log . Error ( "HeadRepo.LoadOwner: %v" , err )
return "" , fmt . Errorf ( "HeadRepo.LoadOwner: %w" , err )
} else if err := pr . BaseRepo . LoadOwner ( ctx ) ; err != nil {
log . Error ( "BaseRepo.LoadOwner: %v" , err )
return "" , fmt . Errorf ( "BaseRepo.LoadOwner: %w" , err )
2019-12-13 23:21:06 +01:00
}
// Clone base repo.
2022-05-08 18:46:32 +02:00
tmpBasePath , err := repo_module . CreateTemporaryPath ( "pull" )
2019-12-13 23:21:06 +01:00
if err != nil {
log . Error ( "CreateTemporaryPath: %v" , err )
return "" , err
}
baseRepoPath := pr . BaseRepo . RepoPath ( )
headRepoPath := pr . HeadRepo . RepoPath ( )
2022-01-20 00:26:57 +01:00
if err := git . InitRepository ( ctx , tmpBasePath , false ) ; err != nil {
2019-12-13 23:21:06 +01:00
log . Error ( "git init tmpBasePath: %v" , err )
2022-05-08 18:46:32 +02:00
if err := repo_module . RemoveTemporaryPath ( tmpBasePath ) ; err != nil {
2019-12-13 23:21:06 +01:00
log . Error ( "CreateTempRepo: RemoveTemporaryPath: %s" , err )
}
return "" , err
}
remoteRepoName := "head_repo"
baseBranch := "base"
2023-03-02 00:53:41 +01: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" )
}
2019-12-13 23:21:06 +01:00
// Add head repo remote.
addCacheRepo := func ( staging , cache string ) error {
p := filepath . Join ( staging , ".git" , "objects" , "info" , "alternates" )
2022-01-20 18:46:10 +01:00
f , err := os . OpenFile ( p , os . O_APPEND | os . O_CREATE | os . O_WRONLY , 0 o600 )
2019-12-13 23:21:06 +01:00
if err != nil {
log . Error ( "Could not create .git/objects/info/alternates file in %s: %v" , staging , err )
return err
}
defer f . Close ( )
data := filepath . Join ( cache , "objects" )
if _ , err := fmt . Fprintln ( f , data ) ; err != nil {
log . Error ( "Could not write to .git/objects/info/alternates file in %s: %v" , staging , err )
return err
}
return nil
}
if err := addCacheRepo ( tmpBasePath , baseRepoPath ) ; err != nil {
log . Error ( "Unable to add base repository to temporary repo [%s -> %s]: %v" , pr . BaseRepo . FullName ( ) , tmpBasePath , err )
2022-05-08 18:46:32 +02:00
if err := repo_module . RemoveTemporaryPath ( tmpBasePath ) ; err != nil {
2019-12-13 23:21:06 +01:00
log . Error ( "CreateTempRepo: RemoveTemporaryPath: %s" , err )
}
2022-10-24 21:29:17 +02:00
return "" , fmt . Errorf ( "Unable to add base repository to temporary repo [%s -> tmpBasePath]: %w" , pr . BaseRepo . FullName ( ) , err )
2019-12-13 23:21:06 +01:00
}
var outbuf , errbuf strings . Builder
2022-10-23 16:44:45 +02:00
if err := git . NewCommand ( ctx , "remote" , "add" , "-t" ) . AddDynamicArguments ( pr . BaseBranch ) . AddArguments ( "-m" ) . AddDynamicArguments ( pr . BaseBranch ) . AddDynamicArguments ( "origin" , baseRepoPath ) .
2022-04-01 04:55:30 +02:00
Run ( & git . RunOpts {
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 13:47:22 +01:00
} ) ; err != nil {
2019-12-13 23:21:06 +01:00
log . Error ( "Unable to add base repository as origin [%s -> %s]: %v\n%s\n%s" , pr . BaseRepo . FullName ( ) , tmpBasePath , err , outbuf . String ( ) , errbuf . String ( ) )
2022-05-08 18:46:32 +02:00
if err := repo_module . RemoveTemporaryPath ( tmpBasePath ) ; err != nil {
2019-12-13 23:21:06 +01:00
log . Error ( "CreateTempRepo: RemoveTemporaryPath: %s" , err )
}
2022-10-24 21:29:17 +02:00
return "" , fmt . Errorf ( "Unable to add base repository as origin [%s -> tmpBasePath]: %w\n%s\n%s" , pr . BaseRepo . FullName ( ) , err , outbuf . String ( ) , errbuf . String ( ) )
2019-12-13 23:21:06 +01:00
}
outbuf . Reset ( )
errbuf . Reset ( )
2023-03-02 00:53:41 +01:00
if err := git . NewCommand ( ctx , "fetch" , "origin" ) . AddArguments ( fetchArgs ... ) . AddDashesAndList ( pr . BaseBranch + ":" + baseBranch , pr . BaseBranch + ":original_" + baseBranch ) .
2022-04-01 04:55:30 +02:00
Run ( & git . RunOpts {
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 13:47:22 +01:00
} ) ; err != nil {
2019-12-13 23:21:06 +01:00
log . Error ( "Unable to fetch origin base branch [%s:%s -> base, original_base in %s]: %v:\n%s\n%s" , pr . BaseRepo . FullName ( ) , pr . BaseBranch , tmpBasePath , err , outbuf . String ( ) , errbuf . String ( ) )
2022-05-08 18:46:32 +02:00
if err := repo_module . RemoveTemporaryPath ( tmpBasePath ) ; err != nil {
2019-12-13 23:21:06 +01:00
log . Error ( "CreateTempRepo: RemoveTemporaryPath: %s" , err )
}
2022-10-24 21:29:17 +02:00
return "" , 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 , outbuf . String ( ) , errbuf . String ( ) )
2019-12-13 23:21:06 +01:00
}
outbuf . Reset ( )
errbuf . Reset ( )
2022-10-23 16:44:45 +02:00
if err := git . NewCommand ( ctx , "symbolic-ref" ) . AddDynamicArguments ( "HEAD" , git . BranchPrefix + baseBranch ) .
2022-04-01 04:55:30 +02:00
Run ( & git . RunOpts {
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 13:47:22 +01:00
} ) ; err != nil {
2019-12-13 23:21:06 +01:00
log . Error ( "Unable to set HEAD as base branch [%s]: %v\n%s\n%s" , tmpBasePath , err , outbuf . String ( ) , errbuf . String ( ) )
2022-05-08 18:46:32 +02:00
if err := repo_module . RemoveTemporaryPath ( tmpBasePath ) ; err != nil {
2019-12-13 23:21:06 +01:00
log . Error ( "CreateTempRepo: RemoveTemporaryPath: %s" , err )
}
2022-10-24 21:29:17 +02:00
return "" , fmt . Errorf ( "Unable to set HEAD as base branch [tmpBasePath]: %w\n%s\n%s" , err , outbuf . String ( ) , errbuf . String ( ) )
2019-12-13 23:21:06 +01:00
}
outbuf . Reset ( )
errbuf . Reset ( )
if err := addCacheRepo ( tmpBasePath , headRepoPath ) ; err != nil {
log . Error ( "Unable to add head repository to temporary repo [%s -> %s]: %v" , pr . HeadRepo . FullName ( ) , tmpBasePath , err )
2022-05-08 18:46:32 +02:00
if err := repo_module . RemoveTemporaryPath ( tmpBasePath ) ; err != nil {
2019-12-13 23:21:06 +01:00
log . Error ( "CreateTempRepo: RemoveTemporaryPath: %s" , err )
}
2022-10-24 21:29:17 +02:00
return "" , fmt . Errorf ( "Unable to head base repository to temporary repo [%s -> tmpBasePath]: %w" , pr . HeadRepo . FullName ( ) , err )
2019-12-13 23:21:06 +01:00
}
2022-10-23 16:44:45 +02:00
if err := git . NewCommand ( ctx , "remote" , "add" ) . AddDynamicArguments ( remoteRepoName , headRepoPath ) .
2022-04-01 04:55:30 +02:00
Run ( & git . RunOpts {
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 13:47:22 +01:00
} ) ; err != nil {
2019-12-13 23:21:06 +01:00
log . Error ( "Unable to add head repository as head_repo [%s -> %s]: %v\n%s\n%s" , pr . HeadRepo . FullName ( ) , tmpBasePath , err , outbuf . String ( ) , errbuf . String ( ) )
2022-05-08 18:46:32 +02:00
if err := repo_module . RemoveTemporaryPath ( tmpBasePath ) ; err != nil {
2019-12-13 23:21:06 +01:00
log . Error ( "CreateTempRepo: RemoveTemporaryPath: %s" , err )
}
2022-10-24 21:29:17 +02:00
return "" , fmt . Errorf ( "Unable to add head repository as head_repo [%s -> tmpBasePath]: %w\n%s\n%s" , pr . HeadRepo . FullName ( ) , err , outbuf . String ( ) , errbuf . String ( ) )
2019-12-13 23:21:06 +01:00
}
outbuf . Reset ( )
errbuf . Reset ( )
trackingBranch := "tracking"
// Fetch head branch
2021-07-28 11:42:56 +02:00
var headBranch string
2022-06-13 11:37:59 +02:00
if pr . Flow == issues_model . PullRequestFlowGithub {
2021-07-28 11:42:56 +02:00
headBranch = git . BranchPrefix + pr . HeadBranch
2022-12-27 14:12:49 +01:00
} else if len ( pr . HeadCommitID ) == git . SHAFullLength { // for not created pull request
2021-07-28 11:42:56 +02:00
headBranch = pr . HeadCommitID
} else {
headBranch = pr . GetGitRefName ( )
}
2023-03-02 00:53:41 +01:00
if err := git . NewCommand ( ctx , "fetch" ) . AddArguments ( fetchArgs ... ) . AddDynamicArguments ( remoteRepoName , headBranch + ":" + trackingBranch ) .
2022-04-01 04:55:30 +02:00
Run ( & git . RunOpts {
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 13:47:22 +01:00
} ) ; err != nil {
2022-05-08 18:46:32 +02:00
if err := repo_module . RemoveTemporaryPath ( tmpBasePath ) ; err != nil {
2019-12-13 23:21:06 +01:00
log . Error ( "CreateTempRepo: RemoveTemporaryPath: %s" , err )
}
2022-01-20 00:26:57 +01:00
if ! git . IsBranchExist ( ctx , pr . HeadRepo . RepoPath ( ) , pr . HeadBranch ) {
2021-07-13 01:26:25 +02:00
return "" , models . ErrBranchDoesNotExist {
BranchName : pr . HeadBranch ,
}
}
log . Error ( "Unable to fetch head_repo head branch [%s:%s -> tracking in %s]: %v:\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , tmpBasePath , err , outbuf . String ( ) , errbuf . String ( ) )
2022-10-24 21:29:17 +02:00
return "" , fmt . Errorf ( "Unable to fetch head_repo head branch [%s:%s -> tracking in tmpBasePath]: %w\n%s\n%s" , pr . HeadRepo . FullName ( ) , headBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2019-12-13 23:21:06 +01:00
}
outbuf . Reset ( )
errbuf . Reset ( )
return tmpBasePath , nil
}