mirror of https://github.com/go-gitea/gitea.git
Merge 1cf2e663ae into 0fb3be7f0e
commit
1c33249c75
|
|
@ -256,6 +256,9 @@ func (pr *PullRequest) LoadHeadRepo(ctx context.Context) (err error) {
|
||||||
return fmt.Errorf("pr[%d].LoadHeadRepo[%d]: %w", pr.ID, pr.HeadRepoID, err)
|
return fmt.Errorf("pr[%d].LoadHeadRepo[%d]: %w", pr.ID, pr.HeadRepoID, err)
|
||||||
}
|
}
|
||||||
pr.isHeadRepoLoaded = true
|
pr.isHeadRepoLoaded = true
|
||||||
|
if pr.IsSameRepo() && pr.BaseRepo == nil {
|
||||||
|
pr.BaseRepo = pr.HeadRepo
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -322,6 +325,9 @@ func (pr *PullRequest) LoadBaseRepo(ctx context.Context) (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("pr[%d].LoadBaseRepo[%d]: %w", pr.ID, pr.BaseRepoID, err)
|
return fmt.Errorf("pr[%d].LoadBaseRepo[%d]: %w", pr.ID, pr.BaseRepoID, err)
|
||||||
}
|
}
|
||||||
|
if pr.IsSameRepo() && pr.HeadRepo == nil {
|
||||||
|
pr.HeadRepo = pr.BaseRepo
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package gitrepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FetchRemoteBranch fetches a remote branch into a local branch
|
||||||
|
func FetchRemoteBranch(ctx context.Context, repo Repository, localBranch string, remoteRepo Repository, remoteBranch string, args ...string) error {
|
||||||
|
_, _, err := gitcmd.NewCommand("fetch", "--no-tags", "--refmap=").
|
||||||
|
AddDynamicArguments(repoPath(remoteRepo)).
|
||||||
|
// + means force fetch
|
||||||
|
AddDynamicArguments(fmt.Sprintf("+refs/heads/%s:%s", remoteBranch, localBranch)).
|
||||||
|
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func FetchRemoteCommit(ctx context.Context, repo, remoteRepo Repository, commitID string) error {
|
||||||
|
_, _, err := gitcmd.NewCommand("fetch", "--no-tags").
|
||||||
|
AddDynamicArguments(repoPath(remoteRepo)).
|
||||||
|
AddDynamicArguments(commitID).
|
||||||
|
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
@ -246,35 +246,30 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store old commit ID for review staleness checking
|
|
||||||
oldHeadCommitID := pr.HeadCommitID
|
|
||||||
|
|
||||||
pr.HeadCommitID = opts.NewCommitIDs[i]
|
pr.HeadCommitID = opts.NewCommitIDs[i]
|
||||||
if err = pull_service.UpdateRef(ctx, pr); err != nil {
|
if err = pull_service.UpdatePullRequestHeadRef(ctx, pr); err != nil {
|
||||||
return nil, fmt.Errorf("failed to update pull ref. Error: %w", err)
|
return nil, fmt.Errorf("failed to update pull ref. Error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark existing reviews as stale when PR content changes (same as regular GitHub flow)
|
// Mark existing reviews as stale when PR content changes (same as regular GitHub flow)
|
||||||
if oldHeadCommitID != opts.NewCommitIDs[i] {
|
if err := issues_model.MarkReviewsAsStale(ctx, pr.IssueID); err != nil {
|
||||||
if err := issues_model.MarkReviewsAsStale(ctx, pr.IssueID); err != nil {
|
log.Error("MarkReviewsAsStale: %v", err)
|
||||||
log.Error("MarkReviewsAsStale: %v", err)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Dismiss all approval reviews if protected branch rule item enabled
|
// Dismiss all approval reviews if protected branch rule item enabled
|
||||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetFirstMatchProtectedBranchRule: %v", err)
|
log.Error("GetFirstMatchProtectedBranchRule: %v", err)
|
||||||
}
|
}
|
||||||
if pb != nil && pb.DismissStaleApprovals {
|
if pb != nil && pb.DismissStaleApprovals {
|
||||||
if err := pull_service.DismissApprovalReviews(ctx, pusher, pr); err != nil {
|
if err := pull_service.DismissApprovalReviews(ctx, pusher, pr); err != nil {
|
||||||
log.Error("DismissApprovalReviews: %v", err)
|
log.Error("DismissApprovalReviews: %v", err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Mark reviews for the new commit as not stale
|
// Mark reviews for the new commit as not stale
|
||||||
if err := issues_model.MarkReviewsAsNotStale(ctx, pr.IssueID, opts.NewCommitIDs[i]); err != nil {
|
if err := issues_model.MarkReviewsAsNotStale(ctx, pr.IssueID, opts.NewCommitIDs[i]); err != nil {
|
||||||
log.Error("MarkReviewsAsNotStale: %v", err)
|
log.Error("MarkReviewsAsNotStale: %v", err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pull_service.StartPullRequestCheckImmediately(ctx, pr)
|
pull_service.StartPullRequestCheckImmediately(ctx, pr)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ package pull
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
|
@ -52,6 +53,10 @@ func CreatePushPullComment(ctx context.Context, pusher *user_model.User, pr *iss
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := pr.LoadIssue(ctx); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to load issue for PR[%d]: %w", pr.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
opts := &issues_model.CreateCommentOptions{
|
opts := &issues_model.CreateCommentOptions{
|
||||||
Type: issues_model.CommentTypePullRequestPush,
|
Type: issues_model.CommentTypePullRequestPush,
|
||||||
Doer: pusher,
|
Doer: pusher,
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,10 @@ type NewPullRequestOptions struct {
|
||||||
|
|
||||||
// NewPullRequest creates new pull request with labels for repository.
|
// NewPullRequest creates new pull request with labels for repository.
|
||||||
func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
|
func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
|
||||||
|
if opts.PullRequest.Flow == issues_model.PullRequestFlowAGit && opts.PullRequest.HeadCommitID == "" {
|
||||||
|
return errors.New("head commit ID cannot be empty for agit flow")
|
||||||
|
}
|
||||||
|
|
||||||
repo, issue, labelIDs, uuids, pr, assigneeIDs := opts.Repo, opts.Issue, opts.LabelIDs, opts.AttachmentUUIDs, opts.PullRequest, opts.AssigneeIDs
|
repo, issue, labelIDs, uuids, pr, assigneeIDs := opts.Repo, opts.Issue, opts.LabelIDs, opts.AttachmentUUIDs, opts.PullRequest, opts.AssigneeIDs
|
||||||
if err := issue.LoadPoster(ctx); err != nil {
|
if err := issue.LoadPoster(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -118,12 +122,8 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
|
||||||
pr.Issue = issue
|
pr.Issue = issue
|
||||||
issue.PullRequest = pr
|
issue.PullRequest = pr
|
||||||
|
|
||||||
if pr.Flow == issues_model.PullRequestFlowGithub {
|
// update head commit id into git repository
|
||||||
err = PushToBaseRepo(ctx, pr)
|
if err = UpdatePullRequestHeadRef(ctx, pr); err != nil {
|
||||||
} else {
|
|
||||||
err = UpdateRef(ctx, pr)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -396,7 +396,7 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
|
||||||
log.Trace("Updating PR[%d]: composing new test task", pr.ID)
|
log.Trace("Updating PR[%d]: composing new test task", pr.ID)
|
||||||
pr.HeadRepo = repo // avoid loading again
|
pr.HeadRepo = repo // avoid loading again
|
||||||
if pr.Flow == issues_model.PullRequestFlowGithub {
|
if pr.Flow == issues_model.PullRequestFlowGithub {
|
||||||
if err := PushToBaseRepo(ctx, pr); err != nil {
|
if err := UpdatePullRequestHeadRef(ctx, pr); err != nil {
|
||||||
log.Error("PushToBaseRepo: %v", err)
|
log.Error("PushToBaseRepo: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -549,68 +549,6 @@ func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest,
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushToBaseRepo pushes commits from branches of head repository to
|
|
||||||
// corresponding branches of base repository.
|
|
||||||
// FIXME: Only push branches that are actually updates?
|
|
||||||
func PushToBaseRepo(ctx context.Context, pr *issues_model.PullRequest) (err error) {
|
|
||||||
return pushToBaseRepoHelper(ctx, pr, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func pushToBaseRepoHelper(ctx context.Context, pr *issues_model.PullRequest, prefixHeadBranch string) (err error) {
|
|
||||||
log.Trace("PushToBaseRepo[%d]: pushing commits to base repo '%s'", pr.BaseRepoID, pr.GetGitHeadRefName())
|
|
||||||
|
|
||||||
if err := pr.LoadHeadRepo(ctx); err != nil {
|
|
||||||
log.Error("Unable to load head repository for PR[%d] Error: %v", pr.ID, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
headRepoPath := pr.HeadRepo.RepoPath()
|
|
||||||
|
|
||||||
if err := pr.LoadBaseRepo(ctx); err != nil {
|
|
||||||
log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
baseRepoPath := pr.BaseRepo.RepoPath()
|
|
||||||
|
|
||||||
if err = pr.LoadIssue(ctx); err != nil {
|
|
||||||
return fmt.Errorf("unable to load issue %d for pr %d: %w", pr.IssueID, pr.ID, err)
|
|
||||||
}
|
|
||||||
if err = pr.Issue.LoadPoster(ctx); err != nil {
|
|
||||||
return fmt.Errorf("unable to load poster %d for pr %d: %w", pr.Issue.PosterID, pr.ID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
gitRefName := pr.GetGitHeadRefName()
|
|
||||||
|
|
||||||
if err := git.Push(ctx, headRepoPath, git.PushOptions{
|
|
||||||
Remote: baseRepoPath,
|
|
||||||
Branch: prefixHeadBranch + pr.HeadBranch + ":" + gitRefName,
|
|
||||||
Force: true,
|
|
||||||
// Use InternalPushingEnvironment here because we know that pre-receive and post-receive do not run on a refs/pulls/...
|
|
||||||
Env: repo_module.InternalPushingEnvironment(pr.Issue.Poster, pr.BaseRepo),
|
|
||||||
}); err != nil {
|
|
||||||
if git.IsErrPushOutOfDate(err) {
|
|
||||||
// This should not happen as we're using force!
|
|
||||||
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)
|
|
||||||
return err
|
|
||||||
} else if git.IsErrPushRejected(err) {
|
|
||||||
rejectErr := err.(*git.ErrPushRejected)
|
|
||||||
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)
|
|
||||||
return err
|
|
||||||
} else if git.IsErrMoreThanOne(err) {
|
|
||||||
if prefixHeadBranch != "" {
|
|
||||||
log.Info("Can't push with %s%s", prefixHeadBranch, pr.HeadBranch)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Info("Retrying to push with %s%s", git.BranchPrefix, pr.HeadBranch)
|
|
||||||
err = pushToBaseRepoHelper(ctx, pr, git.BranchPrefix)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
return fmt.Errorf("Push: %s:%s %s:%s %w", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), gitRefName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdatePullsRefs update all the PRs head file pointers like /refs/pull/1/head so that it will be dependent by other operations
|
// UpdatePullsRefs update all the PRs head file pointers like /refs/pull/1/head so that it will be dependent by other operations
|
||||||
func UpdatePullsRefs(ctx context.Context, repo *repo_model.Repository, update *repo_module.PushUpdateOptions) {
|
func UpdatePullsRefs(ctx context.Context, repo *repo_model.Repository, update *repo_module.PushUpdateOptions) {
|
||||||
branch := update.RefFullName.BranchName()
|
branch := update.RefFullName.BranchName()
|
||||||
|
|
@ -622,27 +560,38 @@ func UpdatePullsRefs(ctx context.Context, repo *repo_model.Repository, update *r
|
||||||
for _, pr := range prs {
|
for _, pr := range prs {
|
||||||
log.Trace("Updating PR[%d]: composing new test task", pr.ID)
|
log.Trace("Updating PR[%d]: composing new test task", pr.ID)
|
||||||
if pr.Flow == issues_model.PullRequestFlowGithub {
|
if pr.Flow == issues_model.PullRequestFlowGithub {
|
||||||
if err := PushToBaseRepo(ctx, pr); err != nil {
|
if err := UpdatePullRequestHeadRef(ctx, pr); err != nil {
|
||||||
log.Error("PushToBaseRepo: %v", err)
|
log.Error("UpdatePullRequestHead: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRef update refs/pull/id/head directly for agit flow pull request
|
// UpdatePullRequestHeadRef updates the head reference of a pull request
|
||||||
func UpdateRef(ctx context.Context, pr *issues_model.PullRequest) (err error) {
|
func UpdatePullRequestHeadRef(ctx context.Context, pr *issues_model.PullRequest) error {
|
||||||
log.Trace("UpdateRef[%d]: upgate pull request ref in base repo '%s'", pr.ID, pr.GetGitHeadRefName())
|
log.Trace("UpdatePullRequestHeadRef[%d]: update pull request ref in base repo '%s'", pr.ID, pr.GetGitHeadRefName())
|
||||||
|
|
||||||
if err := pr.LoadBaseRepo(ctx); err != nil {
|
if err := pr.LoadBaseRepo(ctx); err != nil {
|
||||||
log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gitrepo.UpdateRef(ctx, pr.BaseRepo, pr.GetGitHeadRefName(), pr.HeadCommitID); err != nil {
|
if pr.Flow == issues_model.PullRequestFlowAGit {
|
||||||
log.Error("Unable to update ref in base repository for PR[%d] Error: %v", pr.ID, err)
|
if pr.HeadCommitID == "" {
|
||||||
|
return errors.New("head commit ID cannot be empty for agit flow")
|
||||||
|
}
|
||||||
|
return gitrepo.UpdateRef(ctx, pr.BaseRepo, pr.GetGitHeadRefName(), pr.HeadCommitID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
if !pr.IsSameRepo() { // for cross repository pull request
|
||||||
|
if err := pr.LoadHeadRepo(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gitrepo.FetchRemoteBranch(ctx, pr.BaseRepo, pr.GetGitHeadRefName(), pr.HeadRepo, pr.HeadBranch)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gitrepo.UpdateRef(ctx, pr.BaseRepo, pr.GetGitHeadRefName(), pr.HeadBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// retargetBranchPulls change target branch for all pull requests whose base branch is the branch
|
// retargetBranchPulls change target branch for all pull requests whose base branch is the branch
|
||||||
|
|
|
||||||
|
|
@ -710,6 +710,14 @@ func GetBranchDivergingInfo(ctx reqctx.RequestContext, baseRepo *repo_model.Repo
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we need fetch the necessary commits from the head repo first if it's not the same repository
|
||||||
|
if baseRepo.ID != headRepo.ID {
|
||||||
|
// git default will gc the commit in 2 weeks, so it's safe to do the compare
|
||||||
|
if err := gitrepo.FetchRemoteCommit(ctx, baseRepo, headRepo, headGitBranch.CommitID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if the fork repo has new commits, this call will fail because they are not in the base repo
|
// if the fork repo has new commits, this call will fail because they are not in the base repo
|
||||||
// exit status 128 - fatal: Invalid symmetric difference expression aaaaaaaaaaaa...bbbbbbbbbbbb
|
// exit status 128 - fatal: Invalid symmetric difference expression aaaaaaaaaaaa...bbbbbbbbbbbb
|
||||||
// so at the moment, we first check the update time, then check whether the fork branch has base's head
|
// so at the moment, we first check the update time, then check whether the fork branch has base's head
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,9 @@ func TestAPIPullUpdateByRebase(t *testing.T) {
|
||||||
AddTokenAuth(token)
|
AddTokenAuth(token)
|
||||||
session.MakeRequest(t, req, http.StatusOK)
|
session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
// reload pr
|
||||||
|
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
|
||||||
|
|
||||||
// Test GetDiverging after update
|
// Test GetDiverging after update
|
||||||
diffCount, err = gitrepo.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
diffCount, err = gitrepo.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue