Skip to content

Commit 85f31eb

Browse files
lunnywxiaoguang
andauthored
Fix codeowner detected diff base branch to mergebase (#29783) (#29807)
Fix #29763 Backport #29783 This PR fixes 2 problems with CodeOwner in the pull request. - Don't use the pull request base branch but merge-base as a diff base to detect the code owner. - CodeOwner detection in fork repositories will be disabled because almost all the fork repositories will not change CODEOWNERS files but it should not be used on fork repositories' pull requests. --------- Co-authored-by: wxiaoguang <[email protected]>
1 parent 8242c3c commit 85f31eb

File tree

16 files changed

+281
-96
lines changed

16 files changed

+281
-96
lines changed

models/issues/pull.go

Lines changed: 4 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -706,8 +706,8 @@ func (pr *PullRequest) UpdateColsIfNotMerged(ctx context.Context, cols ...string
706706

707707
// IsWorkInProgress determine if the Pull Request is a Work In Progress by its title
708708
// Issue must be set before this method can be called.
709-
func (pr *PullRequest) IsWorkInProgress() bool {
710-
if err := pr.LoadIssue(db.DefaultContext); err != nil {
709+
func (pr *PullRequest) IsWorkInProgress(ctx context.Context) bool {
710+
if err := pr.LoadIssue(ctx); err != nil {
711711
log.Error("LoadIssue: %v", err)
712712
return false
713713
}
@@ -810,14 +810,14 @@ func UpdateAllowEdits(ctx context.Context, pr *PullRequest) error {
810810
}
811811

812812
// Mergeable returns if the pullrequest is mergeable.
813-
func (pr *PullRequest) Mergeable() bool {
813+
func (pr *PullRequest) Mergeable(ctx context.Context) bool {
814814
// If a pull request isn't mergable if it's:
815815
// - Being conflict checked.
816816
// - Has a conflict.
817817
// - Received a error while being conflict checked.
818818
// - Is a work-in-progress pull request.
819819
return pr.Status != PullRequestStatusChecking && pr.Status != PullRequestStatusConflict &&
820-
pr.Status != PullRequestStatusError && !pr.IsWorkInProgress()
820+
pr.Status != PullRequestStatusError && !pr.IsWorkInProgress(ctx)
821821
}
822822

823823
// HasEnoughApprovals returns true if pr has enough granted approvals.
@@ -887,82 +887,6 @@ func MergeBlockedByOutdatedBranch(protectBranch *git_model.ProtectedBranch, pr *
887887
return protectBranch.BlockOnOutdatedBranch && pr.CommitsBehind > 0
888888
}
889889

890-
func PullRequestCodeOwnersReview(ctx context.Context, pull *Issue, pr *PullRequest) error {
891-
files := []string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}
892-
893-
if pr.IsWorkInProgress() {
894-
return nil
895-
}
896-
897-
if err := pr.LoadBaseRepo(ctx); err != nil {
898-
return err
899-
}
900-
901-
repo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
902-
if err != nil {
903-
return err
904-
}
905-
defer repo.Close()
906-
907-
branch, err := repo.GetDefaultBranch()
908-
if err != nil {
909-
return err
910-
}
911-
912-
commit, err := repo.GetBranchCommit(branch)
913-
if err != nil {
914-
return err
915-
}
916-
917-
var data string
918-
for _, file := range files {
919-
if blob, err := commit.GetBlobByPath(file); err == nil {
920-
data, err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
921-
if err == nil {
922-
break
923-
}
924-
}
925-
}
926-
927-
rules, _ := GetCodeOwnersFromContent(ctx, data)
928-
changedFiles, err := repo.GetFilesChangedBetween(git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName())
929-
if err != nil {
930-
return err
931-
}
932-
933-
uniqUsers := make(map[int64]*user_model.User)
934-
uniqTeams := make(map[string]*org_model.Team)
935-
for _, rule := range rules {
936-
for _, f := range changedFiles {
937-
if (rule.Rule.MatchString(f) && !rule.Negative) || (!rule.Rule.MatchString(f) && rule.Negative) {
938-
for _, u := range rule.Users {
939-
uniqUsers[u.ID] = u
940-
}
941-
for _, t := range rule.Teams {
942-
uniqTeams[fmt.Sprintf("%d/%d", t.OrgID, t.ID)] = t
943-
}
944-
}
945-
}
946-
}
947-
948-
for _, u := range uniqUsers {
949-
if u.ID != pull.Poster.ID {
950-
if _, err := AddReviewRequest(ctx, pull, u, pull.Poster); err != nil {
951-
log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err)
952-
return err
953-
}
954-
}
955-
}
956-
for _, t := range uniqTeams {
957-
if _, err := AddTeamReviewRequest(ctx, pull, t, pull.Poster); err != nil {
958-
log.Warn("Failed add assignee team: %s to PR review: %s#%d, error: %s", t.Name, pr.BaseRepo.Name, pr.ID, err)
959-
return err
960-
}
961-
}
962-
963-
return nil
964-
}
965-
966890
// GetCodeOwnersFromContent returns the code owners configuration
967891
// Return empty slice if files missing
968892
// Return warning messages on parsing errors

models/issues/pull_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,13 +260,13 @@ func TestPullRequest_IsWorkInProgress(t *testing.T) {
260260
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2})
261261
pr.LoadIssue(db.DefaultContext)
262262

263-
assert.False(t, pr.IsWorkInProgress())
263+
assert.False(t, pr.IsWorkInProgress(db.DefaultContext))
264264

265265
pr.Issue.Title = "WIP: " + pr.Issue.Title
266-
assert.True(t, pr.IsWorkInProgress())
266+
assert.True(t, pr.IsWorkInProgress(db.DefaultContext))
267267

268268
pr.Issue.Title = "[wip]: " + pr.Issue.Title
269-
assert.True(t, pr.IsWorkInProgress())
269+
assert.True(t, pr.IsWorkInProgress(db.DefaultContext))
270270
}
271271

272272
func TestPullRequest_GetWorkInProgressPrefixWorkInProgress(t *testing.T) {

routers/web/repo/issue.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1910,7 +1910,7 @@ func ViewIssue(ctx *context.Context) {
19101910
if pull.HasMerged || issue.IsClosed || !ctx.IsSigned {
19111911
return false
19121912
}
1913-
if pull.CanAutoMerge() || pull.IsWorkInProgress() || pull.IsChecking() {
1913+
if pull.CanAutoMerge() || pull.IsWorkInProgress(ctx) || pull.IsChecking() {
19141914
return false
19151915
}
19161916
if (ctx.Doer.IsAdmin || ctx.Repo.IsAdmin()) && prConfig.AllowManualMerge {

routers/web/repo/pull.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
706706
ctx.Data["IsNothingToCompare"] = true
707707
}
708708

709-
if pull.IsWorkInProgress() {
709+
if pull.IsWorkInProgress(ctx) {
710710
ctx.Data["IsPullWorkInProgress"] = true
711711
ctx.Data["WorkInProgressPrefix"] = pull.GetWorkInProgressPrefix(ctx)
712712
}

services/convert/pull.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
6868
PatchURL: pr.Issue.PatchURL(),
6969
HasMerged: pr.HasMerged,
7070
MergeBase: pr.MergeBase,
71-
Mergeable: pr.Mergeable(),
71+
Mergeable: pr.Mergeable(ctx),
7272
Deadline: apiIssue.Deadline,
7373
Created: pr.Issue.CreatedUnix.AsTimePtr(),
7474
Updated: pr.Issue.UpdatedUnix.AsTimePtr(),

services/issue/issue.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func ChangeTitle(ctx context.Context, issue *issues_model.Issue, doer *user_mode
7171
}
7272

7373
if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issues_model.HasWorkInProgressPrefix(title) {
74-
if err := issues_model.PullRequestCodeOwnersReview(ctx, issue, issue.PullRequest); err != nil {
74+
if err := PullRequestCodeOwnersReview(ctx, issue, issue.PullRequest); err != nil {
7575
return err
7676
}
7777
}

services/issue/pull.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package issue
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"time"
10+
11+
issues_model "code.gitea.io/gitea/models/issues"
12+
org_model "code.gitea.io/gitea/models/organization"
13+
user_model "code.gitea.io/gitea/models/user"
14+
"code.gitea.io/gitea/modules/git"
15+
"code.gitea.io/gitea/modules/log"
16+
"code.gitea.io/gitea/modules/setting"
17+
)
18+
19+
func getMergeBase(repo *git.Repository, pr *issues_model.PullRequest, baseBranch, headBranch string) (string, error) {
20+
// Add a temporary remote
21+
tmpRemote := fmt.Sprintf("mergebase-%d-%d", pr.ID, time.Now().UnixNano())
22+
if err := repo.AddRemote(tmpRemote, repo.Path, false); err != nil {
23+
return "", fmt.Errorf("AddRemote: %w", err)
24+
}
25+
defer func() {
26+
if err := repo.RemoveRemote(tmpRemote); err != nil {
27+
log.Error("getMergeBase: RemoveRemote: %v", err)
28+
}
29+
}()
30+
31+
mergeBase, _, err := repo.GetMergeBase(tmpRemote, baseBranch, headBranch)
32+
return mergeBase, err
33+
}
34+
35+
func PullRequestCodeOwnersReview(ctx context.Context, pull *issues_model.Issue, pr *issues_model.PullRequest) error {
36+
files := []string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}
37+
38+
if pr.IsWorkInProgress(ctx) {
39+
return nil
40+
}
41+
42+
if err := pr.LoadHeadRepo(ctx); err != nil {
43+
return err
44+
}
45+
46+
if pr.HeadRepo.IsFork {
47+
return nil
48+
}
49+
50+
if err := pr.LoadBaseRepo(ctx); err != nil {
51+
return err
52+
}
53+
54+
repo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
55+
if err != nil {
56+
return err
57+
}
58+
defer repo.Close()
59+
60+
commit, err := repo.GetBranchCommit(pr.BaseRepo.DefaultBranch)
61+
if err != nil {
62+
return err
63+
}
64+
65+
var data string
66+
for _, file := range files {
67+
if blob, err := commit.GetBlobByPath(file); err == nil {
68+
data, err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
69+
if err == nil {
70+
break
71+
}
72+
}
73+
}
74+
75+
rules, _ := issues_model.GetCodeOwnersFromContent(ctx, data)
76+
77+
// get the mergebase
78+
mergeBase, err := getMergeBase(repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName())
79+
if err != nil {
80+
return err
81+
}
82+
83+
// https://github.com/go-gitea/gitea/issues/29763, we need to get the files changed
84+
// between the merge base and the head commit but not the base branch and the head commit
85+
changedFiles, err := repo.GetFilesChangedBetween(mergeBase, pr.HeadCommitID)
86+
if err != nil {
87+
return err
88+
}
89+
90+
uniqUsers := make(map[int64]*user_model.User)
91+
uniqTeams := make(map[string]*org_model.Team)
92+
for _, rule := range rules {
93+
for _, f := range changedFiles {
94+
if (rule.Rule.MatchString(f) && !rule.Negative) || (!rule.Rule.MatchString(f) && rule.Negative) {
95+
for _, u := range rule.Users {
96+
uniqUsers[u.ID] = u
97+
}
98+
for _, t := range rule.Teams {
99+
uniqTeams[fmt.Sprintf("%d/%d", t.OrgID, t.ID)] = t
100+
}
101+
}
102+
}
103+
}
104+
105+
for _, u := range uniqUsers {
106+
if u.ID != pull.Poster.ID {
107+
if _, err := issues_model.AddReviewRequest(ctx, pull, u, pull.Poster); err != nil {
108+
log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err)
109+
return err
110+
}
111+
}
112+
}
113+
for _, t := range uniqTeams {
114+
if _, err := issues_model.AddTeamReviewRequest(ctx, pull, t, pull.Poster); err != nil {
115+
log.Warn("Failed add assignee team: %s to PR review: %s#%d, error: %s", t.Name, pr.BaseRepo.Name, pr.ID, err)
116+
return err
117+
}
118+
}
119+
120+
return nil
121+
}

services/mailer/mail_issue.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []*user_mo
8282

8383
// =========== Repo watchers ===========
8484
// Make repo watchers last, since it's likely the list with the most users
85-
if !(ctx.Issue.IsPull && ctx.Issue.PullRequest.IsWorkInProgress() && ctx.ActionType != activities_model.ActionCreatePullRequest) {
85+
if !(ctx.Issue.IsPull && ctx.Issue.PullRequest.IsWorkInProgress(ctx) && ctx.ActionType != activities_model.ActionCreatePullRequest) {
8686
ids, err = repo_model.GetRepoWatchersIDs(ctx, ctx.Issue.RepoID)
8787
if err != nil {
8888
return fmt.Errorf("GetRepoWatchersIDs(%d): %w", ctx.Issue.RepoID, err)

services/mailer/notify.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func (m *mailNotifier) IssueChangeTitle(ctx context.Context, doer *user_model.Us
7979
log.Error("issue.LoadPullRequest: %v", err)
8080
return
8181
}
82-
if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issue.PullRequest.IsWorkInProgress() {
82+
if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issue.PullRequest.IsWorkInProgress(ctx) {
8383
if err := MailParticipants(ctx, issue, doer, activities_model.ActionPullRequestReadyForReview, nil); err != nil {
8484
log.Error("MailParticipants: %v", err)
8585
}

services/pull/check.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce
9191
return nil
9292
}
9393

94-
if pr.IsWorkInProgress() {
94+
if pr.IsWorkInProgress(ctx) {
9595
return ErrIsWorkInProgress
9696
}
9797

services/pull/pull.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss
126126
return err
127127
}
128128

129-
if !pr.IsWorkInProgress() {
130-
if err := issues_model.PullRequestCodeOwnersReview(ctx, issue, pr); err != nil {
129+
if !pr.IsWorkInProgress(ctx) {
130+
if err := issue_service.PullRequestCodeOwnersReview(ctx, issue, pr); err != nil {
131131
return err
132132
}
133133
}

services/uinotification/notify.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func (ns *notificationService) IssueChangeTitle(ctx context.Context, doer *user_
114114
log.Error("issue.LoadPullRequest: %v", err)
115115
return
116116
}
117-
if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issue.PullRequest.IsWorkInProgress() {
117+
if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issue.PullRequest.IsWorkInProgress(ctx) {
118118
_ = ns.issueQueue.Push(issueNotificationOpts{
119119
IssueID: issue.ID,
120120
NotificationAuthorID: doer.ID,

templates/shared/issueicon.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
{{if .IsClosed}}
88
{{svg "octicon-git-pull-request" 16 "text red"}}
99
{{else}}
10-
{{if and .PullRequest .PullRequest.IsWorkInProgress}}
10+
{{if and .PullRequest .PullRequest.IsWorkInProgress ctx}}
1111
{{svg "octicon-git-pull-request-draft" 16 "text grey"}}
12-
{{else if and .GetPullRequest .GetPullRequest.IsWorkInProgress}}
12+
{{else if and .GetPullRequest .GetPullRequest.IsWorkInProgress ctx}}
1313
{{svg "octicon-git-pull-request-draft" 16 "text grey"}}
1414
{{else}}
1515
{{svg "octicon-git-pull-request" 16 "text green"}}

tests/integration/pull_create_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package integration
55

66
import (
7+
"fmt"
78
"net/http"
89
"net/http/httptest"
910
"net/url"
@@ -17,6 +18,23 @@ import (
1718
"github.com/stretchr/testify/assert"
1819
)
1920

21+
func createPullRequest(t *testing.T, session *TestSession, user, repo, baseBranch, headBranch, title string) *httptest.ResponseRecorder {
22+
link := fmt.Sprintf("/%s/%s/compare/%s...%s", user, repo, baseBranch, headBranch)
23+
req := NewRequest(t, "GET", link)
24+
resp := session.MakeRequest(t, req, http.StatusOK)
25+
26+
// Submit the form for creating the pull
27+
htmlDoc := NewHTMLParser(t, resp.Body)
28+
link, exists := htmlDoc.doc.Find("form.ui.form").Attr("action")
29+
assert.True(t, exists, "The template has changed")
30+
req = NewRequestWithValues(t, "POST", link, map[string]string{
31+
"_csrf": htmlDoc.GetCSRF(),
32+
"title": title,
33+
})
34+
resp = session.MakeRequest(t, req, http.StatusOK)
35+
return resp
36+
}
37+
2038
func testPullCreate(t *testing.T, session *TestSession, user, repo, branch, title string) *httptest.ResponseRecorder {
2139
req := NewRequest(t, "GET", path.Join(user, repo))
2240
resp := session.MakeRequest(t, req, http.StatusOK)

tests/integration/pull_merge_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,6 @@ func TestConflictChecking(t *testing.T) {
432432
// Check if status is correct.
433433
assert.Equal(t, issues_model.PullRequestStatusConflict, conflictingPR.Status)
434434
// Ensure that mergeable returns false
435-
assert.False(t, conflictingPR.Mergeable())
435+
assert.False(t, conflictingPR.Mergeable(db.DefaultContext))
436436
})
437437
}

0 commit comments

Comments
 (0)