@ -9,7 +9,6 @@ import (
"context"
"context"
"errors"
"errors"
"fmt"
"fmt"
"os"
"strconv"
"strconv"
"strings"
"strings"
@ -27,7 +26,6 @@ import (
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
asymkey_service "code.gitea.io/gitea/services/asymkey"
asymkey_service "code.gitea.io/gitea/services/asymkey"
)
)
@ -50,14 +48,14 @@ func AddToTaskQueue(pr *issues_model.PullRequest) {
pr . Status = issues_model . PullRequestStatusChecking
pr . Status = issues_model . PullRequestStatusChecking
err := pr . UpdateColsIfNotMerged ( "status" )
err := pr . UpdateColsIfNotMerged ( "status" )
if err != nil {
if err != nil {
log . Error ( "AddToTaskQueue.UpdateCols[%d] .(add to queue): %v" , pr . ID , err )
log . Error ( "AddToTaskQueue(%-v) .UpdateCols.(add to queue): %v" , pr , err )
} else {
} else {
log . Trace ( "Adding PR ID: %d to the test pull requests queue" , pr . ID )
log . Trace ( "Adding %-v to the test pull requests queue" , pr )
}
}
return err
return err
} )
} )
if err != nil && err != queue . ErrAlreadyInQueue {
if err != nil && err != queue . ErrAlreadyInQueue {
log . Error ( "Error adding prID %d to the test pull requests queue: %v" , pr . ID , err )
log . Error ( "Error adding %-v to the test pull requests queue: %v" , pr , err )
}
}
}
}
@ -69,12 +67,14 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce
}
}
if err := pr . LoadIssueCtx ( ctx ) ; err != nil {
if err := pr . LoadIssueCtx ( ctx ) ; err != nil {
log . Error ( "Unable to load issue[%d] for %-v: %v" , pr . IssueID , pr , err )
return err
return err
} else if pr . Issue . IsClosed {
} else if pr . Issue . IsClosed {
return ErrIsClosed
return ErrIsClosed
}
}
if allowedMerge , err := IsUserAllowedToMerge ( ctx , pr , * perm , doer ) ; err != nil {
if allowedMerge , err := IsUserAllowedToMerge ( ctx , pr , * perm , doer ) ; err != nil {
log . Error ( "Error whilst checking if %-v is allowed to merge %-v: %v" , doer , pr , err )
return err
return err
} else if ! allowedMerge {
} else if ! allowedMerge {
return ErrUserNotAllowedToMerge
return ErrUserNotAllowedToMerge
@ -98,15 +98,19 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce
}
}
if err := CheckPullBranchProtections ( ctx , pr , false ) ; err != nil {
if err := CheckPullBranchProtections ( ctx , pr , false ) ; err != nil {
if models . IsErrDisallowedToMerge ( err ) {
if ! models . IsErrDisallowedToMerge ( err ) {
if force {
log . Error ( "Error whilst checking pull branch protection for %-v: %v" , pr , err )
if isRepoAdmin , err2 := access_model . IsUserRepoAdmin ( ctx , pr . BaseRepo , doer ) ; err2 != nil {
return err2
} else if ! isRepoAdmin {
return err
return err
}
}
if ! force {
return err
}
}
} else {
if isRepoAdmin , err2 := access_model . IsUserRepoAdmin ( ctx , pr . BaseRepo , doer ) ; err2 != nil {
log . Error ( "Unable to check if %-v is a repo admin in %-v: %v" , doer , pr . BaseRepo , err2 )
return err2
} else if ! isRepoAdmin {
return err
return err
}
}
}
}
@ -143,7 +147,7 @@ func isSignedIfRequired(ctx context.Context, pr *issues_model.PullRequest, doer
// checkAndUpdateStatus checks if pull request is possible to leaving checking status,
// checkAndUpdateStatus checks if pull request is possible to leaving checking status,
// and set to be either conflict or mergeable.
// and set to be either conflict or mergeable.
func checkAndUpdateStatus ( pr * issues_model . PullRequest ) {
func checkAndUpdateStatus ( pr * issues_model . PullRequest ) {
// Status is not changed to conflict means mergeable.
// If status has not been changed to conflict by testPatch then we are mergeable
if pr . Status == issues_model . PullRequestStatusChecking {
if pr . Status == issues_model . PullRequestStatusChecking {
pr . Status = issues_model . PullRequestStatusMergeable
pr . Status = issues_model . PullRequestStatusMergeable
}
}
@ -151,79 +155,69 @@ func checkAndUpdateStatus(pr *issues_model.PullRequest) {
// Make sure there is no waiting test to process before leaving the checking status.
// Make sure there is no waiting test to process before leaving the checking status.
has , err := prPatchCheckerQueue . Has ( strconv . FormatInt ( pr . ID , 10 ) )
has , err := prPatchCheckerQueue . Has ( strconv . FormatInt ( pr . ID , 10 ) )
if err != nil {
if err != nil {
log . Error ( "Unable to check if the queue is waiting to reprocess pr.ID %d . Error: %v" , pr . ID , err )
log . Error ( "Unable to check if the queue is waiting to reprocess %-v . Error: %v" , pr , err )
}
}
if ! has {
if has {
if err := pr . UpdateColsIfNotMerged ( "merge_base" , "status" , "conflicted_files" , "changed_protected_files" ) ; err != nil {
log . Trace ( "Not updating status for %-v as it is due to be rechecked" , pr )
log . Error ( "Update[%d]: %v" , pr . ID , err )
return
}
}
if err := pr . UpdateColsIfNotMerged ( "merge_base" , "status" , "conflicted_files" , "changed_protected_files" ) ; err != nil {
log . Error ( "Update[%-v]: %v" , pr , err )
}
}
}
}
// getMergeCommit checks if a pull request got merged
// getMergeCommit checks if a pull request has been merged
// Returns the git.Commit of the pull request if merged
// Returns the git.Commit of the pull request if merged
func getMergeCommit ( ctx context . Context , pr * issues_model . PullRequest ) ( * git . Commit , error ) {
func getMergeCommit ( ctx context . Context , pr * issues_model . PullRequest ) ( * git . Commit , error ) {
if pr . BaseRepo == nil {
if err := pr . LoadBaseRepoCtx ( ctx ) ; err != nil {
var err error
return nil , fmt . Errorf ( "unable to load base repo for %s: %w" , pr , err )
pr . BaseRepo , err = repo_model . GetRepositoryByID ( pr . BaseRepoID )
if err != nil {
return nil , fmt . Errorf ( "GetRepositoryByID: %w" , err )
}
}
indexTmpPath , err := os . MkdirTemp ( os . TempDir ( ) , "gitea-" + pr . BaseRepo . Name )
if err != nil {
return nil , fmt . Errorf ( "Failed to create temp dir for repository %s: %w" , pr . BaseRepo . RepoPath ( ) , err )
}
defer func ( ) {
if err := util . RemoveAll ( indexTmpPath ) ; err != nil {
log . Warn ( "Unable to remove temporary index path: %s: Error: %v" , indexTmpPath , err )
}
}
} ( )
headFile := pr . GetGitRefName ( )
prHeadRef := pr . GetGitRefName ( )
// Check if a pull request is merged into BaseBranch
// Check if the pull request is merged into BaseBranch
_ , _ , err = git . NewCommand ( ctx , "merge-base" , "--is-ancestor" ) . AddDynamicArguments ( headFile , pr . BaseBranch ) .
if _ , _ , err := git . NewCommand ( ctx , "merge-base" , "--is-ancestor" ) .
RunStdString ( & git . RunOpts { Dir : pr . BaseRepo . RepoPath ( ) , Env : [ ] string { "GIT_INDEX_FILE=" + indexTmpPath , "GIT_DIR=" + pr . BaseRepo . RepoPath ( ) } } )
AddDynamicArguments ( prHeadRef , pr . BaseBranch ) .
if err != nil {
RunStdString ( & git . RunOpts { Dir : pr . BaseRepo . RepoPath ( ) } ) ; err != nil {
// Errors are signaled by a non-zero status that is not 1
if strings . Contains ( err . Error ( ) , "exit status 1" ) {
if strings . Contains ( err . Error ( ) , "exit status 1" ) {
// prHeadRef is not an ancestor of the base branch
return nil , nil
return nil , nil
}
}
return nil , fmt . Errorf ( "git merge-base --is-ancestor: %w" , err )
// Errors are signaled by a non-zero status that is not 1
return nil , fmt . Errorf ( "%-v git merge-base --is-ancestor: %w" , pr , err )
}
}
commitIDBytes , err := os . ReadFile ( pr . BaseRepo . RepoPath ( ) + "/" + headFile )
// If merge-base successfully exits then prHeadRef is an ancestor of pr.BaseBranch
// Find the head commit id
prHeadCommitID , err := git . GetFullCommitID ( ctx , pr . BaseRepo . RepoPath ( ) , prHeadRef )
if err != nil {
if err != nil {
return nil , fmt . Errorf ( "ReadFile(%s): %w" , headFile , err )
return nil , fmt . Errorf ( "GetFullCommitID(%s) in %s: %w" , prHeadRef , pr . BaseRepo . FullName ( ) , err )
}
commitID := string ( commitIDBytes )
if len ( commitID ) < git . SHAFullLength {
return nil , fmt . Errorf ( ` ReadFile(%s): invalid commit-ID "%s" ` , headFile , commitID )
}
}
cmd := commitID [ : git . SHAFullLength ] + ".." + pr . BaseBranch
// Get the commit from BaseBranch where the pull request got merged
// Get the commit from BaseBranch where the pull request got merged
mergeCommit , _ , err := git . NewCommand ( ctx , "rev-list" , "--ancestry-path" , "--merges" , "--reverse" ) . AddDynamicArguments ( cmd ) .
mergeCommit , _ , err := git . NewCommand ( ctx , "rev-list" , "--ancestry-path" , "--merges" , "--reverse" ) .
RunStdString ( & git . RunOpts { Dir : "" , Env : [ ] string { "GIT_INDEX_FILE=" + indexTmpPath , "GIT_DIR=" + pr . BaseRepo . RepoPath ( ) } } )
AddDynamicArguments ( prHeadCommitID + ".." + pr . BaseBranch ) .
RunStdString ( & git . RunOpts { Dir : pr . BaseRepo . RepoPath ( ) } )
if err != nil {
if err != nil {
return nil , fmt . Errorf ( "git rev-list --ancestry-path --merges --reverse: %w" , err )
return nil , fmt . Errorf ( "git rev-list --ancestry-path --merges --reverse: %w" , err )
} else if len ( mergeCommit ) < git . SHAFullLength {
} else if len ( mergeCommit ) < git . SHAFullLength {
// PR was maybe fast-forwarded, so just use last commit of PR
// PR was maybe fast-forwarded, so just use last commit of PR
mergeCommit = commitID [ : git . SHAFullLength ]
mergeCommit = prHeadCommitID
}
}
mergeCommit = strings . TrimSpace ( mergeCommit )
gitRepo , err := git . OpenRepository ( ctx , pr . BaseRepo . RepoPath ( ) )
gitRepo , err := git . OpenRepository ( ctx , pr . BaseRepo . RepoPath ( ) )
if err != nil {
if err != nil {
return nil , fmt . Errorf ( "OpenRepository: %w" , err )
return nil , fmt . Errorf ( "%-v OpenRepository: %w" , pr . BaseRepo , err )
}
}
defer gitRepo . Close ( )
defer gitRepo . Close ( )
commit , err := gitRepo . GetCommit ( mergeCommit [ : git . SHAFullLength ] )
commit , err := gitRepo . GetCommit ( mergeCommit )
if err != nil {
if err != nil {
return nil , fmt . Errorf ( "GetMergeCommit[%v ]: %w" , mergeCommit [ : git . SHAFullLength ] , err )
return nil , fmt . Errorf ( "GetMergeCommit[%s ]: %w" , mergeCommit , err )
}
}
return commit , nil
return commit , nil
@ -233,7 +227,7 @@ func getMergeCommit(ctx context.Context, pr *issues_model.PullRequest) (*git.Com
// When a pull request got manually merged mark the pull request as merged
// When a pull request got manually merged mark the pull request as merged
func manuallyMerged ( ctx context . Context , pr * issues_model . PullRequest ) bool {
func manuallyMerged ( ctx context . Context , pr * issues_model . PullRequest ) bool {
if err := pr . LoadBaseRepoCtx ( ctx ) ; err != nil {
if err := pr . LoadBaseRepoCtx ( ctx ) ; err != nil {
log . Error ( "PullRequest[%d]. LoadBaseRepo: %v" , pr . ID , err )
log . Error ( "%-v LoadBaseRepo: %v" , pr , err )
return false
return false
}
}
@ -243,16 +237,21 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool {
return false
return false
}
}
} else {
} else {
log . Error ( "PullRequest[%d]. BaseRepo.GetUnit(unit.TypePullRequests): %v" , pr . ID , err )
log . Error ( "%-v BaseRepo.GetUnit(unit.TypePullRequests): %v" , pr , err )
return false
return false
}
}
commit , err := getMergeCommit ( ctx , pr )
commit , err := getMergeCommit ( ctx , pr )
if err != nil {
if err != nil {
log . Error ( "PullRequest[%d]. getMergeCommit: %v" , pr . ID , err )
log . Error ( "%-v getMergeCommit: %v" , pr , err )
return false
return false
}
}
if commit != nil {
if commit == nil {
// no merge commit found
return false
}
pr . MergedCommitID = commit . ID . String ( )
pr . MergedCommitID = commit . ID . String ( )
pr . MergedUnix = timeutil . TimeStamp ( commit . Author . When . Unix ( ) )
pr . MergedUnix = timeutil . TimeStamp ( commit . Author . When . Unix ( ) )
pr . Status = issues_model . PullRequestStatusManuallyMerged
pr . Status = issues_model . PullRequestStatusManuallyMerged
@ -262,7 +261,7 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool {
if merger == nil {
if merger == nil {
if pr . BaseRepo . Owner == nil {
if pr . BaseRepo . Owner == nil {
if err = pr . BaseRepo . GetOwner ( ctx ) ; err != nil {
if err = pr . BaseRepo . GetOwner ( ctx ) ; err != nil {
log . Error ( "BaseRepo.GetOwner[%d] : %v" , pr . ID , err )
log . Error ( "%-v BaseRepo.GetOwner: %v" , pr , err )
return false
return false
}
}
}
}
@ -272,7 +271,7 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool {
pr . MergerID = merger . ID
pr . MergerID = merger . ID
if merged , err := pr . SetMerged ( ctx ) ; err != nil {
if merged , err := pr . SetMerged ( ctx ) ; err != nil {
log . Error ( "PullRequest[%d]. setMerged : %v" , pr . ID , err )
log . Error ( "%-v setMerged : %v" , pr , err )
return false
return false
} else if ! merged {
} else if ! merged {
return false
return false
@ -280,10 +279,8 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool {
notification . NotifyMergePullRequest ( pr , merger )
notification . NotifyMergePullRequest ( pr , merger )
log . Info ( "manuallyMerged[%d ]: Marked as manually merged into %s/%s by commit id: %s" , pr . ID , pr . BaseRepo . Name , pr . BaseBranch , commit . ID . String ( ) )
log . Info ( "manuallyMerged[%-v ]: Marked as manually merged into %s/%s by commit id: %s" , pr , pr . BaseRepo . Name , pr . BaseBranch , commit . ID . String ( ) )
return true
return true
}
return false
}
}
// InitializePullRequests checks and tests untested patches of pull requests.
// InitializePullRequests checks and tests untested patches of pull requests.
@ -299,10 +296,10 @@ func InitializePullRequests(ctx context.Context) {
return
return
default :
default :
if err := prPatchCheckerQueue . PushFunc ( strconv . FormatInt ( prID , 10 ) , func ( ) error {
if err := prPatchCheckerQueue . PushFunc ( strconv . FormatInt ( prID , 10 ) , func ( ) error {
log . Trace ( "Adding PR ID: %d to the pull requests patch checking queue" , prID )
log . Trace ( "Adding PR[%d] to the pull requests patch checking queue" , prID )
return nil
return nil
} ) ; err != nil {
} ) ; err != nil {
log . Error ( "Error adding prID: %s to the pull requests patch checking queue %v" , prID , err )
log . Error ( "Error adding PR[%d] to the pull requests patch checking queue %v" , prID , err )
}
}
}
}
}
}
@ -326,23 +323,30 @@ func testPR(id int64) {
pr , err := issues_model . GetPullRequestByID ( ctx , id )
pr , err := issues_model . GetPullRequestByID ( ctx , id )
if err != nil {
if err != nil {
log . Error ( "GetPullRequestByID[%d]: %v" , id , err )
log . Error ( "Unable to GetPullRequestByID[%d] for testPR : %v" , id , err )
return
return
}
}
log . Trace ( "Testing %-v" , pr )
defer func ( ) {
log . Trace ( "Done testing %-v (status: %s)" , pr , pr . Status )
} ( )
if pr . HasMerged {
if pr . HasMerged {
log . Trace ( "%-v is already merged (status: %s, merge commit: %s)" , pr , pr . Status , pr . MergedCommitID )
return
return
}
}
if manuallyMerged ( ctx , pr ) {
if manuallyMerged ( ctx , pr ) {
log . Trace ( "%-v is manually merged (status: %s, merge commit: %s)" , pr , pr . Status , pr . MergedCommitID )
return
return
}
}
if err := TestPatch ( pr ) ; err != nil {
if err := TestPatch ( pr ) ; err != nil {
log . Error ( "testPatch[%d ]: %v" , pr . ID , err )
log . Error ( "testPatch[%-v ]: %v" , pr , err )
pr . Status = issues_model . PullRequestStatusError
pr . Status = issues_model . PullRequestStatusError
if err := pr . UpdateCols ( "status" ) ; err != nil {
if err := pr . UpdateCols ( "status" ) ; err != nil {
log . Error ( "update pr [%d ] status to PullRequestStatusError failed: %v" , pr . ID , err )
log . Error ( "update pr [%-v ] status to PullRequestStatusError failed: %v" , pr , err )
}
}
return
return
}
}