2019-09-27 08:22:36 +08:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2019-09-27 08:22:36 +08:00
package pull
import (
2019-12-15 09:51:28 +00:00
"context"
2019-09-27 08:22:36 +08:00
"fmt"
2022-01-19 23:26:57 +00:00
"io"
2023-01-28 15:54:40 +00:00
"os"
2021-06-25 19:01:43 +02:00
"regexp"
2020-01-09 02:47:45 +01:00
"strings"
2019-09-27 08:22:36 +08:00
"code.gitea.io/gitea/models"
2021-09-19 19:49:59 +08:00
"code.gitea.io/gitea/models/db"
2022-06-12 23:51:54 +08:00
git_model "code.gitea.io/gitea/models/git"
2022-06-13 17:37:59 +08:00
issues_model "code.gitea.io/gitea/models/issues"
2021-12-10 09:27:50 +08:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-24 17:49:20 +08:00
user_model "code.gitea.io/gitea/models/user"
2022-10-12 07:18:26 +02:00
"code.gitea.io/gitea/modules/container"
2019-10-15 11:28:40 +08:00
"code.gitea.io/gitea/modules/git"
2019-12-15 09:51:28 +00:00
"code.gitea.io/gitea/modules/graceful"
2021-07-25 00:03:58 +08:00
"code.gitea.io/gitea/modules/json"
2019-09-27 08:22:36 +08:00
"code.gitea.io/gitea/modules/log"
2019-11-04 04:59:09 +08:00
"code.gitea.io/gitea/modules/notification"
2022-01-19 23:26:57 +00:00
"code.gitea.io/gitea/modules/process"
2022-05-09 00:46:32 +08:00
repo_module "code.gitea.io/gitea/modules/repository"
2020-04-10 13:26:37 +02:00
"code.gitea.io/gitea/modules/setting"
2022-05-04 18:06:23 +02:00
"code.gitea.io/gitea/modules/sync"
2023-01-28 15:54:40 +00:00
"code.gitea.io/gitea/modules/util"
2019-10-29 00:45:43 +08:00
issue_service "code.gitea.io/gitea/services/issue"
2019-09-27 08:22:36 +08:00
)
2022-05-04 18:06:23 +02:00
// TODO: use clustered lock (unique queue? or *abuse* cache)
var pullWorkingPool = sync . NewExclusivePool ( )
2019-09-27 08:22:36 +08:00
// NewPullRequest creates new pull request with labels for repository.
2022-06-13 17:37:59 +08:00
func NewPullRequest ( ctx context . Context , repo * repo_model . Repository , pull * issues_model . Issue , labelIDs [ ] int64 , uuids [ ] string , pr * issues_model . PullRequest , assigneeIDs [ ] int64 ) error {
2019-12-13 22:21:06 +00:00
if err := TestPatch ( pr ) ; err != nil {
return err
}
2022-01-19 23:26:57 +00:00
divergence , err := GetDiverging ( ctx , pr )
2020-04-14 15:53:34 +02:00
if err != nil {
return err
}
pr . CommitsAhead = divergence . Ahead
pr . CommitsBehind = divergence . Behind
2022-06-13 17:37:59 +08:00
if err := issues_model . NewPullRequest ( ctx , repo , pull , labelIDs , uuids , pr ) ; err != nil {
2019-09-27 08:22:36 +08:00
return err
}
2019-10-29 00:45:43 +08:00
for _ , assigneeID := range assigneeIDs {
if err := issue_service . AddAssigneeIfNotAssigned ( pull , pull . Poster , assigneeID ) ; err != nil {
return err
}
}
2019-09-27 08:22:36 +08:00
pr . Issue = pull
pull . PullRequest = pr
2019-11-04 04:59:09 +08:00
2022-01-19 23:26:57 +00:00
// Now - even if the request context has been cancelled as the PR has been created
// in the db and there is no way to cancel that transaction we have to proceed - therefore
// create new context and work from there
prCtx , _ , finished := process . GetManager ( ) . AddContext ( graceful . GetManager ( ) . HammerContext ( ) , fmt . Sprintf ( "NewPullRequest: %s:%d" , repo . FullName ( ) , pr . Index ) )
defer finished ( )
2022-06-13 17:37:59 +08:00
if pr . Flow == issues_model . PullRequestFlowGithub {
2022-01-19 23:26:57 +00:00
err = PushToBaseRepo ( prCtx , pr )
2021-07-28 17:42:56 +08:00
} else {
2022-01-19 23:26:57 +00:00
err = UpdateRef ( prCtx , pr )
2021-07-28 17:42:56 +08:00
}
if err != nil {
2019-12-15 11:28:51 +08:00
return err
}
2022-06-13 17:37:59 +08:00
mentions , err := issues_model . FindAndUpdateIssueMentions ( ctx , pull , pull . Poster , pull . Content )
2021-01-02 18:04:02 +01:00
if err != nil {
return err
}
2022-11-19 09:12:33 +01:00
notification . NotifyNewPullRequest ( prCtx , pr , mentions )
2021-01-17 15:15:57 +01:00
if len ( pull . Labels ) > 0 {
2022-11-19 09:12:33 +01:00
notification . NotifyIssueChangeLabels ( prCtx , pull . Poster , pull , pull . Labels , nil )
2021-01-17 15:15:57 +01:00
}
if pull . Milestone != nil {
2022-11-19 09:12:33 +01:00
notification . NotifyIssueChangeMilestone ( prCtx , pull . Poster , pull , 0 )
2021-01-17 15:15:57 +01:00
}
2019-09-27 08:22:36 +08:00
2020-05-20 20:47:24 +08:00
// add first push codes comment
2022-03-29 21:13:41 +02:00
baseGitRepo , err := git . OpenRepository ( prCtx , pr . BaseRepo . RepoPath ( ) )
2020-05-20 20:47:24 +08:00
if err != nil {
return err
}
defer baseGitRepo . Close ( )
compareInfo , err := baseGitRepo . GetCompareInfo ( pr . BaseRepo . RepoPath ( ) ,
2022-01-18 07:45:43 +00:00
git . BranchPrefix + pr . BaseBranch , pr . GetGitRefName ( ) , false , false )
2020-05-20 20:47:24 +08:00
if err != nil {
return err
}
2021-08-09 20:08:51 +02:00
if len ( compareInfo . Commits ) > 0 {
2022-06-13 17:37:59 +08:00
data := issues_model . PushActionContent { IsForcePush : false }
2021-08-09 20:08:51 +02:00
data . CommitIDs = make ( [ ] string , 0 , len ( compareInfo . Commits ) )
for i := len ( compareInfo . Commits ) - 1 ; i >= 0 ; i -- {
data . CommitIDs = append ( data . CommitIDs , compareInfo . Commits [ i ] . ID . String ( ) )
2020-05-20 20:47:24 +08:00
}
dataJSON , err := json . Marshal ( data )
if err != nil {
return err
}
2022-06-13 17:37:59 +08:00
ops := & issues_model . CreateCommentOptions {
Type : issues_model . CommentTypePullRequestPush ,
2020-05-20 20:47:24 +08:00
Doer : pull . Poster ,
Repo : repo ,
Issue : pr . Issue ,
IsForcePush : false ,
Content : string ( dataJSON ) ,
}
2022-12-10 10:46:31 +08:00
_ , _ = issue_service . CreateComment ( ops )
2020-05-20 20:47:24 +08:00
}
2019-09-27 08:22:36 +08:00
return nil
}
2019-10-15 11:28:40 +08:00
2019-12-16 07:20:25 +01:00
// ChangeTargetBranch changes the target branch of this pull request, as the given user.
2022-06-13 17:37:59 +08:00
func ChangeTargetBranch ( ctx context . Context , pr * issues_model . PullRequest , doer * user_model . User , targetBranch string ) ( err error ) {
2022-05-04 18:06:23 +02:00
pullWorkingPool . CheckIn ( fmt . Sprint ( pr . ID ) )
defer pullWorkingPool . CheckOut ( fmt . Sprint ( pr . ID ) )
2019-12-16 07:20:25 +01:00
// Current target branch is already the same
if pr . BaseBranch == targetBranch {
return nil
}
if pr . Issue . IsClosed {
2022-06-13 17:37:59 +08:00
return issues_model . ErrIssueIsClosed {
2019-12-16 07:20:25 +01:00
ID : pr . Issue . ID ,
RepoID : pr . Issue . RepoID ,
Index : pr . Issue . Index ,
}
}
if pr . HasMerged {
return models . ErrPullRequestHasMerged {
ID : pr . ID ,
IssueID : pr . Index ,
HeadRepoID : pr . HeadRepoID ,
BaseRepoID : pr . BaseRepoID ,
HeadBranch : pr . HeadBranch ,
BaseBranch : pr . BaseBranch ,
}
}
// Check if branches are equal
2022-01-19 23:26:57 +00:00
branchesEqual , err := IsHeadEqualWithBranch ( ctx , pr , targetBranch )
2019-12-16 07:20:25 +01:00
if err != nil {
return err
}
if branchesEqual {
return models . ErrBranchesEqual {
HeadBranchName : pr . HeadBranch ,
BaseBranchName : targetBranch ,
}
}
// Check if pull request for the new target branch already exists
2022-11-19 09:12:33 +01:00
existingPr , err := issues_model . GetUnmergedPullRequest ( ctx , pr . HeadRepoID , pr . BaseRepoID , pr . HeadBranch , targetBranch , issues_model . PullRequestFlowGithub )
2019-12-16 07:20:25 +01:00
if existingPr != nil {
2022-06-13 17:37:59 +08:00
return issues_model . ErrPullRequestAlreadyExists {
2019-12-16 07:20:25 +01:00
ID : existingPr . ID ,
IssueID : existingPr . Index ,
HeadRepoID : existingPr . HeadRepoID ,
BaseRepoID : existingPr . BaseRepoID ,
HeadBranch : existingPr . HeadBranch ,
BaseBranch : existingPr . BaseBranch ,
}
}
2022-06-13 17:37:59 +08:00
if err != nil && ! issues_model . IsErrPullRequestNotExist ( err ) {
2019-12-16 07:20:25 +01:00
return err
}
// Set new target branch
oldBranch := pr . BaseBranch
pr . BaseBranch = targetBranch
// Refresh patch
if err := TestPatch ( pr ) ; err != nil {
return err
}
// Update target branch, PR diff and status
// This is the same as checkAndUpdateStatus in check service, but also updates base_branch
2022-06-13 17:37:59 +08:00
if pr . Status == issues_model . PullRequestStatusChecking {
pr . Status = issues_model . PullRequestStatusMergeable
2019-12-16 07:20:25 +01:00
}
2020-06-16 19:52:33 +02:00
// Update Commit Divergence
2022-01-19 23:26:57 +00:00
divergence , err := GetDiverging ( ctx , pr )
2020-06-16 19:52:33 +02:00
if err != nil {
return err
}
pr . CommitsAhead = divergence . Ahead
pr . CommitsBehind = divergence . Behind
2022-11-19 09:12:33 +01:00
if err := pr . UpdateColsIfNotMerged ( ctx , "merge_base" , "status" , "conflicted_files" , " changed_protected_files ", " base_branch ", " commits_ahead ", " commits_behind "); err != nil {
2019-12-16 07:20:25 +01:00
return err
}
// Create comment
2022-06-13 17:37:59 +08:00
options := &issues_model.CreateCommentOptions{
Type: issues_model.CommentTypeChangeTargetBranch,
2019-12-16 07:20:25 +01:00
Doer: doer,
Repo: pr.Issue.Repo,
Issue: pr.Issue,
OldRef: oldBranch,
NewRef: targetBranch,
}
2022-12-10 10:46:31 +08:00
if _, err = issue_service.CreateComment(options); err != nil {
2022-10-24 21:29:17 +02:00
return fmt.Errorf(" CreateChangeTargetBranchComment : % w ", err)
2019-12-16 07:20:25 +01:00
}
return nil
}
2022-06-13 17:37:59 +08:00
func checkForInvalidation(ctx context.Context, requests issues_model.PullRequestList, repoID int64, doer *user_model.User, branch string) error {
2022-12-03 10:48:26 +08:00
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
2019-10-15 11:28:40 +08:00
if err != nil {
2022-11-19 09:12:33 +01:00
return fmt.Errorf(" GetRepositoryByIDCtx : % w ", err)
2019-10-15 11:28:40 +08:00
}
2022-03-29 21:13:41 +02:00
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
2019-10-15 11:28:40 +08:00
if err != nil {
2022-10-24 21:29:17 +02:00
return fmt.Errorf(" git . OpenRepository : % w ", err)
2019-10-15 11:28:40 +08:00
}
go func() {
2019-12-15 09:51:28 +00:00
// FIXME: graceful: We need to tell the manager we're doing something...
2023-01-18 05:03:44 +08:00
err := InvalidateCodeComments(ctx, requests, doer, gitRepo, branch)
2019-10-15 11:28:40 +08:00
if err != nil {
log.Error(" PullRequestList . InvalidateCodeComments : % v ", err)
}
2019-11-13 07:01:19 +00:00
gitRepo.Close()
2019-10-15 11:28:40 +08:00
}()
return nil
}
// AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
// and generate new patch for testing as needed.
2021-11-24 17:49:20 +08:00
func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string, isSync bool, oldCommitID, newCommitID string) {
2019-10-15 11:28:40 +08:00
log.Trace(" AddTestPullRequestTask [ head_repo_id : % d , head_branch : % s ] : finding pull requests ", repoID, branch)
2019-12-15 09:51:28 +00:00
graceful.GetManager().RunWithShutdownContext(func(ctx context.Context) {
// There is no sensible way to shut this down " : - ( "
// If you don't let it run all the way then you will lose data
2022-05-07 19:05:52 +02:00
// TODO: graceful: AddTestPullRequestTask needs to become a queue!
2019-10-15 11:28:40 +08:00
2022-06-13 17:37:59 +08:00
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(repoID, branch)
2019-12-15 09:51:28 +00:00
if err != nil {
log.Error(" Find pull requests [ head_repo_id : % d , head_branch : % s ] : % v ", repoID, branch, err)
return
2019-10-15 11:28:40 +08:00
}
2019-12-15 09:51:28 +00:00
2023-02-05 19:57:38 +08:00
for _, pr := range prs {
log.Trace(" Updating PR [ % d ] : composing new test task ", pr.ID)
if pr.Flow == issues_model.PullRequestFlowGithub {
if err := PushToBaseRepo(ctx, pr); err != nil {
log.Error(" PushToBaseRepo : % v ", err)
continue
}
} else {
continue
}
AddToTaskQueue(pr)
comment, err := CreatePushPullComment(ctx, doer, pr, oldCommitID, newCommitID)
if err == nil && comment != nil {
notification.NotifyPullRequestPushCommits(ctx, doer, pr, comment)
}
}
2019-12-15 09:51:28 +00:00
if isSync {
2022-06-13 17:37:59 +08:00
requests := issues_model.PullRequestList(prs)
2019-12-15 09:51:28 +00:00
if err = requests.LoadAttributes(); err != nil {
log.Error(" PullRequestList . LoadAttributes : % v ", err)
}
2022-01-19 23:26:57 +00:00
if invalidationErr := checkForInvalidation(ctx, requests, repoID, doer, branch); invalidationErr != nil {
2019-12-15 09:51:28 +00:00
log.Error(" checkForInvalidation : % v ", invalidationErr)
}
if err == nil {
for _, pr := range prs {
2020-01-09 02:47:45 +01:00
if newCommitID != " " && newCommitID != git.EmptySHA {
2022-01-19 23:26:57 +00:00
changed, err := checkIfPRContentChanged(ctx, pr, oldCommitID, newCommitID)
2020-01-09 02:47:45 +01:00
if err != nil {
log.Error(" checkIfPRContentChanged : % v ", err)
}
if changed {
// Mark old reviews as stale if diff to mergebase has changed
2022-06-13 17:37:59 +08:00
if err := issues_model.MarkReviewsAsStale(pr.IssueID); err != nil {
2020-01-09 02:47:45 +01:00
log.Error(" MarkReviewsAsStale : % v ", err)
}
}
2022-06-13 17:37:59 +08:00
if err := issues_model.MarkReviewsAsNotStale(pr.IssueID, newCommitID); err != nil {
2020-01-09 02:47:45 +01:00
log.Error(" MarkReviewsAsNotStale : % v ", err)
}
2022-01-19 23:26:57 +00:00
divergence, err := GetDiverging(ctx, pr)
2020-04-14 15:53:34 +02:00
if err != nil {
log.Error(" GetDiverging : % v ", err)
} else {
2022-05-20 22:08:52 +08:00
err = pr.UpdateCommitDivergence(ctx, divergence.Ahead, divergence.Behind)
2020-04-14 15:53:34 +02:00
if err != nil {
log.Error(" UpdateCommitDivergence : % v ", err)
}
}
2020-01-09 02:47:45 +01:00
}
2022-11-19 09:12:33 +01:00
notification.NotifyPullRequestSynchronized(ctx, doer, pr)
2019-12-15 09:51:28 +00:00
}
2019-10-15 11:28:40 +08:00
}
}
2019-12-15 09:51:28 +00:00
log.Trace(" AddTestPullRequestTask [ base_repo_id : % d , base_branch : % s ] : finding pull requests ", repoID, branch)
2022-06-13 17:37:59 +08:00
prs, err = issues_model.GetUnmergedPullRequestsByBaseInfo(repoID, branch)
2019-12-15 09:51:28 +00:00
if err != nil {
log.Error(" Find pull requests [ base_repo_id : % d , base_branch : % s ] : % v ", repoID, branch, err)
return
}
for _, pr := range prs {
2022-01-19 23:26:57 +00:00
divergence, err := GetDiverging(ctx, pr)
2020-04-14 15:53:34 +02:00
if err != nil {
2021-11-30 20:06:32 +00:00
if models.IsErrBranchDoesNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
2021-07-13 01:26:25 +02:00
log.Warn(" Cannot test PR % s / % d : head_branch % s no longer exists ", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch)
} else {
log.Error(" GetDiverging : % v ", err)
}
2020-04-14 15:53:34 +02:00
} else {
2022-05-20 22:08:52 +08:00
err = pr.UpdateCommitDivergence(ctx, divergence.Ahead, divergence.Behind)
2020-04-14 15:53:34 +02:00
if err != nil {
log.Error(" UpdateCommitDivergence : % v ", err)
}
}
2019-12-15 09:51:28 +00:00
AddToTaskQueue(pr)
}
})
2019-10-15 11:28:40 +08:00
}
2019-12-15 11:28:51 +08:00
2020-01-09 02:47:45 +01:00
// checkIfPRContentChanged checks if diff to target branch has changed by push
// A commit can be considered to leave the PR untouched if the patch/diff with its merge base is unchanged
2022-06-13 17:37:59 +08:00
func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest, oldCommitID, newCommitID string) (hasChanged bool, err error) {
2023-01-28 15:54:40 +00:00
tmpBasePath, err := createTemporaryRepo(ctx, pr)
2020-01-09 02:47:45 +01:00
if err != nil {
2023-01-28 15:54:40 +00:00
log.Error(" CreateTemporaryRepo : % v ", err)
return false, err
2020-01-09 02:47:45 +01:00
}
defer func() {
2023-01-28 15:54:40 +00:00
if err := repo_module.RemoveTemporaryPath(tmpBasePath); err != nil {
log.Error(" checkIfPRContentChanged : RemoveTemporaryPath : % s ", err)
2020-01-09 02:47:45 +01:00
}
}()
2023-01-28 15:54:40 +00:00
tmpRepo, err := git.OpenRepository(ctx, tmpBasePath)
2020-01-09 02:47:45 +01:00
if err != nil {
2023-01-28 15:54:40 +00:00
return false, fmt.Errorf(" OpenRepository : % w ", err)
2020-01-09 02:47:45 +01:00
}
2023-01-28 15:54:40 +00:00
defer tmpRepo.Close()
2020-01-09 02:47:45 +01:00
2023-01-28 15:54:40 +00:00
// Find the merge-base
_, base, err := tmpRepo.GetMergeBase(" ", " base ", " tracking ")
if err != nil {
return false, fmt.Errorf(" GetMergeBase : % w ", err)
2020-01-09 02:47:45 +01:00
}
2023-01-28 15:54:40 +00:00
cmd := git.NewCommand(ctx, " diff ", " -- name - only ", " - z ").AddDynamicArguments(newCommitID, oldCommitID, base)
stdoutReader, stdoutWriter, err := os.Pipe()
if err != nil {
return false, fmt.Errorf(" unable to open pipe for to run diff : % w ", err)
}
if err := cmd.Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: stdoutWriter,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close()
defer func() {
_ = stdoutReader.Close()
}()
return util.IsEmptyReader(stdoutReader)
},
}); err != nil {
if err == util.ErrNotEmpty {
2020-01-09 02:47:45 +01:00
return true, nil
}
2023-01-28 15:54:40 +00:00
log.Error(" Unable to run diff on % s % s % s in tempRepo for PR [ % d ] % s / % s ... % s / % s : Error : % v ",
newCommitID, oldCommitID, base,
pr.ID, pr.BaseRepo.FullName(), pr.BaseBranch, pr.HeadRepo.FullName(), pr.HeadBranch,
err)
return false, fmt.Errorf(" Unable to run git diff -- name - only - z % s % s % s : % w ", newCommitID, oldCommitID, base, err)
2020-01-09 02:47:45 +01:00
}
return false, nil
}
2019-12-15 11:28:51 +08:00
// PushToBaseRepo pushes commits from branches of head repository to
// corresponding branches of base repository.
// FIXME: Only push branches that are actually updates?
2022-06-13 17:37:59 +08:00
func PushToBaseRepo(ctx context.Context, pr *issues_model.PullRequest) (err error) {
2022-01-19 23:26:57 +00:00
return pushToBaseRepoHelper(ctx, pr, " ")
2021-06-24 00:08:26 +03:00
}
2022-06-13 17:37:59 +08:00
func pushToBaseRepoHelper(ctx context.Context, pr *issues_model.PullRequest, prefixHeadBranch string) (err error) {
2019-12-15 11:28:51 +08:00
log.Trace(" PushToBaseRepo [ % d ] : pushing commits to base repo ' % s ' ", pr.BaseRepoID, pr.GetGitRefName())
2022-11-19 09:12:33 +01:00
if err := pr.LoadHeadRepo(ctx); err != nil {
2020-02-21 18:18:13 +00:00
log.Error(" Unable to load head repository for PR [ % d ] Error : % v ", pr.ID, err)
return err
}
2019-12-15 11:28:51 +08:00
headRepoPath := pr.HeadRepo.RepoPath()
2020-01-28 10:23:58 +00:00
2022-11-19 09:12:33 +01:00
if err := pr.LoadBaseRepo(ctx); err != nil {
2020-02-21 18:18:13 +00:00
log.Error(" Unable to load base repository for PR [ % d ] Error : % v ", pr.ID, err)
return err
}
2020-09-15 04:32:31 +01:00
baseRepoPath := pr.BaseRepo.RepoPath()
2019-12-15 11:28:51 +08:00
2022-11-19 09:12:33 +01:00
if err = pr.LoadIssue(ctx); err != nil {
2022-10-24 21:29:17 +02:00
return fmt.Errorf(" unable to load issue % d for pr % d : % w ", pr.IssueID, pr.ID, err)
2019-12-27 21:15:04 +00:00
}
2022-11-19 09:12:33 +01:00
if err = pr.Issue.LoadPoster(ctx); err != nil {
2022-10-24 21:29:17 +02:00
return fmt.Errorf(" unable to load poster % d for pr % d : % w ", pr.Issue.PosterID, pr.ID, err)
2019-12-27 21:15:04 +00:00
}
2020-09-15 04:32:31 +01:00
gitRefName := pr.GetGitRefName()
2022-01-19 23:26:57 +00:00
if err := git.Push(ctx, headRepoPath, git.PushOptions{
2020-09-15 04:32:31 +01:00
Remote: baseRepoPath,
2021-06-24 00:08:26 +03:00
Branch: prefixHeadBranch + pr.HeadBranch + " : " + gitRefName,
2019-12-15 11:28:51 +08:00
Force: true,
2019-12-27 21:15:04 +00:00
// Use InternalPushingEnvironment here because we know that pre-receive and post-receive do not run on a refs/pulls/...
2022-05-09 00:46:32 +08:00
Env: repo_module.InternalPushingEnvironment(pr.Issue.Poster, pr.BaseRepo),
2019-12-15 11:28:51 +08:00
}); err != nil {
2020-06-08 19:07:41 +01:00
if git.IsErrPushOutOfDate(err) {
// This should not happen as we're using force!
2020-09-15 04:32:31 +01:00
log.Error(" Unable to push PR head for % s # % d ( % - v : % s ) due to ErrPushOfDate : % v ", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, gitRefName, err)
2020-06-08 19:07:41 +01:00
return err
} else if git.IsErrPushRejected(err) {
rejectErr := err.(*git.ErrPushRejected)
2020-09-15 04:32:31 +01:00
log.Info(" Unable to push PR head for % s # % d ( % - v : % s ) due to rejection : \ nStdout : % s \ nStderr : % s \ nError : % v ", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, gitRefName, rejectErr.StdOut, rejectErr.StdErr, rejectErr.Err)
2020-06-08 19:07:41 +01:00
return err
2021-06-24 00:08:26 +03:00
} else if git.IsErrMoreThanOne(err) {
if prefixHeadBranch != " " {
log.Info(" Can ' t push with % s % s ", prefixHeadBranch, pr.HeadBranch)
return err
}
2022-01-19 23:26:57 +00:00
log.Info(" Retrying to push with % s % s ", git.BranchPrefix, pr.HeadBranch)
err = pushToBaseRepoHelper(ctx, pr, git.BranchPrefix)
2021-06-24 00:08:26 +03:00
return err
2020-06-08 19:07:41 +01:00
}
2020-09-15 04:32:31 +01:00
log.Error(" Unable to push PR head for % s # % d ( % - v : % s ) due to Error : % v ", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, gitRefName, err)
2022-10-24 21:29:17 +02:00
return fmt.Errorf(" Push : % s : % s % s : % s % w ", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), gitRefName, err)
2019-12-15 11:28:51 +08:00
}
return nil
}
2020-01-25 10:48:22 +08:00
2021-07-28 17:42:56 +08:00
// UpdateRef update refs/pull/id/head directly for agit flow pull request
2022-06-13 17:37:59 +08:00
func UpdateRef(ctx context.Context, pr *issues_model.PullRequest) (err error) {
2021-07-28 17:42:56 +08:00
log.Trace(" UpdateRef [ % d ] : upgate pull request ref in base repo ' % s ' ", pr.ID, pr.GetGitRefName())
2022-11-19 09:12:33 +01:00
if err := pr.LoadBaseRepo(ctx); err != nil {
2021-07-28 17:42:56 +08:00
log.Error(" Unable to load base repository for PR [ % d ] Error : % v ", pr.ID, err)
return err
}
2022-10-23 22:44:45 +08:00
_, _, err = git.NewCommand(ctx, " update - ref ").AddDynamicArguments(pr.GetGitRefName(), pr.HeadCommitID).RunStdString(&git.RunOpts{Dir: pr.BaseRepo.RepoPath()})
2021-07-28 17:42:56 +08:00
if err != nil {
log.Error(" Unable to update ref in base repository for PR [ % d ] Error : % v ", pr.ID, err)
}
return err
}
2020-01-25 10:48:22 +08:00
type errlist []error
func (errs errlist) Error() string {
if len(errs) > 0 {
var buf strings.Builder
for i, err := range errs {
if i > 0 {
buf.WriteString(" , ")
}
buf.WriteString(err.Error())
}
return buf.String()
}
return " "
}
// CloseBranchPulls close all the pull requests who's head branch is the branch
2021-11-24 17:49:20 +08:00
func CloseBranchPulls(doer *user_model.User, repoID int64, branch string) error {
2022-06-13 17:37:59 +08:00
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(repoID, branch)
2020-01-25 10:48:22 +08:00
if err != nil {
return err
}
2022-06-13 17:37:59 +08:00
prs2, err := issues_model.GetUnmergedPullRequestsByBaseInfo(repoID, branch)
2020-01-25 10:48:22 +08:00
if err != nil {
return err
}
prs = append(prs, prs2...)
2022-06-13 17:37:59 +08:00
if err := issues_model.PullRequestList(prs).LoadAttributes(); err != nil {
2020-01-25 10:48:22 +08:00
return err
}
var errs errlist
for _, pr := range prs {
2023-01-25 05:47:53 +01:00
if err = issue_service.ChangeStatus(pr.Issue, doer, " ", true); err != nil && !issues_model.IsErrPullWasClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
2020-01-25 10:48:22 +08:00
errs = append(errs, err)
}
}
if len(errs) > 0 {
return errs
}
return nil
}
2021-03-01 17:39:44 +00:00
// CloseRepoBranchesPulls close all pull requests which head branches are in the given repository, but only whose base repo is not in the given repository
2022-01-19 23:26:57 +00:00
func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *repo_model.Repository) error {
branches, _, err := git.GetBranchesByPath(ctx, repo.RepoPath(), 0, 0)
2020-01-25 10:48:22 +08:00
if err != nil {
return err
}
var errs errlist
for _, branch := range branches {
2022-06-13 17:37:59 +08:00
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(repo.ID, branch.Name)
2020-01-25 10:48:22 +08:00
if err != nil {
return err
}
2022-06-13 17:37:59 +08:00
if err = issues_model.PullRequestList(prs).LoadAttributes(); err != nil {
2020-01-25 10:48:22 +08:00
return err
}
for _, pr := range prs {
2021-03-01 17:39:44 +00:00
// If the base repository for this pr is this repository there is no need to close it
// as it is going to be deleted anyway
if pr.BaseRepoID == repo.ID {
continue
}
2023-01-25 05:47:53 +01:00
if err = issue_service.ChangeStatus(pr.Issue, doer, " ", true); err != nil && !issues_model.IsErrPullWasClosed(err) {
2020-01-25 10:48:22 +08:00
errs = append(errs, err)
}
}
}
if len(errs) > 0 {
return errs
}
return nil
}
2020-04-10 13:26:37 +02:00
2021-06-25 19:01:43 +02:00
var commitMessageTrailersPattern = regexp.MustCompile(`(?:^|\n\n)(?:[\w-]+[ \t]*:[^\n]+\n*(?:[ \t]+[^\n]+\n*)*)+$`)
2020-12-22 00:46:14 +08:00
// GetSquashMergeCommitMessages returns the commit messages between head and merge base (if there is one)
2022-06-13 17:37:59 +08:00
func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequest) string {
2022-11-19 09:12:33 +01:00
if err := pr.LoadIssue(ctx); err != nil {
2020-04-10 13:26:37 +02:00
log.Error(" Cannot load issue % d for PR id % d : Error : % v ", pr.IssueID, pr.ID, err)
return " "
}
2022-11-19 09:12:33 +01:00
if err := pr.Issue.LoadPoster(ctx); err != nil {
2020-04-10 13:26:37 +02:00
log.Error(" Cannot load poster % d for pr id % d , index % d Error : % v ", pr.Issue.PosterID, pr.ID, pr.Index, err)
return " "
}
if pr.HeadRepo == nil {
var err error
2022-12-03 10:48:26 +08:00
pr.HeadRepo, err = repo_model.GetRepositoryByID(ctx, pr.HeadRepoID)
2020-04-10 13:26:37 +02:00
if err != nil {
2022-11-19 09:12:33 +01:00
log.Error(" GetRepositoryByIdCtx [ % d ] : % v ", pr.HeadRepoID, err)
2020-04-10 13:26:37 +02:00
return " "
}
}
2022-01-19 23:26:57 +00:00
gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, pr.HeadRepo.RepoPath())
2020-04-10 13:26:37 +02:00
if err != nil {
log.Error(" Unable to open head repository : Error : % v ", err)
return " "
}
2022-01-19 23:26:57 +00:00
defer closer.Close()
2020-04-10 13:26:37 +02:00
2021-07-28 17:42:56 +08:00
var headCommit *git.Commit
2022-06-13 17:37:59 +08:00
if pr.Flow == issues_model.PullRequestFlowGithub {
2021-07-28 17:42:56 +08:00
headCommit, err = gitRepo.GetBranchCommit(pr.HeadBranch)
} else {
pr.HeadCommitID, err = gitRepo.GetRefCommitID(pr.GetGitRefName())
if err != nil {
log.Error(" Unable to get head commit : % s Error : % v ", pr.GetGitRefName(), err)
return " "
}
headCommit, err = gitRepo.GetCommit(pr.HeadCommitID)
}
2020-04-10 13:26:37 +02:00
if err != nil {
log.Error(" Unable to get head commit : % s Error : % v ", pr.HeadBranch, err)
return " "
}
mergeBase, err := gitRepo.GetCommit(pr.MergeBase)
if err != nil {
log.Error(" Unable to get merge base commit : % s Error : % v ", pr.MergeBase, err)
return " "
}
limit := setting.Repository.PullRequest.DefaultMergeMessageCommitsLimit
2021-08-09 20:08:51 +02:00
commits, err := gitRepo.CommitsBetweenLimit(headCommit, mergeBase, limit, 0)
2020-04-10 13:26:37 +02:00
if err != nil {
log.Error(" Unable to get commits between : % s % s Error : % v ", pr.HeadBranch, pr.MergeBase, err)
return " "
}
posterSig := pr.Issue.Poster.NewGitSig().String()
2022-10-12 07:18:26 +02:00
uniqueAuthors := make(container.Set[string])
2021-08-09 20:08:51 +02:00
authors := make([]string, 0, len(commits))
2020-04-10 13:26:37 +02:00
stringBuilder := strings.Builder{}
2020-12-22 00:46:14 +08:00
2021-06-18 17:08:22 -05:00
if !setting.Repository.PullRequest.PopulateSquashCommentWithCommitMessages {
2021-06-25 19:01:43 +02:00
message := strings.TrimSpace(pr.Issue.Content)
stringBuilder.WriteString(message)
2021-06-18 17:08:22 -05:00
if stringBuilder.Len() > 0 {
stringBuilder.WriteRune('\n')
2021-06-25 19:01:43 +02:00
if !commitMessageTrailersPattern.MatchString(message) {
stringBuilder.WriteRune('\n')
}
2021-06-18 17:08:22 -05:00
}
2020-12-22 00:46:14 +08:00
}
2020-11-25 21:08:17 +01:00
// commits list is in reverse chronological order
2021-08-09 20:08:51 +02:00
first := true
for i := len(commits) - 1; i >= 0; i-- {
commit := commits[i]
2021-06-18 17:08:22 -05:00
if setting.Repository.PullRequest.PopulateSquashCommentWithCommitMessages {
maxSize := setting.Repository.PullRequest.DefaultMergeMessageSize
if maxSize < 0 || stringBuilder.Len() < maxSize {
var toWrite []byte
2021-08-09 20:08:51 +02:00
if first {
first = false
2021-06-18 17:08:22 -05:00
toWrite = []byte(strings.TrimPrefix(commit.CommitMessage, pr.Issue.Title))
} else {
toWrite = []byte(commit.CommitMessage)
}
if len(toWrite) > maxSize-stringBuilder.Len() && maxSize > -1 {
toWrite = append(toWrite[:maxSize-stringBuilder.Len()], " ... "...)
}
if _, err := stringBuilder.Write(toWrite); err != nil {
log.Error(" Unable to write commit message Error : % v ", err)
return " "
}
if _, err := stringBuilder.WriteRune('\n'); err != nil {
log.Error(" Unable to write commit message Error : % v ", err)
return " "
}
}
}
2020-04-10 13:26:37 +02:00
authorString := commit.Author.String()
2022-10-12 07:18:26 +02:00
if uniqueAuthors.Add(authorString) && authorString != posterSig {
2020-04-10 13:26:37 +02:00
authors = append(authors, authorString)
}
}
// Consider collecting the remaining authors
if limit >= 0 && setting.Repository.PullRequest.DefaultMergeMessageAllAuthors {
skip := limit
limit = 30
for {
2021-08-09 20:08:51 +02:00
commits, err := gitRepo.CommitsBetweenLimit(headCommit, mergeBase, limit, skip)
2020-04-10 13:26:37 +02:00
if err != nil {
log.Error(" Unable to get commits between : % s % s Error : % v ", pr.HeadBranch, pr.MergeBase, err)
return " "
}
2021-08-09 20:08:51 +02:00
if len(commits) == 0 {
2020-04-10 13:26:37 +02:00
break
}
2021-08-09 20:08:51 +02:00
for _, commit := range commits {
2020-04-10 13:26:37 +02:00
authorString := commit.Author.String()
2022-10-12 07:18:26 +02:00
if uniqueAuthors.Add(authorString) && authorString != posterSig {
2020-04-10 13:26:37 +02:00
authors = append(authors, authorString)
}
}
2020-11-27 14:00:52 -06:00
skip += limit
2020-04-10 13:26:37 +02:00
}
}
for _, author := range authors {
if _, err := stringBuilder.Write([]byte(" Co - authored - by : ")); err != nil {
log.Error(" Unable to write to string builder Error : % v ", err)
return " "
}
if _, err := stringBuilder.Write([]byte(author)); err != nil {
log.Error(" Unable to write to string builder Error : % v ", err)
return " "
}
if _, err := stringBuilder.WriteRune('\n'); err != nil {
log.Error(" Unable to write to string builder Error : % v ", err)
return " "
}
}
return stringBuilder.String()
}
2022-04-26 17:40:01 -05:00
// GetIssuesLastCommitStatus returns a map of issue ID to the most recent commit's latest status
2022-06-13 17:37:59 +08:00
func GetIssuesLastCommitStatus(ctx context.Context, issues issues_model.IssueList) (map[int64]*git_model.CommitStatus, error) {
2022-04-26 17:40:01 -05:00
_, lastStatus, err := GetIssuesAllCommitStatus(ctx, issues)
return lastStatus, err
}
// GetIssuesAllCommitStatus returns a map of issue ID to a list of all statuses for the most recent commit as well as a map of issue ID to only the commit's latest status
2022-06-13 17:37:59 +08:00
func GetIssuesAllCommitStatus(ctx context.Context, issues issues_model.IssueList) (map[int64][]*git_model.CommitStatus, map[int64]*git_model.CommitStatus, error) {
2022-11-19 09:12:33 +01:00
if err := issues.LoadPullRequests(ctx); err != nil {
2022-04-26 17:40:01 -05:00
return nil, nil, err
2021-04-16 01:34:43 +08:00
}
2022-11-19 09:12:33 +01:00
if _, err := issues.LoadRepositories(ctx); err != nil {
2022-04-26 17:40:01 -05:00
return nil, nil, err
2021-04-16 01:34:43 +08:00
}
var (
gitRepos = make(map[int64]*git.Repository)
2022-06-12 23:51:54 +08:00
res = make(map[int64][]*git_model.CommitStatus)
lastRes = make(map[int64]*git_model.CommitStatus)
2021-04-16 01:34:43 +08:00
err error
)
defer func() {
for _, gitRepo := range gitRepos {
gitRepo.Close()
}
}()
for _, issue := range issues {
if !issue.IsPull {
continue
}
gitRepo, ok := gitRepos[issue.RepoID]
if !ok {
2022-03-29 21:13:41 +02:00
gitRepo, err = git.OpenRepository(ctx, issue.Repo.RepoPath())
2021-04-16 01:34:43 +08:00
if err != nil {
2021-12-16 19:01:14 +00:00
log.Error(" Cannot open git repository % - v for issue # % d [ % d ] . Error : % v ", issue.Repo, issue.Index, issue.ID, err)
continue
2021-04-16 01:34:43 +08:00
}
gitRepos[issue.RepoID] = gitRepo
}
2022-04-26 17:40:01 -05:00
statuses, lastStatus, err := getAllCommitStatus(gitRepo, issue.PullRequest)
2021-04-16 01:34:43 +08:00
if err != nil {
2022-04-26 17:40:01 -05:00
log.Error(" getAllCommitStatus : cant get commit statuses of pull [ % d ] : % v " , issue . PullRequest . ID , err )
2021-05-04 14:03:02 +02:00
continue
2021-04-16 01:34:43 +08:00
}
2022-04-26 17:40:01 -05:00
res [ issue . PullRequest . ID ] = statuses
lastRes [ issue . PullRequest . ID ] = lastStatus
2021-04-16 01:34:43 +08:00
}
2022-04-26 17:40:01 -05:00
return res , lastRes , nil
2021-04-16 01:34:43 +08:00
}
2022-04-26 17:40:01 -05:00
// getAllCommitStatus get pr's commit statuses.
2022-06-13 17:37:59 +08:00
func getAllCommitStatus ( gitRepo * git . Repository , pr * issues_model . PullRequest ) ( statuses [ ] * git_model . CommitStatus , lastStatus * git_model . CommitStatus , err error ) {
2022-04-26 17:40:01 -05:00
sha , shaErr := gitRepo . GetRefCommitID ( pr . GetGitRefName ( ) )
if shaErr != nil {
return nil , nil , shaErr
2020-04-10 13:26:37 +02:00
}
2022-06-12 23:51:54 +08:00
statuses , _ , err = git_model . GetLatestCommitStatus ( db . DefaultContext , pr . BaseRepo . ID , sha , db . ListOptions { } )
lastStatus = git_model . CalcCommitStatus ( statuses )
2022-04-26 17:40:01 -05:00
return statuses , lastStatus , err
2020-04-10 13:26:37 +02:00
}
// IsHeadEqualWithBranch returns if the commits of branchName are available in pull request head
2022-06-13 17:37:59 +08:00
func IsHeadEqualWithBranch ( ctx context . Context , pr * issues_model . PullRequest , branchName string ) ( bool , error ) {
2020-04-10 13:26:37 +02:00
var err error
2022-11-19 09:12:33 +01:00
if err = pr . LoadBaseRepo ( ctx ) ; err != nil {
2020-04-10 13:26:37 +02:00
return false , err
}
2022-01-19 23:26:57 +00:00
baseGitRepo , closer , err := git . RepositoryFromContextOrOpen ( ctx , pr . BaseRepo . RepoPath ( ) )
2020-04-10 13:26:37 +02:00
if err != nil {
return false , err
}
2022-01-19 23:26:57 +00:00
defer closer . Close ( )
2021-01-07 03:23:57 +08:00
2020-04-10 13:26:37 +02:00
baseCommit , err := baseGitRepo . GetBranchCommit ( branchName )
if err != nil {
return false , err
}
2022-11-19 09:12:33 +01:00
if err = pr . LoadHeadRepo ( ctx ) ; err != nil {
2020-04-10 13:26:37 +02:00
return false , err
}
2022-01-19 23:26:57 +00:00
var headGitRepo * git . Repository
if pr . HeadRepoID == pr . BaseRepoID {
headGitRepo = baseGitRepo
} else {
var closer io . Closer
headGitRepo , closer , err = git . RepositoryFromContextOrOpen ( ctx , pr . HeadRepo . RepoPath ( ) )
if err != nil {
return false , err
}
defer closer . Close ( )
2020-04-10 13:26:37 +02:00
}
2021-01-07 03:23:57 +08:00
2021-07-28 17:42:56 +08:00
var headCommit * git . Commit
2022-06-13 17:37:59 +08:00
if pr . Flow == issues_model . PullRequestFlowGithub {
2021-07-28 17:42:56 +08:00
headCommit , err = headGitRepo . GetBranchCommit ( pr . HeadBranch )
if err != nil {
return false , err
}
} else {
pr . HeadCommitID , err = baseGitRepo . GetRefCommitID ( pr . GetGitRefName ( ) )
if err != nil {
return false , err
}
if headCommit , err = baseGitRepo . GetCommit ( pr . HeadCommitID ) ; err != nil {
return false , err
}
2020-04-10 13:26:37 +02:00
}
return baseCommit . HasPreviousCommit ( headCommit . ID )
}