2019-06-23 02:35:34 +09:00
// Copyright 2019 The Gitea Authors.
// All rights reserved.
2022-11-28 03:20:29 +09:00
// SPDX-License-Identifier: MIT
2019-06-23 02:35:34 +09:00
package pull
import (
"bufio"
"bytes"
2022-01-20 08:26:57 +09:00
"context"
2019-06-23 02:35:34 +09:00
"fmt"
"os"
"path/filepath"
2019-11-01 09:30:02 +09:00
"regexp"
2022-05-08 21:32:45 +09:00
"strconv"
2019-06-23 02:35:34 +09:00
"strings"
2019-10-16 22:42:42 +09:00
"time"
2019-06-23 02:35:34 +09:00
"code.gitea.io/gitea/models"
2022-05-04 04:46:28 +09:00
"code.gitea.io/gitea/models/db"
2022-06-13 00:51:54 +09:00
git_model "code.gitea.io/gitea/models/git"
2022-06-13 18:37:59 +09:00
issues_model "code.gitea.io/gitea/models/issues"
2022-05-11 19:09:36 +09:00
access_model "code.gitea.io/gitea/models/perm/access"
2022-05-08 02:05:52 +09:00
pull_model "code.gitea.io/gitea/models/pull"
2021-12-10 10:27:50 +09:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-10 04:57:58 +09:00
"code.gitea.io/gitea/models/unit"
2021-11-24 18:49:20 +09:00
user_model "code.gitea.io/gitea/models/user"
2019-06-23 02:35:34 +09:00
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
2022-10-12 01:26:22 +09:00
"code.gitea.io/gitea/modules/graceful"
2019-06-23 02:35:34 +09:00
"code.gitea.io/gitea/modules/log"
2019-11-05 20:04:08 +09:00
"code.gitea.io/gitea/modules/notification"
2019-11-18 22:13:07 +09:00
"code.gitea.io/gitea/modules/references"
2022-05-09 01:46:32 +09:00
repo_module "code.gitea.io/gitea/modules/repository"
2019-06-23 02:35:34 +09:00
"code.gitea.io/gitea/modules/setting"
2019-08-15 23:46:21 +09:00
"code.gitea.io/gitea/modules/timeutil"
2021-12-10 17:14:24 +09:00
asymkey_service "code.gitea.io/gitea/services/asymkey"
2019-11-18 22:13:07 +09:00
issue_service "code.gitea.io/gitea/services/issue"
2019-06-23 02:35:34 +09:00
)
2022-05-08 21:32:45 +09:00
// GetDefaultMergeMessage returns default message used when merging pull request
2022-12-29 21:40:20 +09:00
func GetDefaultMergeMessage ( ctx context . Context , baseGitRepo * git . Repository , pr * issues_model . PullRequest , mergeStyle repo_model . MergeStyle ) ( message , body string , err error ) {
2022-11-19 17:12:33 +09:00
if err := pr . LoadHeadRepo ( ctx ) ; err != nil {
2022-12-29 21:40:20 +09:00
return "" , "" , err
2022-05-08 21:32:45 +09:00
}
2022-11-19 17:12:33 +09:00
if err := pr . LoadBaseRepo ( ctx ) ; err != nil {
2022-12-29 21:40:20 +09:00
return "" , "" , err
2022-05-08 21:32:45 +09:00
}
if pr . BaseRepo == nil {
2022-12-29 21:40:20 +09:00
return "" , "" , repo_model . ErrRepoNotExist { ID : pr . BaseRepoID }
2022-05-08 21:32:45 +09:00
}
2022-11-19 17:12:33 +09:00
if err := pr . LoadIssue ( ctx ) ; err != nil {
2022-12-29 21:40:20 +09:00
return "" , "" , err
2022-05-08 21:32:45 +09:00
}
2022-12-10 11:46:31 +09:00
isExternalTracker := pr . BaseRepo . UnitEnabled ( ctx , unit . TypeExternalTracker )
2022-05-08 21:32:45 +09:00
issueReference := "#"
if isExternalTracker {
issueReference = "!"
}
if mergeStyle != "" {
templateFilepath := fmt . Sprintf ( ".gitea/default_merge_message/%s_TEMPLATE.md" , strings . ToUpper ( string ( mergeStyle ) ) )
commit , err := baseGitRepo . GetBranchCommit ( pr . BaseRepo . DefaultBranch )
if err != nil {
2022-12-29 21:40:20 +09:00
return "" , "" , err
2022-05-08 21:32:45 +09:00
}
templateContent , err := commit . GetFileContent ( templateFilepath , setting . Repository . PullRequest . DefaultMergeMessageSize )
if err != nil {
if ! git . IsErrNotExist ( err ) {
2022-12-29 21:40:20 +09:00
return "" , "" , err
2022-05-08 21:32:45 +09:00
}
} else {
vars := map [ string ] string {
"BaseRepoOwnerName" : pr . BaseRepo . OwnerName ,
"BaseRepoName" : pr . BaseRepo . Name ,
"BaseBranch" : pr . BaseBranch ,
"HeadRepoOwnerName" : "" ,
"HeadRepoName" : "" ,
"HeadBranch" : pr . HeadBranch ,
"PullRequestTitle" : pr . Issue . Title ,
"PullRequestDescription" : pr . Issue . Content ,
"PullRequestPosterName" : pr . Issue . Poster . Name ,
"PullRequestIndex" : strconv . FormatInt ( pr . Index , 10 ) ,
"PullRequestReference" : fmt . Sprintf ( "%s%d" , issueReference , pr . Index ) ,
}
if pr . HeadRepo != nil {
vars [ "HeadRepoOwnerName" ] = pr . HeadRepo . OwnerName
vars [ "HeadRepoName" ] = pr . HeadRepo . Name
}
2022-11-19 17:12:33 +09:00
refs , err := pr . ResolveCrossReferences ( ctx )
2022-05-08 21:32:45 +09:00
if err == nil {
closeIssueIndexes := make ( [ ] string , 0 , len ( refs ) )
closeWord := "close"
if len ( setting . Repository . PullRequest . CloseKeywords ) > 0 {
closeWord = setting . Repository . PullRequest . CloseKeywords [ 0 ]
}
for _ , ref := range refs {
if ref . RefAction == references . XRefActionCloses {
closeIssueIndexes = append ( closeIssueIndexes , fmt . Sprintf ( "%s %s%d" , closeWord , issueReference , ref . Issue . Index ) )
}
}
if len ( closeIssueIndexes ) > 0 {
vars [ "ClosingIssues" ] = strings . Join ( closeIssueIndexes , ", " )
} else {
vars [ "ClosingIssues" ] = ""
}
}
2022-12-29 21:40:20 +09:00
message , body = expandDefaultMergeMessage ( templateContent , vars )
return message , body , nil
2022-05-08 21:32:45 +09:00
}
}
// Squash merge has a different from other styles.
if mergeStyle == repo_model . MergeStyleSquash {
2022-12-29 21:40:20 +09:00
return fmt . Sprintf ( "%s (%s%d)" , pr . Issue . Title , issueReference , pr . Issue . Index ) , "" , nil
2022-05-08 21:32:45 +09:00
}
if pr . BaseRepoID == pr . HeadRepoID {
2022-12-29 21:40:20 +09:00
return fmt . Sprintf ( "Merge pull request '%s' (%s%d) from %s into %s" , pr . Issue . Title , issueReference , pr . Issue . Index , pr . HeadBranch , pr . BaseBranch ) , "" , nil
2022-05-08 21:32:45 +09:00
}
if pr . HeadRepo == nil {
2022-12-29 21:40:20 +09:00
return fmt . Sprintf ( "Merge pull request '%s' (%s%d) from <deleted>:%s into %s" , pr . Issue . Title , issueReference , pr . Issue . Index , pr . HeadBranch , pr . BaseBranch ) , "" , nil
2022-05-08 21:32:45 +09:00
}
2022-12-29 21:40:20 +09:00
return fmt . Sprintf ( "Merge pull request '%s' (%s%d) from %s:%s into %s" , pr . Issue . Title , issueReference , pr . Issue . Index , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseBranch ) , "" , nil
}
func expandDefaultMergeMessage ( template string , vars map [ string ] string ) ( message , body string ) {
message = strings . TrimSpace ( template )
if splits := strings . SplitN ( message , "\n" , 2 ) ; len ( splits ) == 2 {
message = splits [ 0 ]
body = strings . TrimSpace ( splits [ 1 ] )
}
mapping := func ( s string ) string { return vars [ s ] }
return os . Expand ( message , mapping ) , os . Expand ( body , mapping )
2022-05-08 21:32:45 +09:00
}
2019-06-23 02:35:34 +09:00
// Merge merges pull request to base repository.
2020-01-11 16:29:34 +09:00
// Caller should check PR is ready to be merged (review and status checks)
2022-11-04 00:49:00 +09:00
func Merge ( ctx context . Context , pr * issues_model . PullRequest , doer * user_model . User , baseGitRepo * git . Repository , mergeStyle repo_model . MergeStyle , expectedHeadCommitID , message string , wasAutoMerged bool ) error {
2022-11-19 17:12:33 +09:00
if err := pr . LoadHeadRepo ( ctx ) ; err != nil {
2020-03-03 07:31:55 +09:00
log . Error ( "LoadHeadRepo: %v" , err )
2022-10-25 04:29:17 +09:00
return fmt . Errorf ( "LoadHeadRepo: %w" , err )
2022-11-19 17:12:33 +09:00
} else if err := pr . LoadBaseRepo ( ctx ) ; err != nil {
2020-03-03 07:31:55 +09:00
log . Error ( "LoadBaseRepo: %v" , err )
2022-10-25 04:29:17 +09:00
return fmt . Errorf ( "LoadBaseRepo: %w" , err )
2019-06-23 02:35:34 +09:00
}
2022-05-05 01:06:23 +09:00
pullWorkingPool . CheckIn ( fmt . Sprint ( pr . ID ) )
defer pullWorkingPool . CheckOut ( fmt . Sprint ( pr . ID ) )
2022-05-08 02:05:52 +09:00
// Removing an auto merge pull and ignore if not exist
2022-12-10 11:46:31 +09:00
if err := pull_model . DeleteScheduledAutoMerge ( ctx , pr . ID ) ; err != nil && ! db . IsErrNotExist ( err ) {
2022-05-08 02:05:52 +09:00
return err
}
2022-12-10 11:46:31 +09:00
prUnit , err := pr . BaseRepo . GetUnit ( ctx , unit . TypePullRequests )
2019-06-23 02:35:34 +09:00
if err != nil {
2021-11-10 04:57:58 +09:00
log . Error ( "pr.BaseRepo.GetUnit(unit.TypePullRequests): %v" , err )
2019-06-23 02:35:34 +09:00
return err
}
prConfig := prUnit . PullRequestsConfig ( )
// Check if merge style is correct and allowed
if ! prConfig . IsMergeStyleAllowed ( mergeStyle ) {
return models . ErrInvalidMergeStyle { ID : pr . BaseRepo . ID , Style : mergeStyle }
}
defer func ( ) {
2020-01-09 10:47:45 +09:00
go AddTestPullRequestTask ( doer , pr . BaseRepo . ID , pr . BaseBranch , false , "" , "" )
2019-06-23 02:35:34 +09:00
} ( )
2022-10-12 01:26:22 +09:00
// Run the merge in the hammer context to prevent cancellation
hammerCtx := graceful . GetManager ( ) . HammerContext ( )
pr . MergedCommitID , err = rawMerge ( hammerCtx , pr , doer , mergeStyle , expectedHeadCommitID , message )
2020-01-17 15:03:40 +09:00
if err != nil {
2020-02-10 08:09:31 +09:00
return err
2020-01-17 15:03:40 +09:00
}
pr . MergedUnix = timeutil . TimeStampNow ( )
pr . Merger = doer
pr . MergerID = doer . ID
2022-10-12 01:26:22 +09:00
if _ , err := pr . SetMerged ( hammerCtx ) ; err != nil {
2022-11-19 17:12:33 +09:00
log . Error ( "SetMerged [%d]: %v" , pr . ID , err )
2020-01-17 15:03:40 +09:00
}
2022-11-19 17:12:33 +09:00
if err := pr . LoadIssue ( hammerCtx ) ; err != nil {
log . Error ( "LoadIssue [%d]: %v" , pr . ID , err )
2020-02-10 08:09:31 +09:00
}
2022-10-12 01:26:22 +09:00
if err := pr . Issue . LoadRepo ( hammerCtx ) ; err != nil {
2022-11-19 17:12:33 +09:00
log . Error ( "LoadRepo for issue [%d]: %v" , pr . ID , err )
2020-02-10 08:09:31 +09:00
}
2022-10-12 01:26:22 +09:00
if err := pr . Issue . Repo . GetOwner ( hammerCtx ) ; err != nil {
2022-11-19 17:12:33 +09:00
log . Error ( "GetOwner for PR [%d]: %v" , pr . ID , err )
2020-02-10 08:09:31 +09:00
}
2022-11-04 00:49:00 +09:00
if wasAutoMerged {
2022-11-19 17:12:33 +09:00
notification . NotifyAutoMergePullRequest ( hammerCtx , doer , pr )
2022-11-04 00:49:00 +09:00
} else {
2022-11-19 17:12:33 +09:00
notification . NotifyMergePullRequest ( hammerCtx , doer , pr )
2022-11-04 00:49:00 +09:00
}
2020-01-17 15:03:40 +09:00
// Reset cached commit count
cache . Remove ( pr . Issue . Repo . GetCommitsCountCacheKey ( pr . BaseBranch , true ) )
// Resolve cross references
2022-10-12 01:26:22 +09:00
refs , err := pr . ResolveCrossReferences ( hammerCtx )
2020-01-17 15:03:40 +09:00
if err != nil {
log . Error ( "ResolveCrossReferences: %v" , err )
return nil
}
for _ , ref := range refs {
2022-11-19 17:12:33 +09:00
if err = ref . LoadIssue ( hammerCtx ) ; err != nil {
2020-01-17 15:03:40 +09:00
return err
}
2022-10-12 01:26:22 +09:00
if err = ref . Issue . LoadRepo ( hammerCtx ) ; err != nil {
2020-01-17 15:03:40 +09:00
return err
}
2021-04-09 16:40:34 +09:00
close := ref . RefAction == references . XRefActionCloses
2020-01-17 15:03:40 +09:00
if close != ref . Issue . IsClosed {
2023-01-25 13:47:53 +09:00
if err = issue_service . ChangeStatus ( ref . Issue , doer , pr . MergedCommitID , close ) ; err != nil {
2022-01-19 08:26:42 +09:00
// Allow ErrDependenciesLeft
2022-06-13 18:37:59 +09:00
if ! issues_model . IsErrDependenciesLeft ( err ) {
2022-01-19 08:26:42 +09:00
return err
}
2020-01-17 15:03:40 +09:00
}
}
}
return nil
}
// rawMerge perform the merge operation without changing any pull information in database
2022-06-13 18:37:59 +09:00
func rawMerge ( ctx context . Context , pr * issues_model . PullRequest , doer * user_model . User , mergeStyle repo_model . MergeStyle , expectedHeadCommitID , message string ) ( string , error ) {
2019-06-23 02:35:34 +09:00
// Clone base repo.
2022-01-20 08:26:57 +09:00
tmpBasePath , err := createTemporaryRepo ( ctx , pr )
2019-06-23 02:35:34 +09:00
if err != nil {
2019-11-10 17:42:51 +09:00
log . Error ( "CreateTemporaryPath: %v" , err )
2020-02-10 08:09:31 +09:00
return "" , err
2019-06-23 02:35:34 +09:00
}
defer func ( ) {
2022-05-09 01:46:32 +09:00
if err := repo_module . RemoveTemporaryPath ( tmpBasePath ) ; err != nil {
2019-06-23 02:35:34 +09:00
log . Error ( "Merge: RemoveTemporaryPath: %s" , err )
}
} ( )
2019-10-12 09:13:27 +09:00
baseBranch := "base"
trackingBranch := "tracking"
stagingBranch := "staging"
2019-06-23 02:35:34 +09:00
2021-12-20 09:32:54 +09:00
if expectedHeadCommitID != "" {
2022-10-23 23:44:45 +09:00
trackingCommitID , _ , err := git . NewCommand ( ctx , "show-ref" , "--hash" ) . AddDynamicArguments ( git . BranchPrefix + trackingBranch ) . RunStdString ( & git . RunOpts { Dir : tmpBasePath } )
2021-12-20 09:32:54 +09:00
if err != nil {
log . Error ( "show-ref[%s] --hash refs/heads/trackingn: %v" , tmpBasePath , git . BranchPrefix + trackingBranch , err )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "getDiffTree: %w" , err )
2021-12-20 09:32:54 +09:00
}
if strings . TrimSpace ( trackingCommitID ) != expectedHeadCommitID {
return "" , models . ErrSHADoesNotMatch {
GivenSHA : expectedHeadCommitID ,
CurrentSHA : trackingCommitID ,
}
}
}
2019-12-14 07:21:06 +09:00
var outbuf , errbuf strings . Builder
2019-06-23 02:35:34 +09:00
// Enable sparse-checkout
2022-01-20 08:26:57 +09:00
sparseCheckoutList , err := getDiffTree ( ctx , tmpBasePath , baseBranch , trackingBranch )
2019-06-23 02:35:34 +09:00
if err != nil {
2019-11-10 17:42:51 +09:00
log . Error ( "getDiffTree(%s, %s, %s): %v" , tmpBasePath , baseBranch , trackingBranch , err )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "getDiffTree: %w" , err )
2019-06-23 02:35:34 +09:00
}
infoPath := filepath . Join ( tmpBasePath , ".git" , "info" )
2022-01-21 02:46:10 +09:00
if err := os . MkdirAll ( infoPath , 0 o700 ) ; err != nil {
2019-11-10 17:42:51 +09:00
log . Error ( "Unable to create .git/info in %s: %v" , tmpBasePath , err )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "Unable to create .git/info in tmpBasePath: %w" , err )
2019-06-23 02:35:34 +09:00
}
2019-11-10 17:42:51 +09:00
2019-06-23 02:35:34 +09:00
sparseCheckoutListPath := filepath . Join ( infoPath , "sparse-checkout" )
2022-01-21 02:46:10 +09:00
if err := os . WriteFile ( sparseCheckoutListPath , [ ] byte ( sparseCheckoutList ) , 0 o600 ) ; err != nil {
2019-11-10 17:42:51 +09:00
log . Error ( "Unable to write .git/info/sparse-checkout file in %s: %v" , tmpBasePath , err )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "Unable to write .git/info/sparse-checkout file in tmpBasePath: %w" , err )
2019-06-23 02:35:34 +09:00
}
2022-06-17 00:47:44 +09:00
gitConfigCommand := func ( ) * git . Command {
return git . NewCommand ( ctx , "config" , "--local" )
2019-11-10 17:42:51 +09:00
}
2019-10-12 09:13:27 +09:00
2019-06-23 02:35:34 +09:00
// Switch off LFS process (set required, clean and smudge here also)
2022-02-11 21:47:22 +09:00
if err := gitConfigCommand ( ) . AddArguments ( "filter.lfs.process" , "" ) .
2022-04-01 11:55:30 +09:00
Run ( & git . RunOpts {
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 21:47:22 +09:00
} ) ; err != nil {
2019-11-10 17:42:51 +09:00
log . Error ( "git config [filter.lfs.process -> <> ]: %v\n%s\n%s" , err , outbuf . String ( ) , errbuf . String ( ) )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "git config [filter.lfs.process -> <> ]: %w\n%s\n%s" , err , outbuf . String ( ) , errbuf . String ( ) )
2019-06-23 02:35:34 +09:00
}
2019-11-10 17:42:51 +09:00
outbuf . Reset ( )
errbuf . Reset ( )
2022-02-11 21:47:22 +09:00
if err := gitConfigCommand ( ) . AddArguments ( "filter.lfs.required" , "false" ) .
2022-04-01 11:55:30 +09:00
Run ( & git . RunOpts {
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 21:47:22 +09:00
} ) ; err != nil {
2019-11-10 17:42:51 +09:00
log . Error ( "git config [filter.lfs.required -> <false> ]: %v\n%s\n%s" , err , outbuf . String ( ) , errbuf . String ( ) )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "git config [filter.lfs.required -> <false> ]: %w\n%s\n%s" , err , outbuf . String ( ) , errbuf . String ( ) )
2019-06-23 02:35:34 +09:00
}
2019-11-10 17:42:51 +09:00
outbuf . Reset ( )
errbuf . Reset ( )
2022-02-11 21:47:22 +09:00
if err := gitConfigCommand ( ) . AddArguments ( "filter.lfs.clean" , "" ) .
2022-04-01 11:55:30 +09:00
Run ( & git . RunOpts {
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 21:47:22 +09:00
} ) ; err != nil {
2019-11-10 17:42:51 +09:00
log . Error ( "git config [filter.lfs.clean -> <> ]: %v\n%s\n%s" , err , outbuf . String ( ) , errbuf . String ( ) )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "git config [filter.lfs.clean -> <> ]: %w\n%s\n%s" , err , outbuf . String ( ) , errbuf . String ( ) )
2019-06-23 02:35:34 +09:00
}
2019-11-10 17:42:51 +09:00
outbuf . Reset ( )
errbuf . Reset ( )
2022-02-11 21:47:22 +09:00
if err := gitConfigCommand ( ) . AddArguments ( "filter.lfs.smudge" , "" ) .
2022-04-01 11:55:30 +09:00
Run ( & git . RunOpts {
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 21:47:22 +09:00
} ) ; err != nil {
2019-11-10 17:42:51 +09:00
log . Error ( "git config [filter.lfs.smudge -> <> ]: %v\n%s\n%s" , err , outbuf . String ( ) , errbuf . String ( ) )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "git config [filter.lfs.smudge -> <> ]: %w\n%s\n%s" , err , outbuf . String ( ) , errbuf . String ( ) )
2019-06-23 02:35:34 +09:00
}
2019-11-10 17:42:51 +09:00
outbuf . Reset ( )
errbuf . Reset ( )
2019-06-23 02:35:34 +09:00
2022-02-11 21:47:22 +09:00
if err := gitConfigCommand ( ) . AddArguments ( "core.sparseCheckout" , "true" ) .
2022-04-01 11:55:30 +09:00
Run ( & git . RunOpts {
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 21:47:22 +09:00
} ) ; err != nil {
2019-11-10 17:42:51 +09:00
log . Error ( "git config [core.sparseCheckout -> true ]: %v\n%s\n%s" , err , outbuf . String ( ) , errbuf . String ( ) )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "git config [core.sparsecheckout -> true]: %w\n%s\n%s" , err , outbuf . String ( ) , errbuf . String ( ) )
2019-06-23 02:35:34 +09:00
}
2019-11-10 17:42:51 +09:00
outbuf . Reset ( )
errbuf . Reset ( )
2019-06-23 02:35:34 +09:00
// Read base branch index
2022-02-11 21:47:22 +09:00
if err := git . NewCommand ( ctx , "read-tree" , "HEAD" ) .
2022-04-01 11:55:30 +09:00
Run ( & git . RunOpts {
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 21:47:22 +09:00
} ) ; err != nil {
2019-11-10 17:42:51 +09:00
log . Error ( "git read-tree HEAD: %v\n%s\n%s" , err , outbuf . String ( ) , errbuf . String ( ) )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "Unable to read base branch in to the index: %w\n%s\n%s" , err , outbuf . String ( ) , errbuf . String ( ) )
2019-06-23 02:35:34 +09:00
}
2019-11-10 17:42:51 +09:00
outbuf . Reset ( )
errbuf . Reset ( )
2019-06-23 02:35:34 +09:00
2020-09-20 01:44:55 +09:00
sig := doer . NewGitSig ( )
committer := sig
Refactor git command package to improve security and maintainability (#22678)
This PR follows #21535 (and replace #22592)
## Review without space diff
https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1
## Purpose of this PR
1. Make git module command completely safe (risky user inputs won't be
passed as argument option anymore)
2. Avoid low-level mistakes like
https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918
3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg`
type
4. Simplify code when using git command
## The main idea of this PR
* Move the `git.CmdArg` to the `internal` package, then no other package
except `git` could use it. Then developers could never do
`AddArguments(git.CmdArg(userInput))` any more.
* Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already
trusted arguments. It's only used in a few cases, for example: use git
arguments from config file, help unit test with some arguments.
* Introduce `AddOptionValues` and `AddOptionFormat`, they make code more
clear and simple:
* Before: `AddArguments("-m").AddDynamicArguments(message)`
* After: `AddOptionValues("-m", message)`
* -
* Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'",
sig.Name, sig.Email)))`
* After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)`
## FAQ
### Why these changes were not done in #21535 ?
#21535 is mainly a search&replace, it did its best to not change too
much logic.
Making the framework better needs a lot of changes, so this separate PR
is needed as the second step.
### The naming of `AddOptionXxx`
According to git's manual, the `--xxx` part is called `option`.
### How can it guarantee that `internal.CmdArg` won't be not misused?
Go's specification guarantees that. Trying to access other package's
internal package causes compilation error.
And, `golangci-lint` also denies the git/internal package. Only the
`git/command.go` can use it carefully.
### There is still a `ToTrustedCmdArgs`, will it still allow developers
to make mistakes and pass untrusted arguments?
Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code
will be very complex (see the changes for examples). Then developers and
reviewers can know that something might be unreasonable.
### Why there was a `CmdArgCheck` and why it's removed?
At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck`
was introduced as a hacky patch. Now, almost all code could be written
as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for
`CmdArgCheck` anymore.
### Why many codes for `signArg == ""` is deleted?
Because in the old code, `signArg` could never be empty string, it's
either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just
dead code.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-04 11:30:43 +09:00
// Determine if we should sign. If no signKeyID, use --no-gpg-sign to countermand the sign config (from gitconfig)
var signArgs git . TrustedCmdArgs
sign , signKeyID , signer , _ := asymkey_service . SignMerge ( ctx , pr , doer , tmpBasePath , "HEAD" , trackingBranch )
2022-06-17 00:47:44 +09:00
if sign {
if pr . BaseRepo . GetTrustModel ( ) == repo_model . CommitterTrustModel || pr . BaseRepo . GetTrustModel ( ) == repo_model . CollaboratorCommitterTrustModel {
committer = signer
2019-10-16 22:42:42 +09:00
}
Refactor git command package to improve security and maintainability (#22678)
This PR follows #21535 (and replace #22592)
## Review without space diff
https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1
## Purpose of this PR
1. Make git module command completely safe (risky user inputs won't be
passed as argument option anymore)
2. Avoid low-level mistakes like
https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918
3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg`
type
4. Simplify code when using git command
## The main idea of this PR
* Move the `git.CmdArg` to the `internal` package, then no other package
except `git` could use it. Then developers could never do
`AddArguments(git.CmdArg(userInput))` any more.
* Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already
trusted arguments. It's only used in a few cases, for example: use git
arguments from config file, help unit test with some arguments.
* Introduce `AddOptionValues` and `AddOptionFormat`, they make code more
clear and simple:
* Before: `AddArguments("-m").AddDynamicArguments(message)`
* After: `AddOptionValues("-m", message)`
* -
* Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'",
sig.Name, sig.Email)))`
* After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)`
## FAQ
### Why these changes were not done in #21535 ?
#21535 is mainly a search&replace, it did its best to not change too
much logic.
Making the framework better needs a lot of changes, so this separate PR
is needed as the second step.
### The naming of `AddOptionXxx`
According to git's manual, the `--xxx` part is called `option`.
### How can it guarantee that `internal.CmdArg` won't be not misused?
Go's specification guarantees that. Trying to access other package's
internal package causes compilation error.
And, `golangci-lint` also denies the git/internal package. Only the
`git/command.go` can use it carefully.
### There is still a `ToTrustedCmdArgs`, will it still allow developers
to make mistakes and pass untrusted arguments?
Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code
will be very complex (see the changes for examples). Then developers and
reviewers can know that something might be unreasonable.
### Why there was a `CmdArgCheck` and why it's removed?
At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck`
was introduced as a hacky patch. Now, almost all code could be written
as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for
`CmdArgCheck` anymore.
### Why many codes for `signArg == ""` is deleted?
Because in the old code, `signArg` could never be empty string, it's
either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just
dead code.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-04 11:30:43 +09:00
signArgs = git . ToTrustedCmdArgs ( [ ] string { "-S" + signKeyID } )
2022-06-17 00:47:44 +09:00
} else {
Refactor git command package to improve security and maintainability (#22678)
This PR follows #21535 (and replace #22592)
## Review without space diff
https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1
## Purpose of this PR
1. Make git module command completely safe (risky user inputs won't be
passed as argument option anymore)
2. Avoid low-level mistakes like
https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918
3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg`
type
4. Simplify code when using git command
## The main idea of this PR
* Move the `git.CmdArg` to the `internal` package, then no other package
except `git` could use it. Then developers could never do
`AddArguments(git.CmdArg(userInput))` any more.
* Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already
trusted arguments. It's only used in a few cases, for example: use git
arguments from config file, help unit test with some arguments.
* Introduce `AddOptionValues` and `AddOptionFormat`, they make code more
clear and simple:
* Before: `AddArguments("-m").AddDynamicArguments(message)`
* After: `AddOptionValues("-m", message)`
* -
* Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'",
sig.Name, sig.Email)))`
* After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)`
## FAQ
### Why these changes were not done in #21535 ?
#21535 is mainly a search&replace, it did its best to not change too
much logic.
Making the framework better needs a lot of changes, so this separate PR
is needed as the second step.
### The naming of `AddOptionXxx`
According to git's manual, the `--xxx` part is called `option`.
### How can it guarantee that `internal.CmdArg` won't be not misused?
Go's specification guarantees that. Trying to access other package's
internal package causes compilation error.
And, `golangci-lint` also denies the git/internal package. Only the
`git/command.go` can use it carefully.
### There is still a `ToTrustedCmdArgs`, will it still allow developers
to make mistakes and pass untrusted arguments?
Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code
will be very complex (see the changes for examples). Then developers and
reviewers can know that something might be unreasonable.
### Why there was a `CmdArgCheck` and why it's removed?
At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck`
was introduced as a hacky patch. Now, almost all code could be written
as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for
`CmdArgCheck` anymore.
### Why many codes for `signArg == ""` is deleted?
Because in the old code, `signArg` could never be empty string, it's
either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just
dead code.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-04 11:30:43 +09:00
signArgs = append ( signArgs , "--no-gpg-sign" )
2019-10-16 22:42:42 +09:00
}
commitTimeStr := time . Now ( ) . Format ( time . RFC3339 )
// Because this may call hooks we should pass in the environment
env := append ( os . Environ ( ) ,
"GIT_AUTHOR_NAME=" + sig . Name ,
"GIT_AUTHOR_EMAIL=" + sig . Email ,
"GIT_AUTHOR_DATE=" + commitTimeStr ,
2020-09-20 01:44:55 +09:00
"GIT_COMMITTER_NAME=" + committer . Name ,
"GIT_COMMITTER_EMAIL=" + committer . Email ,
2019-10-16 22:42:42 +09:00
"GIT_COMMITTER_DATE=" + commitTimeStr ,
)
2019-06-23 02:35:34 +09:00
// Merge commits.
switch mergeStyle {
2021-12-10 10:27:50 +09:00
case repo_model . MergeStyleMerge :
2022-10-23 23:44:45 +09:00
cmd := git . NewCommand ( ctx , "merge" , "--no-ff" , "--no-commit" ) . AddDynamicArguments ( trackingBranch )
2019-11-10 17:42:51 +09:00
if err := runMergeCommand ( pr , mergeStyle , cmd , tmpBasePath ) ; err != nil {
log . Error ( "Unable to merge tracking into base: %v" , err )
2020-02-10 08:09:31 +09:00
return "" , err
2019-06-23 02:35:34 +09:00
}
Refactor git command package to improve security and maintainability (#22678)
This PR follows #21535 (and replace #22592)
## Review without space diff
https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1
## Purpose of this PR
1. Make git module command completely safe (risky user inputs won't be
passed as argument option anymore)
2. Avoid low-level mistakes like
https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918
3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg`
type
4. Simplify code when using git command
## The main idea of this PR
* Move the `git.CmdArg` to the `internal` package, then no other package
except `git` could use it. Then developers could never do
`AddArguments(git.CmdArg(userInput))` any more.
* Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already
trusted arguments. It's only used in a few cases, for example: use git
arguments from config file, help unit test with some arguments.
* Introduce `AddOptionValues` and `AddOptionFormat`, they make code more
clear and simple:
* Before: `AddArguments("-m").AddDynamicArguments(message)`
* After: `AddOptionValues("-m", message)`
* -
* Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'",
sig.Name, sig.Email)))`
* After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)`
## FAQ
### Why these changes were not done in #21535 ?
#21535 is mainly a search&replace, it did its best to not change too
much logic.
Making the framework better needs a lot of changes, so this separate PR
is needed as the second step.
### The naming of `AddOptionXxx`
According to git's manual, the `--xxx` part is called `option`.
### How can it guarantee that `internal.CmdArg` won't be not misused?
Go's specification guarantees that. Trying to access other package's
internal package causes compilation error.
And, `golangci-lint` also denies the git/internal package. Only the
`git/command.go` can use it carefully.
### There is still a `ToTrustedCmdArgs`, will it still allow developers
to make mistakes and pass untrusted arguments?
Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code
will be very complex (see the changes for examples). Then developers and
reviewers can know that something might be unreasonable.
### Why there was a `CmdArgCheck` and why it's removed?
At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck`
was introduced as a hacky patch. Now, almost all code could be written
as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for
`CmdArgCheck` anymore.
### Why many codes for `signArg == ""` is deleted?
Because in the old code, `signArg` could never be empty string, it's
either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just
dead code.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-04 11:30:43 +09:00
if err := commitAndSignNoAuthor ( ctx , pr , message , signArgs , tmpBasePath , env ) ; err != nil {
2019-11-10 17:42:51 +09:00
log . Error ( "Unable to make final commit: %v" , err )
2020-02-10 08:09:31 +09:00
return "" , err
2019-06-23 02:35:34 +09:00
}
2021-12-10 10:27:50 +09:00
case repo_model . MergeStyleRebase :
2019-11-10 17:42:51 +09:00
fallthrough
2021-12-10 10:27:50 +09:00
case repo_model . MergeStyleRebaseUpdate :
2021-08-31 23:03:45 +09:00
fallthrough
2021-12-10 10:27:50 +09:00
case repo_model . MergeStyleRebaseMerge :
2019-06-23 02:35:34 +09:00
// Checkout head branch
2022-10-23 23:44:45 +09:00
if err := git . NewCommand ( ctx , "checkout" , "-b" ) . AddDynamicArguments ( stagingBranch , trackingBranch ) .
2022-04-01 11:55:30 +09:00
Run ( & git . RunOpts {
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 21:47:22 +09:00
} ) ; err != nil {
2019-11-10 17:42:51 +09:00
log . Error ( "git checkout base prior to merge post staging rebase [%s:%s -> %s:%s]: %v\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "git checkout base prior to merge post staging rebase [%s:%s -> %s:%s]: %w\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2019-06-23 02:35:34 +09:00
}
2019-11-10 17:42:51 +09:00
outbuf . Reset ( )
errbuf . Reset ( )
2019-06-23 02:35:34 +09:00
// Rebase before merging
2022-10-23 23:44:45 +09:00
if err := git . NewCommand ( ctx , "rebase" ) . AddDynamicArguments ( baseBranch ) .
2022-04-01 11:55:30 +09:00
Run ( & git . RunOpts {
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 21:47:22 +09:00
} ) ; err != nil {
2019-11-10 17:42:51 +09:00
// Rebase will leave a REBASE_HEAD file in .git if there is a conflict
if _ , statErr := os . Stat ( filepath . Join ( tmpBasePath , ".git" , "REBASE_HEAD" ) ) ; statErr == nil {
2020-04-04 01:00:41 +09:00
var commitSha string
ok := false
failingCommitPaths := [ ] string {
filepath . Join ( tmpBasePath , ".git" , "rebase-apply" , "original-commit" ) , // Git < 2.26
filepath . Join ( tmpBasePath , ".git" , "rebase-merge" , "stopped-sha" ) , // Git >= 2.26
}
for _ , failingCommitPath := range failingCommitPaths {
2021-11-15 15:02:53 +09:00
if _ , statErr := os . Stat ( failingCommitPath ) ; statErr == nil {
commitShaBytes , readErr := os . ReadFile ( failingCommitPath )
2020-04-04 01:00:41 +09:00
if readErr != nil {
// Abandon this attempt to handle the error
log . Error ( "git rebase staging on to base [%s:%s -> %s:%s]: %v\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "git rebase staging on to base [%s:%s -> %s:%s]: %w\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2020-04-04 01:00:41 +09:00
}
commitSha = strings . TrimSpace ( string ( commitShaBytes ) )
ok = true
break
}
}
if ! ok {
log . Error ( "Unable to determine failing commit sha for this rebase message. Cannot cast as models.ErrRebaseConflicts." )
2019-11-10 17:42:51 +09:00
log . Error ( "git rebase staging on to base [%s:%s -> %s:%s]: %v\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "git rebase staging on to base [%s:%s -> %s:%s]: %w\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2019-11-10 17:42:51 +09:00
}
2020-04-04 01:00:41 +09:00
log . Debug ( "RebaseConflict at %s [%s:%s -> %s:%s]: %v\n%s\n%s" , commitSha , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2020-02-10 08:09:31 +09:00
return "" , models . ErrRebaseConflicts {
2019-11-10 17:42:51 +09:00
Style : mergeStyle ,
2020-04-04 01:00:41 +09:00
CommitSHA : commitSha ,
2019-11-10 17:42:51 +09:00
StdOut : outbuf . String ( ) ,
StdErr : errbuf . String ( ) ,
Err : err ,
}
}
log . Error ( "git rebase staging on to base [%s:%s -> %s:%s]: %v\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "git rebase staging on to base [%s:%s -> %s:%s]: %w\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2019-06-23 02:35:34 +09:00
}
2019-11-10 17:42:51 +09:00
outbuf . Reset ( )
errbuf . Reset ( )
2021-08-31 23:03:45 +09:00
// not need merge, just update by rebase. so skip
2021-12-10 10:27:50 +09:00
if mergeStyle == repo_model . MergeStyleRebaseUpdate {
2021-08-31 23:03:45 +09:00
break
}
2019-06-23 02:35:34 +09:00
// Checkout base branch again
2022-10-23 23:44:45 +09:00
if err := git . NewCommand ( ctx , "checkout" ) . AddDynamicArguments ( baseBranch ) .
2022-04-01 11:55:30 +09:00
Run ( & git . RunOpts {
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 21:47:22 +09:00
} ) ; err != nil {
2019-11-10 17:42:51 +09:00
log . Error ( "git checkout base prior to merge post staging rebase [%s:%s -> %s:%s]: %v\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "git checkout base prior to merge post staging rebase [%s:%s -> %s:%s]: %w\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2019-06-23 02:35:34 +09:00
}
2019-11-10 17:42:51 +09:00
outbuf . Reset ( )
errbuf . Reset ( )
2019-06-23 02:35:34 +09:00
2022-02-07 04:01:47 +09:00
cmd := git . NewCommand ( ctx , "merge" )
2021-12-10 10:27:50 +09:00
if mergeStyle == repo_model . MergeStyleRebase {
2019-11-10 17:42:51 +09:00
cmd . AddArguments ( "--ff-only" )
2019-10-16 22:42:42 +09:00
} else {
2019-11-10 17:42:51 +09:00
cmd . AddArguments ( "--no-ff" , "--no-commit" )
2019-06-23 02:35:34 +09:00
}
2022-10-23 23:44:45 +09:00
cmd . AddDynamicArguments ( stagingBranch )
2019-06-23 02:35:34 +09:00
2019-11-10 17:42:51 +09:00
// Prepare merge with commit
if err := runMergeCommand ( pr , mergeStyle , cmd , tmpBasePath ) ; err != nil {
log . Error ( "Unable to merge staging into base: %v" , err )
2020-02-10 08:09:31 +09:00
return "" , err
2019-11-10 17:42:51 +09:00
}
2021-12-10 10:27:50 +09:00
if mergeStyle == repo_model . MergeStyleRebaseMerge {
Refactor git command package to improve security and maintainability (#22678)
This PR follows #21535 (and replace #22592)
## Review without space diff
https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1
## Purpose of this PR
1. Make git module command completely safe (risky user inputs won't be
passed as argument option anymore)
2. Avoid low-level mistakes like
https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918
3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg`
type
4. Simplify code when using git command
## The main idea of this PR
* Move the `git.CmdArg` to the `internal` package, then no other package
except `git` could use it. Then developers could never do
`AddArguments(git.CmdArg(userInput))` any more.
* Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already
trusted arguments. It's only used in a few cases, for example: use git
arguments from config file, help unit test with some arguments.
* Introduce `AddOptionValues` and `AddOptionFormat`, they make code more
clear and simple:
* Before: `AddArguments("-m").AddDynamicArguments(message)`
* After: `AddOptionValues("-m", message)`
* -
* Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'",
sig.Name, sig.Email)))`
* After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)`
## FAQ
### Why these changes were not done in #21535 ?
#21535 is mainly a search&replace, it did its best to not change too
much logic.
Making the framework better needs a lot of changes, so this separate PR
is needed as the second step.
### The naming of `AddOptionXxx`
According to git's manual, the `--xxx` part is called `option`.
### How can it guarantee that `internal.CmdArg` won't be not misused?
Go's specification guarantees that. Trying to access other package's
internal package causes compilation error.
And, `golangci-lint` also denies the git/internal package. Only the
`git/command.go` can use it carefully.
### There is still a `ToTrustedCmdArgs`, will it still allow developers
to make mistakes and pass untrusted arguments?
Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code
will be very complex (see the changes for examples). Then developers and
reviewers can know that something might be unreasonable.
### Why there was a `CmdArgCheck` and why it's removed?
At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck`
was introduced as a hacky patch. Now, almost all code could be written
as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for
`CmdArgCheck` anymore.
### Why many codes for `signArg == ""` is deleted?
Because in the old code, `signArg` could never be empty string, it's
either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just
dead code.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-04 11:30:43 +09:00
if err := commitAndSignNoAuthor ( ctx , pr , message , signArgs , tmpBasePath , env ) ; err != nil {
2019-11-10 17:42:51 +09:00
log . Error ( "Unable to make final commit: %v" , err )
2020-02-10 08:09:31 +09:00
return "" , err
2019-11-10 17:42:51 +09:00
}
}
2021-12-10 10:27:50 +09:00
case repo_model . MergeStyleSquash :
2019-06-23 02:35:34 +09:00
// Merge with squash
2022-10-23 23:44:45 +09:00
cmd := git . NewCommand ( ctx , "merge" , "--squash" ) . AddDynamicArguments ( trackingBranch )
2019-11-10 17:42:51 +09:00
if err := runMergeCommand ( pr , mergeStyle , cmd , tmpBasePath ) ; err != nil {
log . Error ( "Unable to merge --squash tracking into base: %v" , err )
2020-02-10 08:09:31 +09:00
return "" , err
2019-06-23 02:35:34 +09:00
}
2019-11-10 17:42:51 +09:00
2022-11-19 17:12:33 +09:00
if err = pr . Issue . LoadPoster ( ctx ) ; err != nil {
2020-04-10 19:40:36 +09:00
log . Error ( "LoadPoster: %v" , err )
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "LoadPoster: %w" , err )
2020-04-10 19:40:36 +09:00
}
2019-06-23 02:35:34 +09:00
sig := pr . Issue . Poster . NewGitSig ( )
Refactor git command package to improve security and maintainability (#22678)
This PR follows #21535 (and replace #22592)
## Review without space diff
https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1
## Purpose of this PR
1. Make git module command completely safe (risky user inputs won't be
passed as argument option anymore)
2. Avoid low-level mistakes like
https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918
3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg`
type
4. Simplify code when using git command
## The main idea of this PR
* Move the `git.CmdArg` to the `internal` package, then no other package
except `git` could use it. Then developers could never do
`AddArguments(git.CmdArg(userInput))` any more.
* Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already
trusted arguments. It's only used in a few cases, for example: use git
arguments from config file, help unit test with some arguments.
* Introduce `AddOptionValues` and `AddOptionFormat`, they make code more
clear and simple:
* Before: `AddArguments("-m").AddDynamicArguments(message)`
* After: `AddOptionValues("-m", message)`
* -
* Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'",
sig.Name, sig.Email)))`
* After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)`
## FAQ
### Why these changes were not done in #21535 ?
#21535 is mainly a search&replace, it did its best to not change too
much logic.
Making the framework better needs a lot of changes, so this separate PR
is needed as the second step.
### The naming of `AddOptionXxx`
According to git's manual, the `--xxx` part is called `option`.
### How can it guarantee that `internal.CmdArg` won't be not misused?
Go's specification guarantees that. Trying to access other package's
internal package causes compilation error.
And, `golangci-lint` also denies the git/internal package. Only the
`git/command.go` can use it carefully.
### There is still a `ToTrustedCmdArgs`, will it still allow developers
to make mistakes and pass untrusted arguments?
Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code
will be very complex (see the changes for examples). Then developers and
reviewers can know that something might be unreasonable.
### Why there was a `CmdArgCheck` and why it's removed?
At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck`
was introduced as a hacky patch. Now, almost all code could be written
as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for
`CmdArgCheck` anymore.
### Why many codes for `signArg == ""` is deleted?
Because in the old code, `signArg` could never be empty string, it's
either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just
dead code.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-04 11:30:43 +09:00
if setting . Repository . PullRequest . AddCoCommitterTrailers && committer . String ( ) != sig . String ( ) {
// add trailer
message += fmt . Sprintf ( "\nCo-authored-by: %s\nCo-committed-by: %s\n" , sig . String ( ) , sig . String ( ) )
}
if err := git . NewCommand ( ctx , "commit" ) .
AddArguments ( signArgs ... ) .
AddOptionFormat ( "--author='%s <%s>'" , sig . Name , sig . Email ) .
AddOptionValues ( "-m" , message ) .
Run ( & git . RunOpts {
Env : env ,
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
} ) ; err != nil {
log . Error ( "git commit [%s:%s -> %s:%s]: %v\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
return "" , fmt . Errorf ( "git commit [%s:%s -> %s:%s]: %w\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2019-06-23 02:35:34 +09:00
}
2019-11-10 17:42:51 +09:00
outbuf . Reset ( )
errbuf . Reset ( )
2019-06-23 02:35:34 +09:00
default :
2020-02-10 08:09:31 +09:00
return "" , models . ErrInvalidMergeStyle { ID : pr . BaseRepo . ID , Style : mergeStyle }
2019-06-23 02:35:34 +09:00
}
// OK we should cache our current head and origin/headbranch
2022-01-20 08:26:57 +09:00
mergeHeadSHA , err := git . GetFullCommitID ( ctx , tmpBasePath , "HEAD" )
2019-06-23 02:35:34 +09:00
if err != nil {
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "Failed to get full commit id for HEAD: %w" , err )
2019-06-23 02:35:34 +09:00
}
2022-01-20 08:26:57 +09:00
mergeBaseSHA , err := git . GetFullCommitID ( ctx , tmpBasePath , "original_" + baseBranch )
2019-06-23 02:35:34 +09:00
if err != nil {
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "Failed to get full commit id for origin/%s: %w" , pr . BaseBranch , err )
2020-02-10 08:09:31 +09:00
}
2022-01-20 08:26:57 +09:00
mergeCommitID , err := git . GetFullCommitID ( ctx , tmpBasePath , baseBranch )
2020-02-10 08:09:31 +09:00
if err != nil {
2022-10-25 04:29:17 +09:00
return "" , fmt . Errorf ( "Failed to get full commit id for the new merge: %w" , err )
2019-06-23 02:35:34 +09:00
}
// Now it's questionable about where this should go - either after or before the push
// I think in the interests of data safety - failures to push to the lfs should prevent
// the merge as you can always remerge.
if setting . LFS . StartServer {
2022-01-20 08:26:57 +09:00
if err := LFSPush ( ctx , tmpBasePath , mergeHeadSHA , mergeBaseSHA , pr ) ; err != nil {
2020-02-10 08:09:31 +09:00
return "" , err
2019-06-23 02:35:34 +09:00
}
}
2021-11-24 18:49:20 +09:00
var headUser * user_model . User
2022-03-23 00:22:54 +09:00
err = pr . HeadRepo . GetOwner ( ctx )
2019-07-01 10:18:13 +09:00
if err != nil {
2021-11-24 18:49:20 +09:00
if ! user_model . IsErrUserNotExist ( err ) {
2019-10-18 20:13:31 +09:00
log . Error ( "Can't find user: %d for head repository - %v" , pr . HeadRepo . OwnerID , err )
2020-02-10 08:09:31 +09:00
return "" , err
2019-07-01 10:18:13 +09:00
}
2019-10-18 20:13:31 +09:00
log . Error ( "Can't find user: %d for head repository - defaulting to doer: %s - %v" , pr . HeadRepo . OwnerID , doer . Name , err )
2019-07-01 10:18:13 +09:00
headUser = doer
2019-10-18 20:13:31 +09:00
} else {
headUser = pr . HeadRepo . Owner
2019-07-01 10:18:13 +09:00
}
2021-08-31 23:03:45 +09:00
var pushCmd * git . Command
2021-12-10 10:27:50 +09:00
if mergeStyle == repo_model . MergeStyleRebaseUpdate {
2022-01-10 18:32:37 +09:00
// force push the rebase result to head branch
2023-01-20 07:31:44 +09:00
env = repo_module . FullPushingEnvironment (
headUser ,
doer ,
pr . HeadRepo ,
pr . HeadRepo . Name ,
pr . ID ,
)
2022-10-23 23:44:45 +09:00
pushCmd = git . NewCommand ( ctx , "push" , "-f" , "head_repo" ) . AddDynamicArguments ( stagingBranch + ":" + git . BranchPrefix + pr . HeadBranch )
2021-08-31 23:03:45 +09:00
} else {
2023-01-20 07:31:44 +09:00
env = repo_module . FullPushingEnvironment (
headUser ,
doer ,
pr . BaseRepo ,
pr . BaseRepo . Name ,
pr . ID ,
)
2022-10-23 23:44:45 +09:00
pushCmd = git . NewCommand ( ctx , "push" , "origin" ) . AddDynamicArguments ( baseBranch + ":" + git . BranchPrefix + pr . BaseBranch )
2021-08-31 23:03:45 +09:00
}
2019-06-23 02:35:34 +09:00
// Push back to upstream.
2022-05-04 04:46:28 +09:00
// TODO: this cause an api call to "/api/internal/hook/post-receive/...",
// that prevents us from doint the whole merge in one db transaction
2022-04-01 11:55:30 +09:00
if err := pushCmd . Run ( & git . RunOpts {
Env : env ,
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 21:47:22 +09:00
} ) ; err != nil {
2019-11-10 17:42:51 +09:00
if strings . Contains ( errbuf . String ( ) , "non-fast-forward" ) {
2020-03-28 13:13:18 +09:00
return "" , & git . ErrPushOutOfDate {
2019-11-10 17:42:51 +09:00
StdOut : outbuf . String ( ) ,
StdErr : errbuf . String ( ) ,
Err : err ,
}
2020-02-22 22:08:48 +09:00
} else if strings . Contains ( errbuf . String ( ) , "! [remote rejected]" ) {
2020-03-28 13:13:18 +09:00
err := & git . ErrPushRejected {
2020-02-22 22:08:48 +09:00
StdOut : outbuf . String ( ) ,
StdErr : errbuf . String ( ) ,
Err : err ,
}
err . GenerateMessage ( )
return "" , err
2019-11-10 17:42:51 +09:00
}
2020-02-10 08:09:31 +09:00
return "" , fmt . Errorf ( "git push: %s" , errbuf . String ( ) )
2019-06-23 02:35:34 +09:00
}
2019-11-10 17:42:51 +09:00
outbuf . Reset ( )
errbuf . Reset ( )
2019-06-23 02:35:34 +09:00
2020-02-10 08:09:31 +09:00
return mergeCommitID , nil
2019-06-23 02:35:34 +09:00
}
Refactor git command package to improve security and maintainability (#22678)
This PR follows #21535 (and replace #22592)
## Review without space diff
https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1
## Purpose of this PR
1. Make git module command completely safe (risky user inputs won't be
passed as argument option anymore)
2. Avoid low-level mistakes like
https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918
3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg`
type
4. Simplify code when using git command
## The main idea of this PR
* Move the `git.CmdArg` to the `internal` package, then no other package
except `git` could use it. Then developers could never do
`AddArguments(git.CmdArg(userInput))` any more.
* Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already
trusted arguments. It's only used in a few cases, for example: use git
arguments from config file, help unit test with some arguments.
* Introduce `AddOptionValues` and `AddOptionFormat`, they make code more
clear and simple:
* Before: `AddArguments("-m").AddDynamicArguments(message)`
* After: `AddOptionValues("-m", message)`
* -
* Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'",
sig.Name, sig.Email)))`
* After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)`
## FAQ
### Why these changes were not done in #21535 ?
#21535 is mainly a search&replace, it did its best to not change too
much logic.
Making the framework better needs a lot of changes, so this separate PR
is needed as the second step.
### The naming of `AddOptionXxx`
According to git's manual, the `--xxx` part is called `option`.
### How can it guarantee that `internal.CmdArg` won't be not misused?
Go's specification guarantees that. Trying to access other package's
internal package causes compilation error.
And, `golangci-lint` also denies the git/internal package. Only the
`git/command.go` can use it carefully.
### There is still a `ToTrustedCmdArgs`, will it still allow developers
to make mistakes and pass untrusted arguments?
Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code
will be very complex (see the changes for examples). Then developers and
reviewers can know that something might be unreasonable.
### Why there was a `CmdArgCheck` and why it's removed?
At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck`
was introduced as a hacky patch. Now, almost all code could be written
as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for
`CmdArgCheck` anymore.
### Why many codes for `signArg == ""` is deleted?
Because in the old code, `signArg` could never be empty string, it's
either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just
dead code.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-04 11:30:43 +09:00
func commitAndSignNoAuthor ( ctx context . Context , pr * issues_model . PullRequest , message string , signArgs git . TrustedCmdArgs , tmpBasePath string , env [ ] string ) error {
2019-11-10 17:42:51 +09:00
var outbuf , errbuf strings . Builder
Refactor git command package to improve security and maintainability (#22678)
This PR follows #21535 (and replace #22592)
## Review without space diff
https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1
## Purpose of this PR
1. Make git module command completely safe (risky user inputs won't be
passed as argument option anymore)
2. Avoid low-level mistakes like
https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918
3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg`
type
4. Simplify code when using git command
## The main idea of this PR
* Move the `git.CmdArg` to the `internal` package, then no other package
except `git` could use it. Then developers could never do
`AddArguments(git.CmdArg(userInput))` any more.
* Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already
trusted arguments. It's only used in a few cases, for example: use git
arguments from config file, help unit test with some arguments.
* Introduce `AddOptionValues` and `AddOptionFormat`, they make code more
clear and simple:
* Before: `AddArguments("-m").AddDynamicArguments(message)`
* After: `AddOptionValues("-m", message)`
* -
* Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'",
sig.Name, sig.Email)))`
* After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)`
## FAQ
### Why these changes were not done in #21535 ?
#21535 is mainly a search&replace, it did its best to not change too
much logic.
Making the framework better needs a lot of changes, so this separate PR
is needed as the second step.
### The naming of `AddOptionXxx`
According to git's manual, the `--xxx` part is called `option`.
### How can it guarantee that `internal.CmdArg` won't be not misused?
Go's specification guarantees that. Trying to access other package's
internal package causes compilation error.
And, `golangci-lint` also denies the git/internal package. Only the
`git/command.go` can use it carefully.
### There is still a `ToTrustedCmdArgs`, will it still allow developers
to make mistakes and pass untrusted arguments?
Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code
will be very complex (see the changes for examples). Then developers and
reviewers can know that something might be unreasonable.
### Why there was a `CmdArgCheck` and why it's removed?
At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck`
was introduced as a hacky patch. Now, almost all code could be written
as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for
`CmdArgCheck` anymore.
### Why many codes for `signArg == ""` is deleted?
Because in the old code, `signArg` could never be empty string, it's
either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just
dead code.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-04 11:30:43 +09:00
if err := git . NewCommand ( ctx , "commit" ) . AddArguments ( signArgs ... ) . AddOptionValues ( "-m" , message ) .
Run ( & git . RunOpts {
Env : env ,
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
} ) ; err != nil {
log . Error ( "git commit [%s:%s -> %s:%s]: %v\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
return fmt . Errorf ( "git commit [%s:%s -> %s:%s]: %w\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2019-11-10 17:42:51 +09:00
}
return nil
}
2022-06-13 18:37:59 +09:00
func runMergeCommand ( pr * issues_model . PullRequest , mergeStyle repo_model . MergeStyle , cmd * git . Command , tmpBasePath string ) error {
2019-11-10 17:42:51 +09:00
var outbuf , errbuf strings . Builder
2022-04-01 11:55:30 +09:00
if err := cmd . Run ( & git . RunOpts {
Dir : tmpBasePath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 21:47:22 +09:00
} ) ; err != nil {
2019-11-10 17:42:51 +09:00
// Merge will leave a MERGE_HEAD file in the .git folder if there is a conflict
if _ , statErr := os . Stat ( filepath . Join ( tmpBasePath , ".git" , "MERGE_HEAD" ) ) ; statErr == nil {
// We have a merge conflict error
log . Debug ( "MergeConflict [%s:%s -> %s:%s]: %v\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
return models . ErrMergeConflicts {
Style : mergeStyle ,
StdOut : outbuf . String ( ) ,
StdErr : errbuf . String ( ) ,
Err : err ,
}
} else if strings . Contains ( errbuf . String ( ) , "refusing to merge unrelated histories" ) {
log . Debug ( "MergeUnrelatedHistories [%s:%s -> %s:%s]: %v\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
return models . ErrMergeUnrelatedHistories {
Style : mergeStyle ,
StdOut : outbuf . String ( ) ,
StdErr : errbuf . String ( ) ,
Err : err ,
}
}
log . Error ( "git merge [%s:%s -> %s:%s]: %v\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2022-10-25 04:29:17 +09:00
return fmt . Errorf ( "git merge [%s:%s -> %s:%s]: %w\n%s\n%s" , pr . HeadRepo . FullName ( ) , pr . HeadBranch , pr . BaseRepo . FullName ( ) , pr . BaseBranch , err , outbuf . String ( ) , errbuf . String ( ) )
2019-11-10 17:42:51 +09:00
}
return nil
}
2019-11-01 09:30:02 +09:00
var escapedSymbols = regexp . MustCompile ( ` ([*[?! \\]) ` )
2022-01-20 08:26:57 +09:00
func getDiffTree ( ctx context . Context , repoPath , baseBranch , headBranch string ) ( string , error ) {
2019-06-23 02:35:34 +09:00
getDiffTreeFromBranch := func ( repoPath , baseBranch , headBranch string ) ( string , error ) {
var outbuf , errbuf strings . Builder
// Compute the diff-tree for sparse-checkout
2022-10-23 23:44:45 +09:00
if err := git . NewCommand ( ctx , "diff-tree" , "--no-commit-id" , "--name-only" , "-r" , "-z" , "--root" ) . AddDynamicArguments ( baseBranch , headBranch ) .
2022-04-01 11:55:30 +09:00
Run ( & git . RunOpts {
Dir : repoPath ,
Stdout : & outbuf ,
Stderr : & errbuf ,
2022-02-11 21:47:22 +09:00
} ) ; err != nil {
2019-06-23 02:35:34 +09:00
return "" , fmt . Errorf ( "git diff-tree [%s base:%s head:%s]: %s" , repoPath , baseBranch , headBranch , errbuf . String ( ) )
}
return outbuf . String ( ) , nil
}
2019-11-01 09:30:02 +09:00
scanNullTerminatedStrings := func ( data [ ] byte , atEOF bool ) ( advance int , token [ ] byte , err error ) {
if atEOF && len ( data ) == 0 {
return 0 , nil , nil
}
if i := bytes . IndexByte ( data , '\x00' ) ; i >= 0 {
return i + 1 , data [ 0 : i ] , nil
}
if atEOF {
return len ( data ) , data , nil
}
return 0 , nil , nil
}
2019-06-23 02:35:34 +09:00
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 ) )
2019-11-01 09:30:02 +09:00
scanner . Split ( scanNullTerminatedStrings )
2019-06-23 02:35:34 +09:00
for scanner . Scan ( ) {
2019-11-01 09:30:02 +09:00
filepath := scanner . Text ( )
// escape '*', '?', '[', spaces and '!' prefix
filepath = escapedSymbols . ReplaceAllString ( filepath , ` \$1 ` )
// no necessary to escape the first '#' symbol because the first symbol is '/'
fmt . Fprintf ( & out , "/%s\n" , filepath )
2019-06-23 02:35:34 +09:00
}
2019-11-01 09:30:02 +09:00
2019-06-23 02:35:34 +09:00
return out . String ( ) , nil
}
2020-01-11 16:29:34 +09:00
// IsUserAllowedToMerge check if user is allowed to merge PR with given permissions and branch protections
2022-06-13 18:37:59 +09:00
func IsUserAllowedToMerge ( ctx context . Context , pr * issues_model . PullRequest , p access_model . Permission , user * user_model . User ) ( bool , error ) {
2020-04-15 01:29:31 +09:00
if user == nil {
return false , nil
}
2020-01-11 16:29:34 +09:00
2023-01-16 17:00:22 +09:00
pb , err := git_model . GetFirstMatchProtectedBranchRule ( ctx , pr . BaseRepoID , pr . BaseBranch )
2020-01-11 16:29:34 +09:00
if err != nil {
return false , err
}
2023-01-16 17:00:22 +09:00
if ( p . CanWrite ( unit . TypeCode ) && pb == nil ) || ( pb != nil && git_model . IsUserMergeWhitelisted ( ctx , pb , user . ID , p ) ) {
2020-01-11 16:29:34 +09:00
return true , nil
}
return false , nil
}
2022-05-02 08:54:44 +09:00
// CheckPullBranchProtections checks whether the PR is ready to be merged (reviews and status checks)
2022-06-13 18:37:59 +09:00
func CheckPullBranchProtections ( ctx context . Context , pr * issues_model . PullRequest , skipProtectedFilesCheck bool ) ( err error ) {
2022-11-19 17:12:33 +09:00
if err = pr . LoadBaseRepo ( ctx ) ; err != nil {
2022-10-25 04:29:17 +09:00
return fmt . Errorf ( "LoadBaseRepo: %w" , err )
2020-03-03 07:31:55 +09:00
}
2023-01-16 17:00:22 +09:00
pb , err := git_model . GetFirstMatchProtectedBranchRule ( ctx , pr . BaseRepoID , pr . BaseBranch )
if err != nil {
return fmt . Errorf ( "LoadProtectedBranch: %v" , err )
2020-01-11 16:29:34 +09:00
}
2023-01-16 17:00:22 +09:00
if pb == nil {
2020-03-03 07:31:55 +09:00
return nil
2020-01-11 16:29:34 +09:00
}
2022-01-20 08:26:57 +09:00
isPass , err := IsPullCommitStatusPass ( ctx , pr )
2020-01-11 16:29:34 +09:00
if err != nil {
return err
}
if ! isPass {
2022-03-31 23:53:08 +09:00
return models . ErrDisallowedToMerge {
2020-01-11 16:29:34 +09:00
Reason : "Not all required status checks successful" ,
}
}
2023-01-16 17:00:22 +09:00
if ! issues_model . HasEnoughApprovals ( ctx , pb , pr ) {
2022-03-31 23:53:08 +09:00
return models . ErrDisallowedToMerge {
2020-01-11 16:29:34 +09:00
Reason : "Does not have enough approvals" ,
}
}
2023-01-16 17:00:22 +09:00
if issues_model . MergeBlockedByRejectedReview ( ctx , pb , pr ) {
2022-03-31 23:53:08 +09:00
return models . ErrDisallowedToMerge {
2020-01-11 16:29:34 +09:00
Reason : "There are requested changes" ,
}
}
2023-01-16 17:00:22 +09:00
if issues_model . MergeBlockedByOfficialReviewRequests ( ctx , pb , pr ) {
2022-03-31 23:53:08 +09:00
return models . ErrDisallowedToMerge {
2020-11-29 04:30:46 +09:00
Reason : "There are official review requests" ,
}
}
2020-01-11 16:29:34 +09:00
2023-01-16 17:00:22 +09:00
if issues_model . MergeBlockedByOutdatedBranch ( pb , pr ) {
2022-03-31 23:53:08 +09:00
return models . ErrDisallowedToMerge {
2020-04-17 10:00:36 +09:00
Reason : "The head branch is behind the base branch" ,
}
}
2020-10-14 03:50:57 +09:00
if skipProtectedFilesCheck {
return nil
}
2023-01-16 17:00:22 +09:00
if pb . MergeBlockedByProtectedFiles ( pr . ChangedProtectedFiles ) {
2022-03-31 23:53:08 +09:00
return models . ErrDisallowedToMerge {
2020-10-14 03:50:57 +09:00
Reason : "Changed protected files" ,
}
}
2020-01-11 16:29:34 +09:00
return nil
}
2021-03-04 12:41:23 +09:00
// MergedManually mark pr as merged manually
2022-06-13 18:37:59 +09:00
func MergedManually ( pr * issues_model . PullRequest , doer * user_model . User , baseGitRepo * git . Repository , commitID string ) error {
2022-05-05 01:06:23 +09:00
pullWorkingPool . CheckIn ( fmt . Sprint ( pr . ID ) )
defer pullWorkingPool . CheckOut ( fmt . Sprint ( pr . ID ) )
2022-11-13 05:18:50 +09:00
if err := db . WithTx ( db . DefaultContext , func ( ctx context . Context ) error {
2023-01-16 17:00:22 +09:00
if err := pr . LoadBaseRepo ( ctx ) ; err != nil {
return err
}
2022-12-10 11:46:31 +09:00
prUnit , err := pr . BaseRepo . GetUnit ( ctx , unit . TypePullRequests )
2022-05-04 04:46:28 +09:00
if err != nil {
return err
}
prConfig := prUnit . PullRequestsConfig ( )
2021-03-04 12:41:23 +09:00
2022-05-04 04:46:28 +09:00
// Check if merge style is correct and allowed
if ! prConfig . IsMergeStyleAllowed ( repo_model . MergeStyleManuallyMerged ) {
return models . ErrInvalidMergeStyle { ID : pr . BaseRepo . ID , Style : repo_model . MergeStyleManuallyMerged }
}
2021-03-04 12:41:23 +09:00
2022-12-27 22:12:49 +09:00
if len ( commitID ) < git . SHAFullLength {
2021-03-04 12:41:23 +09:00
return fmt . Errorf ( "Wrong commit ID" )
}
2022-05-04 04:46:28 +09:00
commit , err := baseGitRepo . GetCommit ( commitID )
if err != nil {
if git . IsErrNotExist ( err ) {
return fmt . Errorf ( "Wrong commit ID" )
}
return err
}
commitID = commit . ID . String ( )
2021-03-04 12:41:23 +09:00
2022-05-04 04:46:28 +09:00
ok , err := baseGitRepo . IsCommitInBranch ( commitID , pr . BaseBranch )
if err != nil {
return err
}
if ! ok {
return fmt . Errorf ( "Wrong commit ID" )
}
2021-03-04 12:41:23 +09:00
2022-05-04 04:46:28 +09:00
pr . MergedCommitID = commitID
pr . MergedUnix = timeutil . TimeStamp ( commit . Author . When . Unix ( ) )
2022-06-13 18:37:59 +09:00
pr . Status = issues_model . PullRequestStatusManuallyMerged
2022-05-04 04:46:28 +09:00
pr . Merger = doer
pr . MergerID = doer . ID
2022-06-20 19:02:49 +09:00
var merged bool
2022-05-04 04:46:28 +09:00
if merged , err = pr . SetMerged ( ctx ) ; err != nil {
return err
} else if ! merged {
return fmt . Errorf ( "SetMerged failed" )
}
return nil
} ) ; err != nil {
return err
2021-03-04 12:41:23 +09:00
}
2022-11-19 17:12:33 +09:00
notification . NotifyMergePullRequest ( baseGitRepo . Ctx , doer , pr )
2022-05-04 04:46:28 +09:00
log . Info ( "manuallyMerged[%d]: Marked as manually merged into %s/%s by commit id: %s" , pr . ID , pr . BaseRepo . Name , pr . BaseBranch , commitID )
2021-03-04 12:41:23 +09:00
return nil
}