2015-10-19 02:30:39 +03:00
// Copyright 2015 The Gogs Authors. All rights reserved.
2019-04-19 15:17:27 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2015-10-19 02:30:39 +03:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
2019-01-23 13:26:32 +03:00
"bufio"
2015-10-19 02:30:39 +03:00
"fmt"
2017-02-05 16:07:44 +03:00
"io/ioutil"
2015-10-19 02:30:39 +03:00
"os"
"path"
2016-12-29 17:59:52 +03:00
"path/filepath"
"strconv"
2015-10-19 02:30:39 +03:00
"strings"
"time"
2017-01-29 01:14:56 +03:00
"code.gitea.io/gitea/modules/base"
2019-03-27 12:33:00 +03:00
"code.gitea.io/gitea/modules/git"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
2019-05-11 13:21:34 +03:00
api "code.gitea.io/gitea/modules/structs"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/sync"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/timeutil"
2017-02-05 16:07:44 +03:00
2019-08-23 19:40:30 +03:00
"github.com/unknwon/com"
2019-10-17 12:26:49 +03:00
"xorm.io/xorm"
2015-10-19 02:30:39 +03:00
)
2016-11-28 18:31:06 +03:00
var pullRequestQueue = sync . NewUniqueQueue ( setting . Repository . PullRequestQueueLength )
2016-08-31 01:50:30 +03:00
2016-11-28 18:31:06 +03:00
// PullRequestType defines pull request type
2015-10-19 02:30:39 +03:00
type PullRequestType int
2016-11-28 18:31:06 +03:00
// Enumerate all the pull request types
2015-10-19 02:30:39 +03:00
const (
2016-11-07 18:37:32 +03:00
PullRequestGitea PullRequestType = iota
PullRequestGit
2015-10-19 02:30:39 +03:00
)
2016-11-28 18:31:06 +03:00
// PullRequestStatus defines pull request status
2015-10-19 02:30:39 +03:00
type PullRequestStatus int
2016-11-28 18:31:06 +03:00
// Enumerate all the pull request status
2015-10-19 02:30:39 +03:00
const (
2016-11-07 18:37:32 +03:00
PullRequestStatusConflict PullRequestStatus = iota
PullRequestStatusChecking
PullRequestStatusMergeable
2017-02-05 16:07:44 +03:00
PullRequestStatusManuallyMerged
2015-10-19 02:30:39 +03:00
)
// PullRequest represents relation between pull request and repositories.
type PullRequest struct {
2019-02-05 14:54:49 +03:00
ID int64 ` xorm:"pk autoincr" `
Type PullRequestType
Status PullRequestStatus
ConflictedFiles [ ] string ` xorm:"TEXT JSON" `
2015-10-19 02:30:39 +03:00
IssueID int64 ` xorm:"INDEX" `
Issue * Issue ` xorm:"-" `
Index int64
2018-12-11 14:28:37 +03:00
HeadRepoID int64 ` xorm:"INDEX" `
HeadRepo * Repository ` xorm:"-" `
BaseRepoID int64 ` xorm:"INDEX" `
BaseRepo * Repository ` xorm:"-" `
HeadBranch string
BaseBranch string
ProtectedBranch * ProtectedBranch ` xorm:"-" `
MergeBase string ` xorm:"VARCHAR(40)" `
2015-10-19 02:30:39 +03:00
2019-08-15 17:46:21 +03:00
HasMerged bool ` xorm:"INDEX" `
MergedCommitID string ` xorm:"VARCHAR(40)" `
MergerID int64 ` xorm:"INDEX" `
Merger * User ` xorm:"-" `
MergedUnix timeutil . TimeStamp ` xorm:"updated INDEX" `
2015-10-19 02:30:39 +03:00
}
2019-10-18 14:13:31 +03:00
// MustHeadUserName returns the HeadRepo's username if failed return blank
func ( pr * PullRequest ) MustHeadUserName ( ) string {
if err := pr . LoadHeadRepo ( ) ; err != nil {
log . Error ( "LoadHeadRepo: %v" , err )
return ""
}
return pr . HeadRepo . MustOwnerName ( )
}
2016-08-14 13:32:24 +03:00
// Note: don't try to get Issue because will end up recursive querying.
func ( pr * PullRequest ) loadAttributes ( e Engine ) ( err error ) {
if pr . HasMerged && pr . Merger == nil {
pr . Merger , err = getUserByID ( e , pr . MergerID )
if IsErrUserNotExist ( err ) {
pr . MergerID = - 1
pr . Merger = NewGhostUser ( )
} else if err != nil {
return fmt . Errorf ( "getUserByID [%d]: %v" , pr . MergerID , err )
}
}
return nil
}
2016-11-28 18:31:06 +03:00
// LoadAttributes loads pull request attributes from database
2016-08-14 13:32:24 +03:00
func ( pr * PullRequest ) LoadAttributes ( ) error {
return pr . loadAttributes ( x )
}
2019-09-18 08:39:45 +03:00
// LoadBaseRepo loads pull request base repository from database
func ( pr * PullRequest ) LoadBaseRepo ( ) error {
if pr . BaseRepo == nil {
2019-10-18 14:13:31 +03:00
if pr . HeadRepoID == pr . BaseRepoID && pr . HeadRepo != nil {
pr . BaseRepo = pr . HeadRepo
return nil
}
2019-09-18 08:39:45 +03:00
var repo Repository
if has , err := x . ID ( pr . BaseRepoID ) . Get ( & repo ) ; err != nil {
return err
} else if ! has {
return ErrRepoNotExist { ID : pr . BaseRepoID }
}
pr . BaseRepo = & repo
}
return nil
}
2019-10-18 14:13:31 +03:00
// LoadHeadRepo loads pull request head repository from database
func ( pr * PullRequest ) LoadHeadRepo ( ) error {
if pr . HeadRepo == nil {
if pr . HeadRepoID == pr . BaseRepoID && pr . BaseRepo != nil {
pr . HeadRepo = pr . BaseRepo
return nil
}
var repo Repository
if has , err := x . ID ( pr . HeadRepoID ) . Get ( & repo ) ; err != nil {
return err
} else if ! has {
return ErrRepoNotExist { ID : pr . BaseRepoID }
}
pr . HeadRepo = & repo
}
return nil
}
2016-11-28 18:31:06 +03:00
// LoadIssue loads issue information from database
2016-08-14 13:32:24 +03:00
func ( pr * PullRequest ) LoadIssue ( ) ( err error ) {
2017-02-27 03:42:55 +03:00
return pr . loadIssue ( x )
}
func ( pr * PullRequest ) loadIssue ( e Engine ) ( err error ) {
2016-08-16 20:19:09 +03:00
if pr . Issue != nil {
return nil
}
2017-02-27 03:42:55 +03:00
pr . Issue , err = getIssueByID ( e , pr . IssueID )
2016-08-14 13:32:24 +03:00
return err
}
2018-12-11 14:28:37 +03:00
// LoadProtectedBranch loads the protected branch of the base branch
func ( pr * PullRequest ) LoadProtectedBranch ( ) ( err error ) {
2018-12-17 20:35:21 +03:00
if pr . BaseRepo == nil {
if pr . BaseRepoID == 0 {
return nil
}
pr . BaseRepo , err = GetRepositoryByID ( pr . BaseRepoID )
if err != nil {
return
}
}
2018-12-11 14:28:37 +03:00
pr . ProtectedBranch , err = GetProtectedBranchBy ( pr . BaseRepo . ID , pr . BaseBranch )
return
}
2018-01-05 21:56:50 +03:00
// GetDefaultMergeMessage returns default message used when merging pull request
func ( pr * PullRequest ) GetDefaultMergeMessage ( ) string {
if pr . HeadRepo == nil {
var err error
pr . HeadRepo , err = GetRepositoryByID ( pr . HeadRepoID )
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "GetRepositoryById[%d]: %v" , pr . HeadRepoID , err )
2018-01-05 21:56:50 +03:00
return ""
}
}
2019-10-18 14:13:31 +03:00
return fmt . Sprintf ( "Merge branch '%s' of %s/%s into %s" , pr . HeadBranch , pr . MustHeadUserName ( ) , pr . HeadRepo . Name , pr . BaseBranch )
2018-01-05 21:56:50 +03:00
}
// GetDefaultSquashMessage returns default message used when squash and merging pull request
func ( pr * PullRequest ) GetDefaultSquashMessage ( ) string {
if err := pr . LoadIssue ( ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "LoadIssue: %v" , err )
2018-01-05 21:56:50 +03:00
return ""
}
return fmt . Sprintf ( "%s (#%d)" , pr . Issue . Title , pr . Issue . Index )
}
2018-01-19 09:18:51 +03:00
// GetGitRefName returns git ref for hidden pull request branch
func ( pr * PullRequest ) GetGitRefName ( ) string {
return fmt . Sprintf ( "refs/pull/%d/head" , pr . Index )
}
2016-11-28 18:31:06 +03:00
// APIFormat assumes following fields have been assigned with valid values:
2016-08-14 13:32:24 +03:00
// Required - Issue
// Optional - Merger
func ( pr * PullRequest ) APIFormat ( ) * api . PullRequest {
2018-12-13 18:55:43 +03:00
return pr . apiFormat ( x )
}
func ( pr * PullRequest ) apiFormat ( e Engine ) * api . PullRequest {
2016-12-05 14:17:39 +03:00
var (
2019-04-19 15:17:27 +03:00
baseBranch * git . Branch
headBranch * git . Branch
2016-12-05 14:17:39 +03:00
baseCommit * git . Commit
headCommit * git . Commit
err error
)
2018-12-13 18:55:43 +03:00
if err = pr . Issue . loadRepo ( e ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "loadRepo[%d]: %v" , pr . ID , err )
2018-12-13 18:55:43 +03:00
return nil
}
apiIssue := pr . Issue . apiFormat ( e )
2016-12-05 14:17:39 +03:00
if pr . BaseRepo == nil {
2018-12-13 18:55:43 +03:00
pr . BaseRepo , err = getRepositoryByID ( e , pr . BaseRepoID )
2016-12-05 14:17:39 +03:00
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "GetRepositoryById[%d]: %v" , pr . ID , err )
2016-12-05 14:17:39 +03:00
return nil
}
}
if pr . HeadRepo == nil {
2018-12-13 18:55:43 +03:00
pr . HeadRepo , err = getRepositoryByID ( e , pr . HeadRepoID )
2016-12-05 14:17:39 +03:00
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "GetRepositoryById[%d]: %v" , pr . ID , err )
2016-12-05 14:17:39 +03:00
return nil
}
}
2018-12-13 18:55:43 +03:00
2019-06-12 22:41:28 +03:00
if err = pr . Issue . loadRepo ( e ) ; err != nil {
log . Error ( "pr.Issue.loadRepo[%d]: %v" , pr . ID , err )
return nil
}
2018-12-13 18:55:43 +03:00
2016-08-14 13:32:24 +03:00
apiPullRequest := & api . PullRequest {
ID : pr . ID ,
2019-07-08 10:32:46 +03:00
URL : pr . Issue . HTMLURL ( ) ,
2016-08-14 13:32:24 +03:00
Index : pr . Index ,
2016-08-16 20:19:09 +03:00
Poster : apiIssue . Poster ,
2016-08-14 13:32:24 +03:00
Title : apiIssue . Title ,
Body : apiIssue . Body ,
Labels : apiIssue . Labels ,
Milestone : apiIssue . Milestone ,
Assignee : apiIssue . Assignee ,
2018-05-09 19:29:04 +03:00
Assignees : apiIssue . Assignees ,
2016-08-16 20:19:09 +03:00
State : apiIssue . State ,
2016-08-14 13:32:24 +03:00
Comments : apiIssue . Comments ,
2016-08-16 20:19:09 +03:00
HTMLURL : pr . Issue . HTMLURL ( ) ,
2016-12-02 14:10:39 +03:00
DiffURL : pr . Issue . DiffURL ( ) ,
PatchURL : pr . Issue . PatchURL ( ) ,
2016-08-14 13:32:24 +03:00
HasMerged : pr . HasMerged ,
2016-12-02 14:10:39 +03:00
MergeBase : pr . MergeBase ,
2018-05-01 22:05:28 +03:00
Deadline : apiIssue . Deadline ,
2017-12-11 07:37:04 +03:00
Created : pr . Issue . CreatedUnix . AsTimePtr ( ) ,
Updated : pr . Issue . UpdatedUnix . AsTimePtr ( ) ,
2016-08-14 13:32:24 +03:00
}
2019-07-08 10:32:46 +03:00
baseBranch , err = pr . BaseRepo . GetBranch ( pr . BaseBranch )
if err != nil {
if git . IsErrBranchNotExist ( err ) {
apiPullRequest . Base = nil
} else {
log . Error ( "GetBranch[%s]: %v" , pr . BaseBranch , err )
return nil
}
} else {
apiBaseBranchInfo := & api . PRBranchInfo {
Name : pr . BaseBranch ,
Ref : pr . BaseBranch ,
RepoID : pr . BaseRepoID ,
Repository : pr . BaseRepo . innerAPIFormat ( e , AccessModeNone , false ) ,
}
baseCommit , err = baseBranch . GetCommit ( )
if err != nil {
if git . IsErrNotExist ( err ) {
apiBaseBranchInfo . Sha = ""
} else {
log . Error ( "GetCommit[%s]: %v" , baseBranch . Name , err )
return nil
}
} else {
apiBaseBranchInfo . Sha = baseCommit . ID . String ( )
}
apiPullRequest . Base = apiBaseBranchInfo
}
headBranch , err = pr . HeadRepo . GetBranch ( pr . HeadBranch )
if err != nil {
if git . IsErrBranchNotExist ( err ) {
apiPullRequest . Head = nil
} else {
log . Error ( "GetBranch[%s]: %v" , pr . HeadBranch , err )
return nil
}
} else {
apiHeadBranchInfo := & api . PRBranchInfo {
Name : pr . HeadBranch ,
Ref : pr . HeadBranch ,
RepoID : pr . HeadRepoID ,
Repository : pr . HeadRepo . innerAPIFormat ( e , AccessModeNone , false ) ,
}
headCommit , err = headBranch . GetCommit ( )
if err != nil {
if git . IsErrNotExist ( err ) {
apiHeadBranchInfo . Sha = ""
} else {
log . Error ( "GetCommit[%s]: %v" , headBranch . Name , err )
return nil
}
} else {
apiHeadBranchInfo . Sha = headCommit . ID . String ( )
}
apiPullRequest . Head = apiHeadBranchInfo
}
2016-08-14 13:32:24 +03:00
2016-11-07 18:37:32 +03:00
if pr . Status != PullRequestStatusChecking {
2018-08-13 22:04:39 +03:00
mergeable := pr . Status != PullRequestStatusConflict && ! pr . IsWorkInProgress ( )
2016-11-29 11:25:47 +03:00
apiPullRequest . Mergeable = mergeable
2016-08-14 13:32:24 +03:00
}
if pr . HasMerged {
2017-12-11 07:37:04 +03:00
apiPullRequest . Merged = pr . MergedUnix . AsTimePtr ( )
2016-08-14 13:32:24 +03:00
apiPullRequest . MergedCommitID = & pr . MergedCommitID
apiPullRequest . MergedBy = pr . Merger . APIFormat ( )
}
return apiPullRequest
}
2015-10-26 01:35:27 +03:00
func ( pr * PullRequest ) getHeadRepo ( e Engine ) ( err error ) {
pr . HeadRepo , err = getRepositoryByID ( e , pr . HeadRepoID )
2015-10-24 10:36:47 +03:00
if err != nil && ! IsErrRepoNotExist ( err ) {
2015-10-26 16:16:24 +03:00
return fmt . Errorf ( "getRepositoryByID(head): %v" , err )
2015-10-24 10:36:47 +03:00
}
return nil
}
2016-11-28 18:31:06 +03:00
// GetHeadRepo loads the head repository
2016-08-14 13:32:24 +03:00
func ( pr * PullRequest ) GetHeadRepo ( ) error {
2015-10-26 01:35:27 +03:00
return pr . getHeadRepo ( x )
}
2016-11-28 18:31:06 +03:00
// GetBaseRepo loads the target repository
2015-10-24 10:36:47 +03:00
func ( pr * PullRequest ) GetBaseRepo ( ) ( err error ) {
if pr . BaseRepo != nil {
return nil
}
pr . BaseRepo , err = GetRepositoryByID ( pr . BaseRepoID )
if err != nil {
2015-10-26 16:16:24 +03:00
return fmt . Errorf ( "GetRepositoryByID(base): %v" , err )
2015-10-24 10:36:47 +03:00
}
return nil
}
// IsChecking returns true if this pull request is still checking conflict.
func ( pr * PullRequest ) IsChecking ( ) bool {
2016-11-07 18:37:32 +03:00
return pr . Status == PullRequestStatusChecking
2015-10-24 10:36:47 +03:00
}
2015-10-19 02:30:39 +03:00
// CanAutoMerge returns true if this pull request can be merged automatically.
func ( pr * PullRequest ) CanAutoMerge ( ) bool {
2016-11-07 18:37:32 +03:00
return pr . Status == PullRequestStatusMergeable
2015-10-19 02:30:39 +03:00
}
2019-04-02 22:54:29 +03:00
// GetLastCommitStatus returns the last commit status for this pull request.
func ( pr * PullRequest ) GetLastCommitStatus ( ) ( status * CommitStatus , err error ) {
if err = pr . GetHeadRepo ( ) ; err != nil {
return nil , err
}
2019-04-26 06:03:39 +03:00
if pr . HeadRepo == nil {
return nil , ErrPullRequestHeadRepoMissing { pr . ID , pr . HeadRepoID }
}
2019-04-02 22:54:29 +03:00
headGitRepo , err := git . OpenRepository ( pr . HeadRepo . RepoPath ( ) )
if err != nil {
return nil , err
}
lastCommitID , err := headGitRepo . GetBranchCommitID ( pr . HeadBranch )
if err != nil {
return nil , err
}
2019-09-30 04:07:43 +03:00
err = pr . LoadBaseRepo ( )
if err != nil {
return nil , err
}
statusList , err := GetLatestCommitStatus ( pr . BaseRepo , lastCommitID , 0 )
2019-04-02 22:54:29 +03:00
if err != nil {
return nil , err
}
return CalcCommitStatus ( statusList ) , nil
}
2018-01-05 21:56:50 +03:00
// MergeStyle represents the approach to merge commits into base branch.
type MergeStyle string
const (
// MergeStyleMerge create merge commit
MergeStyleMerge MergeStyle = "merge"
// MergeStyleRebase rebase before merging
MergeStyleRebase MergeStyle = "rebase"
2018-12-27 13:27:08 +03:00
// MergeStyleRebaseMerge rebase before merging with merge commit (--no-ff)
MergeStyleRebaseMerge MergeStyle = "rebase-merge"
2018-01-05 21:56:50 +03:00
// MergeStyleSquash squash commits into single commit before merging
MergeStyleSquash MergeStyle = "squash"
)
2018-03-13 06:46:14 +03:00
// CheckUserAllowedToMerge checks whether the user is allowed to merge
func ( pr * PullRequest ) CheckUserAllowedToMerge ( doer * User ) ( err error ) {
if doer == nil {
return ErrNotAllowedToMerge {
"Not signed in" ,
}
}
if pr . BaseRepo == nil {
if err = pr . GetBaseRepo ( ) ; err != nil {
return fmt . Errorf ( "GetBaseRepo: %v" , err )
}
}
2018-12-11 14:28:37 +03:00
if protected , err := pr . BaseRepo . IsProtectedBranchForMerging ( pr , pr . BaseBranch , doer ) ; err != nil {
2018-03-13 06:46:14 +03:00
return fmt . Errorf ( "IsProtectedBranch: %v" , err )
} else if protected {
return ErrNotAllowedToMerge {
"The branch is protected" ,
}
}
return nil
}
2019-06-22 20:35:34 +03:00
// SetMerged sets a pull request to merged and closes the corresponding issue
func ( pr * PullRequest ) SetMerged ( ) ( err error ) {
2017-02-05 16:07:44 +03:00
if pr . HasMerged {
return fmt . Errorf ( "PullRequest[%d] already merged" , pr . Index )
}
2017-12-11 07:37:04 +03:00
if pr . MergedCommitID == "" || pr . MergedUnix == 0 || pr . Merger == nil {
2017-02-05 16:07:44 +03:00
return fmt . Errorf ( "Unable to merge PullRequest[%d], some required fields are empty" , pr . Index )
}
pr . HasMerged = true
sess := x . NewSession ( )
2017-06-21 03:57:05 +03:00
defer sess . Close ( )
2017-02-05 16:07:44 +03:00
if err = sess . Begin ( ) ; err != nil {
return err
}
2017-02-27 03:42:55 +03:00
if err = pr . loadIssue ( sess ) ; err != nil {
2017-02-05 16:07:44 +03:00
return err
}
2017-02-27 03:42:55 +03:00
if err = pr . Issue . loadRepo ( sess ) ; err != nil {
return err
}
if err = pr . Issue . Repo . getOwner ( sess ) ; err != nil {
return err
2017-02-05 16:07:44 +03:00
}
2018-12-13 18:55:43 +03:00
if err = pr . Issue . changeStatus ( sess , pr . Merger , true ) ; err != nil {
2017-02-05 16:07:44 +03:00
return fmt . Errorf ( "Issue.changeStatus: %v" , err )
}
2017-10-06 00:25:40 +03:00
if _ , err = sess . ID ( pr . ID ) . Cols ( "has_merged, status, merged_commit_id, merger_id, merged_unix" ) . Update ( pr ) ; err != nil {
2017-02-05 16:07:44 +03:00
return fmt . Errorf ( "update pull request: %v" , err )
}
if err = sess . Commit ( ) ; err != nil {
return fmt . Errorf ( "Commit: %v" , err )
}
return nil
}
// manuallyMerged checks if a pull request got manually merged
// When a pull request got manually merged mark the pull request as merged
func ( pr * PullRequest ) manuallyMerged ( ) bool {
commit , err := pr . getMergeCommit ( )
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "PullRequest[%d].getMergeCommit: %v" , pr . ID , err )
2017-02-05 16:07:44 +03:00
return false
}
if commit != nil {
pr . MergedCommitID = commit . ID . String ( )
2019-08-15 17:46:21 +03:00
pr . MergedUnix = timeutil . TimeStamp ( commit . Author . When . Unix ( ) )
2017-02-05 16:07:44 +03:00
pr . Status = PullRequestStatusManuallyMerged
merger , _ := GetUserByEmail ( commit . Author . Email )
// 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 ( x ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "BaseRepo.getOwner[%d]: %v" , pr . ID , err )
2017-02-05 16:07:44 +03:00
return false
}
}
merger = pr . BaseRepo . Owner
}
pr . Merger = merger
pr . MergerID = merger . ID
2019-06-22 20:35:34 +03:00
if err = pr . SetMerged ( ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "PullRequest[%d].setMerged : %v" , pr . ID , err )
2017-02-05 16:07:44 +03:00
return false
}
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
}
// getMergeCommit checks if a pull request got merged
// Returns the git.Commit of the pull request if merged
func ( pr * PullRequest ) getMergeCommit ( ) ( * git . Commit , error ) {
if pr . BaseRepo == nil {
var err error
pr . BaseRepo , err = GetRepositoryByID ( pr . BaseRepoID )
if err != nil {
return nil , fmt . Errorf ( "GetRepositoryByID: %v" , err )
}
}
indexTmpPath := filepath . Join ( os . TempDir ( ) , "gitea-" + pr . BaseRepo . Name + "-" + strconv . Itoa ( time . Now ( ) . Nanosecond ( ) ) )
defer os . Remove ( indexTmpPath )
2018-01-19 09:18:51 +03:00
headFile := pr . GetGitRefName ( )
2017-02-05 16:07:44 +03:00
// Check if a pull request is merged into BaseBranch
_ , stderr , err := process . GetManager ( ) . ExecDirEnv ( - 1 , "" , fmt . Sprintf ( "isMerged (git merge-base --is-ancestor): %d" , pr . BaseRepo . ID ) ,
[ ] string { "GIT_INDEX_FILE=" + indexTmpPath , "GIT_DIR=" + pr . BaseRepo . RepoPath ( ) } ,
2019-06-26 21:15:26 +03:00
git . GitExecutable , "merge-base" , "--is-ancestor" , headFile , pr . BaseBranch )
2017-02-05 16:07:44 +03:00
if err != nil {
// Errors are signaled by a non-zero status that is not 1
2017-06-30 13:29:58 +03:00
if strings . Contains ( err . Error ( ) , "exit status 1" ) {
2017-02-05 16:07:44 +03:00
return nil , nil
}
return nil , fmt . Errorf ( "git merge-base --is-ancestor: %v %v" , stderr , err )
}
2017-06-01 03:51:24 +03:00
commitIDBytes , err := ioutil . ReadFile ( pr . BaseRepo . RepoPath ( ) + "/" + headFile )
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
2017-02-05 16:07:44 +03:00
// Get the commit from BaseBranch where the pull request got merged
mergeCommit , stderr , err := process . GetManager ( ) . ExecDirEnv ( - 1 , "" , fmt . Sprintf ( "isMerged (git rev-list --ancestry-path --merges --reverse): %d" , pr . BaseRepo . ID ) ,
[ ] string { "GIT_INDEX_FILE=" + indexTmpPath , "GIT_DIR=" + pr . BaseRepo . RepoPath ( ) } ,
2019-06-26 21:15:26 +03:00
git . GitExecutable , "rev-list" , "--ancestry-path" , "--merges" , "--reverse" , cmd )
2017-02-05 16:07:44 +03:00
if err != nil {
return nil , fmt . Errorf ( "git rev-list --ancestry-path --merges --reverse: %v %v" , stderr , err )
2017-06-22 04:06:57 +03:00
} else if len ( mergeCommit ) < 40 {
// PR was fast-forwarded, so just use last commit of PR
mergeCommit = commitID [ : 40 ]
2017-02-05 16:07:44 +03:00
}
gitRepo , err := git . OpenRepository ( pr . BaseRepo . RepoPath ( ) )
if err != nil {
return nil , fmt . Errorf ( "OpenRepository: %v" , err )
}
commit , err := gitRepo . GetCommit ( mergeCommit [ : 40 ] )
if err != nil {
return nil , fmt . Errorf ( "GetCommit: %v" , err )
}
return commit , nil
}
2016-12-01 04:05:32 +03:00
// patchConflicts is a list of conflict description from Git.
2015-10-24 21:48:11 +03:00
var patchConflicts = [ ] string {
"patch does not apply" ,
"already exists in working directory" ,
2015-10-30 03:40:57 +03:00
"unrecognized input" ,
2016-02-03 20:28:03 +03:00
"error:" ,
2015-10-24 21:48:11 +03:00
}
2016-12-01 04:05:32 +03:00
// testPatch checks if patch can be merged to base repository without conflict.
2019-01-14 05:29:58 +03:00
func ( pr * PullRequest ) testPatch ( e Engine ) ( err error ) {
2015-10-24 10:36:47 +03:00
if pr . BaseRepo == nil {
2019-01-14 05:29:58 +03:00
pr . BaseRepo , err = getRepositoryByID ( e , pr . BaseRepoID )
2015-10-24 10:36:47 +03:00
if err != nil {
return fmt . Errorf ( "GetRepositoryByID: %v" , err )
}
}
2019-01-14 05:29:58 +03:00
patchPath , err := pr . BaseRepo . patchPath ( e , pr . Index )
2015-10-24 10:36:47 +03:00
if err != nil {
return fmt . Errorf ( "BaseRepo.PatchPath: %v" , err )
}
2017-01-05 03:50:34 +03:00
// Fast fail if patch does not exist, this assumes data is corrupted.
2015-10-25 10:10:22 +03:00
if ! com . IsFile ( patchPath ) {
2017-01-05 03:50:34 +03:00
log . Trace ( "PullRequest[%d].testPatch: ignored corrupted data" , pr . ID )
2015-10-25 10:10:22 +03:00
return nil
}
2016-08-30 15:07:50 +03:00
repoWorkingPool . CheckIn ( com . ToStr ( pr . BaseRepoID ) )
defer repoWorkingPool . CheckOut ( com . ToStr ( pr . BaseRepoID ) )
2016-02-03 20:28:03 +03:00
log . Trace ( "PullRequest[%d].testPatch (patchPath): %s" , pr . ID , patchPath )
2015-10-24 10:36:47 +03:00
2016-12-29 17:59:52 +03:00
pr . Status = PullRequestStatusChecking
indexTmpPath := filepath . Join ( os . TempDir ( ) , "gitea-" + pr . BaseRepo . Name + "-" + strconv . Itoa ( time . Now ( ) . Nanosecond ( ) ) )
defer os . Remove ( indexTmpPath )
2016-12-09 01:08:28 +03:00
2016-12-29 17:59:52 +03:00
var stderr string
2017-01-17 08:58:58 +03:00
_ , stderr , err = process . GetManager ( ) . ExecDirEnv ( - 1 , "" , fmt . Sprintf ( "testPatch (git read-tree): %d" , pr . BaseRepo . ID ) ,
2016-12-29 17:59:52 +03:00
[ ] string { "GIT_DIR=" + pr . BaseRepo . RepoPath ( ) , "GIT_INDEX_FILE=" + indexTmpPath } ,
2019-06-26 21:15:26 +03:00
git . GitExecutable , "read-tree" , pr . BaseBranch )
2016-12-29 17:59:52 +03:00
if err != nil {
return fmt . Errorf ( "git read-tree --index-output=%s %s: %v - %s" , indexTmpPath , pr . BaseBranch , err , stderr )
2015-10-24 10:36:47 +03:00
}
2019-01-14 05:29:58 +03:00
prUnit , err := pr . BaseRepo . getUnit ( e , UnitTypePullRequests )
2018-01-05 21:56:50 +03:00
if err != nil {
return err
}
prConfig := prUnit . PullRequestsConfig ( )
args := [ ] string { "apply" , "--check" , "--cached" }
if prConfig . IgnoreWhitespaceConflicts {
args = append ( args , "--ignore-whitespace" )
}
args = append ( args , patchPath )
2019-02-05 14:54:49 +03:00
pr . ConflictedFiles = [ ] string { }
2018-01-05 21:56:50 +03:00
2017-01-17 08:58:58 +03:00
_ , stderr , err = process . GetManager ( ) . ExecDirEnv ( - 1 , "" , fmt . Sprintf ( "testPatch (git apply --check): %d" , pr . BaseRepo . ID ) ,
2016-12-29 17:59:52 +03:00
[ ] string { "GIT_INDEX_FILE=" + indexTmpPath , "GIT_DIR=" + pr . BaseRepo . RepoPath ( ) } ,
2019-06-26 21:15:26 +03:00
git . GitExecutable , args ... )
2015-10-24 10:36:47 +03:00
if err != nil {
2015-10-24 21:48:11 +03:00
for i := range patchConflicts {
if strings . Contains ( stderr , patchConflicts [ i ] ) {
2019-08-20 20:43:00 +03:00
log . Trace ( "PullRequest[%d].testPatch (apply): has conflict: %s" , pr . ID , stderr )
2019-02-05 14:54:49 +03:00
const prefix = "error: patch failed:"
2016-11-07 18:37:32 +03:00
pr . Status = PullRequestStatusConflict
2019-02-05 14:54:49 +03:00
pr . ConflictedFiles = make ( [ ] string , 0 , 5 )
scanner := bufio . NewScanner ( strings . NewReader ( stderr ) )
for scanner . Scan ( ) {
line := scanner . Text ( )
if strings . HasPrefix ( line , prefix ) {
2019-06-15 17:22:26 +03:00
var found bool
var filepath = strings . TrimSpace ( strings . Split ( line [ len ( prefix ) : ] , ":" ) [ 0 ] )
for _ , f := range pr . ConflictedFiles {
if f == filepath {
found = true
break
}
}
if ! found {
pr . ConflictedFiles = append ( pr . ConflictedFiles , filepath )
}
2019-02-05 14:54:49 +03:00
}
// only list 10 conflicted files
if len ( pr . ConflictedFiles ) >= 10 {
break
}
}
if len ( pr . ConflictedFiles ) > 0 {
log . Trace ( "Found %d files conflicted: %v" , len ( pr . ConflictedFiles ) , pr . ConflictedFiles )
}
2015-10-24 21:48:11 +03:00
return nil
}
2015-10-24 10:36:47 +03:00
}
2015-10-24 21:48:11 +03:00
return fmt . Errorf ( "git apply --check: %v - %s" , err , stderr )
2015-10-24 10:36:47 +03:00
}
return nil
}
2015-10-19 02:30:39 +03:00
// NewPullRequest creates new pull request with labels for repository.
2018-05-09 19:29:04 +03:00
func NewPullRequest ( repo * Repository , pull * Issue , labelIDs [ ] int64 , uuids [ ] string , pr * PullRequest , patch [ ] byte , assigneeIDs [ ] int64 ) ( err error ) {
2019-10-03 01:28:30 +03:00
// Retry several times in case INSERT fails due to duplicate key for (repo_id, index); see #7887
i := 0
for {
if err = newPullRequestAttempt ( repo , pull , labelIDs , uuids , pr , patch , assigneeIDs ) ; err == nil {
return nil
}
if ! IsErrNewIssueInsert ( err ) {
return err
}
if i ++ ; i == issueMaxDupIndexAttempts {
break
}
log . Error ( "NewPullRequest: error attempting to insert the new issue; will retry. Original error: %v" , err )
}
return fmt . Errorf ( "NewPullRequest: too many errors attempting to insert the new issue. Last error was: %v" , err )
}
func newPullRequestAttempt ( repo * Repository , pull * Issue , labelIDs [ ] int64 , uuids [ ] string , pr * PullRequest , patch [ ] byte , assigneeIDs [ ] int64 ) ( err error ) {
2015-10-19 02:30:39 +03:00
sess := x . NewSession ( )
2017-06-21 03:57:05 +03:00
defer sess . Close ( )
2015-10-19 02:30:39 +03:00
if err = sess . Begin ( ) ; err != nil {
return err
}
2017-02-01 05:36:08 +03:00
if err = newIssue ( sess , pull . Poster , NewIssueOptions {
2016-08-16 04:40:32 +03:00
Repo : repo ,
Issue : pull ,
2017-03-01 04:08:45 +03:00
LabelIDs : labelIDs ,
2016-08-16 04:40:32 +03:00
Attachments : uuids ,
IsPull : true ,
2018-05-09 19:29:04 +03:00
AssigneeIDs : assigneeIDs ,
2016-08-16 04:40:32 +03:00
} ) ; err != nil {
2019-10-03 01:28:30 +03:00
if IsErrUserDoesNotHaveAccessToRepo ( err ) || IsErrNewIssueInsert ( err ) {
2018-05-09 19:29:04 +03:00
return err
}
2015-10-19 02:30:39 +03:00
return fmt . Errorf ( "newIssue: %v" , err )
}
2015-10-24 21:48:11 +03:00
pr . Index = pull . Index
2015-10-24 10:36:47 +03:00
pr . BaseRepo = repo
2019-08-20 20:43:00 +03:00
pr . Status = PullRequestStatusChecking
if len ( patch ) > 0 {
if err = repo . savePatch ( sess , pr . Index , patch ) ; err != nil {
return fmt . Errorf ( "SavePatch: %v" , err )
}
if err = pr . testPatch ( sess ) ; err != nil {
return fmt . Errorf ( "testPatch: %v" , err )
}
2015-10-19 02:30:39 +03:00
}
2016-08-14 13:32:24 +03:00
// No conflict appears after test means mergeable.
2016-11-07 18:37:32 +03:00
if pr . Status == PullRequestStatusChecking {
pr . Status = PullRequestStatusMergeable
2015-10-19 02:30:39 +03:00
}
pr . IssueID = pull . ID
if _ , err = sess . Insert ( pr ) ; err != nil {
return fmt . Errorf ( "insert pull repo: %v" , err )
}
2016-07-15 19:36:39 +03:00
if err = sess . Commit ( ) ; err != nil {
return fmt . Errorf ( "Commit: %v" , err )
}
return nil
2015-10-19 02:30:39 +03:00
}
2016-12-02 14:10:39 +03:00
// PullRequestsOptions holds the options for PRs
type PullRequestsOptions struct {
Page int
State string
SortType string
Labels [ ] string
MilestoneID int64
}
2017-01-01 21:15:09 +03:00
func listPullRequestStatement ( baseRepoID int64 , opts * PullRequestsOptions ) ( * xorm . Session , error ) {
2016-12-02 14:10:39 +03:00
sess := x . Where ( "pull_request.base_repo_id=?" , baseRepoID )
sess . Join ( "INNER" , "issue" , "pull_request.issue_id = issue.id" )
switch opts . State {
case "closed" , "open" :
sess . And ( "issue.is_closed=?" , opts . State == "closed" )
}
2017-01-01 21:15:09 +03:00
if labelIDs , err := base . StringsToInt64s ( opts . Labels ) ; err != nil {
return nil , err
} else if len ( labelIDs ) > 0 {
sess . Join ( "INNER" , "issue_label" , "issue.id = issue_label.issue_id" ) .
In ( "issue_label.label_id" , labelIDs )
}
if opts . MilestoneID > 0 {
sess . And ( "issue.milestone_id=?" , opts . MilestoneID )
}
return sess , nil
2016-12-02 14:10:39 +03:00
}
// PullRequests returns all pull requests for a base Repo by the given conditions
func PullRequests ( baseRepoID int64 , opts * PullRequestsOptions ) ( [ ] * PullRequest , int64 , error ) {
if opts . Page <= 0 {
opts . Page = 1
}
2017-01-01 21:15:09 +03:00
countSession , err := listPullRequestStatement ( baseRepoID , opts )
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "listPullRequestStatement: %v" , err )
2017-01-01 21:15:09 +03:00
return nil , 0 , err
}
2016-12-02 14:10:39 +03:00
maxResults , err := countSession . Count ( new ( PullRequest ) )
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "Count PRs: %v" , err )
2016-12-02 14:10:39 +03:00
return nil , maxResults , err
}
prs := make ( [ ] * PullRequest , 0 , ItemsPerPage )
2017-01-01 21:15:09 +03:00
findSession , err := listPullRequestStatement ( baseRepoID , opts )
2017-06-30 19:50:57 +03:00
sortIssuesSession ( findSession , opts . SortType )
2017-01-01 21:15:09 +03:00
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "listPullRequestStatement: %v" , err )
2017-01-01 21:15:09 +03:00
return nil , maxResults , err
}
2016-12-02 14:10:39 +03:00
findSession . Limit ( ItemsPerPage , ( opts . Page - 1 ) * ItemsPerPage )
return prs , maxResults , findSession . Find ( & prs )
}
2017-01-05 03:50:34 +03:00
// GetUnmergedPullRequest returns a pull request that is open and has not been merged
2015-10-19 02:30:39 +03:00
// by given head/base and repo/branch.
func GetUnmergedPullRequest ( headRepoID , baseRepoID int64 , headBranch , baseBranch string ) ( * PullRequest , error ) {
pr := new ( PullRequest )
2016-11-10 18:16:32 +03:00
has , err := x .
Where ( "head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?" ,
headRepoID , headBranch , baseRepoID , baseBranch , false , false ) .
Join ( "INNER" , "issue" , "issue.id=pull_request.issue_id" ) .
Get ( pr )
2015-10-19 02:30:39 +03:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrPullRequestNotExist { 0 , 0 , headRepoID , baseRepoID , headBranch , baseBranch }
}
return pr , nil
}
2017-01-05 03:50:34 +03:00
// GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged
2015-10-24 10:36:47 +03:00
// by given head information (repo and branch).
2015-10-24 21:48:11 +03:00
func GetUnmergedPullRequestsByHeadInfo ( repoID int64 , branch string ) ( [ ] * PullRequest , error ) {
2015-10-24 10:36:47 +03:00
prs := make ( [ ] * PullRequest , 0 , 2 )
2016-11-10 18:16:32 +03:00
return prs , x .
Where ( "head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ?" ,
repoID , branch , false , false ) .
Join ( "INNER" , "issue" , "issue.id = pull_request.issue_id" ) .
Find ( & prs )
2015-10-24 21:48:11 +03:00
}
2019-06-27 17:15:30 +03:00
// GetLatestPullRequestByHeadInfo returns the latest pull request (regardless of its status)
// by given head information (repo and branch).
func GetLatestPullRequestByHeadInfo ( repoID int64 , branch string ) ( * PullRequest , error ) {
pr := new ( PullRequest )
has , err := x .
Where ( "head_repo_id = ? AND head_branch = ?" , repoID , branch ) .
OrderBy ( "id DESC" ) .
Get ( pr )
if ! has {
return nil , err
}
return pr , err
}
2017-01-05 03:50:34 +03:00
// GetUnmergedPullRequestsByBaseInfo returns all pull requests that are open and has not been merged
2015-10-24 21:48:11 +03:00
// by given base information (repo and branch).
func GetUnmergedPullRequestsByBaseInfo ( repoID int64 , branch string ) ( [ ] * PullRequest , error ) {
prs := make ( [ ] * PullRequest , 0 , 2 )
2016-11-10 18:16:32 +03:00
return prs , x .
Where ( "base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?" ,
repoID , branch , false , false ) .
Join ( "INNER" , "issue" , "issue.id=pull_request.issue_id" ) .
Find ( & prs )
2015-10-24 10:36:47 +03:00
}
2016-12-02 14:10:39 +03:00
// GetPullRequestByIndex returns a pull request by the given index
func GetPullRequestByIndex ( repoID int64 , index int64 ) ( * PullRequest , error ) {
pr := & PullRequest {
BaseRepoID : repoID ,
Index : index ,
}
has , err := x . Get ( pr )
if err != nil {
return nil , err
} else if ! has {
2017-01-01 21:15:09 +03:00
return nil , ErrPullRequestNotExist { 0 , 0 , 0 , repoID , "" , "" }
2016-12-02 14:10:39 +03:00
}
2016-12-05 14:17:39 +03:00
if err = pr . LoadAttributes ( ) ; err != nil {
return nil , err
}
if err = pr . LoadIssue ( ) ; err != nil {
return nil , err
}
2016-12-02 14:10:39 +03:00
return pr , nil
}
2016-08-14 13:32:24 +03:00
func getPullRequestByID ( e Engine , id int64 ) ( * PullRequest , error ) {
2015-10-24 10:36:47 +03:00
pr := new ( PullRequest )
2017-10-05 07:43:04 +03:00
has , err := e . ID ( id ) . Get ( pr )
2015-10-24 10:36:47 +03:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrPullRequestNotExist { id , 0 , 0 , 0 , "" , "" }
}
2016-08-14 13:32:24 +03:00
return pr , pr . loadAttributes ( e )
2015-10-24 10:36:47 +03:00
}
2016-08-14 13:32:24 +03:00
// GetPullRequestByID returns a pull request by given ID.
func GetPullRequestByID ( id int64 ) ( * PullRequest , error ) {
return getPullRequestByID ( x , id )
}
func getPullRequestByIssueID ( e Engine , issueID int64 ) ( * PullRequest , error ) {
2015-10-22 21:47:24 +03:00
pr := & PullRequest {
IssueID : issueID ,
}
2016-08-14 13:32:24 +03:00
has , err := e . Get ( pr )
2015-10-19 02:30:39 +03:00
if err != nil {
return nil , err
} else if ! has {
2015-10-22 21:47:24 +03:00
return nil , ErrPullRequestNotExist { 0 , issueID , 0 , 0 , "" , "" }
2015-10-19 02:30:39 +03:00
}
2016-08-14 13:32:24 +03:00
return pr , pr . loadAttributes ( e )
}
// GetPullRequestByIssueID returns pull request by given issue ID.
func GetPullRequestByIssueID ( issueID int64 ) ( * PullRequest , error ) {
return getPullRequestByIssueID ( x , issueID )
2015-10-19 02:30:39 +03:00
}
2015-10-24 10:36:47 +03:00
// Update updates all fields of pull request.
func ( pr * PullRequest ) Update ( ) error {
2017-10-05 07:43:04 +03:00
_ , err := x . ID ( pr . ID ) . AllCols ( ) . Update ( pr )
2015-10-24 10:36:47 +03:00
return err
}
2016-11-28 18:31:06 +03:00
// UpdateCols updates specific fields of pull request.
2015-10-24 10:36:47 +03:00
func ( pr * PullRequest ) UpdateCols ( cols ... string ) error {
2017-10-05 07:43:04 +03:00
_ , err := x . ID ( pr . ID ) . Cols ( cols ... ) . Update ( pr )
2015-10-24 10:36:47 +03:00
return err
}
2015-10-25 10:10:22 +03:00
// UpdatePatch generates and saves a new patch.
2015-11-04 01:25:39 +03:00
func ( pr * PullRequest ) UpdatePatch ( ) ( err error ) {
if err = pr . GetHeadRepo ( ) ; err != nil {
2015-10-25 10:10:22 +03:00
return fmt . Errorf ( "GetHeadRepo: %v" , err )
} else if pr . HeadRepo == nil {
2019-07-01 04:18:13 +03:00
log . Trace ( "PullRequest[%d].UpdatePatch: ignored corrupted data" , pr . ID )
2015-10-25 10:10:22 +03:00
return nil
2015-10-24 10:36:47 +03:00
}
2015-11-04 01:25:39 +03:00
if err = pr . GetBaseRepo ( ) ; err != nil {
2015-10-25 10:10:22 +03:00
return fmt . Errorf ( "GetBaseRepo: %v" , err )
2015-10-24 10:36:47 +03:00
}
2015-10-25 10:10:22 +03:00
2015-11-27 01:33:45 +03:00
headGitRepo , err := git . OpenRepository ( pr . HeadRepo . RepoPath ( ) )
2015-10-25 10:10:22 +03:00
if err != nil {
return fmt . Errorf ( "OpenRepository: %v" , err )
}
2015-11-04 01:25:39 +03:00
// Add a temporary remote.
tmpRemote := com . ToStr ( time . Now ( ) . UnixNano ( ) )
2015-12-10 04:46:05 +03:00
if err = headGitRepo . AddRemote ( tmpRemote , RepoPath ( pr . BaseRepo . MustOwner ( ) . Name , pr . BaseRepo . Name ) , true ) ; err != nil {
2015-11-04 01:25:39 +03:00
return fmt . Errorf ( "AddRemote: %v" , err )
}
defer func ( ) {
2019-06-12 22:41:28 +03:00
if err := headGitRepo . RemoveRemote ( tmpRemote ) ; err != nil {
log . Error ( "UpdatePatch: RemoveRemote: %s" , err )
}
2015-11-04 01:25:39 +03:00
} ( )
2019-06-12 02:32:08 +03:00
pr . MergeBase , _ , err = headGitRepo . GetMergeBase ( tmpRemote , pr . BaseBranch , pr . HeadBranch )
2015-11-04 01:25:39 +03:00
if err != nil {
return fmt . Errorf ( "GetMergeBase: %v" , err )
} else if err = pr . Update ( ) ; err != nil {
return fmt . Errorf ( "Update: %v" , err )
}
2015-10-25 10:10:22 +03:00
patch , err := headGitRepo . GetPatch ( pr . MergeBase , pr . HeadBranch )
if err != nil {
return fmt . Errorf ( "GetPatch: %v" , err )
}
if err = pr . BaseRepo . SavePatch ( pr . Index , patch ) ; err != nil {
return fmt . Errorf ( "BaseRepo.SavePatch: %v" , err )
}
return nil
2015-10-24 10:36:47 +03:00
}
2016-02-20 06:16:26 +03: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 21:00:42 +03:00
func ( pr * PullRequest ) PushToBaseRepo ( ) ( err error ) {
2018-01-19 09:18:51 +03:00
log . Trace ( "PushToBaseRepo[%d]: pushing commits to base repo '%s'" , pr . BaseRepoID , pr . GetGitRefName ( ) )
2016-02-04 21:00:42 +03:00
2016-02-20 06:16:26 +03:00
headRepoPath := pr . HeadRepo . RepoPath ( )
headGitRepo , err := git . OpenRepository ( headRepoPath )
2016-02-04 21:00:42 +03:00
if err != nil {
2016-02-20 06:16:26 +03:00
return fmt . Errorf ( "OpenRepository: %v" , err )
2016-02-04 21:00:42 +03:00
}
2016-02-20 06:16:26 +03:00
tmpRemoteName := fmt . Sprintf ( "tmp-pull-%d" , pr . ID )
if err = headGitRepo . AddRemote ( tmpRemoteName , pr . BaseRepo . RepoPath ( ) , false ) ; err != nil {
return fmt . Errorf ( "headGitRepo.AddRemote: %v" , err )
2016-02-04 21:00:42 +03:00
}
// Make sure to remove the remote even if the push fails
2019-06-12 22:41:28 +03:00
defer func ( ) {
if err := headGitRepo . RemoveRemote ( tmpRemoteName ) ; err != nil {
log . Error ( "PushToBaseRepo: RemoveRemote: %s" , err )
}
} ( )
2016-02-04 21:00:42 +03:00
2018-01-19 09:18:51 +03:00
headFile := pr . GetGitRefName ( )
2016-03-05 00:53:03 +03:00
// Remove head in case there is a conflict.
2016-12-01 02:56:15 +03:00
file := path . Join ( pr . BaseRepo . RepoPath ( ) , headFile )
2016-03-05 00:53:03 +03:00
2016-12-04 00:31:54 +03:00
_ = os . Remove ( file )
2017-05-30 12:32:01 +03:00
if err = git . Push ( headRepoPath , git . PushOptions {
Remote : tmpRemoteName ,
Branch : fmt . Sprintf ( "%s:%s" , pr . HeadBranch , headFile ) ,
2018-01-19 09:53:45 +03:00
Force : true ,
2017-05-30 12:32:01 +03:00
} ) ; err != nil {
2016-02-20 06:16:26 +03:00
return fmt . Errorf ( "Push: %v" , err )
2016-02-04 21:00:42 +03:00
}
return nil
}
2015-10-25 10:10:22 +03:00
// AddToTaskQueue adds itself to pull request test task queue.
func ( pr * PullRequest ) AddToTaskQueue ( ) {
2016-11-28 18:31:06 +03:00
go pullRequestQueue . AddFunc ( pr . ID , func ( ) {
2016-11-07 18:37:32 +03:00
pr . Status = PullRequestStatusChecking
2015-10-24 21:48:11 +03:00
if err := pr . UpdateCols ( "status" ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "AddToTaskQueue.UpdateCols[%d].(add to queue): %v" , pr . ID , err )
2015-10-24 21:48:11 +03:00
}
} )
}
2015-10-24 10:36:47 +03:00
2016-11-28 18:31:06 +03:00
// PullRequestList defines a list of pull requests
2016-08-14 13:32:24 +03:00
type PullRequestList [ ] * PullRequest
func ( prs PullRequestList ) loadAttributes ( e Engine ) error {
if len ( prs ) == 0 {
return nil
}
// Load issues.
2018-08-06 07:43:22 +03:00
issueIDs := prs . getIssueIDs ( )
2016-08-14 13:32:24 +03:00
issues := make ( [ ] * Issue , 0 , len ( issueIDs ) )
2016-11-10 18:16:32 +03:00
if err := e .
Where ( "id > 0" ) .
In ( "id" , issueIDs ) .
Find ( & issues ) ; err != nil {
2016-08-14 13:32:24 +03:00
return fmt . Errorf ( "find issues: %v" , err )
}
set := make ( map [ int64 ] * Issue )
for i := range issues {
set [ issues [ i ] . ID ] = issues [ i ]
}
for i := range prs {
prs [ i ] . Issue = set [ prs [ i ] . IssueID ]
}
return nil
}
2018-08-06 07:43:22 +03:00
func ( prs PullRequestList ) getIssueIDs ( ) [ ] int64 {
issueIDs := make ( [ ] int64 , 0 , len ( prs ) )
for i := range prs {
issueIDs = append ( issueIDs , prs [ i ] . IssueID )
}
return issueIDs
}
2016-11-28 18:31:06 +03:00
// LoadAttributes load all the prs attributes
2016-08-14 13:32:24 +03:00
func ( prs PullRequestList ) LoadAttributes ( ) error {
return prs . loadAttributes ( x )
}
2018-08-06 07:43:22 +03:00
func ( prs PullRequestList ) invalidateCodeComments ( e Engine , doer * User , repo * git . Repository , branch string ) error {
if len ( prs ) == 0 {
return nil
}
issueIDs := prs . getIssueIDs ( )
var codeComments [ ] * Comment
if err := e .
Where ( "type = ? and invalidated = ?" , CommentTypeCode , false ) .
In ( "issue_id" , issueIDs ) .
Find ( & codeComments ) ; err != nil {
return fmt . Errorf ( "find code comments: %v" , err )
}
for _ , comment := range codeComments {
if err := comment . CheckInvalidation ( repo , doer , branch ) ; err != nil {
return err
}
}
return nil
}
// InvalidateCodeComments will lookup the prs for code comments which got invalidated by change
func ( prs PullRequestList ) InvalidateCodeComments ( doer * User , repo * git . Repository , branch string ) error {
return prs . invalidateCodeComments ( x , doer , repo , branch )
}
2017-01-05 03:50:34 +03:00
// checkAndUpdateStatus checks if pull request is possible to leaving checking status,
2015-10-25 10:10:22 +03:00
// and set to be either conflict or mergeable.
func ( pr * PullRequest ) checkAndUpdateStatus ( ) {
// Status is not changed to conflict means mergeable.
2016-11-07 18:37:32 +03:00
if pr . Status == PullRequestStatusChecking {
pr . Status = PullRequestStatusMergeable
2015-10-25 10:10:22 +03:00
}
2017-01-05 03:50:34 +03:00
// Make sure there is no waiting test to process before leaving the checking status.
2016-11-28 18:31:06 +03:00
if ! pullRequestQueue . Exist ( pr . ID ) {
2019-02-05 14:54:49 +03:00
if err := pr . UpdateCols ( "status, conflicted_files" ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "Update[%d]: %v" , pr . ID , err )
2015-10-25 10:10:22 +03:00
}
2015-10-24 10:36:47 +03:00
}
}
2018-08-13 22:04:39 +03:00
// IsWorkInProgress determine if the Pull Request is a Work In Progress by its title
func ( pr * PullRequest ) IsWorkInProgress ( ) bool {
if err := pr . LoadIssue ( ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "LoadIssue: %v" , err )
2018-08-13 22:04:39 +03:00
return false
}
for _ , prefix := range setting . Repository . PullRequest . WorkInProgressPrefixes {
if strings . HasPrefix ( strings . ToUpper ( pr . Issue . Title ) , prefix ) {
return true
}
}
return false
}
2019-02-05 14:54:49 +03:00
// IsFilesConflicted determines if the Pull Request has changes conflicting with the target branch.
func ( pr * PullRequest ) IsFilesConflicted ( ) bool {
return len ( pr . ConflictedFiles ) > 0
}
2018-08-13 22:04:39 +03:00
// GetWorkInProgressPrefix returns the prefix used to mark the pull request as a work in progress.
// It returns an empty string when none were found
func ( pr * PullRequest ) GetWorkInProgressPrefix ( ) string {
if err := pr . LoadIssue ( ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "LoadIssue: %v" , err )
2018-08-13 22:04:39 +03:00
return ""
}
for _ , prefix := range setting . Repository . PullRequest . WorkInProgressPrefixes {
if strings . HasPrefix ( strings . ToUpper ( pr . Issue . Title ) , prefix ) {
return pr . Issue . Title [ 0 : len ( prefix ) ]
}
}
return ""
}
2015-10-24 10:36:47 +03: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 )
2017-05-05 03:47:03 +03:00
err := x . Where ( "status = ?" , PullRequestStatusChecking ) . Find ( & prs )
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "Find Checking PRs: %v" , err )
2017-05-05 03:47:03 +03:00
return
}
var checkedPRs = make ( map [ int64 ] struct { } )
2015-10-24 10:36:47 +03:00
// Update pull request status.
for _ , pr := range prs {
2017-05-05 03:47:03 +03:00
checkedPRs [ pr . ID ] = struct { } { }
if err := pr . GetBaseRepo ( ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "GetBaseRepo: %v" , err )
2017-05-05 03:47:03 +03:00
continue
}
if pr . manuallyMerged ( ) {
continue
}
2019-01-14 05:29:58 +03:00
if err := pr . testPatch ( x ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "testPatch: %v" , err )
2017-05-05 03:47:03 +03:00
continue
}
2015-10-24 10:36:47 +03:00
pr . checkAndUpdateStatus ( )
}
// Start listening on new test requests.
2016-11-28 18:31:06 +03:00
for prID := range pullRequestQueue . Queue ( ) {
2015-10-24 10:36:47 +03:00
log . Trace ( "TestPullRequests[%v]: processing test task" , prID )
2016-11-28 18:31:06 +03:00
pullRequestQueue . Remove ( prID )
2015-10-24 10:36:47 +03:00
2017-05-05 03:47:03 +03:00
id := com . StrTo ( prID ) . MustInt64 ( )
if _ , ok := checkedPRs [ id ] ; ok {
continue
}
pr , err := GetPullRequestByID ( id )
2015-10-24 10:36:47 +03:00
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "GetPullRequestByID[%s]: %v" , prID , err )
2015-10-24 10:36:47 +03:00
continue
2017-02-05 16:07:44 +03:00
} else if pr . manuallyMerged ( ) {
continue
2019-01-14 05:29:58 +03:00
} else if err = pr . testPatch ( x ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "testPatch[%d]: %v" , pr . ID , err )
2015-10-24 10:36:47 +03:00
continue
}
pr . checkAndUpdateStatus ( )
}
}
2016-11-28 18:31:06 +03:00
// InitTestPullRequests runs the task to test all the checking status pull requests
2015-10-24 10:36:47 +03:00
func InitTestPullRequests ( ) {
go TestPullRequests ( )
}