code.gitea.io/gitea@v1.22.3/services/pull/pull.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package pull
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"regexp"
    13  	"strings"
    14  	"time"
    15  
    16  	"code.gitea.io/gitea/models"
    17  	"code.gitea.io/gitea/models/db"
    18  	git_model "code.gitea.io/gitea/models/git"
    19  	issues_model "code.gitea.io/gitea/models/issues"
    20  	access_model "code.gitea.io/gitea/models/perm/access"
    21  	repo_model "code.gitea.io/gitea/models/repo"
    22  	"code.gitea.io/gitea/models/unit"
    23  	user_model "code.gitea.io/gitea/models/user"
    24  	"code.gitea.io/gitea/modules/base"
    25  	"code.gitea.io/gitea/modules/container"
    26  	"code.gitea.io/gitea/modules/git"
    27  	"code.gitea.io/gitea/modules/gitrepo"
    28  	"code.gitea.io/gitea/modules/graceful"
    29  	"code.gitea.io/gitea/modules/json"
    30  	"code.gitea.io/gitea/modules/log"
    31  	repo_module "code.gitea.io/gitea/modules/repository"
    32  	"code.gitea.io/gitea/modules/setting"
    33  	"code.gitea.io/gitea/modules/sync"
    34  	"code.gitea.io/gitea/modules/util"
    35  	gitea_context "code.gitea.io/gitea/services/context"
    36  	issue_service "code.gitea.io/gitea/services/issue"
    37  	notify_service "code.gitea.io/gitea/services/notify"
    38  )
    39  
    40  // TODO: use clustered lock (unique queue? or *abuse* cache)
    41  var pullWorkingPool = sync.NewExclusivePool()
    42  
    43  // NewPullRequest creates new pull request with labels for repository.
    44  func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, pr *issues_model.PullRequest, assigneeIDs []int64) error {
    45  	if err := issue.LoadPoster(ctx); err != nil {
    46  		return err
    47  	}
    48  
    49  	if user_model.IsUserBlockedBy(ctx, issue.Poster, repo.OwnerID) || user_model.IsUserBlockedBy(ctx, issue.Poster, assigneeIDs...) {
    50  		return user_model.ErrBlockedUser
    51  	}
    52  
    53  	// user should be a collaborator or a member of the organization for base repo
    54  	if !issue.Poster.IsAdmin {
    55  		canCreate, err := repo_model.IsOwnerMemberCollaborator(ctx, repo, issue.Poster.ID)
    56  		if err != nil {
    57  			return err
    58  		}
    59  
    60  		if !canCreate {
    61  			// or user should have write permission in the head repo
    62  			if err := pr.LoadHeadRepo(ctx); err != nil {
    63  				return err
    64  			}
    65  			perm, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, issue.Poster)
    66  			if err != nil {
    67  				return err
    68  			}
    69  			if !perm.CanWrite(unit.TypeCode) {
    70  				return issues_model.ErrMustCollaborator
    71  			}
    72  		}
    73  	}
    74  
    75  	prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
    76  	if err != nil {
    77  		if !git_model.IsErrBranchNotExist(err) {
    78  			log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
    79  		}
    80  		return err
    81  	}
    82  	defer cancel()
    83  
    84  	if err := testPatch(ctx, prCtx, pr); err != nil {
    85  		return err
    86  	}
    87  
    88  	divergence, err := git.GetDivergingCommits(ctx, prCtx.tmpBasePath, baseBranch, trackingBranch)
    89  	if err != nil {
    90  		return err
    91  	}
    92  	pr.CommitsAhead = divergence.Ahead
    93  	pr.CommitsBehind = divergence.Behind
    94  
    95  	assigneeCommentMap := make(map[int64]*issues_model.Comment)
    96  
    97  	// add first push codes comment
    98  	baseGitRepo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo)
    99  	if err != nil {
   100  		return err
   101  	}
   102  	defer baseGitRepo.Close()
   103  
   104  	var reviewNotifiers []*issue_service.ReviewRequestNotifier
   105  	if err := db.WithTx(ctx, func(ctx context.Context) error {
   106  		if err := issues_model.NewPullRequest(ctx, repo, issue, labelIDs, uuids, pr); err != nil {
   107  			return err
   108  		}
   109  
   110  		for _, assigneeID := range assigneeIDs {
   111  			comment, err := issue_service.AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID, false)
   112  			if err != nil {
   113  				return err
   114  			}
   115  			assigneeCommentMap[assigneeID] = comment
   116  		}
   117  
   118  		pr.Issue = issue
   119  		issue.PullRequest = pr
   120  
   121  		if pr.Flow == issues_model.PullRequestFlowGithub {
   122  			err = PushToBaseRepo(ctx, pr)
   123  		} else {
   124  			err = UpdateRef(ctx, pr)
   125  		}
   126  		if err != nil {
   127  			return err
   128  		}
   129  
   130  		compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(),
   131  			git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false)
   132  		if err != nil {
   133  			return err
   134  		}
   135  		if len(compareInfo.Commits) == 0 {
   136  			return nil
   137  		}
   138  
   139  		data := issues_model.PushActionContent{IsForcePush: false}
   140  		data.CommitIDs = make([]string, 0, len(compareInfo.Commits))
   141  		for i := len(compareInfo.Commits) - 1; i >= 0; i-- {
   142  			data.CommitIDs = append(data.CommitIDs, compareInfo.Commits[i].ID.String())
   143  		}
   144  
   145  		dataJSON, err := json.Marshal(data)
   146  		if err != nil {
   147  			return err
   148  		}
   149  
   150  		ops := &issues_model.CreateCommentOptions{
   151  			Type:        issues_model.CommentTypePullRequestPush,
   152  			Doer:        issue.Poster,
   153  			Repo:        repo,
   154  			Issue:       pr.Issue,
   155  			IsForcePush: false,
   156  			Content:     string(dataJSON),
   157  		}
   158  
   159  		if _, err = issues_model.CreateComment(ctx, ops); err != nil {
   160  			return err
   161  		}
   162  
   163  		if !pr.IsWorkInProgress(ctx) {
   164  			reviewNotifiers, err = issue_service.PullRequestCodeOwnersReview(ctx, issue, pr)
   165  			if err != nil {
   166  				return err
   167  			}
   168  		}
   169  		return nil
   170  	}); err != nil {
   171  		// cleanup: this will only remove the reference, the real commit will be clean up when next GC
   172  		if err1 := baseGitRepo.RemoveReference(pr.GetGitRefName()); err1 != nil {
   173  			log.Error("RemoveReference: %v", err1)
   174  		}
   175  		return err
   176  	}
   177  	baseGitRepo.Close() // close immediately to avoid notifications will open the repository again
   178  
   179  	issue_service.ReviewRequestNotify(ctx, issue, issue.Poster, reviewNotifiers)
   180  
   181  	mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, issue, issue.Poster, issue.Content)
   182  	if err != nil {
   183  		return err
   184  	}
   185  	notify_service.NewPullRequest(ctx, pr, mentions)
   186  	if len(issue.Labels) > 0 {
   187  		notify_service.IssueChangeLabels(ctx, issue.Poster, issue, issue.Labels, nil)
   188  	}
   189  	if issue.Milestone != nil {
   190  		notify_service.IssueChangeMilestone(ctx, issue.Poster, issue, 0)
   191  	}
   192  	for _, assigneeID := range assigneeIDs {
   193  		assignee, err := user_model.GetUserByID(ctx, assigneeID)
   194  		if err != nil {
   195  			return ErrDependenciesLeft
   196  		}
   197  		notify_service.IssueChangeAssignee(ctx, issue.Poster, issue, assignee, false, assigneeCommentMap[assigneeID])
   198  	}
   199  
   200  	return nil
   201  }
   202  
   203  // ChangeTargetBranch changes the target branch of this pull request, as the given user.
   204  func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, targetBranch string) (err error) {
   205  	pullWorkingPool.CheckIn(fmt.Sprint(pr.ID))
   206  	defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID))
   207  
   208  	// Current target branch is already the same
   209  	if pr.BaseBranch == targetBranch {
   210  		return nil
   211  	}
   212  
   213  	if pr.Issue.IsClosed {
   214  		return issues_model.ErrIssueIsClosed{
   215  			ID:     pr.Issue.ID,
   216  			RepoID: pr.Issue.RepoID,
   217  			Index:  pr.Issue.Index,
   218  		}
   219  	}
   220  
   221  	if pr.HasMerged {
   222  		return models.ErrPullRequestHasMerged{
   223  			ID:         pr.ID,
   224  			IssueID:    pr.Index,
   225  			HeadRepoID: pr.HeadRepoID,
   226  			BaseRepoID: pr.BaseRepoID,
   227  			HeadBranch: pr.HeadBranch,
   228  			BaseBranch: pr.BaseBranch,
   229  		}
   230  	}
   231  
   232  	// Check if branches are equal
   233  	branchesEqual, err := IsHeadEqualWithBranch(ctx, pr, targetBranch)
   234  	if err != nil {
   235  		return err
   236  	}
   237  	if branchesEqual {
   238  		return git_model.ErrBranchesEqual{
   239  			HeadBranchName: pr.HeadBranch,
   240  			BaseBranchName: targetBranch,
   241  		}
   242  	}
   243  
   244  	// Check if pull request for the new target branch already exists
   245  	existingPr, err := issues_model.GetUnmergedPullRequest(ctx, pr.HeadRepoID, pr.BaseRepoID, pr.HeadBranch, targetBranch, issues_model.PullRequestFlowGithub)
   246  	if existingPr != nil {
   247  		return issues_model.ErrPullRequestAlreadyExists{
   248  			ID:         existingPr.ID,
   249  			IssueID:    existingPr.Index,
   250  			HeadRepoID: existingPr.HeadRepoID,
   251  			BaseRepoID: existingPr.BaseRepoID,
   252  			HeadBranch: existingPr.HeadBranch,
   253  			BaseBranch: existingPr.BaseBranch,
   254  		}
   255  	}
   256  	if err != nil && !issues_model.IsErrPullRequestNotExist(err) {
   257  		return err
   258  	}
   259  
   260  	// Set new target branch
   261  	oldBranch := pr.BaseBranch
   262  	pr.BaseBranch = targetBranch
   263  
   264  	// Refresh patch
   265  	if err := TestPatch(pr); err != nil {
   266  		return err
   267  	}
   268  
   269  	// Update target branch, PR diff and status
   270  	// This is the same as checkAndUpdateStatus in check service, but also updates base_branch
   271  	if pr.Status == issues_model.PullRequestStatusChecking {
   272  		pr.Status = issues_model.PullRequestStatusMergeable
   273  	}
   274  
   275  	// Update Commit Divergence
   276  	divergence, err := GetDiverging(ctx, pr)
   277  	if err != nil {
   278  		return err
   279  	}
   280  	pr.CommitsAhead = divergence.Ahead
   281  	pr.CommitsBehind = divergence.Behind
   282  
   283  	if err := pr.UpdateColsIfNotMerged(ctx, "merge_base", "status", "conflicted_files", "changed_protected_files", "base_branch", "commits_ahead", "commits_behind"); err != nil {
   284  		return err
   285  	}
   286  
   287  	// Create comment
   288  	options := &issues_model.CreateCommentOptions{
   289  		Type:   issues_model.CommentTypeChangeTargetBranch,
   290  		Doer:   doer,
   291  		Repo:   pr.Issue.Repo,
   292  		Issue:  pr.Issue,
   293  		OldRef: oldBranch,
   294  		NewRef: targetBranch,
   295  	}
   296  	if _, err = issues_model.CreateComment(ctx, options); err != nil {
   297  		return fmt.Errorf("CreateChangeTargetBranchComment: %w", err)
   298  	}
   299  
   300  	return nil
   301  }
   302  
   303  func checkForInvalidation(ctx context.Context, requests issues_model.PullRequestList, repoID int64, doer *user_model.User, branch string) error {
   304  	repo, err := repo_model.GetRepositoryByID(ctx, repoID)
   305  	if err != nil {
   306  		return fmt.Errorf("GetRepositoryByIDCtx: %w", err)
   307  	}
   308  	gitRepo, err := gitrepo.OpenRepository(ctx, repo)
   309  	if err != nil {
   310  		return fmt.Errorf("gitrepo.OpenRepository: %w", err)
   311  	}
   312  	go func() {
   313  		// FIXME: graceful: We need to tell the manager we're doing something...
   314  		err := InvalidateCodeComments(ctx, requests, doer, gitRepo, branch)
   315  		if err != nil {
   316  			log.Error("PullRequestList.InvalidateCodeComments: %v", err)
   317  		}
   318  		gitRepo.Close()
   319  	}()
   320  	return nil
   321  }
   322  
   323  // AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
   324  // and generate new patch for testing as needed.
   325  func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string, isSync bool, oldCommitID, newCommitID string) {
   326  	log.Trace("AddTestPullRequestTask [head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch)
   327  	graceful.GetManager().RunWithShutdownContext(func(ctx context.Context) {
   328  		// There is no sensible way to shut this down ":-("
   329  		// If you don't let it run all the way then you will lose data
   330  		// TODO: graceful: AddTestPullRequestTask needs to become a queue!
   331  
   332  		// GetUnmergedPullRequestsByHeadInfo() only return open and unmerged PR.
   333  		prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repoID, branch)
   334  		if err != nil {
   335  			log.Error("Find pull requests [head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
   336  			return
   337  		}
   338  
   339  		for _, pr := range prs {
   340  			log.Trace("Updating PR[%d]: composing new test task", pr.ID)
   341  			if pr.Flow == issues_model.PullRequestFlowGithub {
   342  				if err := PushToBaseRepo(ctx, pr); err != nil {
   343  					log.Error("PushToBaseRepo: %v", err)
   344  					continue
   345  				}
   346  			} else {
   347  				continue
   348  			}
   349  
   350  			AddToTaskQueue(ctx, pr)
   351  			comment, err := CreatePushPullComment(ctx, doer, pr, oldCommitID, newCommitID)
   352  			if err == nil && comment != nil {
   353  				notify_service.PullRequestPushCommits(ctx, doer, pr, comment)
   354  			}
   355  		}
   356  
   357  		if isSync {
   358  			requests := issues_model.PullRequestList(prs)
   359  			if err = requests.LoadAttributes(ctx); err != nil {
   360  				log.Error("PullRequestList.LoadAttributes: %v", err)
   361  			}
   362  			if invalidationErr := checkForInvalidation(ctx, requests, repoID, doer, branch); invalidationErr != nil {
   363  				log.Error("checkForInvalidation: %v", invalidationErr)
   364  			}
   365  			if err == nil {
   366  				for _, pr := range prs {
   367  					objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName)
   368  					if newCommitID != "" && newCommitID != objectFormat.EmptyObjectID().String() {
   369  						changed, err := checkIfPRContentChanged(ctx, pr, oldCommitID, newCommitID)
   370  						if err != nil {
   371  							log.Error("checkIfPRContentChanged: %v", err)
   372  						}
   373  						if changed {
   374  							// Mark old reviews as stale if diff to mergebase has changed
   375  							if err := issues_model.MarkReviewsAsStale(ctx, pr.IssueID); err != nil {
   376  								log.Error("MarkReviewsAsStale: %v", err)
   377  							}
   378  
   379  							// dismiss all approval reviews if protected branch rule item enabled.
   380  							pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
   381  							if err != nil {
   382  								log.Error("GetFirstMatchProtectedBranchRule: %v", err)
   383  							}
   384  							if pb != nil && pb.DismissStaleApprovals {
   385  								if err := DismissApprovalReviews(ctx, doer, pr); err != nil {
   386  									log.Error("DismissApprovalReviews: %v", err)
   387  								}
   388  							}
   389  						}
   390  						if err := issues_model.MarkReviewsAsNotStale(ctx, pr.IssueID, newCommitID); err != nil {
   391  							log.Error("MarkReviewsAsNotStale: %v", err)
   392  						}
   393  						divergence, err := GetDiverging(ctx, pr)
   394  						if err != nil {
   395  							log.Error("GetDiverging: %v", err)
   396  						} else {
   397  							err = pr.UpdateCommitDivergence(ctx, divergence.Ahead, divergence.Behind)
   398  							if err != nil {
   399  								log.Error("UpdateCommitDivergence: %v", err)
   400  							}
   401  						}
   402  					}
   403  
   404  					notify_service.PullRequestSynchronized(ctx, doer, pr)
   405  				}
   406  			}
   407  		}
   408  
   409  		log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
   410  		prs, err = issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repoID, branch)
   411  		if err != nil {
   412  			log.Error("Find pull requests [base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err)
   413  			return
   414  		}
   415  		for _, pr := range prs {
   416  			divergence, err := GetDiverging(ctx, pr)
   417  			if err != nil {
   418  				if git_model.IsErrBranchNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
   419  					log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch)
   420  				} else {
   421  					log.Error("GetDiverging: %v", err)
   422  				}
   423  			} else {
   424  				err = pr.UpdateCommitDivergence(ctx, divergence.Ahead, divergence.Behind)
   425  				if err != nil {
   426  					log.Error("UpdateCommitDivergence: %v", err)
   427  				}
   428  			}
   429  			AddToTaskQueue(ctx, pr)
   430  		}
   431  	})
   432  }
   433  
   434  // checkIfPRContentChanged checks if diff to target branch has changed by push
   435  // A commit can be considered to leave the PR untouched if the patch/diff with its merge base is unchanged
   436  func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest, oldCommitID, newCommitID string) (hasChanged bool, err error) {
   437  	prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
   438  	if err != nil {
   439  		log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
   440  		return false, err
   441  	}
   442  	defer cancel()
   443  
   444  	tmpRepo, err := git.OpenRepository(ctx, prCtx.tmpBasePath)
   445  	if err != nil {
   446  		return false, fmt.Errorf("OpenRepository: %w", err)
   447  	}
   448  	defer tmpRepo.Close()
   449  
   450  	// Find the merge-base
   451  	_, base, err := tmpRepo.GetMergeBase("", "base", "tracking")
   452  	if err != nil {
   453  		return false, fmt.Errorf("GetMergeBase: %w", err)
   454  	}
   455  
   456  	cmd := git.NewCommand(ctx, "diff", "--name-only", "-z").AddDynamicArguments(newCommitID, oldCommitID, base)
   457  	stdoutReader, stdoutWriter, err := os.Pipe()
   458  	if err != nil {
   459  		return false, fmt.Errorf("unable to open pipe for to run diff: %w", err)
   460  	}
   461  
   462  	stderr := new(bytes.Buffer)
   463  	if err := cmd.Run(&git.RunOpts{
   464  		Dir:    prCtx.tmpBasePath,
   465  		Stdout: stdoutWriter,
   466  		Stderr: stderr,
   467  		PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
   468  			_ = stdoutWriter.Close()
   469  			defer func() {
   470  				_ = stdoutReader.Close()
   471  			}()
   472  			return util.IsEmptyReader(stdoutReader)
   473  		},
   474  	}); err != nil {
   475  		if err == util.ErrNotEmpty {
   476  			return true, nil
   477  		}
   478  		err = git.ConcatenateError(err, stderr.String())
   479  
   480  		log.Error("Unable to run diff on %s %s %s in tempRepo for PR[%d]%s/%s...%s/%s: Error: %v",
   481  			newCommitID, oldCommitID, base,
   482  			pr.ID, pr.BaseRepo.FullName(), pr.BaseBranch, pr.HeadRepo.FullName(), pr.HeadBranch,
   483  			err)
   484  
   485  		return false, fmt.Errorf("Unable to run git diff --name-only -z %s %s %s: %w", newCommitID, oldCommitID, base, err)
   486  	}
   487  
   488  	return false, nil
   489  }
   490  
   491  // PushToBaseRepo pushes commits from branches of head repository to
   492  // corresponding branches of base repository.
   493  // FIXME: Only push branches that are actually updates?
   494  func PushToBaseRepo(ctx context.Context, pr *issues_model.PullRequest) (err error) {
   495  	return pushToBaseRepoHelper(ctx, pr, "")
   496  }
   497  
   498  func pushToBaseRepoHelper(ctx context.Context, pr *issues_model.PullRequest, prefixHeadBranch string) (err error) {
   499  	log.Trace("PushToBaseRepo[%d]: pushing commits to base repo '%s'", pr.BaseRepoID, pr.GetGitRefName())
   500  
   501  	if err := pr.LoadHeadRepo(ctx); err != nil {
   502  		log.Error("Unable to load head repository for PR[%d] Error: %v", pr.ID, err)
   503  		return err
   504  	}
   505  	headRepoPath := pr.HeadRepo.RepoPath()
   506  
   507  	if err := pr.LoadBaseRepo(ctx); err != nil {
   508  		log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err)
   509  		return err
   510  	}
   511  	baseRepoPath := pr.BaseRepo.RepoPath()
   512  
   513  	if err = pr.LoadIssue(ctx); err != nil {
   514  		return fmt.Errorf("unable to load issue %d for pr %d: %w", pr.IssueID, pr.ID, err)
   515  	}
   516  	if err = pr.Issue.LoadPoster(ctx); err != nil {
   517  		return fmt.Errorf("unable to load poster %d for pr %d: %w", pr.Issue.PosterID, pr.ID, err)
   518  	}
   519  
   520  	gitRefName := pr.GetGitRefName()
   521  
   522  	if err := git.Push(ctx, headRepoPath, git.PushOptions{
   523  		Remote: baseRepoPath,
   524  		Branch: prefixHeadBranch + pr.HeadBranch + ":" + gitRefName,
   525  		Force:  true,
   526  		// Use InternalPushingEnvironment here because we know that pre-receive and post-receive do not run on a refs/pulls/...
   527  		Env: repo_module.InternalPushingEnvironment(pr.Issue.Poster, pr.BaseRepo),
   528  	}); err != nil {
   529  		if git.IsErrPushOutOfDate(err) {
   530  			// This should not happen as we're using force!
   531  			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)
   532  			return err
   533  		} else if git.IsErrPushRejected(err) {
   534  			rejectErr := err.(*git.ErrPushRejected)
   535  			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)
   536  			return err
   537  		} else if git.IsErrMoreThanOne(err) {
   538  			if prefixHeadBranch != "" {
   539  				log.Info("Can't push with %s%s", prefixHeadBranch, pr.HeadBranch)
   540  				return err
   541  			}
   542  			log.Info("Retrying to push with %s%s", git.BranchPrefix, pr.HeadBranch)
   543  			err = pushToBaseRepoHelper(ctx, pr, git.BranchPrefix)
   544  			return err
   545  		}
   546  		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)
   547  		return fmt.Errorf("Push: %s:%s %s:%s %w", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), gitRefName, err)
   548  	}
   549  
   550  	return nil
   551  }
   552  
   553  // UpdatePullsRefs update all the PRs head file pointers like /refs/pull/1/head so that it will be dependent by other operations
   554  func UpdatePullsRefs(ctx context.Context, repo *repo_model.Repository, update *repo_module.PushUpdateOptions) {
   555  	branch := update.RefFullName.BranchName()
   556  	// GetUnmergedPullRequestsByHeadInfo() only return open and unmerged PR.
   557  	prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repo.ID, branch)
   558  	if err != nil {
   559  		log.Error("Find pull requests [head_repo_id: %d, head_branch: %s]: %v", repo.ID, branch, err)
   560  	} else {
   561  		for _, pr := range prs {
   562  			log.Trace("Updating PR[%d]: composing new test task", pr.ID)
   563  			if pr.Flow == issues_model.PullRequestFlowGithub {
   564  				if err := PushToBaseRepo(ctx, pr); err != nil {
   565  					log.Error("PushToBaseRepo: %v", err)
   566  				}
   567  			}
   568  		}
   569  	}
   570  }
   571  
   572  // UpdateRef update refs/pull/id/head directly for agit flow pull request
   573  func UpdateRef(ctx context.Context, pr *issues_model.PullRequest) (err error) {
   574  	log.Trace("UpdateRef[%d]: upgate pull request ref in base repo '%s'", pr.ID, pr.GetGitRefName())
   575  	if err := pr.LoadBaseRepo(ctx); err != nil {
   576  		log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err)
   577  		return err
   578  	}
   579  
   580  	_, _, err = git.NewCommand(ctx, "update-ref").AddDynamicArguments(pr.GetGitRefName(), pr.HeadCommitID).RunStdString(&git.RunOpts{Dir: pr.BaseRepo.RepoPath()})
   581  	if err != nil {
   582  		log.Error("Unable to update ref in base repository for PR[%d] Error: %v", pr.ID, err)
   583  	}
   584  
   585  	return err
   586  }
   587  
   588  type errlist []error
   589  
   590  func (errs errlist) Error() string {
   591  	if len(errs) > 0 {
   592  		var buf strings.Builder
   593  		for i, err := range errs {
   594  			if i > 0 {
   595  				buf.WriteString(", ")
   596  			}
   597  			buf.WriteString(err.Error())
   598  		}
   599  		return buf.String()
   600  	}
   601  	return ""
   602  }
   603  
   604  // RetargetChildrenOnMerge retarget children pull requests on merge if possible
   605  func RetargetChildrenOnMerge(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) error {
   606  	if setting.Repository.PullRequest.RetargetChildrenOnMerge && pr.BaseRepoID == pr.HeadRepoID {
   607  		return RetargetBranchPulls(ctx, doer, pr.HeadRepoID, pr.HeadBranch, pr.BaseBranch)
   608  	}
   609  	return nil
   610  }
   611  
   612  // RetargetBranchPulls change target branch for all pull requests whose base branch is the branch
   613  // Both branch and targetBranch must be in the same repo (for security reasons)
   614  func RetargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int64, branch, targetBranch string) error {
   615  	prs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repoID, branch)
   616  	if err != nil {
   617  		return err
   618  	}
   619  
   620  	if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil {
   621  		return err
   622  	}
   623  
   624  	var errs errlist
   625  	for _, pr := range prs {
   626  		if err = pr.Issue.LoadRepo(ctx); err != nil {
   627  			errs = append(errs, err)
   628  		} else if err = ChangeTargetBranch(ctx, pr, doer, targetBranch); err != nil &&
   629  			!issues_model.IsErrIssueIsClosed(err) && !models.IsErrPullRequestHasMerged(err) &&
   630  			!issues_model.IsErrPullRequestAlreadyExists(err) {
   631  			errs = append(errs, err)
   632  		}
   633  	}
   634  
   635  	if len(errs) > 0 {
   636  		return errs
   637  	}
   638  	return nil
   639  }
   640  
   641  // CloseBranchPulls close all the pull requests who's head branch is the branch
   642  func CloseBranchPulls(ctx context.Context, doer *user_model.User, repoID int64, branch string) error {
   643  	prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repoID, branch)
   644  	if err != nil {
   645  		return err
   646  	}
   647  
   648  	prs2, err := issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repoID, branch)
   649  	if err != nil {
   650  		return err
   651  	}
   652  
   653  	prs = append(prs, prs2...)
   654  	if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil {
   655  		return err
   656  	}
   657  
   658  	var errs errlist
   659  	for _, pr := range prs {
   660  		if err = issue_service.ChangeStatus(ctx, pr.Issue, doer, "", true); err != nil && !issues_model.IsErrPullWasClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
   661  			errs = append(errs, err)
   662  		}
   663  	}
   664  	if len(errs) > 0 {
   665  		return errs
   666  	}
   667  	return nil
   668  }
   669  
   670  // CloseRepoBranchesPulls close all pull requests which head branches are in the given repository, but only whose base repo is not in the given repository
   671  func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *repo_model.Repository) error {
   672  	branches, _, err := gitrepo.GetBranchesByPath(ctx, repo, 0, 0)
   673  	if err != nil {
   674  		return err
   675  	}
   676  
   677  	var errs errlist
   678  	for _, branch := range branches {
   679  		prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repo.ID, branch.Name)
   680  		if err != nil {
   681  			return err
   682  		}
   683  
   684  		if err = issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil {
   685  			return err
   686  		}
   687  
   688  		for _, pr := range prs {
   689  			// If the base repository for this pr is this repository there is no need to close it
   690  			// as it is going to be deleted anyway
   691  			if pr.BaseRepoID == repo.ID {
   692  				continue
   693  			}
   694  			if err = issue_service.ChangeStatus(ctx, pr.Issue, doer, "", true); err != nil && !issues_model.IsErrPullWasClosed(err) {
   695  				errs = append(errs, err)
   696  			}
   697  		}
   698  	}
   699  
   700  	if len(errs) > 0 {
   701  		return errs
   702  	}
   703  	return nil
   704  }
   705  
   706  var commitMessageTrailersPattern = regexp.MustCompile(`(?:^|\n\n)(?:[\w-]+[ \t]*:[^\n]+\n*(?:[ \t]+[^\n]+\n*)*)+$`)
   707  
   708  // GetSquashMergeCommitMessages returns the commit messages between head and merge base (if there is one)
   709  func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequest) string {
   710  	if err := pr.LoadIssue(ctx); err != nil {
   711  		log.Error("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err)
   712  		return ""
   713  	}
   714  
   715  	if err := pr.Issue.LoadPoster(ctx); err != nil {
   716  		log.Error("Cannot load poster %d for pr id %d, index %d Error: %v", pr.Issue.PosterID, pr.ID, pr.Index, err)
   717  		return ""
   718  	}
   719  
   720  	if pr.HeadRepo == nil {
   721  		var err error
   722  		pr.HeadRepo, err = repo_model.GetRepositoryByID(ctx, pr.HeadRepoID)
   723  		if err != nil {
   724  			log.Error("GetRepositoryByIdCtx[%d]: %v", pr.HeadRepoID, err)
   725  			return ""
   726  		}
   727  	}
   728  
   729  	gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.HeadRepo)
   730  	if err != nil {
   731  		log.Error("Unable to open head repository: Error: %v", err)
   732  		return ""
   733  	}
   734  	defer closer.Close()
   735  
   736  	var headCommit *git.Commit
   737  	if pr.Flow == issues_model.PullRequestFlowGithub {
   738  		headCommit, err = gitRepo.GetBranchCommit(pr.HeadBranch)
   739  	} else {
   740  		pr.HeadCommitID, err = gitRepo.GetRefCommitID(pr.GetGitRefName())
   741  		if err != nil {
   742  			log.Error("Unable to get head commit: %s Error: %v", pr.GetGitRefName(), err)
   743  			return ""
   744  		}
   745  		headCommit, err = gitRepo.GetCommit(pr.HeadCommitID)
   746  	}
   747  	if err != nil {
   748  		log.Error("Unable to get head commit: %s Error: %v", pr.HeadBranch, err)
   749  		return ""
   750  	}
   751  
   752  	mergeBase, err := gitRepo.GetCommit(pr.MergeBase)
   753  	if err != nil {
   754  		log.Error("Unable to get merge base commit: %s Error: %v", pr.MergeBase, err)
   755  		return ""
   756  	}
   757  
   758  	limit := setting.Repository.PullRequest.DefaultMergeMessageCommitsLimit
   759  
   760  	commits, err := gitRepo.CommitsBetweenLimit(headCommit, mergeBase, limit, 0)
   761  	if err != nil {
   762  		log.Error("Unable to get commits between: %s %s Error: %v", pr.HeadBranch, pr.MergeBase, err)
   763  		return ""
   764  	}
   765  
   766  	posterSig := pr.Issue.Poster.NewGitSig().String()
   767  
   768  	uniqueAuthors := make(container.Set[string])
   769  	authors := make([]string, 0, len(commits))
   770  	stringBuilder := strings.Builder{}
   771  
   772  	if !setting.Repository.PullRequest.PopulateSquashCommentWithCommitMessages {
   773  		message := strings.TrimSpace(pr.Issue.Content)
   774  		stringBuilder.WriteString(message)
   775  		if stringBuilder.Len() > 0 {
   776  			stringBuilder.WriteRune('\n')
   777  			if !commitMessageTrailersPattern.MatchString(message) {
   778  				stringBuilder.WriteRune('\n')
   779  			}
   780  		}
   781  	}
   782  
   783  	// commits list is in reverse chronological order
   784  	first := true
   785  	for i := len(commits) - 1; i >= 0; i-- {
   786  		commit := commits[i]
   787  
   788  		if setting.Repository.PullRequest.PopulateSquashCommentWithCommitMessages {
   789  			maxSize := setting.Repository.PullRequest.DefaultMergeMessageSize
   790  			if maxSize < 0 || stringBuilder.Len() < maxSize {
   791  				var toWrite []byte
   792  				if first {
   793  					first = false
   794  					toWrite = []byte(strings.TrimPrefix(commit.CommitMessage, pr.Issue.Title))
   795  				} else {
   796  					toWrite = []byte(commit.CommitMessage)
   797  				}
   798  
   799  				if len(toWrite) > maxSize-stringBuilder.Len() && maxSize > -1 {
   800  					toWrite = append(toWrite[:maxSize-stringBuilder.Len()], "..."...)
   801  				}
   802  				if _, err := stringBuilder.Write(toWrite); err != nil {
   803  					log.Error("Unable to write commit message Error: %v", err)
   804  					return ""
   805  				}
   806  
   807  				if _, err := stringBuilder.WriteRune('\n'); err != nil {
   808  					log.Error("Unable to write commit message Error: %v", err)
   809  					return ""
   810  				}
   811  			}
   812  		}
   813  
   814  		authorString := commit.Author.String()
   815  		if uniqueAuthors.Add(authorString) && authorString != posterSig {
   816  			// Compare use account as well to avoid adding the same author multiple times
   817  			// times when email addresses are private or multiple emails are used.
   818  			commitUser, _ := user_model.GetUserByEmail(ctx, commit.Author.Email)
   819  			if commitUser == nil || commitUser.ID != pr.Issue.Poster.ID {
   820  				authors = append(authors, authorString)
   821  			}
   822  		}
   823  	}
   824  
   825  	// Consider collecting the remaining authors
   826  	if limit >= 0 && setting.Repository.PullRequest.DefaultMergeMessageAllAuthors {
   827  		skip := limit
   828  		limit = 30
   829  		for {
   830  			commits, err := gitRepo.CommitsBetweenLimit(headCommit, mergeBase, limit, skip)
   831  			if err != nil {
   832  				log.Error("Unable to get commits between: %s %s Error: %v", pr.HeadBranch, pr.MergeBase, err)
   833  				return ""
   834  			}
   835  			if len(commits) == 0 {
   836  				break
   837  			}
   838  			for _, commit := range commits {
   839  				authorString := commit.Author.String()
   840  				if uniqueAuthors.Add(authorString) && authorString != posterSig {
   841  					commitUser, _ := user_model.GetUserByEmail(ctx, commit.Author.Email)
   842  					if commitUser == nil || commitUser.ID != pr.Issue.Poster.ID {
   843  						authors = append(authors, authorString)
   844  					}
   845  				}
   846  			}
   847  			skip += limit
   848  		}
   849  	}
   850  
   851  	for _, author := range authors {
   852  		if _, err := stringBuilder.WriteString("Co-authored-by: "); err != nil {
   853  			log.Error("Unable to write to string builder Error: %v", err)
   854  			return ""
   855  		}
   856  		if _, err := stringBuilder.WriteString(author); err != nil {
   857  			log.Error("Unable to write to string builder Error: %v", err)
   858  			return ""
   859  		}
   860  		if _, err := stringBuilder.WriteRune('\n'); err != nil {
   861  			log.Error("Unable to write to string builder Error: %v", err)
   862  			return ""
   863  		}
   864  	}
   865  
   866  	return stringBuilder.String()
   867  }
   868  
   869  // GetIssuesLastCommitStatus returns a map of issue ID to the most recent commit's latest status
   870  func GetIssuesLastCommitStatus(ctx context.Context, issues issues_model.IssueList) (map[int64]*git_model.CommitStatus, error) {
   871  	_, lastStatus, err := GetIssuesAllCommitStatus(ctx, issues)
   872  	return lastStatus, err
   873  }
   874  
   875  // 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
   876  func GetIssuesAllCommitStatus(ctx context.Context, issues issues_model.IssueList) (map[int64][]*git_model.CommitStatus, map[int64]*git_model.CommitStatus, error) {
   877  	if err := issues.LoadPullRequests(ctx); err != nil {
   878  		return nil, nil, err
   879  	}
   880  	if _, err := issues.LoadRepositories(ctx); err != nil {
   881  		return nil, nil, err
   882  	}
   883  
   884  	var (
   885  		gitRepos = make(map[int64]*git.Repository)
   886  		res      = make(map[int64][]*git_model.CommitStatus)
   887  		lastRes  = make(map[int64]*git_model.CommitStatus)
   888  		err      error
   889  	)
   890  	defer func() {
   891  		for _, gitRepo := range gitRepos {
   892  			gitRepo.Close()
   893  		}
   894  	}()
   895  
   896  	for _, issue := range issues {
   897  		if !issue.IsPull {
   898  			continue
   899  		}
   900  		gitRepo, ok := gitRepos[issue.RepoID]
   901  		if !ok {
   902  			gitRepo, err = gitrepo.OpenRepository(ctx, issue.Repo)
   903  			if err != nil {
   904  				log.Error("Cannot open git repository %-v for issue #%d[%d]. Error: %v", issue.Repo, issue.Index, issue.ID, err)
   905  				continue
   906  			}
   907  			gitRepos[issue.RepoID] = gitRepo
   908  		}
   909  
   910  		statuses, lastStatus, err := getAllCommitStatus(ctx, gitRepo, issue.PullRequest)
   911  		if err != nil {
   912  			log.Error("getAllCommitStatus: cant get commit statuses of pull [%d]: %v", issue.PullRequest.ID, err)
   913  			continue
   914  		}
   915  		res[issue.PullRequest.ID] = statuses
   916  		lastRes[issue.PullRequest.ID] = lastStatus
   917  	}
   918  	return res, lastRes, nil
   919  }
   920  
   921  // getAllCommitStatus get pr's commit statuses.
   922  func getAllCommitStatus(ctx context.Context, gitRepo *git.Repository, pr *issues_model.PullRequest) (statuses []*git_model.CommitStatus, lastStatus *git_model.CommitStatus, err error) {
   923  	sha, shaErr := gitRepo.GetRefCommitID(pr.GetGitRefName())
   924  	if shaErr != nil {
   925  		return nil, nil, shaErr
   926  	}
   927  
   928  	statuses, _, err = git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll)
   929  	lastStatus = git_model.CalcCommitStatus(statuses)
   930  	return statuses, lastStatus, err
   931  }
   932  
   933  // IsHeadEqualWithBranch returns if the commits of branchName are available in pull request head
   934  func IsHeadEqualWithBranch(ctx context.Context, pr *issues_model.PullRequest, branchName string) (bool, error) {
   935  	var err error
   936  	if err = pr.LoadBaseRepo(ctx); err != nil {
   937  		return false, err
   938  	}
   939  	baseGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.BaseRepo)
   940  	if err != nil {
   941  		return false, err
   942  	}
   943  	defer closer.Close()
   944  
   945  	baseCommit, err := baseGitRepo.GetBranchCommit(branchName)
   946  	if err != nil {
   947  		return false, err
   948  	}
   949  
   950  	if err = pr.LoadHeadRepo(ctx); err != nil {
   951  		return false, err
   952  	}
   953  	var headGitRepo *git.Repository
   954  	if pr.HeadRepoID == pr.BaseRepoID {
   955  		headGitRepo = baseGitRepo
   956  	} else {
   957  		var closer io.Closer
   958  
   959  		headGitRepo, closer, err = gitrepo.RepositoryFromContextOrOpen(ctx, pr.HeadRepo)
   960  		if err != nil {
   961  			return false, err
   962  		}
   963  		defer closer.Close()
   964  	}
   965  
   966  	var headCommit *git.Commit
   967  	if pr.Flow == issues_model.PullRequestFlowGithub {
   968  		headCommit, err = headGitRepo.GetBranchCommit(pr.HeadBranch)
   969  		if err != nil {
   970  			return false, err
   971  		}
   972  	} else {
   973  		pr.HeadCommitID, err = baseGitRepo.GetRefCommitID(pr.GetGitRefName())
   974  		if err != nil {
   975  			return false, err
   976  		}
   977  		if headCommit, err = baseGitRepo.GetCommit(pr.HeadCommitID); err != nil {
   978  			return false, err
   979  		}
   980  	}
   981  	return baseCommit.HasPreviousCommit(headCommit.ID)
   982  }
   983  
   984  type CommitInfo struct {
   985  	Summary               string `json:"summary"`
   986  	CommitterOrAuthorName string `json:"committer_or_author_name"`
   987  	ID                    string `json:"id"`
   988  	ShortSha              string `json:"short_sha"`
   989  	Time                  string `json:"time"`
   990  }
   991  
   992  // GetPullCommits returns all commits on given pull request and the last review commit sha
   993  // Attention: The last review commit sha must be from the latest review whose commit id is not empty.
   994  // So the type of the latest review cannot be "ReviewTypeRequest".
   995  func GetPullCommits(ctx *gitea_context.Context, issue *issues_model.Issue) ([]CommitInfo, string, error) {
   996  	pull := issue.PullRequest
   997  
   998  	baseGitRepo := ctx.Repo.GitRepo
   999  
  1000  	if err := pull.LoadBaseRepo(ctx); err != nil {
  1001  		return nil, "", err
  1002  	}
  1003  	baseBranch := pull.BaseBranch
  1004  	if pull.HasMerged {
  1005  		baseBranch = pull.MergeBase
  1006  	}
  1007  	prInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(), baseBranch, pull.GetGitRefName(), true, false)
  1008  	if err != nil {
  1009  		return nil, "", err
  1010  	}
  1011  
  1012  	commits := make([]CommitInfo, 0, len(prInfo.Commits))
  1013  
  1014  	for _, commit := range prInfo.Commits {
  1015  		var committerOrAuthorName string
  1016  		var commitTime time.Time
  1017  		if commit.Author != nil {
  1018  			committerOrAuthorName = commit.Author.Name
  1019  			commitTime = commit.Author.When
  1020  		} else {
  1021  			committerOrAuthorName = commit.Committer.Name
  1022  			commitTime = commit.Committer.When
  1023  		}
  1024  
  1025  		commits = append(commits, CommitInfo{
  1026  			Summary:               commit.Summary(),
  1027  			CommitterOrAuthorName: committerOrAuthorName,
  1028  			ID:                    commit.ID.String(),
  1029  			ShortSha:              base.ShortSha(commit.ID.String()),
  1030  			Time:                  commitTime.Format(time.RFC3339),
  1031  		})
  1032  	}
  1033  
  1034  	var lastReviewCommitID string
  1035  	if ctx.IsSigned {
  1036  		// get last review of current user and store information in context (if available)
  1037  		lastreview, err := issues_model.FindLatestReviews(ctx, issues_model.FindReviewOptions{
  1038  			IssueID:    issue.ID,
  1039  			ReviewerID: ctx.Doer.ID,
  1040  			Types: []issues_model.ReviewType{
  1041  				issues_model.ReviewTypeApprove,
  1042  				issues_model.ReviewTypeComment,
  1043  				issues_model.ReviewTypeReject,
  1044  			},
  1045  		})
  1046  
  1047  		if err != nil && !issues_model.IsErrReviewNotExist(err) {
  1048  			return nil, "", err
  1049  		}
  1050  		if len(lastreview) > 0 {
  1051  			lastReviewCommitID = lastreview[0].CommitID
  1052  		}
  1053  	}
  1054  
  1055  	return commits, lastReviewCommitID, nil
  1056  }