2019-12-07 05:44:10 +03:00
// Copyright 2019 The Gitea Authors.
// All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package pull
import (
2019-12-15 12:51:28 +03:00
"context"
2019-12-07 05:44:10 +03:00
"fmt"
"os"
2020-02-03 02:19:58 +03:00
"strconv"
2019-12-07 05:44:10 +03:00
"strings"
"code.gitea.io/gitea/models"
2021-11-09 22:57:58 +03:00
"code.gitea.io/gitea/models/unit"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2019-12-07 05:44:10 +03:00
"code.gitea.io/gitea/modules/git"
2019-12-15 12:51:28 +03:00
"code.gitea.io/gitea/modules/graceful"
2019-12-07 05:44:10 +03:00
"code.gitea.io/gitea/modules/log"
2019-12-16 00:57:34 +03:00
"code.gitea.io/gitea/modules/notification"
2020-02-03 02:19:58 +03:00
"code.gitea.io/gitea/modules/queue"
2019-12-07 05:44:10 +03:00
"code.gitea.io/gitea/modules/timeutil"
2020-08-11 23:05:34 +03:00
"code.gitea.io/gitea/modules/util"
2019-12-07 05:44:10 +03:00
)
2020-02-03 02:19:58 +03:00
// prQueue represents a queue to handle update pull request tests
var prQueue queue . UniqueQueue
2019-12-07 05:44:10 +03:00
// AddToTaskQueue adds itself to pull request test task queue.
func AddToTaskQueue ( pr * models . PullRequest ) {
2021-06-09 22:52:55 +03:00
err := prQueue . PushFunc ( strconv . FormatInt ( pr . ID , 10 ) , func ( ) error {
pr . Status = models . PullRequestStatusChecking
err := pr . UpdateColsIfNotMerged ( "status" )
if err != nil {
log . Error ( "AddToTaskQueue.UpdateCols[%d].(add to queue): %v" , pr . ID , err )
} else {
log . Trace ( "Adding PR ID: %d to the test pull requests queue" , pr . ID )
2019-12-07 05:44:10 +03:00
}
2021-06-09 22:52:55 +03:00
return err
} )
if err != nil && err != queue . ErrAlreadyInQueue {
log . Error ( "Error adding prID %d to the test pull requests queue: %v" , pr . ID , err )
}
2019-12-07 05:44:10 +03:00
}
// checkAndUpdateStatus checks if pull request is possible to leaving checking status,
// and set to be either conflict or mergeable.
func checkAndUpdateStatus ( pr * models . PullRequest ) {
// Status is not changed to conflict means mergeable.
if pr . Status == models . PullRequestStatusChecking {
pr . Status = models . PullRequestStatusMergeable
}
// Make sure there is no waiting test to process before leaving the checking status.
2020-02-03 02:19:58 +03:00
has , err := prQueue . Has ( strconv . FormatInt ( pr . ID , 10 ) )
if err != nil {
log . Error ( "Unable to check if the queue is waiting to reprocess pr.ID %d. Error: %v" , pr . ID , err )
}
if ! has {
2020-10-13 21:50:57 +03:00
if err := pr . UpdateColsIfNotMerged ( "merge_base" , "status" , "conflicted_files" , "changed_protected_files" ) ; err != nil {
2019-12-07 05:44:10 +03:00
log . Error ( "Update[%d]: %v" , pr . ID , err )
}
}
}
// getMergeCommit checks if a pull request got merged
// Returns the git.Commit of the pull request if merged
func getMergeCommit ( pr * models . PullRequest ) ( * git . Commit , error ) {
if pr . BaseRepo == nil {
var err error
pr . BaseRepo , err = models . GetRepositoryByID ( pr . BaseRepoID )
if err != nil {
return nil , fmt . Errorf ( "GetRepositoryByID: %v" , err )
}
}
2021-09-22 08:38:34 +03:00
indexTmpPath , err := os . MkdirTemp ( os . TempDir ( ) , "gitea-" + pr . BaseRepo . Name )
2019-12-16 08:17:55 +03:00
if err != nil {
return nil , fmt . Errorf ( "Failed to create temp dir for repository %s: %v" , pr . BaseRepo . RepoPath ( ) , err )
}
2020-08-11 23:05:34 +03:00
defer func ( ) {
if err := util . RemoveAll ( indexTmpPath ) ; err != nil {
log . Warn ( "Unable to remove temporary index path: %s: Error: %v" , indexTmpPath , err )
}
} ( )
2019-12-07 05:44:10 +03:00
headFile := pr . GetGitRefName ( )
// Check if a pull request is merged into BaseBranch
2020-02-03 02:19:58 +03:00
_ , err = git . NewCommand ( "merge-base" , "--is-ancestor" , headFile , pr . BaseBranch ) .
RunInDirWithEnv ( pr . BaseRepo . RepoPath ( ) , [ ] string { "GIT_INDEX_FILE=" + indexTmpPath , "GIT_DIR=" + pr . BaseRepo . RepoPath ( ) } )
2019-12-07 05:44:10 +03:00
if err != nil {
// Errors are signaled by a non-zero status that is not 1
if strings . Contains ( err . Error ( ) , "exit status 1" ) {
return nil , nil
}
return nil , fmt . Errorf ( "git merge-base --is-ancestor: %v" , err )
}
2021-09-22 08:38:34 +03:00
commitIDBytes , err := os . ReadFile ( pr . BaseRepo . RepoPath ( ) + "/" + headFile )
2019-12-07 05:44:10 +03:00
if err != nil {
return nil , fmt . Errorf ( "ReadFile(%s): %v" , headFile , err )
}
commitID := string ( commitIDBytes )
if len ( commitID ) < 40 {
return nil , fmt . Errorf ( ` ReadFile(%s): invalid commit-ID "%s" ` , headFile , commitID )
}
cmd := commitID [ : 40 ] + ".." + pr . BaseBranch
// Get the commit from BaseBranch where the pull request got merged
2020-02-03 02:19:58 +03:00
mergeCommit , err := git . NewCommand ( "rev-list" , "--ancestry-path" , "--merges" , "--reverse" , cmd ) .
RunInDirWithEnv ( "" , [ ] string { "GIT_INDEX_FILE=" + indexTmpPath , "GIT_DIR=" + pr . BaseRepo . RepoPath ( ) } )
2019-12-07 05:44:10 +03:00
if err != nil {
return nil , fmt . Errorf ( "git rev-list --ancestry-path --merges --reverse: %v" , err )
} else if len ( mergeCommit ) < 40 {
2021-03-04 06:41:23 +03:00
// PR was maybe fast-forwarded, so just use last commit of PR
2019-12-07 05:44:10 +03:00
mergeCommit = commitID [ : 40 ]
}
gitRepo , err := git . OpenRepository ( pr . BaseRepo . RepoPath ( ) )
if err != nil {
return nil , fmt . Errorf ( "OpenRepository: %v" , err )
}
defer gitRepo . Close ( )
commit , err := gitRepo . GetCommit ( mergeCommit [ : 40 ] )
if err != nil {
2021-08-31 04:18:15 +03:00
return nil , fmt . Errorf ( "GetMergeCommit[%v]: %v" , mergeCommit [ : 40 ] , err )
2019-12-07 05:44:10 +03:00
}
return commit , nil
}
// manuallyMerged checks if a pull request got manually merged
// When a pull request got manually merged mark the pull request as merged
func manuallyMerged ( pr * models . PullRequest ) bool {
2021-03-04 06:41:23 +03:00
if err := pr . LoadBaseRepo ( ) ; err != nil {
log . Error ( "PullRequest[%d].LoadBaseRepo: %v" , pr . ID , err )
return false
}
2021-11-09 22:57:58 +03:00
if unit , err := pr . BaseRepo . GetUnit ( unit . TypePullRequests ) ; err == nil {
2021-03-04 06:41:23 +03:00
config := unit . PullRequestsConfig ( )
if ! config . AutodetectManualMerge {
return false
}
} else {
2021-11-09 22:57:58 +03:00
log . Error ( "PullRequest[%d].BaseRepo.GetUnit(unit.TypePullRequests): %v" , pr . ID , err )
2021-03-04 06:41:23 +03:00
return false
}
2019-12-07 05:44:10 +03:00
commit , err := getMergeCommit ( pr )
if err != nil {
log . Error ( "PullRequest[%d].getMergeCommit: %v" , pr . ID , err )
return false
}
if commit != nil {
pr . MergedCommitID = commit . ID . String ( )
pr . MergedUnix = timeutil . TimeStamp ( commit . Author . When . Unix ( ) )
pr . Status = models . PullRequestStatusManuallyMerged
2021-11-24 12:49:20 +03:00
merger , _ := user_model . GetUserByEmail ( commit . Author . Email )
2019-12-07 05:44:10 +03:00
// When the commit author is unknown set the BaseRepo owner as merger
if merger == nil {
if pr . BaseRepo . Owner == nil {
if err = pr . BaseRepo . GetOwner ( ) ; err != nil {
log . Error ( "BaseRepo.GetOwner[%d]: %v" , pr . ID , err )
return false
}
}
merger = pr . BaseRepo . Owner
}
pr . Merger = merger
pr . MergerID = merger . ID
2020-02-10 02:09:31 +03:00
if merged , err := pr . SetMerged ( ) ; err != nil {
2019-12-07 05:44:10 +03:00
log . Error ( "PullRequest[%d].setMerged : %v" , pr . ID , err )
return false
2020-02-10 02:09:31 +03:00
} else if ! merged {
return false
2019-12-07 05:44:10 +03:00
}
2019-12-16 00:57:34 +03:00
2020-01-16 19:24:20 +03:00
notification . NotifyMergePullRequest ( pr , merger )
2019-12-16 00:57:34 +03:00
2019-12-07 05:44:10 +03:00
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 ( ) )
return true
}
return false
}
2020-02-03 02:19:58 +03:00
// InitializePullRequests checks and tests untested patches of pull requests.
func InitializePullRequests ( ctx context . Context ) {
prs , err := models . GetPullRequestIDsByCheckStatus ( models . PullRequestStatusChecking )
if err != nil {
log . Error ( "Find Checking PRs: %v" , err )
return
}
for _ , prID := range prs {
select {
case <- ctx . Done ( ) :
2019-12-15 12:51:28 +03:00
return
2020-02-03 02:19:58 +03:00
default :
if err := prQueue . PushFunc ( strconv . FormatInt ( prID , 10 ) , func ( ) error {
log . Trace ( "Adding PR ID: %d to the pull requests patch checking queue" , prID )
return nil
} ) ; err != nil {
log . Error ( "Error adding prID: %s to the pull requests patch checking queue %v" , prID , err )
2019-12-15 12:51:28 +03:00
}
2019-12-07 05:44:10 +03:00
}
2020-02-03 02:19:58 +03:00
}
}
2019-12-07 05:44:10 +03:00
2020-02-03 02:19:58 +03:00
// handle passed PR IDs and test the PRs
func handle ( data ... queue . Data ) {
for _ , datum := range data {
2020-12-25 12:59:32 +03:00
id , _ := strconv . ParseInt ( datum . ( string ) , 10 , 64 )
2019-12-15 12:51:28 +03:00
2020-02-03 02:19:58 +03:00
log . Trace ( "Testing PR ID %d from the pull requests patch checking queue" , id )
2019-12-15 12:51:28 +03:00
2020-02-03 02:19:58 +03:00
pr , err := models . GetPullRequestByID ( id )
if err != nil {
2020-12-25 12:59:32 +03:00
log . Error ( "GetPullRequestByID[%s]: %v" , datum , err )
2020-02-03 02:19:58 +03:00
continue
2020-02-10 02:09:31 +03:00
} else if pr . HasMerged {
2020-02-03 02:19:58 +03:00
continue
} else if manuallyMerged ( pr ) {
continue
} else if err = TestPatch ( pr ) ; err != nil {
log . Error ( "testPatch[%d]: %v" , pr . ID , err )
pr . Status = models . PullRequestStatusError
if err := pr . UpdateCols ( "status" ) ; err != nil {
log . Error ( "update pr [%d] status to PullRequestStatusError failed: %v" , pr . ID , err )
2019-12-15 12:51:28 +03:00
}
2020-02-03 02:19:58 +03:00
continue
2019-12-07 05:44:10 +03:00
}
2020-02-03 02:19:58 +03:00
checkAndUpdateStatus ( pr )
2019-12-07 05:44:10 +03:00
}
}
2020-10-13 21:50:57 +03:00
// CheckPrsForBaseBranch check all pulls with bseBrannch
func CheckPrsForBaseBranch ( baseRepo * models . Repository , baseBranchName string ) error {
prs , err := models . GetUnmergedPullRequestsByBaseInfo ( baseRepo . ID , baseBranchName )
if err != nil {
return err
}
for _ , pr := range prs {
AddToTaskQueue ( pr )
}
return nil
}
2019-12-07 05:44:10 +03:00
// Init runs the task queue to test all the checking status pull requests
2020-02-03 02:19:58 +03:00
func Init ( ) error {
2021-10-17 22:47:12 +03:00
prQueue = queue . CreateUniqueQueue ( "pr_patch_checker" , handle , "" )
2020-02-03 02:19:58 +03:00
if prQueue == nil {
return fmt . Errorf ( "Unable to create pr_patch_checker Queue" )
}
go graceful . GetManager ( ) . RunWithShutdownFns ( prQueue . Run )
go graceful . GetManager ( ) . RunWithShutdownContext ( InitializePullRequests )
return nil
2019-12-07 05:44:10 +03:00
}