Skip to content

Commit 40ec270

Browse files
committed
rework for calculating head branch of migrating pull requests
1 parent 87362b4 commit 40ec270

File tree

11 files changed

+101
-210
lines changed

11 files changed

+101
-210
lines changed

models/issues/pull.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ type PullRequest struct {
138138
BaseRepoID int64 `xorm:"INDEX"`
139139
BaseRepo *repo_model.Repository `xorm:"-"`
140140
HeadBranch string
141-
HeadCommitID string `xorm:"-"`
142141
BaseBranch string
143142
MergeBase string `xorm:"VARCHAR(64)"`
144143
AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT false"`

routers/web/repo/issue_comment.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ func NewComment(ctx *context.Context) {
9696

9797
// Regenerate patch and test conflict.
9898
if pr == nil {
99-
issue.PullRequest.HeadCommitID = ""
10099
pull_service.StartPullRequestCheckImmediately(ctx, issue.PullRequest)
101100
}
102101

routers/web/repo/pull_review.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,12 @@ func UpdateViewedFiles(ctx *context.Context) {
318318

319319
// Expect the review to have been now if no head commit was supplied
320320
if data.HeadCommitSHA == "" {
321-
data.HeadCommitSHA = pull.HeadCommitID
321+
data.HeadCommitSHA, err = ctx.Repo.GitRepo.GetRefCommitID(pull.GetGitHeadRefName())
322+
if err != nil {
323+
log.Warn("Attempted to update a review but could not get head commit SHA: %v", err)
324+
ctx.Resp.WriteHeader(http.StatusBadRequest)
325+
return
326+
}
322327
}
323328

324329
updatedFiles := make(map[string]pull_model.ViewedState, len(data.Files))
@@ -332,7 +337,7 @@ func UpdateViewedFiles(ctx *context.Context) {
332337
}
333338

334339
if err := pull_model.UpdateReviewState(ctx, ctx.Doer.ID, pull.ID, data.HeadCommitSHA, updatedFiles); err != nil {
335-
ctx.ServerError("UpdateReview", err)
340+
ctx.ServerError("UpdateReviewState", err)
336341
}
337342
}
338343

routers/web/repo/pull_review_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func TestRenderConversation(t *testing.T) {
4141

4242
var preparedComment *issues_model.Comment
4343
run("prepare", func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder) {
44-
comment, err := pull.CreateCodeComment(ctx, pr.Issue.Poster, ctx.Repo.GitRepo, pr.Issue, 1, "content", "", false, 0, pr.HeadCommitID, nil)
44+
comment, err := pull.CreateCodeComment(ctx, pr.Issue.Poster, ctx.Repo.GitRepo, pr.Issue, 1, "content", "", false, 0, pr.HeadBranch, nil)
4545
require.NoError(t, err)
4646

4747
comment.Invalidated = true

services/agit/agit.go

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -142,21 +142,21 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
142142
}
143143

144144
pr := &issues_model.PullRequest{
145-
HeadRepoID: repo.ID,
146-
BaseRepoID: repo.ID,
147-
HeadBranch: headBranch,
148-
HeadCommitID: opts.NewCommitIDs[i],
149-
BaseBranch: baseBranchName,
150-
HeadRepo: repo,
151-
BaseRepo: repo,
152-
MergeBase: "",
153-
Type: issues_model.PullRequestGitea,
154-
Flow: issues_model.PullRequestFlowAGit,
145+
HeadRepoID: repo.ID,
146+
BaseRepoID: repo.ID,
147+
HeadBranch: headBranch,
148+
BaseBranch: baseBranchName,
149+
HeadRepo: repo,
150+
BaseRepo: repo,
151+
MergeBase: "",
152+
Type: issues_model.PullRequestGitea,
153+
Flow: issues_model.PullRequestFlowAGit,
155154
}
156155
prOpts := &pull_service.NewPullRequestOptions{
157-
Repo: repo,
158-
Issue: prIssue,
159-
PullRequest: pr,
156+
Repo: repo,
157+
Issue: prIssue,
158+
PullRequest: pr,
159+
HeadCommitID: opts.NewCommitIDs[i],
160160
}
161161
if err := pull_service.NewPullRequest(ctx, prOpts); err != nil {
162162
return nil, err
@@ -214,35 +214,29 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
214214
}
215215
}
216216

217-
// Store old commit ID for review staleness checking
218-
oldHeadCommitID := pr.HeadCommitID
219-
220-
pr.HeadCommitID = opts.NewCommitIDs[i]
221-
if err = pull_service.UpdateRef(ctx, pr); err != nil {
217+
if err = pull_service.UpdateRef(ctx, pr, opts.NewCommitIDs[i]); err != nil {
222218
return nil, fmt.Errorf("failed to update pull ref. Error: %w", err)
223219
}
224220

225221
// Mark existing reviews as stale when PR content changes (same as regular GitHub flow)
226-
if oldHeadCommitID != opts.NewCommitIDs[i] {
227-
if err := issues_model.MarkReviewsAsStale(ctx, pr.IssueID); err != nil {
228-
log.Error("MarkReviewsAsStale: %v", err)
229-
}
222+
if err := issues_model.MarkReviewsAsStale(ctx, pr.IssueID); err != nil {
223+
log.Error("MarkReviewsAsStale: %v", err)
224+
}
230225

231-
// Dismiss all approval reviews if protected branch rule item enabled
232-
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
233-
if err != nil {
234-
log.Error("GetFirstMatchProtectedBranchRule: %v", err)
235-
}
236-
if pb != nil && pb.DismissStaleApprovals {
237-
if err := pull_service.DismissApprovalReviews(ctx, pusher, pr); err != nil {
238-
log.Error("DismissApprovalReviews: %v", err)
239-
}
226+
// Dismiss all approval reviews if protected branch rule item enabled
227+
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
228+
if err != nil {
229+
log.Error("GetFirstMatchProtectedBranchRule: %v", err)
230+
}
231+
if pb != nil && pb.DismissStaleApprovals {
232+
if err := pull_service.DismissApprovalReviews(ctx, pusher, pr); err != nil {
233+
log.Error("DismissApprovalReviews: %v", err)
240234
}
235+
}
241236

242-
// Mark reviews for the new commit as not stale
243-
if err := issues_model.MarkReviewsAsNotStale(ctx, pr.IssueID, opts.NewCommitIDs[i]); err != nil {
244-
log.Error("MarkReviewsAsNotStale: %v", err)
245-
}
237+
// Mark reviews for the new commit as not stale
238+
if err := issues_model.MarkReviewsAsNotStale(ctx, pr.IssueID, opts.NewCommitIDs[i]); err != nil {
239+
log.Error("MarkReviewsAsNotStale: %v", err)
246240
}
247241

248242
pull_service.StartPullRequestCheckImmediately(ctx, pr)

services/migrations/gitea_uploader.go

Lines changed: 43 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import (
88
"context"
99
"fmt"
1010
"io"
11-
"os"
12-
"path/filepath"
1311
"strconv"
1412
"strings"
1513
"time"
@@ -31,7 +29,7 @@ import (
3129
"code.gitea.io/gitea/modules/timeutil"
3230
"code.gitea.io/gitea/modules/uri"
3331
"code.gitea.io/gitea/modules/util"
34-
"code.gitea.io/gitea/services/pull"
32+
pull_service "code.gitea.io/gitea/services/pull"
3533
repo_service "code.gitea.io/gitea/services/repository"
3634

3735
"github.com/google/uuid"
@@ -554,164 +552,65 @@ func (g *GiteaLocalUploader) CreatePullRequests(ctx context.Context, prs ...*bas
554552
if err := issues_model.InsertPullRequests(ctx, gprs...); err != nil {
555553
return err
556554
}
557-
for _, pr := range gprs {
555+
for i, pr := range gprs {
558556
g.issues[pr.Issue.Index] = pr.Issue
559-
pull.StartPullRequestCheckImmediately(ctx, pr)
557+
// create head commit ref immediately, return error if failed.
558+
if err := pull_service.UpdateRef(ctx, pr, prs[i].Head.SHA); err != nil {
559+
return err
560+
}
561+
562+
pull_service.StartPullRequestCheckImmediately(ctx, pr)
560563
}
561564
return nil
562565
}
563566

564-
func (g *GiteaLocalUploader) updateGitForPullRequest(ctx context.Context, pr *base.PullRequest) (head string, err error) {
565-
// SECURITY: this pr must have been must have been ensured safe
567+
func (g *GiteaLocalUploader) updateHeadBranchForPullRequest(pr *base.PullRequest) (head string, err error) {
568+
// SECURITY: this pr must have been ensured safe
566569
if !pr.EnsuredSafe {
567570
log.Error("PR #%d in %s/%s has not been checked for safety.", pr.Number, g.repoOwner, g.repoName)
568571
return "", fmt.Errorf("the PR[%d] was not checked for safety", pr.Number)
569572
}
570573

571-
// Anonymous function to download the patch file (allows us to use defer)
572-
err = func() error {
573-
// if the patchURL is empty there is nothing to download
574-
if pr.PatchURL == "" {
575-
return nil
576-
}
577-
578-
// SECURITY: We will assume that the pr.PatchURL has been checked
579-
// pr.PatchURL maybe a local file - but note EnsureSafe should be asserting that this safe
580-
ret, err := uri.Open(pr.PatchURL) // TODO: This probably needs to use the downloader as there may be rate limiting issues here
581-
if err != nil {
582-
return err
583-
}
584-
defer ret.Close()
585-
586-
pullDir := filepath.Join(g.repo.RepoPath(), "pulls")
587-
if err = os.MkdirAll(pullDir, os.ModePerm); err != nil {
588-
return err
589-
}
590-
591-
f, err := os.Create(filepath.Join(pullDir, fmt.Sprintf("%d.patch", pr.Number)))
592-
if err != nil {
593-
return err
594-
}
595-
defer f.Close()
596-
597-
// TODO: Should there be limits on the size of this file?
598-
_, err = io.Copy(f, ret)
599-
600-
return err
601-
}()
602-
if err != nil {
603-
return "", err
604-
}
605-
606-
head = "unknown repository"
607-
if pr.IsForkPullRequest() && pr.State != "closed" {
608-
// OK we want to fetch the current head as a branch from its CloneURL
609-
610-
// 1. Is there a head clone URL available?
611-
// 2. Is there a head ref available?
612-
if pr.Head.CloneURL == "" || pr.Head.Ref == "" {
613-
return head, nil
574+
if pr.IsForkPullRequest() {
575+
if pr.Head.SHA == "" {
576+
return "", fmt.Errorf("the PR[%d] does not have a head commit SHA", pr.Number)
614577
}
615-
616-
// 3. We need to create a remote for this clone url
617-
// ... maybe we already have a name for this remote
618-
remote, ok := g.prHeadCache[pr.Head.CloneURL+":"]
619-
if !ok {
620-
// ... let's try ownername as a reasonable name
621-
remote = pr.Head.OwnerName
622-
if !git.IsValidRefPattern(remote) {
623-
// ... let's try something less nice
624-
remote = "head-pr-" + strconv.FormatInt(pr.Number, 10)
625-
}
626-
// ... now add the remote
627-
err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true)
628-
if err != nil {
629-
log.Error("PR #%d in %s/%s AddRemote[%s] failed: %v", pr.Number, g.repoOwner, g.repoName, remote, err)
630-
} else {
631-
g.prHeadCache[pr.Head.CloneURL+":"] = remote
632-
ok = true
578+
// ignore the original branch name because it belongs to the head repository
579+
headBranch := fmt.Sprintf("branch_for_pr_%d", pr.Number)
580+
if pr.State != "closed" {
581+
// create the head branch
582+
if err := g.gitRepo.CreateBranch(headBranch, pr.Head.SHA); err != nil {
583+
return "", fmt.Errorf("failed to create head branch[%s] for PR[%d]: %w", headBranch, pr.Number, err)
633584
}
634585
}
635-
if !ok {
636-
return head, nil
637-
}
586+
return headBranch, nil // assign a non-exist branch
587+
}
638588

639-
// 4. Check if we already have this ref?
640-
localRef, ok := g.prHeadCache[pr.Head.CloneURL+":"+pr.Head.Ref]
641-
if !ok {
642-
// ... We would normally name this migrated branch as <OwnerName>/<HeadRef> but we need to ensure that is safe
643-
localRef = git.SanitizeRefPattern(pr.Head.OwnerName + "/" + pr.Head.Ref)
644-
645-
// ... Now we must assert that this does not exist
646-
if g.gitRepo.IsBranchExist(localRef) {
647-
localRef = "head-pr-" + strconv.FormatInt(pr.Number, 10) + "/" + localRef
648-
i := 0
649-
for g.gitRepo.IsBranchExist(localRef) {
650-
if i > 5 {
651-
// ... We tried, we really tried but this is just a seriously unfriendly repo
652-
return head, nil
653-
}
654-
// OK just try some uuids!
655-
localRef = git.SanitizeRefPattern("head-pr-" + strconv.FormatInt(pr.Number, 10) + uuid.New().String())
656-
i++
589+
if pr.Head.SHA != "" {
590+
if pr.Head.Ref == "" {
591+
headBranch := fmt.Sprintf("branch_for_pr_%d", pr.Number)
592+
if pr.State != "closed" {
593+
// create the head branch
594+
if err := g.gitRepo.CreateBranch(headBranch, pr.Head.SHA); err != nil {
595+
return "", fmt.Errorf("failed to create head branch[%s] for PR[%d]: %w", headBranch, pr.Number, err)
657596
}
658597
}
659-
660-
fetchArg := pr.Head.Ref + ":" + git.BranchPrefix + localRef
661-
if strings.HasPrefix(fetchArg, "-") {
662-
fetchArg = git.BranchPrefix + fetchArg
663-
}
664-
665-
_, _, err = git.NewCommand("fetch", "--no-tags").AddDashesAndList(remote, fetchArg).RunStdString(ctx, &git.RunOpts{Dir: g.repo.RepoPath()})
666-
if err != nil {
667-
log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err)
668-
return head, nil
669-
}
670-
g.prHeadCache[pr.Head.CloneURL+":"+pr.Head.Ref] = localRef
671-
head = localRef
672-
}
673-
674-
// 5. Now if pr.Head.SHA == "" we should recover this to the head of this branch
675-
if pr.Head.SHA == "" {
676-
headSha, err := g.gitRepo.GetBranchCommitID(localRef)
677-
if err != nil {
678-
log.Error("unable to get head SHA of local head for PR #%d from %s in %s/%s. Error: %v", pr.Number, pr.Head.Ref, g.repoOwner, g.repoName, err)
679-
return head, nil
680-
}
681-
pr.Head.SHA = headSha
682-
}
683-
684-
_, _, err = git.NewCommand("update-ref", "--no-deref").AddDynamicArguments(pr.GetGitHeadRefName(), pr.Head.SHA).RunStdString(ctx, &git.RunOpts{Dir: g.repo.RepoPath()})
685-
if err != nil {
686-
return "", err
598+
return headBranch, nil // assign a non-exist branch
687599
}
688-
689-
return head, nil
600+
// it's unnecessary to check whether the ref exists because the git repository will be mirrored cloned.
601+
return pr.Head.Ref, nil
690602
}
691603

692-
if pr.Head.Ref != "" {
693-
head = pr.Head.Ref
604+
if pr.Head.Ref == "" {
605+
return "", fmt.Errorf("the PR[%d] does not have a head commit SHA or ref", pr.Number)
694606
}
695607

696-
// Ensure the closed PR SHA still points to an existing ref
697-
if pr.Head.SHA == "" {
698-
// The SHA is empty
699-
log.Warn("Empty reference, no pull head for PR #%d in %s/%s", pr.Number, g.repoOwner, g.repoName)
700-
} else {
701-
_, _, err = git.NewCommand("rev-list", "--quiet", "-1").AddDynamicArguments(pr.Head.SHA).RunStdString(ctx, &git.RunOpts{Dir: g.repo.RepoPath()})
702-
if err != nil {
703-
// Git update-ref remove bad references with a relative path
704-
log.Warn("Deprecated local head %s for PR #%d in %s/%s, removing %s", pr.Head.SHA, pr.Number, g.repoOwner, g.repoName, pr.GetGitHeadRefName())
705-
} else {
706-
// set head information
707-
_, _, err = git.NewCommand("update-ref", "--no-deref").AddDynamicArguments(pr.GetGitHeadRefName(), pr.Head.SHA).RunStdString(ctx, &git.RunOpts{Dir: g.repo.RepoPath()})
708-
if err != nil {
709-
log.Error("unable to set %s as the local head for PR #%d from %s in %s/%s. Error: %v", pr.Head.SHA, pr.Number, pr.Head.Ref, g.repoOwner, g.repoName, err)
710-
}
711-
}
608+
// if there is no sha, we need to get the sha from the head branch
609+
pr.Head.SHA, err = g.gitRepo.GetRefCommitID(pr.Head.Ref)
610+
if err != nil {
611+
return "", fmt.Errorf("the PR[%d] head ref[%s] is invalid: %w", pr.Number, pr.Head.Ref, err)
712612
}
713-
714-
return head, nil
613+
return pr.Head.Ref, nil
715614
}
716615

717616
func (g *GiteaLocalUploader) newPullRequest(ctx context.Context, pr *base.PullRequest) (*issues_model.PullRequest, error) {
@@ -725,9 +624,10 @@ func (g *GiteaLocalUploader) newPullRequest(ctx context.Context, pr *base.PullRe
725624

726625
milestoneID := g.milestones[pr.Milestone]
727626

728-
head, err := g.updateGitForPullRequest(ctx, pr)
627+
// recalculate and create head branch when necessary
628+
headBranch, err := g.updateHeadBranchForPullRequest(pr)
729629
if err != nil {
730-
return nil, fmt.Errorf("updateGitForPullRequest: %w", err)
630+
return nil, fmt.Errorf("updateHeadBranchForPullRequest: %w", err)
731631
}
732632

733633
// Now we may need to fix the mergebase
@@ -795,14 +695,13 @@ func (g *GiteaLocalUploader) newPullRequest(ctx context.Context, pr *base.PullRe
795695

796696
pullRequest := issues_model.PullRequest{
797697
HeadRepoID: g.repo.ID,
798-
HeadBranch: head,
698+
HeadBranch: headBranch,
799699
BaseRepoID: g.repo.ID,
800700
BaseBranch: pr.Base.Ref,
801701
MergeBase: pr.Base.SHA,
802702
Index: pr.Number,
803703
HasMerged: pr.Merged,
804-
805-
Issue: &issue,
704+
Issue: &issue,
806705
}
807706

808707
if pullRequest.Issue.IsClosed && pr.Closed != nil {

services/migrations/gitea_uploader_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) {
507507

508508
testCase.pr.EnsuredSafe = true
509509

510-
head, err := uploader.updateGitForPullRequest(ctx, &testCase.pr)
510+
head, err := uploader.updateHeadBranchForPullRequest(&testCase.pr)
511511
assert.NoError(t, err)
512512
assert.Equal(t, testCase.head, head)
513513

0 commit comments

Comments
 (0)