2024-02-18 19:39:41 -05:00
package database
2015-10-18 19:30:39 -04:00
import (
2022-06-25 18:07:39 +08:00
"context"
2015-10-18 19:30:39 -04:00
"fmt"
"os"
2020-02-22 09:05:26 +08:00
"path/filepath"
2015-10-18 19:30:39 -04:00
"time"
2026-01-22 08:20:53 -05:00
"github.com/cockroachdb/errors"
2019-10-23 23:03:17 -07:00
"github.com/unknwon/com"
2026-01-24 15:43:22 +00:00
"gorm.io/gorm"
2020-02-20 02:25:02 +08:00
log "unknwon.dev/clog/v2"
2015-10-18 19:30:39 -04:00
2018-05-27 08:53:48 +08:00
"github.com/gogs/git-module"
api "github.com/gogs/go-gogs-client"
2015-12-09 20:46:05 -05:00
2020-02-22 09:05:26 +08:00
"gogs.io/gogs/internal/conf"
2020-03-16 01:22:27 +08:00
"gogs.io/gogs/internal/errutil"
2020-02-17 22:48:24 +08:00
"gogs.io/gogs/internal/osutil"
2019-10-24 01:51:46 -07:00
"gogs.io/gogs/internal/process"
"gogs.io/gogs/internal/sync"
2015-10-18 19:30:39 -04:00
)
2020-02-22 15:22:32 +08:00
var PullRequestQueue = sync . NewUniqueQueue ( 1000 )
2016-08-30 15:50:30 -07:00
2015-10-18 19:30:39 -04:00
type PullRequestType int
const (
2025-04-14 18:00:07 -04:00
PullRequestTypeGogs PullRequestType = iota
PullRequestTypeGit
2015-10-18 19:30:39 -04:00
)
type PullRequestStatus int
const (
2025-04-14 18:00:07 -04:00
PullRequestStatusConflict PullRequestStatus = iota
PullRequestStatusChecking
PullRequestStatusMergeable
2015-10-18 19:30:39 -04:00
)
// PullRequest represents relation between pull request and repositories.
type PullRequest struct {
2022-11-25 22:40:20 +08:00
ID int64 ` gorm:"primaryKey" `
2015-10-18 19:30:39 -04:00
Type PullRequestType
Status PullRequestStatus
2022-11-25 22:40:20 +08:00
IssueID int64 ` xorm:"INDEX" gorm:"index" `
Issue * Issue ` xorm:"-" json:"-" gorm:"-" `
2015-10-18 19:30:39 -04:00
Index int64
2015-10-24 03:36:47 -04:00
HeadRepoID int64
2022-11-25 22:40:20 +08:00
HeadRepo * Repository ` xorm:"-" json:"-" gorm:"-" `
2015-10-24 03:36:47 -04:00
BaseRepoID int64
2022-11-25 22:40:20 +08:00
BaseRepo * Repository ` xorm:"-" json:"-" gorm:"-" `
2015-10-24 03:36:47 -04:00
HeadUserName string
HeadBranch string
BaseBranch string
2022-11-25 22:40:20 +08:00
MergeBase string ` xorm:"VARCHAR(40)" gorm:"type:VARCHAR(40)" `
2015-10-18 19:30:39 -04:00
2015-10-24 03:36:47 -04:00
HasMerged bool
2022-11-25 22:40:20 +08:00
MergedCommitID string ` xorm:"VARCHAR(40)" gorm:"type:VARCHAR(40)" `
2015-10-24 03:36:47 -04:00
MergerID int64
2022-11-25 22:40:20 +08:00
Merger * User ` xorm:"-" json:"-" gorm:"-" `
Merged time . Time ` xorm:"-" json:"-" gorm:"-" `
2016-03-09 19:53:30 -05:00
MergedUnix int64
}
func ( pr * PullRequest ) BeforeUpdate ( ) {
2016-07-23 20:24:44 +08:00
pr . MergedUnix = pr . Merged . Unix ( )
2015-10-18 19:30:39 -04:00
}
2016-08-14 03:32:24 -07:00
// Note: don't try to get Issue because will end up recursive querying.
2026-01-24 15:43:22 +00:00
func ( pr * PullRequest ) AfterFind ( tx * gorm . DB ) error {
if pr . HasMerged {
2016-03-09 19:53:30 -05:00
pr . Merged = time . Unix ( pr . MergedUnix , 0 ) . Local ( )
2015-10-18 19:30:39 -04:00
}
2026-01-24 15:43:22 +00:00
return nil
2015-10-18 19:30:39 -04:00
}
2016-08-14 03:32:24 -07:00
// Note: don't try to get Issue because will end up recursive querying.
2026-01-24 15:43:22 +00:00
func ( pr * PullRequest ) loadAttributes ( db * gorm . DB ) ( err error ) {
2016-12-22 01:28:06 -05:00
if pr . HeadRepo == nil {
2026-01-24 15:43:22 +00:00
pr . HeadRepo , err = getRepositoryByID ( db , pr . HeadRepoID )
2020-03-16 01:22:27 +08:00
if err != nil && ! IsErrRepoNotExist ( err ) {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "get head repository by ID: %v" , err )
2016-12-22 01:28:06 -05:00
}
}
if pr . BaseRepo == nil {
2026-01-24 15:43:22 +00:00
pr . BaseRepo , err = getRepositoryByID ( db , pr . BaseRepoID )
2016-12-22 01:28:06 -05:00
if err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "get base repository by ID: %v" , err )
2016-12-22 01:28:06 -05:00
}
}
2016-08-14 03:32:24 -07:00
if pr . HasMerged && pr . Merger == nil {
2026-01-24 15:43:22 +00:00
pr . Merger , err = getUserByID ( db , pr . MergerID )
2020-03-16 01:22:27 +08:00
if IsErrUserNotExist ( err ) {
2016-08-14 03:32:24 -07:00
pr . MergerID = - 1
pr . Merger = NewGhostUser ( )
} else if err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "get merger by ID: %v" , err )
2016-08-14 03:32:24 -07:00
}
}
return nil
}
func ( pr * PullRequest ) LoadAttributes ( ) error {
2026-01-24 15:43:22 +00:00
return pr . loadAttributes ( db )
2016-08-14 03:32:24 -07:00
}
func ( pr * PullRequest ) LoadIssue ( ) ( err error ) {
2016-08-16 10:19:09 -07:00
if pr . Issue != nil {
return nil
}
2016-08-14 03:32:24 -07:00
pr . Issue , err = GetIssueByID ( pr . IssueID )
return err
}
// This method assumes following fields have been assigned with valid values:
2016-12-22 01:28:06 -05:00
// Required - Issue, BaseRepo
// Optional - HeadRepo, Merger
2016-08-14 03:32:24 -07:00
func ( pr * PullRequest ) APIFormat ( ) * api . PullRequest {
2016-12-22 01:28:06 -05:00
// In case of head repo has been deleted.
var apiHeadRepo * api . Repository
if pr . HeadRepo == nil {
apiHeadRepo = & api . Repository {
Name : "deleted" ,
}
} else {
2022-06-25 18:07:39 +08:00
apiHeadRepo = pr . HeadRepo . APIFormatLegacy ( nil )
2016-12-22 01:28:06 -05:00
}
2016-08-14 03:32:24 -07:00
apiIssue := pr . Issue . APIFormat ( )
apiPullRequest := & api . PullRequest {
2016-12-22 01:01:15 -05:00
ID : pr . ID ,
Index : pr . Index ,
Poster : apiIssue . Poster ,
Title : apiIssue . Title ,
Body : apiIssue . Body ,
Labels : apiIssue . Labels ,
Milestone : apiIssue . Milestone ,
Assignee : apiIssue . Assignee ,
State : apiIssue . State ,
Comments : apiIssue . Comments ,
HeadBranch : pr . HeadBranch ,
2016-12-22 01:28:06 -05:00
HeadRepo : apiHeadRepo ,
2016-12-22 01:01:15 -05:00
BaseBranch : pr . BaseBranch ,
2022-06-25 18:07:39 +08:00
BaseRepo : pr . BaseRepo . APIFormatLegacy ( nil ) ,
2016-12-22 01:01:15 -05:00
HTMLURL : pr . Issue . HTMLURL ( ) ,
HasMerged : pr . HasMerged ,
2016-08-14 03:32:24 -07:00
}
2025-04-14 18:00:07 -04:00
if pr . Status != PullRequestStatusChecking {
mergeable := pr . Status != PullRequestStatusConflict
2016-08-14 03:32:24 -07:00
apiPullRequest . Mergeable = & mergeable
}
if pr . HasMerged {
apiPullRequest . Merged = & pr . Merged
apiPullRequest . MergedCommitID = & pr . MergedCommitID
apiPullRequest . MergedBy = pr . Merger . APIFormat ( )
}
return apiPullRequest
}
2015-10-24 03:36:47 -04:00
// IsChecking returns true if this pull request is still checking conflict.
func ( pr * PullRequest ) IsChecking ( ) bool {
2025-04-14 18:00:07 -04:00
return pr . Status == PullRequestStatusChecking
2015-10-24 03:36:47 -04:00
}
2015-10-18 19:30:39 -04:00
// CanAutoMerge returns true if this pull request can be merged automatically.
func ( pr * PullRequest ) CanAutoMerge ( ) bool {
2025-04-14 18:00:07 -04:00
return pr . Status == PullRequestStatusMergeable
2015-10-18 19:30:39 -04:00
}
2017-11-15 23:27:52 -05:00
// MergeStyle represents the approach to merge commits into base branch.
type MergeStyle string
const (
2025-04-14 18:00:07 -04:00
MergeStyleRegular MergeStyle = "create_merge_commit"
MergeStyleRebase MergeStyle = "rebase_before_merging"
2017-11-15 23:27:52 -05:00
)
2015-10-18 19:30:39 -04:00
// Merge merges pull request to base repository.
2016-08-14 03:32:24 -07:00
// FIXME: add repoWorkingPull make sure two merges does not happen at same time.
2018-06-26 21:16:29 +08:00
func ( pr * PullRequest ) Merge ( doer * User , baseGitRepo * git . Repository , mergeStyle MergeStyle , commitDescription string ) ( err error ) {
2022-06-25 18:07:39 +08:00
ctx := context . TODO ( )
2016-08-14 03:32:24 -07:00
defer func ( ) {
go HookQueue . Add ( pr . BaseRepo . ID )
go AddTestPullRequestTask ( doer , pr . BaseRepo . ID , pr . BaseBranch , false )
} ( )
2026-01-24 15:43:22 +00:00
return db . Transaction ( func ( tx * gorm . DB ) error {
if err := pr . Issue . changeStatus ( tx , doer , pr . Issue . Repo , true ) ; err != nil {
return errors . Newf ( "Issue.changeStatus: %v" , err )
}
2015-10-24 03:36:47 -04:00
2026-01-24 15:48:04 +00:00
headRepoPath := RepoPath ( pr . HeadUserName , pr . HeadRepo . Name )
2026-01-24 15:47:50 +00:00
headGitRepo , err := git . Open ( headRepoPath )
if err != nil {
return errors . Newf ( "open repository: %v" , err )
}
2017-11-15 23:27:52 -05:00
2026-01-24 15:47:50 +00:00
// Create temporary directory to store temporary copy of the base repository,
// and clean it up when operation finished regardless of succeed or not.
tmpBasePath := filepath . Join ( conf . Server . AppDataPath , "tmp" , "repos" , com . ToStr ( time . Now ( ) . Nanosecond ( ) ) + ".git" )
if err = os . MkdirAll ( filepath . Dir ( tmpBasePath ) , os . ModePerm ) ; err != nil {
return err
}
defer func ( ) {
_ = os . RemoveAll ( filepath . Dir ( tmpBasePath ) )
} ( )
// Clone the base repository to the defined temporary directory,
// and checks out to base branch directly.
var stderr string
if _ , stderr , err = process . ExecTimeout ( 5 * time . Minute ,
fmt . Sprintf ( "PullRequest.Merge (git clone): %s" , tmpBasePath ) ,
"git" , "clone" , "-b" , pr . BaseBranch , baseGitRepo . Path ( ) , tmpBasePath ) ; err != nil {
return errors . Newf ( "git clone: %s" , stderr )
}
2018-03-21 19:11:36 -04:00
2026-01-24 15:47:50 +00:00
// Add remote which points to the head repository.
2017-10-01 05:37:24 -07:00
if _ , stderr , err = process . ExecDir ( - 1 , tmpBasePath ,
2026-01-24 15:47:50 +00:00
fmt . Sprintf ( "PullRequest.Merge (git remote add): %s" , tmpBasePath ) ,
"git" , "remote" , "add" , "head_repo" , headRepoPath ) ; err != nil {
return errors . Newf ( "git remote add [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
2017-10-01 05:37:24 -07:00
}
2015-11-20 02:53:54 -05:00
2026-01-24 15:47:50 +00:00
// Fetch information from head repository to the temporary copy.
2017-10-01 05:37:24 -07:00
if _ , stderr , err = process . ExecDir ( - 1 , tmpBasePath ,
2026-01-24 15:47:50 +00:00
fmt . Sprintf ( "PullRequest.Merge (git fetch): %s" , tmpBasePath ) ,
"git" , "fetch" , "head_repo" ) ; err != nil {
return errors . Newf ( "git fetch [%s -> %s]: %s" , headRepoPath , tmpBasePath , stderr )
2017-10-01 05:37:24 -07:00
}
2017-11-15 23:27:52 -05:00
2026-01-24 15:47:50 +00:00
remoteHeadBranch := "head_repo/" + pr . HeadBranch
2018-03-21 19:11:36 -04:00
2026-01-24 15:47:50 +00:00
// Check if merge style is allowed, reset to default style if not
if mergeStyle == MergeStyleRebase && ! pr . BaseRepo . PullsAllowRebase {
mergeStyle = MergeStyleRegular
2018-03-21 19:11:36 -04:00
}
2026-01-24 15:47:50 +00:00
switch mergeStyle {
case MergeStyleRegular : // Create merge commit
// Merge changes from head branch.
if _ , stderr , err = process . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git merge --no-ff --no-commit): %s" , tmpBasePath ) ,
"git" , "merge" , "--no-ff" , "--no-commit" , remoteHeadBranch ) ; err != nil {
return errors . Newf ( "git merge --no-ff --no-commit [%s]: %v - %s" , tmpBasePath , err , stderr )
}
// Create a merge commit for the base branch.
if _ , stderr , err = process . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git merge): %s" , tmpBasePath ) ,
"git" , "commit" , fmt . Sprintf ( "--author='%s <%s>'" , doer . DisplayName ( ) , doer . Email ) ,
"-m" , fmt . Sprintf ( "Merge branch '%s' of %s/%s into %s" , pr . HeadBranch , pr . HeadUserName , pr . HeadRepo . Name , pr . BaseBranch ) ,
"-m" , commitDescription ) ; err != nil {
return errors . Newf ( "git commit [%s]: %v - %s" , tmpBasePath , err , stderr )
}
case MergeStyleRebase : // Rebase before merging
// Rebase head branch based on base branch, this creates a non-branch commit state.
if _ , stderr , err = process . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git rebase): %s" , tmpBasePath ) ,
"git" , "rebase" , "--quiet" , pr . BaseBranch , remoteHeadBranch ) ; err != nil {
return errors . Newf ( "git rebase [%s on %s]: %s" , remoteHeadBranch , pr . BaseBranch , stderr )
}
// Name non-branch commit state to a new temporary branch in order to save changes.
tmpBranch := com . ToStr ( time . Now ( ) . UnixNano ( ) , 10 )
if _ , stderr , err = process . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git checkout): %s" , tmpBasePath ) ,
"git" , "checkout" , "-b" , tmpBranch ) ; err != nil {
return errors . Newf ( "git checkout '%s': %s" , tmpBranch , stderr )
}
// Check out the base branch to be operated on.
if _ , stderr , err = process . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git checkout): %s" , tmpBasePath ) ,
"git" , "checkout" , pr . BaseBranch ) ; err != nil {
return errors . Newf ( "git checkout '%s': %s" , pr . BaseBranch , stderr )
}
// Merge changes from temporary branch to the base branch.
if _ , stderr , err = process . ExecDir ( - 1 , tmpBasePath ,
fmt . Sprintf ( "PullRequest.Merge (git merge): %s" , tmpBasePath ) ,
"git" , "merge" , tmpBranch ) ; err != nil {
return errors . Newf ( "git merge [%s]: %v - %s" , tmpBasePath , err , stderr )
}
default :
return errors . Newf ( "unknown merge style: %s" , mergeStyle )
2018-03-21 19:11:36 -04:00
}
2026-01-24 15:47:50 +00:00
// Push changes on base branch to upstream.
2018-03-21 19:11:36 -04:00
if _ , stderr , err = process . ExecDir ( - 1 , tmpBasePath ,
2026-01-24 15:47:50 +00:00
fmt . Sprintf ( "PullRequest.Merge (git push): %s" , tmpBasePath ) ,
"git" , "push" , baseGitRepo . Path ( ) , pr . BaseBranch ) ; err != nil {
return errors . Newf ( "git push: %s" , stderr )
2018-03-21 19:11:36 -04:00
}
2026-01-24 15:47:50 +00:00
pr . MergedCommitID , err = headGitRepo . BranchCommitID ( pr . HeadBranch )
if err != nil {
return errors . Newf ( "get head branch %q commit ID: %v" , pr . HeadBranch , err )
2017-11-15 23:27:52 -05:00
}
2026-01-24 15:47:50 +00:00
pr . HasMerged = true
pr . Merged = time . Now ( )
pr . MergerID = doer . ID
if err := tx . Model ( & PullRequest { } ) . Where ( "id = ?" , pr . ID ) . Updates ( pr ) . Error ; err != nil {
return errors . Newf ( "update pull request: %v" , err )
}
2015-12-09 20:46:05 -05:00
2026-01-24 15:47:50 +00:00
if err = Handle . Actions ( ) . MergePullRequest ( ctx , doer , pr . Issue . Repo . Owner , pr . Issue . Repo , pr . Issue ) ; err != nil {
log . Error ( "Failed to create action for merge pull request, pull_request_id: %d, error: %v" , pr . ID , err )
}
2016-08-14 03:32:24 -07:00
2026-01-24 15:47:50 +00:00
// Reload pull request information.
if err = pr . LoadAttributes ( ) ; err != nil {
log . Error ( "LoadAttributes: %v" , err )
return nil
}
if err = PrepareWebhooks ( pr . Issue . Repo , HookEventTypePullRequest , & api . PullRequestPayload {
Action : api . HOOK_ISSUE_CLOSED ,
Index : pr . Index ,
PullRequest : pr . APIFormat ( ) ,
Repository : pr . Issue . Repo . APIFormatLegacy ( nil ) ,
Sender : doer . APIFormat ( ) ,
} ) ; err != nil {
log . Error ( "PrepareWebhooks: %v" , err )
return nil
}
2016-08-14 03:32:24 -07:00
2026-01-24 15:47:50 +00:00
commits , err := headGitRepo . RevList ( [ ] string { pr . MergeBase + "..." + pr . MergedCommitID } )
if err != nil {
log . Error ( "Failed to list commits [merge_base: %s, merged_commit_id: %s]: %v" , pr . MergeBase , pr . MergedCommitID , err )
return nil
}
2016-08-14 03:32:24 -07:00
2026-01-24 15:47:50 +00:00
// NOTE: It is possible that head branch is not fully sync with base branch
// for merge commits, so we need to get latest head commit and append merge
// commit manually to avoid strange diff commits produced.
mergeCommit , err := baseGitRepo . BranchCommit ( pr . BaseBranch )
if err != nil {
log . Error ( "Failed to get base branch %q commit: %v" , pr . BaseBranch , err )
return nil
}
if mergeStyle == MergeStyleRegular {
commits = append ( [ ] * git . Commit { mergeCommit } , commits ... )
}
2016-08-14 03:32:24 -07:00
2026-01-24 15:47:50 +00:00
pcs , err := CommitsToPushCommits ( commits ) . APIFormat ( ctx , Handle . Users ( ) , pr . BaseRepo . RepoPath ( ) , pr . BaseRepo . HTMLURL ( ) )
if err != nil {
log . Error ( "Failed to convert to API payload commits: %v" , err )
return nil
}
2017-03-16 17:33:04 -04:00
2026-01-24 15:47:50 +00:00
p := & api . PushPayload {
Ref : git . RefsHeads + pr . BaseBranch ,
Before : pr . MergeBase ,
After : mergeCommit . ID . String ( ) ,
CompareURL : conf . Server . ExternalURL + pr . BaseRepo . ComposeCompareURL ( pr . MergeBase , pr . MergedCommitID ) ,
Commits : pcs ,
Repo : pr . BaseRepo . APIFormatLegacy ( nil ) ,
Pusher : pr . HeadRepo . MustOwner ( ) . APIFormat ( ) ,
Sender : doer . APIFormat ( ) ,
}
if err = PrepareWebhooks ( pr . BaseRepo , HookEventTypePush , p ) ; err != nil {
log . Error ( "Failed to prepare webhooks: %v" , err )
return nil
}
2017-03-16 17:33:04 -04:00
return nil
2026-01-24 15:47:50 +00:00
} )
2015-10-18 19:30:39 -04:00
}
2021-05-19 10:42:09 +05:30
// testPatch checks if patch can be merged to base repository without conflict.
2015-11-15 14:41:36 -05:00
// FIXME: make a mechanism to clean up stable local copies.
2015-10-24 03:36:47 -04:00
func ( pr * PullRequest ) testPatch ( ) ( err error ) {
if pr . BaseRepo == nil {
pr . BaseRepo , err = GetRepositoryByID ( pr . BaseRepoID )
if err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "GetRepositoryByID: %v" , err )
2015-10-24 03:36:47 -04:00
}
}
patchPath , err := pr . BaseRepo . PatchPath ( pr . Index )
if err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "BaseRepo.PatchPath: %v" , err )
2015-10-24 03:36:47 -04:00
}
2021-05-19 10:42:09 +05:30
// Fast fail if patch does not exist, this assumes data is corrupted.
2020-02-17 22:48:24 +08:00
if ! osutil . IsFile ( patchPath ) {
2021-05-19 10:42:09 +05:30
log . Trace ( "PullRequest[%d].testPatch: ignored corrupted data" , pr . ID )
2015-10-25 03:10:22 -04:00
return nil
}
2016-08-30 05:07:50 -07:00
repoWorkingPool . CheckIn ( com . ToStr ( pr . BaseRepoID ) )
defer repoWorkingPool . CheckOut ( com . ToStr ( pr . BaseRepoID ) )
2016-02-03 12:28:03 -05:00
log . Trace ( "PullRequest[%d].testPatch (patchPath): %s" , pr . ID , patchPath )
2015-10-24 03:36:47 -04:00
2016-08-14 23:02:14 -07:00
if err := pr . BaseRepo . UpdateLocalCopyBranch ( pr . BaseBranch ) ; err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "UpdateLocalCopy [%d]: %v" , pr . BaseRepoID , err )
2015-10-24 03:36:47 -04:00
}
2017-11-16 22:22:38 -05:00
args := [ ] string { "apply" , "--check" }
if pr . BaseRepo . PullsIgnoreWhitespace {
args = append ( args , "--ignore-whitespace" )
}
args = append ( args , patchPath )
2025-04-14 18:00:07 -04:00
pr . Status = PullRequestStatusChecking
2016-08-14 23:02:14 -07:00
_ , stderr , err := process . ExecDir ( - 1 , pr . BaseRepo . LocalCopyPath ( ) ,
2016-02-03 12:28:03 -05:00
fmt . Sprintf ( "testPatch (git apply --check): %d" , pr . BaseRepo . ID ) ,
2017-11-16 22:22:38 -05:00
"git" , args ... )
2015-10-24 03:36:47 -04:00
if err != nil {
2021-05-19 10:42:09 +05:30
log . Trace ( "PullRequest[%d].testPatch (apply): has conflict\n%s" , pr . ID , stderr )
2025-04-14 18:00:07 -04:00
pr . Status = PullRequestStatusConflict
2017-03-09 13:36:40 -05:00
return nil
2015-10-24 03:36:47 -04:00
}
return nil
}
2015-10-18 19:30:39 -04:00
// NewPullRequest creates new pull request with labels for repository.
func NewPullRequest ( repo * Repository , pull * Issue , labelIDs [ ] int64 , uuids [ ] string , pr * PullRequest , patch [ ] byte ) ( err error ) {
2026-01-24 15:43:22 +00:00
err = db . Transaction ( func ( tx * gorm . DB ) error {
if err := newIssue ( tx , NewIssueOptions {
Repo : repo ,
Issue : pull ,
LableIDs : labelIDs ,
Attachments : uuids ,
IsPull : true ,
} ) ; err != nil {
return errors . Newf ( "newIssue: %v" , err )
}
2015-10-18 19:30:39 -04:00
2026-01-24 15:43:22 +00:00
pr . Index = pull . Index
if err := repo . SavePatch ( pr . Index , patch ) ; err != nil {
return errors . Newf ( "SavePatch: %v" , err )
}
2015-10-18 19:30:39 -04:00
2026-01-24 15:43:22 +00:00
pr . BaseRepo = repo
if err := pr . testPatch ( ) ; err != nil {
return errors . Newf ( "testPatch: %v" , err )
2026-01-24 15:48:04 +00:00
}
// No conflict appears after test means mergeable.
if pr . Status == PullRequestStatusChecking {
pr . Status = PullRequestStatusMergeable
}
2015-10-18 19:30:39 -04:00
2026-01-24 15:48:04 +00:00
pr . IssueID = pull . ID
2026-01-24 15:47:50 +00:00
if err := tx . Create ( pr ) . Error ; err != nil {
return errors . Newf ( "insert pull repo: %v" , err )
}
2015-10-18 19:30:39 -04:00
2026-01-24 15:47:50 +00:00
return nil
} )
if err != nil {
return err
2016-07-16 00:36:39 +08:00
}
2016-08-14 03:32:24 -07:00
if err = NotifyWatchers ( & Action {
ActUserID : pull . Poster . ID ,
ActUserName : pull . Poster . Name ,
2022-06-25 18:07:39 +08:00
OpType : ActionCreatePullRequest ,
2016-08-14 03:32:24 -07:00
Content : fmt . Sprintf ( "%d|%s" , pull . Index , pull . Title ) ,
RepoID : repo . ID ,
RepoUserName : repo . Owner . Name ,
RepoName : repo . Name ,
IsPrivate : repo . IsPrivate ,
} ) ; err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "NotifyWatchers: %v" , err )
2017-03-09 00:03:29 -05:00
}
if err = pull . MailParticipants ( ) ; err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "MailParticipants: %v" , err )
2016-07-16 00:36:39 +08:00
}
2016-08-14 03:32:24 -07:00
pr . Issue = pull
pull . PullRequest = pr
2025-04-14 18:00:07 -04:00
if err = PrepareWebhooks ( repo , HookEventTypePullRequest , & api . PullRequestPayload {
2016-08-14 03:32:24 -07:00
Action : api . HOOK_ISSUE_OPENED ,
Index : pull . Index ,
PullRequest : pr . APIFormat ( ) ,
2022-06-25 18:07:39 +08:00
Repository : repo . APIFormatLegacy ( nil ) ,
2016-08-14 03:32:24 -07:00
Sender : pull . Poster . APIFormat ( ) ,
} ) ; err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "PrepareWebhooks: %v" , err )
2016-08-14 03:32:24 -07:00
}
2016-07-16 00:36:39 +08:00
return nil
2015-10-18 19:30:39 -04:00
}
2021-05-19 10:42:09 +05:30
// GetUnmergedPullRequest returns a pull request that is open and has not been merged
2015-10-18 19:30:39 -04:00
// by given head/base and repo/branch.
func GetUnmergedPullRequest ( headRepoID , baseRepoID int64 , headBranch , baseBranch string ) ( * PullRequest , error ) {
pr := new ( PullRequest )
2026-01-24 15:43:22 +00:00
err := db . Joins ( "INNER JOIN issue ON issue.id = pull_request.issue_id" ) .
Where ( "pull_request.head_repo_id = ? AND pull_request.head_branch = ? AND pull_request.base_repo_id = ? AND pull_request.base_branch = ? AND pull_request.has_merged = ? AND issue.is_closed = ?" ,
headRepoID , headBranch , baseRepoID , baseBranch , false , false ) .
First ( pr ) . Error
2015-10-18 19:30:39 -04:00
if err != nil {
2026-01-24 15:43:22 +00:00
if errors . Is ( err , gorm . ErrRecordNotFound ) {
return nil , ErrPullRequestNotExist { args : map [ string ] any {
"headRepoID" : headRepoID ,
"baseRepoID" : baseRepoID ,
"headBranch" : headBranch ,
"baseBranch" : baseBranch ,
} }
}
2015-10-18 19:30:39 -04:00
return nil , err
}
return pr , nil
}
2021-05-19 10:42:09 +05:30
// GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged
2015-10-24 03:36:47 -04:00
// by given head information (repo and branch).
2015-10-24 14:48:11 -04:00
func GetUnmergedPullRequestsByHeadInfo ( repoID int64 , branch string ) ( [ ] * PullRequest , error ) {
2015-10-24 03:36:47 -04:00
prs := make ( [ ] * PullRequest , 0 , 2 )
2026-01-24 15:43:22 +00:00
err := db . Joins ( "INNER JOIN issue ON issue.id = pull_request.issue_id" ) .
Where ( "pull_request.head_repo_id = ? AND pull_request.head_branch = ? AND pull_request.has_merged = ? AND issue.is_closed = ?" ,
repoID , branch , false , false ) .
Find ( & prs ) . Error
return prs , err
2015-10-24 14:48:11 -04:00
}
2021-05-19 10:42:09 +05:30
// GetUnmergedPullRequestsByBaseInfo returns all pull requests that are open and has not been merged
2015-10-24 14:48:11 -04:00
// by given base information (repo and branch).
func GetUnmergedPullRequestsByBaseInfo ( repoID int64 , branch string ) ( [ ] * PullRequest , error ) {
prs := make ( [ ] * PullRequest , 0 , 2 )
2026-01-24 15:43:22 +00:00
err := db . Joins ( "INNER JOIN issue ON issue.id = pull_request.issue_id" ) .
Where ( "pull_request.base_repo_id = ? AND pull_request.base_branch = ? AND pull_request.has_merged = ? AND issue.is_closed = ?" ,
repoID , branch , false , false ) .
Find ( & prs ) . Error
return prs , err
2015-10-24 03:36:47 -04:00
}
2020-03-16 01:22:27 +08:00
var _ errutil . NotFound = ( * ErrPullRequestNotExist ) ( nil )
type ErrPullRequestNotExist struct {
2023-02-02 21:25:25 +08:00
args map [ string ] any
2020-03-16 01:22:27 +08:00
}
func IsErrPullRequestNotExist ( err error ) bool {
_ , ok := err . ( ErrPullRequestNotExist )
return ok
}
func ( err ErrPullRequestNotExist ) Error ( ) string {
return fmt . Sprintf ( "pull request does not exist: %v" , err . args )
}
func ( ErrPullRequestNotExist ) NotFound ( ) bool {
return true
}
2026-01-24 15:43:22 +00:00
func getPullRequestByID ( db * gorm . DB , id int64 ) ( * PullRequest , error ) {
2015-10-24 03:36:47 -04:00
pr := new ( PullRequest )
2026-01-24 15:43:22 +00:00
err := db . First ( pr , id ) . Error
2015-10-24 03:36:47 -04:00
if err != nil {
2026-01-24 15:43:22 +00:00
if errors . Is ( err , gorm . ErrRecordNotFound ) {
return nil , ErrPullRequestNotExist { args : map [ string ] any { "pullRequestID" : id } }
}
2015-10-24 03:36:47 -04:00
return nil , err
}
2026-01-24 15:43:22 +00:00
return pr , pr . loadAttributes ( db )
2015-10-24 03:36:47 -04:00
}
2016-08-14 03:32:24 -07:00
// GetPullRequestByID returns a pull request by given ID.
func GetPullRequestByID ( id int64 ) ( * PullRequest , error ) {
2026-01-24 15:43:22 +00:00
return getPullRequestByID ( db , id )
2016-08-14 03:32:24 -07:00
}
2026-01-24 15:43:22 +00:00
func getPullRequestByIssueID ( db * gorm . DB , issueID int64 ) ( * PullRequest , error ) {
pr := & PullRequest { }
err := db . Where ( "issue_id = ?" , issueID ) . First ( pr ) . Error
2015-10-18 19:30:39 -04:00
if err != nil {
2026-01-24 15:43:22 +00:00
if errors . Is ( err , gorm . ErrRecordNotFound ) {
return nil , ErrPullRequestNotExist { args : map [ string ] any { "issueID" : issueID } }
}
2015-10-18 19:30:39 -04:00
return nil , err
}
2026-01-24 15:43:22 +00:00
return pr , pr . loadAttributes ( db )
2016-08-14 03:32:24 -07:00
}
// GetPullRequestByIssueID returns pull request by given issue ID.
func GetPullRequestByIssueID ( issueID int64 ) ( * PullRequest , error ) {
2026-01-24 15:43:22 +00:00
return getPullRequestByIssueID ( db , issueID )
2015-10-18 19:30:39 -04:00
}
2015-10-24 03:36:47 -04:00
// Update updates all fields of pull request.
func ( pr * PullRequest ) Update ( ) error {
2026-01-24 15:43:22 +00:00
return db . Model ( & PullRequest { } ) . Where ( "id = ?" , pr . ID ) . Updates ( pr ) . Error
2015-10-24 03:36:47 -04:00
}
// Update updates specific fields of pull request.
func ( pr * PullRequest ) UpdateCols ( cols ... string ) error {
2026-01-24 15:43:22 +00:00
updates := make ( map [ string ] any )
for _ , col := range cols {
switch col {
case "status" :
updates [ "status" ] = pr . Status
case "merge_base" :
updates [ "merge_base" ] = pr . MergeBase
case "has_merged" :
updates [ "has_merged" ] = pr . HasMerged
case "merged_commit_id" :
updates [ "merged_commit_id" ] = pr . MergedCommitID
case "merger_id" :
updates [ "merger_id" ] = pr . MergerID
case "merged_unix" :
updates [ "merged_unix" ] = pr . MergedUnix
}
}
return db . Model ( & PullRequest { } ) . Where ( "id = ?" , pr . ID ) . Updates ( updates ) . Error
2015-10-24 03:36:47 -04:00
}
2015-10-25 03:10:22 -04:00
// UpdatePatch generates and saves a new patch.
2015-11-03 17:25:39 -05:00
func ( pr * PullRequest ) UpdatePatch ( ) ( err error ) {
2020-03-08 19:09:31 +08:00
headGitRepo , err := git . Open ( pr . HeadRepo . RepoPath ( ) )
2015-10-25 03:10:22 -04:00
if err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "open repository: %v" , err )
2015-10-25 03:10:22 -04:00
}
2015-11-03 17:25:39 -05:00
// Add a temporary remote.
tmpRemote := com . ToStr ( time . Now ( ) . UnixNano ( ) )
2020-03-08 19:09:31 +08:00
baseRepoPath := RepoPath ( pr . BaseRepo . MustOwner ( ) . Name , pr . BaseRepo . Name )
2022-03-22 00:55:36 +08:00
err = headGitRepo . RemoteAdd ( tmpRemote , baseRepoPath , git . RemoteAddOptions { Fetch : true } )
2020-03-08 19:09:31 +08:00
if err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "add remote %q [repo_id: %d]: %v" , tmpRemote , pr . HeadRepoID , err )
2015-11-03 17:25:39 -05:00
}
defer func ( ) {
2022-03-22 00:55:36 +08:00
if err := headGitRepo . RemoteRemove ( tmpRemote ) ; err != nil {
2020-03-08 19:09:31 +08:00
log . Error ( "Failed to remove remote %q [repo_id: %d]: %v" , tmpRemote , pr . HeadRepoID , err )
}
2015-11-03 17:25:39 -05:00
} ( )
2020-03-08 19:09:31 +08:00
2015-11-03 17:25:39 -05:00
remoteBranch := "remotes/" + tmpRemote + "/" + pr . BaseBranch
2020-03-08 19:09:31 +08:00
pr . MergeBase , err = headGitRepo . MergeBase ( remoteBranch , pr . HeadBranch )
2015-11-03 17:25:39 -05:00
if err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "get merge base: %v" , err )
2015-11-03 17:25:39 -05:00
} else if err = pr . Update ( ) ; err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "update: %v" , err )
2015-11-03 17:25:39 -05:00
}
2020-03-08 19:09:31 +08:00
patch , err := headGitRepo . DiffBinary ( pr . MergeBase , pr . HeadBranch )
2015-10-25 03:10:22 -04:00
if err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "get binary patch: %v" , err )
2015-10-25 03:10:22 -04:00
}
if err = pr . BaseRepo . SavePatch ( pr . Index , patch ) ; err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "save patch: %v" , err )
2015-10-25 03:10:22 -04:00
}
2020-03-08 19:09:31 +08:00
log . Trace ( "PullRequest[%d].UpdatePatch: patch saved" , pr . ID )
2015-10-25 03:10:22 -04:00
return nil
2015-10-24 03:36:47 -04:00
}
2016-02-19 22:16:26 -05:00
// PushToBaseRepo pushes commits from branches of head repository to
// corresponding branches of base repository.
// FIXME: Only push branches that are actually updates?
2016-02-04 19:00:42 +01:00
func ( pr * PullRequest ) PushToBaseRepo ( ) ( err error ) {
2016-03-04 15:43:01 -05:00
log . Trace ( "PushToBaseRepo[%d]: pushing commits to base repo 'refs/pull/%d/head'" , pr . BaseRepoID , pr . Index )
2016-02-04 19:00:42 +01:00
2016-02-19 22:16:26 -05:00
headRepoPath := pr . HeadRepo . RepoPath ( )
2020-03-08 19:09:31 +08:00
headGitRepo , err := git . Open ( headRepoPath )
2016-02-04 19:00:42 +01:00
if err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "open repository: %v" , err )
2016-02-04 19:00:42 +01:00
}
2020-03-08 19:09:31 +08:00
tmpRemote := fmt . Sprintf ( "tmp-pull-%d" , pr . ID )
2022-03-22 00:55:36 +08:00
if err = headGitRepo . RemoteAdd ( tmpRemote , pr . BaseRepo . RepoPath ( ) ) ; err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "add remote %q [repo_id: %d]: %v" , tmpRemote , pr . HeadRepoID , err )
2016-02-04 19:00:42 +01:00
}
2020-03-08 19:09:31 +08:00
// Make sure to remove the remote even if the push fails
defer func ( ) {
2022-03-22 00:55:36 +08:00
if err := headGitRepo . RemoteRemove ( tmpRemote ) ; err != nil {
2020-03-08 19:09:31 +08:00
log . Error ( "Failed to remove remote %q [repo_id: %d]: %v" , tmpRemote , pr . HeadRepoID , err )
}
} ( )
2016-03-04 16:53:03 -05:00
2020-03-08 19:09:31 +08:00
headRefspec := fmt . Sprintf ( "refs/pull/%d/head" , pr . Index )
headFile := filepath . Join ( pr . BaseRepo . RepoPath ( ) , headRefspec )
2026-01-21 22:22:07 -05:00
if osutil . Exist ( headFile ) {
2020-03-08 19:09:31 +08:00
err = os . Remove ( headFile )
if err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "remove head file [repo_id: %d]: %v" , pr . BaseRepoID , err )
2020-03-08 19:09:31 +08:00
}
}
2016-03-04 16:53:03 -05:00
2020-03-08 19:09:31 +08:00
err = headGitRepo . Push ( tmpRemote , fmt . Sprintf ( "%s:%s" , pr . HeadBranch , headRefspec ) )
if err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "push: %v" , err )
2016-02-04 19:00:42 +01:00
}
return nil
}
2015-10-25 03:10:22 -04:00
// AddToTaskQueue adds itself to pull request test task queue.
func ( pr * PullRequest ) AddToTaskQueue ( ) {
2015-10-24 14:48:11 -04:00
go PullRequestQueue . AddFunc ( pr . ID , func ( ) {
2025-04-14 18:00:07 -04:00
pr . Status = PullRequestStatusChecking
2015-10-24 14:48:11 -04:00
if err := pr . UpdateCols ( "status" ) ; err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "AddToTaskQueue.UpdateCols[%d].(add to queue): %v" , pr . ID , err )
2015-10-24 14:48:11 -04:00
}
} )
}
2015-10-24 03:36:47 -04:00
2016-08-14 03:32:24 -07:00
type PullRequestList [ ] * PullRequest
2026-01-24 15:43:22 +00:00
func ( prs PullRequestList ) loadAttributes ( db * gorm . DB ) ( err error ) {
2016-08-14 03:32:24 -07:00
if len ( prs ) == 0 {
return nil
}
2017-02-09 15:45:35 -05:00
// Load issues
2017-03-17 19:17:40 -04:00
set := make ( map [ int64 ] * Issue )
2016-08-14 03:32:24 -07:00
for i := range prs {
2017-03-17 19:17:40 -04:00
set [ prs [ i ] . IssueID ] = nil
}
issueIDs := make ( [ ] int64 , 0 , len ( prs ) )
for issueID := range set {
issueIDs = append ( issueIDs , issueID )
2016-08-14 03:32:24 -07:00
}
issues := make ( [ ] * Issue , 0 , len ( issueIDs ) )
2026-01-24 15:43:22 +00:00
if err = db . Where ( "id IN ?" , issueIDs ) . Find ( & issues ) . Error ; err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "find issues: %v" , err )
2016-08-14 03:32:24 -07:00
}
for i := range issues {
set [ issues [ i ] . ID ] = issues [ i ]
}
for i := range prs {
prs [ i ] . Issue = set [ prs [ i ] . IssueID ]
}
2017-02-09 15:45:35 -05:00
// Load attributes
for i := range prs {
2026-01-24 15:47:50 +00:00
if err = prs [ i ] . loadAttributes ( db ) ; err != nil {
2026-01-22 08:20:53 -05:00
return errors . Newf ( "loadAttributes [%d]: %v" , prs [ i ] . ID , err )
2017-02-09 15:45:35 -05:00
}
}
2016-08-14 03:32:24 -07:00
return nil
}
func ( prs PullRequestList ) LoadAttributes ( ) error {
2026-01-24 15:47:50 +00:00
return prs . loadAttributes ( db )
2016-08-14 03:32:24 -07:00
}
2015-10-24 14:48:11 -04:00
func addHeadRepoTasks ( prs [ ] * PullRequest ) {
2015-10-24 03:36:47 -04:00
for _ , pr := range prs {
2022-06-04 13:10:15 +08:00
if pr . HeadRepo == nil {
log . Trace ( "addHeadRepoTasks[%d]: missing head repository" , pr . ID )
continue
}
2015-10-24 14:48:11 -04:00
log . Trace ( "addHeadRepoTasks[%d]: composing new test task" , pr . ID )
2015-10-25 03:10:22 -04:00
if err := pr . UpdatePatch ( ) ; err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "UpdatePatch: %v" , err )
2015-10-24 03:36:47 -04:00
continue
2016-02-04 19:00:42 +01:00
} else if err := pr . PushToBaseRepo ( ) ; err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "PushToBaseRepo: %v" , err )
2016-02-04 19:00:42 +01:00
continue
2015-10-24 03:36:47 -04:00
}
2015-10-25 03:10:22 -04:00
pr . AddToTaskQueue ( )
2015-10-24 14:48:11 -04:00
}
}
// AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
// and generate new patch for testing as needed.
2016-08-14 03:32:24 -07:00
func AddTestPullRequestTask ( doer * User , repoID int64 , branch string , isSync bool ) {
log . Trace ( "AddTestPullRequestTask [head_repo_id: %d, head_branch: %s]: finding pull requests" , repoID , branch )
2015-10-24 14:48:11 -04:00
prs , err := GetUnmergedPullRequestsByHeadInfo ( repoID , branch )
if err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "Find pull requests [head_repo_id: %d, head_branch: %s]: %v" , repoID , branch , err )
2015-10-24 14:48:11 -04:00
return
}
2016-08-14 03:32:24 -07:00
if isSync {
if err = PullRequestList ( prs ) . LoadAttributes ( ) ; err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "PullRequestList.LoadAttributes: %v" , err )
2016-08-14 03:32:24 -07:00
}
if err == nil {
for _ , pr := range prs {
pr . Issue . PullRequest = pr
if err = pr . Issue . LoadAttributes ( ) ; err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "LoadAttributes: %v" , err )
2016-08-14 03:32:24 -07:00
continue
}
2025-04-14 18:00:07 -04:00
if err = PrepareWebhooks ( pr . Issue . Repo , HookEventTypePullRequest , & api . PullRequestPayload {
2016-08-14 03:32:24 -07:00
Action : api . HOOK_ISSUE_SYNCHRONIZED ,
Index : pr . Issue . Index ,
PullRequest : pr . Issue . PullRequest . APIFormat ( ) ,
2022-06-25 18:07:39 +08:00
Repository : pr . Issue . Repo . APIFormatLegacy ( nil ) ,
2016-08-14 03:32:24 -07:00
Sender : doer . APIFormat ( ) ,
} ) ; err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "PrepareWebhooks [pull_id: %v]: %v" , pr . ID , err )
2016-08-14 03:32:24 -07:00
continue
}
}
}
}
2015-10-24 14:48:11 -04:00
addHeadRepoTasks ( prs )
2016-08-14 03:32:24 -07:00
log . Trace ( "AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests" , repoID , branch )
2015-10-24 14:48:11 -04:00
prs , err = GetUnmergedPullRequestsByBaseInfo ( repoID , branch )
if err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "Find pull requests [base_repo_id: %d, base_branch: %s]: %v" , repoID , branch , err )
2015-10-24 14:48:11 -04:00
return
}
for _ , pr := range prs {
2015-10-25 03:10:22 -04:00
pr . AddToTaskQueue ( )
}
}
2021-05-19 10:42:09 +05:30
// checkAndUpdateStatus checks if pull request is possible to leaving checking status,
2015-10-25 03:10:22 -04:00
// and set to be either conflict or mergeable.
func ( pr * PullRequest ) checkAndUpdateStatus ( ) {
// Status is not changed to conflict means mergeable.
2025-04-14 18:00:07 -04:00
if pr . Status == PullRequestStatusChecking {
pr . Status = PullRequestStatusMergeable
2015-10-25 03:10:22 -04:00
}
2021-05-19 10:42:09 +05:30
// Make sure there is no waiting test to process before leaving the checking status.
2015-10-25 03:10:22 -04:00
if ! PullRequestQueue . Exist ( pr . ID ) {
if err := pr . UpdateCols ( "status" ) ; err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "Update[%d]: %v" , pr . ID , err )
2015-10-25 03:10:22 -04:00
}
2015-10-24 03:36:47 -04:00
}
}
// TestPullRequests checks and tests untested patches of pull requests.
// TODO: test more pull requests at same time.
func TestPullRequests ( ) {
prs := make ( [ ] * PullRequest , 0 , 10 )
2026-01-24 15:43:22 +00:00
db . Where ( "status = ?" , PullRequestStatusChecking ) . FindInBatches ( & prs , 100 , func ( tx * gorm . DB , batch int ) error {
for i := range prs {
pr := prs [ i ]
2015-10-24 03:36:47 -04:00
2016-12-22 01:28:06 -05:00
if err := pr . LoadAttributes ( ) ; err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "LoadAttributes: %v" , err )
2026-01-24 15:43:22 +00:00
continue
2015-10-24 03:36:47 -04:00
}
if err := pr . testPatch ( ) ; err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "testPatch: %v" , err )
2026-01-24 15:43:22 +00:00
continue
2015-10-24 03:36:47 -04:00
}
2026-01-24 15:43:22 +00:00
}
return nil
} )
2015-10-24 03:36:47 -04:00
// Update pull request status.
for _ , pr := range prs {
pr . checkAndUpdateStatus ( )
}
// Start listening on new test requests.
for prID := range PullRequestQueue . Queue ( ) {
log . Trace ( "TestPullRequests[%v]: processing test task" , prID )
PullRequestQueue . Remove ( prID )
pr , err := GetPullRequestByID ( com . StrTo ( prID ) . MustInt64 ( ) )
if err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "GetPullRequestByID[%s]: %v" , prID , err )
2015-10-24 03:36:47 -04:00
continue
} else if err = pr . testPatch ( ) ; err != nil {
2020-02-20 02:25:02 +08:00
log . Error ( "testPatch[%d]: %v" , pr . ID , err )
2015-10-24 03:36:47 -04:00
continue
}
pr . checkAndUpdateStatus ( )
}
}
func InitTestPullRequests ( ) {
go TestPullRequests ( )
}