2019-06-22 23:05:34 +05:30
// Copyright 2019 The Gitea Authors.
// All rights reserved.
2022-11-27 23:50:29 +05:30
// SPDX-License-Identifier: MIT
2019-06-22 23:05:34 +05:30
package pull
import (
2022-01-20 04:56:57 +05:30
"context"
2019-06-22 23:05:34 +05:30
"fmt"
"os"
"path/filepath"
2019-11-01 06:00:02 +05:30
"regexp"
2022-05-08 18:02:45 +05:30
"strconv"
2019-06-22 23:05:34 +05:30
"strings"
"code.gitea.io/gitea/models"
2022-05-04 01:16:28 +05:30
"code.gitea.io/gitea/models/db"
2022-06-12 21:21:54 +05:30
git_model "code.gitea.io/gitea/models/git"
2022-06-13 15:07:59 +05:30
issues_model "code.gitea.io/gitea/models/issues"
2022-05-11 15:39:36 +05:30
access_model "code.gitea.io/gitea/models/perm/access"
2022-05-07 22:35:52 +05:30
pull_model "code.gitea.io/gitea/models/pull"
2021-12-10 06:57:50 +05:30
repo_model "code.gitea.io/gitea/models/repo"
2021-11-10 01:27:58 +05:30
"code.gitea.io/gitea/models/unit"
2021-11-24 15:19:20 +05:30
user_model "code.gitea.io/gitea/models/user"
2019-06-22 23:05:34 +05:30
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
2019-11-05 16:34:08 +05:30
"code.gitea.io/gitea/modules/notification"
2019-11-18 18:43:07 +05:30
"code.gitea.io/gitea/modules/references"
2022-05-08 22:16:32 +05:30
repo_module "code.gitea.io/gitea/modules/repository"
2019-06-22 23:05:34 +05:30
"code.gitea.io/gitea/modules/setting"
2019-08-15 20:16:21 +05:30
"code.gitea.io/gitea/modules/timeutil"
2019-11-18 18:43:07 +05:30
issue_service "code.gitea.io/gitea/services/issue"
2019-06-22 23:05:34 +05:30
)
2023-05-22 06:31:46 +05:30
// getMergeMessage composes the message used when merging a pull request.
func getMergeMessage ( ctx context . Context , baseGitRepo * git . Repository , pr * issues_model . PullRequest , mergeStyle repo_model . MergeStyle , extraVars map [ string ] string ) ( message , body string , err error ) {
2022-11-19 13:42:33 +05:30
if err := pr . LoadBaseRepo ( ctx ) ; err != nil {
2022-12-29 18:10:20 +05:30
return "" , "" , err
2022-05-08 18:02:45 +05:30
}
2023-03-08 01:37:35 +05:30
if err := pr . LoadHeadRepo ( ctx ) ; err != nil {
return "" , "" , err
2022-05-08 18:02:45 +05:30
}
2022-11-19 13:42:33 +05:30
if err := pr . LoadIssue ( ctx ) ; err != nil {
2022-12-29 18:10:20 +05:30
return "" , "" , err
2022-05-08 18:02:45 +05:30
}
2022-12-10 08:16:31 +05:30
isExternalTracker := pr . BaseRepo . UnitEnabled ( ctx , unit . TypeExternalTracker )
2022-05-08 18:02:45 +05:30
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 18:10:20 +05:30
return "" , "" , err
2022-05-08 18:02:45 +05:30
}
templateContent , err := commit . GetFileContent ( templateFilepath , setting . Repository . PullRequest . DefaultMergeMessageSize )
if err != nil {
if ! git . IsErrNotExist ( err ) {
2022-12-29 18:10:20 +05:30
return "" , "" , err
2022-05-08 18:02:45 +05:30
}
} 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
}
2023-05-22 06:31:46 +05:30
for extraKey , extraValue := range extraVars {
vars [ extraKey ] = extraValue
}
2022-11-19 13:42:33 +05:30
refs , err := pr . ResolveCrossReferences ( ctx )
2022-05-08 18:02:45 +05:30
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 {
2023-02-09 08:17:52 +05:30
if err := ref . LoadIssue ( ctx ) ; err != nil {
return "" , "" , err
}
2022-05-08 18:02:45 +05:30
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 18:10:20 +05:30
message , body = expandDefaultMergeMessage ( templateContent , vars )
return message , body , nil
2022-05-08 18:02:45 +05:30
}
}
2023-07-10 13:42:50 +05:30
if mergeStyle == repo_model . MergeStyleRebase {
// for fast-forward rebase, do not amend the last commit if there is no template
return "" , "" , nil
}
2022-05-08 18:02:45 +05:30
// Squash merge has a different from other styles.
if mergeStyle == repo_model . MergeStyleSquash {
2022-12-29 18:10:20 +05:30
return fmt . Sprintf ( "%s (%s%d)" , pr . Issue . Title , issueReference , pr . Issue . Index ) , "" , nil
2022-05-08 18:02:45 +05:30
}
if pr . BaseRepoID == pr . HeadRepoID {
2022-12-29 18:10:20 +05:30
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 18:02:45 +05:30
}
if pr . HeadRepo == nil {
2022-12-29 18:10:20 +05:30
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 18:02:45 +05:30
}
2022-12-29 18:10:20 +05:30
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 18:02:45 +05:30
}
2023-05-22 06:31:46 +05:30
// GetDefaultMergeMessage returns default message used when merging pull request
func GetDefaultMergeMessage ( ctx context . Context , baseGitRepo * git . Repository , pr * issues_model . PullRequest , mergeStyle repo_model . MergeStyle ) ( message , body string , err error ) {
return getMergeMessage ( ctx , baseGitRepo , pr , mergeStyle , nil )
}
2019-06-22 23:05:34 +05:30
// Merge merges pull request to base repository.
2020-01-11 12:59:34 +05:30
// Caller should check PR is ready to be merged (review and status checks)
2022-11-03 21:19:00 +05:30
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 {
2023-03-08 01:37:35 +05:30
if err := pr . LoadBaseRepo ( ctx ) ; err != nil {
log . Error ( "Unable to load base repo: %v" , err )
return fmt . Errorf ( "unable to load base repo: %w" , err )
} else if err := pr . LoadHeadRepo ( ctx ) ; err != nil {
log . Error ( "Unable to load head repo: %v" , err )
return fmt . Errorf ( "unable to load head repo: %w" , err )
2019-06-22 23:05:34 +05:30
}
2022-05-04 21:36:23 +05:30
pullWorkingPool . CheckIn ( fmt . Sprint ( pr . ID ) )
defer pullWorkingPool . CheckOut ( fmt . Sprint ( pr . ID ) )
2022-05-07 22:35:52 +05:30
// Removing an auto merge pull and ignore if not exist
2023-03-08 01:37:35 +05:30
// FIXME: is this the correct point to do this? Shouldn't this be after IsMergeStyleAllowed?
2022-12-10 08:16:31 +05:30
if err := pull_model . DeleteScheduledAutoMerge ( ctx , pr . ID ) ; err != nil && ! db . IsErrNotExist ( err ) {
2022-05-07 22:35:52 +05:30
return err
}
2022-12-10 08:16:31 +05:30
prUnit , err := pr . BaseRepo . GetUnit ( ctx , unit . TypePullRequests )
2019-06-22 23:05:34 +05:30
if err != nil {
2021-11-10 01:27:58 +05:30
log . Error ( "pr.BaseRepo.GetUnit(unit.TypePullRequests): %v" , err )
2019-06-22 23:05:34 +05:30
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 07:17:45 +05:30
go AddTestPullRequestTask ( doer , pr . BaseRepo . ID , pr . BaseBranch , false , "" , "" )
2019-06-22 23:05:34 +05:30
} ( )
2023-07-22 19:44:27 +05:30
pr . MergedCommitID , err = doMergeAndPush ( ctx , pr , doer , mergeStyle , expectedHeadCommitID , message )
2020-01-17 11:33:40 +05:30
if err != nil {
2020-02-10 04:39:31 +05:30
return err
2020-01-17 11:33:40 +05:30
}
pr . MergedUnix = timeutil . TimeStampNow ( )
pr . Merger = doer
pr . MergerID = doer . ID
2023-07-22 19:44:27 +05:30
if _ , err := pr . SetMerged ( ctx ) ; err != nil {
2023-03-08 01:37:35 +05:30
log . Error ( "SetMerged %-v: %v" , pr , err )
2020-01-17 11:33:40 +05:30
}
2023-07-22 19:44:27 +05:30
if err := pr . LoadIssue ( ctx ) ; err != nil {
2023-03-08 01:37:35 +05:30
log . Error ( "LoadIssue %-v: %v" , pr , err )
2020-02-10 04:39:31 +05:30
}
2023-07-22 19:44:27 +05:30
if err := pr . Issue . LoadRepo ( ctx ) ; err != nil {
2023-03-08 01:37:35 +05:30
log . Error ( "pr.Issue.LoadRepo %-v: %v" , pr , err )
2020-02-10 04:39:31 +05:30
}
2023-07-22 19:44:27 +05:30
if err := pr . Issue . Repo . LoadOwner ( ctx ) ; err != nil {
2023-03-08 01:37:35 +05:30
log . Error ( "LoadOwner for %-v: %v" , pr , err )
2020-02-10 04:39:31 +05:30
}
2022-11-03 21:19:00 +05:30
if wasAutoMerged {
2023-07-22 19:44:27 +05:30
notification . NotifyAutoMergePullRequest ( ctx , doer , pr )
2022-11-03 21:19:00 +05:30
} else {
2023-07-22 19:44:27 +05:30
notification . NotifyMergePullRequest ( ctx , doer , pr )
2022-11-03 21:19:00 +05:30
}
2020-01-17 11:33:40 +05:30
// Reset cached commit count
cache . Remove ( pr . Issue . Repo . GetCommitsCountCacheKey ( pr . BaseBranch , true ) )
// Resolve cross references
2023-07-22 19:44:27 +05:30
refs , err := pr . ResolveCrossReferences ( ctx )
2020-01-17 11:33:40 +05:30
if err != nil {
log . Error ( "ResolveCrossReferences: %v" , err )
return nil
}
for _ , ref := range refs {
2023-07-22 19:44:27 +05:30
if err = ref . LoadIssue ( ctx ) ; err != nil {
2020-01-17 11:33:40 +05:30
return err
}
2023-07-22 19:44:27 +05:30
if err = ref . Issue . LoadRepo ( ctx ) ; err != nil {
2020-01-17 11:33:40 +05:30
return err
}
2021-04-09 13:10:34 +05:30
close := ref . RefAction == references . XRefActionCloses
2020-01-17 11:33:40 +05:30
if close != ref . Issue . IsClosed {
2023-07-22 19:44:27 +05:30
if err = issue_service . ChangeStatus ( ctx , ref . Issue , doer , pr . MergedCommitID , close ) ; err != nil {
2022-01-19 04:56:42 +05:30
// Allow ErrDependenciesLeft
2022-06-13 15:07:59 +05:30
if ! issues_model . IsErrDependenciesLeft ( err ) {
2022-01-19 04:56:42 +05:30
return err
}
2020-01-17 11:33:40 +05:30
}
}
}
return nil
}
2023-03-08 01:37:35 +05:30
// doMergeAndPush performs the merge operation without changing any pull information in database and pushes it up to the base repository
func doMergeAndPush ( ctx context . Context , pr * issues_model . PullRequest , doer * user_model . User , mergeStyle repo_model . MergeStyle , expectedHeadCommitID , message string ) ( string , error ) {
2019-06-22 23:05:34 +05:30
// Clone base repo.
2023-03-08 01:37:35 +05:30
mergeCtx , cancel , err := createTemporaryRepoForMerge ( ctx , pr , doer , expectedHeadCommitID )
2019-06-22 23:05:34 +05:30
if err != nil {
2020-02-10 04:39:31 +05:30
return "" , err
2019-06-22 23:05:34 +05:30
}
2023-03-08 01:37:35 +05:30
defer cancel ( )
2019-10-16 19:12:42 +05:30
2019-06-22 23:05:34 +05:30
// Merge commits.
switch mergeStyle {
2021-12-10 06:57:50 +05:30
case repo_model . MergeStyleMerge :
2023-03-08 01:37:35 +05:30
if err := doMergeStyleMerge ( mergeCtx , message ) ; err != nil {
2020-02-10 04:39:31 +05:30
return "" , err
2019-06-22 23:05:34 +05:30
}
2023-03-08 01:37:35 +05:30
case repo_model . MergeStyleRebase , repo_model . MergeStyleRebaseMerge :
if err := doMergeStyleRebase ( mergeCtx , mergeStyle , message ) ; err != nil {
2020-02-10 04:39:31 +05:30
return "" , err
2019-06-22 23:05:34 +05:30
}
2021-12-10 06:57:50 +05:30
case repo_model . MergeStyleSquash :
2023-03-08 01:37:35 +05:30
if err := doMergeStyleSquash ( mergeCtx , message ) ; err != nil {
2020-02-10 04:39:31 +05:30
return "" , err
2019-06-22 23:05:34 +05:30
}
default :
2020-02-10 04:39:31 +05:30
return "" , models . ErrInvalidMergeStyle { ID : pr . BaseRepo . ID , Style : mergeStyle }
2019-06-22 23:05:34 +05:30
}
// OK we should cache our current head and origin/headbranch
2023-03-08 01:37:35 +05:30
mergeHeadSHA , err := git . GetFullCommitID ( ctx , mergeCtx . tmpBasePath , "HEAD" )
2019-06-22 23:05:34 +05:30
if err != nil {
2022-10-25 00:59:17 +05:30
return "" , fmt . Errorf ( "Failed to get full commit id for HEAD: %w" , err )
2019-06-22 23:05:34 +05:30
}
2023-03-08 01:37:35 +05:30
mergeBaseSHA , err := git . GetFullCommitID ( ctx , mergeCtx . tmpBasePath , "original_" + baseBranch )
2019-06-22 23:05:34 +05:30
if err != nil {
2022-10-25 00:59:17 +05:30
return "" , fmt . Errorf ( "Failed to get full commit id for origin/%s: %w" , pr . BaseBranch , err )
2020-02-10 04:39:31 +05:30
}
2023-03-08 01:37:35 +05:30
mergeCommitID , err := git . GetFullCommitID ( ctx , mergeCtx . tmpBasePath , baseBranch )
2020-02-10 04:39:31 +05:30
if err != nil {
2022-10-25 00:59:17 +05:30
return "" , fmt . Errorf ( "Failed to get full commit id for the new merge: %w" , err )
2019-06-22 23:05:34 +05:30
}
// 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 {
2023-03-08 01:37:35 +05:30
if err := LFSPush ( ctx , mergeCtx . tmpBasePath , mergeHeadSHA , mergeBaseSHA , pr ) ; err != nil {
2020-02-10 04:39:31 +05:30
return "" , err
2019-06-22 23:05:34 +05:30
}
}
2021-11-24 15:19:20 +05:30
var headUser * user_model . User
2023-02-18 17:41:03 +05:30
err = pr . HeadRepo . LoadOwner ( ctx )
2019-07-01 06:48:13 +05:30
if err != nil {
2021-11-24 15:19:20 +05:30
if ! user_model . IsErrUserNotExist ( err ) {
2023-03-08 01:37:35 +05:30
log . Error ( "Can't find user: %d for head repository in %-v: %v" , pr . HeadRepo . OwnerID , pr , err )
2020-02-10 04:39:31 +05:30
return "" , err
2019-07-01 06:48:13 +05:30
}
2023-03-08 01:37:35 +05:30
log . Warn ( "Can't find user: %d for head repository in %-v - defaulting to doer: %s - %v" , pr . HeadRepo . OwnerID , pr , doer . Name , err )
2019-07-01 06:48:13 +05:30
headUser = doer
2019-10-18 16:43:31 +05:30
} else {
headUser = pr . HeadRepo . Owner
2019-07-01 06:48:13 +05:30
}
2023-03-08 01:37:35 +05:30
mergeCtx . env = repo_module . FullPushingEnvironment (
headUser ,
doer ,
pr . BaseRepo ,
pr . BaseRepo . Name ,
pr . ID ,
)
pushCmd := git . NewCommand ( ctx , "push" , "origin" ) . AddDynamicArguments ( baseBranch + ":" + git . BranchPrefix + pr . BaseBranch )
2021-08-31 19:33:45 +05:30
2019-06-22 23:05:34 +05:30
// Push back to upstream.
2022-05-04 01:16:28 +05:30
// TODO: this cause an api call to "/api/internal/hook/post-receive/...",
// that prevents us from doint the whole merge in one db transaction
2023-03-08 01:37:35 +05:30
if err := pushCmd . Run ( mergeCtx . RunOpts ( ) ) ; err != nil {
if strings . Contains ( mergeCtx . errbuf . String ( ) , "non-fast-forward" ) {
2020-03-28 09:43:18 +05:30
return "" , & git . ErrPushOutOfDate {
2023-03-08 01:37:35 +05:30
StdOut : mergeCtx . outbuf . String ( ) ,
StdErr : mergeCtx . errbuf . String ( ) ,
2019-11-10 14:12:51 +05:30
Err : err ,
}
2023-03-08 01:37:35 +05:30
} else if strings . Contains ( mergeCtx . errbuf . String ( ) , "! [remote rejected]" ) {
2020-03-28 09:43:18 +05:30
err := & git . ErrPushRejected {
2023-03-08 01:37:35 +05:30
StdOut : mergeCtx . outbuf . String ( ) ,
StdErr : mergeCtx . errbuf . String ( ) ,
2020-02-22 18:38:48 +05:30
Err : err ,
}
err . GenerateMessage ( )
return "" , err
2019-11-10 14:12:51 +05:30
}
2023-03-08 01:37:35 +05:30
return "" , fmt . Errorf ( "git push: %s" , mergeCtx . errbuf . String ( ) )
2019-06-22 23:05:34 +05:30
}
2023-03-08 01:37:35 +05:30
mergeCtx . outbuf . Reset ( )
mergeCtx . errbuf . Reset ( )
2019-06-22 23:05:34 +05:30
2020-02-10 04:39:31 +05:30
return mergeCommitID , nil
2019-06-22 23:05:34 +05:30
}
2023-03-08 01:37:35 +05:30
func commitAndSignNoAuthor ( ctx * mergeContext , message string ) error {
2023-03-09 21:18:52 +05:30
cmdCommit := git . NewCommand ( ctx , "commit" ) . AddOptionFormat ( "--message=%s" , message )
if ctx . signKeyID == "" {
cmdCommit . AddArguments ( "--no-gpg-sign" )
} else {
cmdCommit . AddOptionFormat ( "-S%s" , ctx . signKeyID )
}
if err := cmdCommit . Run ( ctx . RunOpts ( ) ) ; err != nil {
2023-03-08 01:37:35 +05:30
log . Error ( "git commit %-v: %v\n%s\n%s" , ctx . pr , err , ctx . outbuf . String ( ) , ctx . errbuf . String ( ) )
return fmt . Errorf ( "git commit %v: %w\n%s\n%s" , ctx . pr , err , ctx . outbuf . String ( ) , ctx . errbuf . String ( ) )
2019-11-10 14:12:51 +05:30
}
return nil
}
2023-03-08 01:37:35 +05:30
func runMergeCommand ( ctx * mergeContext , mergeStyle repo_model . MergeStyle , cmd * git . Command ) error {
if err := cmd . Run ( ctx . RunOpts ( ) ) ; err != nil {
2019-11-10 14:12:51 +05:30
// Merge will leave a MERGE_HEAD file in the .git folder if there is a conflict
2023-03-08 01:37:35 +05:30
if _ , statErr := os . Stat ( filepath . Join ( ctx . tmpBasePath , ".git" , "MERGE_HEAD" ) ) ; statErr == nil {
2019-11-10 14:12:51 +05:30
// We have a merge conflict error
2023-03-08 01:37:35 +05:30
log . Debug ( "MergeConflict %-v: %v\n%s\n%s" , ctx . pr , err , ctx . outbuf . String ( ) , ctx . errbuf . String ( ) )
2019-11-10 14:12:51 +05:30
return models . ErrMergeConflicts {
Style : mergeStyle ,
2023-03-08 01:37:35 +05:30
StdOut : ctx . outbuf . String ( ) ,
StdErr : ctx . errbuf . String ( ) ,
2019-11-10 14:12:51 +05:30
Err : err ,
}
2023-03-08 01:37:35 +05:30
} else if strings . Contains ( ctx . errbuf . String ( ) , "refusing to merge unrelated histories" ) {
log . Debug ( "MergeUnrelatedHistories %-v: %v\n%s\n%s" , ctx . pr , err , ctx . outbuf . String ( ) , ctx . errbuf . String ( ) )
2019-11-10 14:12:51 +05:30
return models . ErrMergeUnrelatedHistories {
Style : mergeStyle ,
2023-03-08 01:37:35 +05:30
StdOut : ctx . outbuf . String ( ) ,
StdErr : ctx . errbuf . String ( ) ,
2019-11-10 14:12:51 +05:30
Err : err ,
}
}
2023-03-08 01:37:35 +05:30
log . Error ( "git merge %-v: %v\n%s\n%s" , ctx . pr , err , ctx . outbuf . String ( ) , ctx . errbuf . String ( ) )
return fmt . Errorf ( "git merge %v: %w\n%s\n%s" , ctx . pr , err , ctx . outbuf . String ( ) , ctx . errbuf . String ( ) )
2019-11-10 14:12:51 +05:30
}
2023-03-08 01:37:35 +05:30
ctx . outbuf . Reset ( )
ctx . errbuf . Reset ( )
2019-11-10 14:12:51 +05:30
return nil
}
2019-11-01 06:00:02 +05:30
var escapedSymbols = regexp . MustCompile ( ` ([*[?! \\]) ` )
2020-01-11 12:59:34 +05:30
// IsUserAllowedToMerge check if user is allowed to merge PR with given permissions and branch protections
2022-06-13 15:07:59 +05:30
func IsUserAllowedToMerge ( ctx context . Context , pr * issues_model . PullRequest , p access_model . Permission , user * user_model . User ) ( bool , error ) {
2020-04-14 21:59:31 +05:30
if user == nil {
return false , nil
}
2020-01-11 12:59:34 +05:30
2023-01-16 13:30:22 +05:30
pb , err := git_model . GetFirstMatchProtectedBranchRule ( ctx , pr . BaseRepoID , pr . BaseBranch )
2020-01-11 12:59:34 +05:30
if err != nil {
return false , err
}
2023-01-16 13:30:22 +05:30
if ( p . CanWrite ( unit . TypeCode ) && pb == nil ) || ( pb != nil && git_model . IsUserMergeWhitelisted ( ctx , pb , user . ID , p ) ) {
2020-01-11 12:59:34 +05:30
return true , nil
}
return false , nil
}
2022-05-02 05:24:44 +05:30
// CheckPullBranchProtections checks whether the PR is ready to be merged (reviews and status checks)
2022-06-13 15:07:59 +05:30
func CheckPullBranchProtections ( ctx context . Context , pr * issues_model . PullRequest , skipProtectedFilesCheck bool ) ( err error ) {
2022-11-19 13:42:33 +05:30
if err = pr . LoadBaseRepo ( ctx ) ; err != nil {
2022-10-25 00:59:17 +05:30
return fmt . Errorf ( "LoadBaseRepo: %w" , err )
2020-03-03 04:01:55 +05:30
}
2023-01-16 13:30:22 +05:30
pb , err := git_model . GetFirstMatchProtectedBranchRule ( ctx , pr . BaseRepoID , pr . BaseBranch )
if err != nil {
return fmt . Errorf ( "LoadProtectedBranch: %v" , err )
2020-01-11 12:59:34 +05:30
}
2023-01-16 13:30:22 +05:30
if pb == nil {
2020-03-03 04:01:55 +05:30
return nil
2020-01-11 12:59:34 +05:30
}
2022-01-20 04:56:57 +05:30
isPass , err := IsPullCommitStatusPass ( ctx , pr )
2020-01-11 12:59:34 +05:30
if err != nil {
return err
}
if ! isPass {
2022-03-31 20:23:08 +05:30
return models . ErrDisallowedToMerge {
2020-01-11 12:59:34 +05:30
Reason : "Not all required status checks successful" ,
}
}
2023-01-16 13:30:22 +05:30
if ! issues_model . HasEnoughApprovals ( ctx , pb , pr ) {
2022-03-31 20:23:08 +05:30
return models . ErrDisallowedToMerge {
2020-01-11 12:59:34 +05:30
Reason : "Does not have enough approvals" ,
}
}
2023-01-16 13:30:22 +05:30
if issues_model . MergeBlockedByRejectedReview ( ctx , pb , pr ) {
2022-03-31 20:23:08 +05:30
return models . ErrDisallowedToMerge {
2020-01-11 12:59:34 +05:30
Reason : "There are requested changes" ,
}
}
2023-01-16 13:30:22 +05:30
if issues_model . MergeBlockedByOfficialReviewRequests ( ctx , pb , pr ) {
2022-03-31 20:23:08 +05:30
return models . ErrDisallowedToMerge {
2020-11-29 01:00:46 +05:30
Reason : "There are official review requests" ,
}
}
2020-01-11 12:59:34 +05:30
2023-01-16 13:30:22 +05:30
if issues_model . MergeBlockedByOutdatedBranch ( pb , pr ) {
2022-03-31 20:23:08 +05:30
return models . ErrDisallowedToMerge {
2020-04-17 06:30:36 +05:30
Reason : "The head branch is behind the base branch" ,
}
}
2020-10-14 00:20:57 +05:30
if skipProtectedFilesCheck {
return nil
}
2023-01-16 13:30:22 +05:30
if pb . MergeBlockedByProtectedFiles ( pr . ChangedProtectedFiles ) {
2022-03-31 20:23:08 +05:30
return models . ErrDisallowedToMerge {
2020-10-14 00:20:57 +05:30
Reason : "Changed protected files" ,
}
}
2020-01-11 12:59:34 +05:30
return nil
}
2021-03-04 09:11:23 +05:30
// MergedManually mark pr as merged manually
2022-06-13 15:07:59 +05:30
func MergedManually ( pr * issues_model . PullRequest , doer * user_model . User , baseGitRepo * git . Repository , commitID string ) error {
2022-05-04 21:36:23 +05:30
pullWorkingPool . CheckIn ( fmt . Sprint ( pr . ID ) )
defer pullWorkingPool . CheckOut ( fmt . Sprint ( pr . ID ) )
2022-11-13 01:48:50 +05:30
if err := db . WithTx ( db . DefaultContext , func ( ctx context . Context ) error {
2023-01-16 13:30:22 +05:30
if err := pr . LoadBaseRepo ( ctx ) ; err != nil {
return err
}
2022-12-10 08:16:31 +05:30
prUnit , err := pr . BaseRepo . GetUnit ( ctx , unit . TypePullRequests )
2022-05-04 01:16:28 +05:30
if err != nil {
return err
}
prConfig := prUnit . PullRequestsConfig ( )
2021-03-04 09:11:23 +05:30
2022-05-04 01:16:28 +05:30
// 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 09:11:23 +05:30
2022-12-27 18:42:49 +05:30
if len ( commitID ) < git . SHAFullLength {
2021-03-04 09:11:23 +05:30
return fmt . Errorf ( "Wrong commit ID" )
}
2022-05-04 01:16:28 +05:30
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 09:11:23 +05:30
2022-05-04 01:16:28 +05:30
ok , err := baseGitRepo . IsCommitInBranch ( commitID , pr . BaseBranch )
if err != nil {
return err
}
if ! ok {
return fmt . Errorf ( "Wrong commit ID" )
}
2021-03-04 09:11:23 +05:30
2022-05-04 01:16:28 +05:30
pr . MergedCommitID = commitID
pr . MergedUnix = timeutil . TimeStamp ( commit . Author . When . Unix ( ) )
2022-06-13 15:07:59 +05:30
pr . Status = issues_model . PullRequestStatusManuallyMerged
2022-05-04 01:16:28 +05:30
pr . Merger = doer
pr . MergerID = doer . ID
2022-06-20 15:32:49 +05:30
var merged bool
2022-05-04 01:16:28 +05:30
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 09:11:23 +05:30
}
2022-11-19 13:42:33 +05:30
notification . NotifyMergePullRequest ( baseGitRepo . Ctx , doer , pr )
2022-05-04 01:16:28 +05:30
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 09:11:23 +05:30
return nil
}