Skip to content

Commit 1797046

Browse files
corytoddGiteaBot
andauthored
Fix duplicate Reviewed-by trailers (#24796)
Enable deduplication of unofficial reviews. When pull requests are configured to include all approvers, not just official ones, in the default merge messages it was possible to generate duplicated Reviewed-by lines for a single person. Add an option to find only distinct reviews for a given query. fixes #24795 --------- Signed-off-by: Cory Todd <[email protected]> Co-authored-by: Giteabot <[email protected]>
1 parent 81211db commit 1797046

File tree

5 files changed

+73
-1
lines changed

5 files changed

+73
-1
lines changed

models/fixtures/review.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,30 @@
105105
official: true
106106
updated_unix: 1603196749
107107
created_unix: 1603196749
108+
109+
-
110+
id: 13
111+
type: 1
112+
reviewer_id: 5
113+
issue_id: 11
114+
content: "old review from user5"
115+
updated_unix: 946684820
116+
created_unix: 946684820
117+
118+
-
119+
id: 14
120+
type: 1
121+
reviewer_id: 5
122+
issue_id: 11
123+
content: "duplicate review from user5 (latest)"
124+
updated_unix: 946684830
125+
created_unix: 946684830
126+
127+
-
128+
id: 15
129+
type: 1
130+
reviewer_id: 6
131+
issue_id: 11
132+
content: "singular review from user6 and final review for this pr"
133+
updated_unix: 946684831
134+
created_unix: 946684831

models/issues/pull.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ func (pr *PullRequest) getReviewedByLines(writer io.Writer) error {
404404
defer committer.Close()
405405

406406
// Note: This doesn't page as we only expect a very limited number of reviews
407-
reviews, err := FindReviews(ctx, FindReviewOptions{
407+
reviews, err := FindLatestReviews(ctx, FindReviewOptions{
408408
Type: ReviewTypeApprove,
409409
IssueID: pr.IssueID,
410410
OfficialOnly: setting.Repository.PullRequest.DefaultMergeMessageOfficialApproversOnly,

models/issues/pull_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
issues_model "code.gitea.io/gitea/models/issues"
1111
"code.gitea.io/gitea/models/unittest"
1212
user_model "code.gitea.io/gitea/models/user"
13+
"code.gitea.io/gitea/modules/setting"
1314

1415
"github.com/stretchr/testify/assert"
1516
)
@@ -325,3 +326,14 @@ func TestParseCodeOwnersLine(t *testing.T) {
325326
assert.Equal(t, g.Tokens, tokens, "Codeowners tokenizer failed")
326327
}
327328
}
329+
330+
func TestGetApprovers(t *testing.T) {
331+
assert.NoError(t, unittest.PrepareTestDatabase())
332+
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 5})
333+
// Official reviews are already deduplicated. Allow unofficial reviews
334+
// to assert that there are no duplicated approvers.
335+
setting.Repository.PullRequest.DefaultMergeMessageOfficialApproversOnly = false
336+
approvers := pr.GetApprovers()
337+
expected := "Reviewed-by: User Five <[email protected]>\nReviewed-by: User Six <[email protected]>\n"
338+
assert.EqualValues(t, expected, approvers)
339+
}

models/issues/review.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,27 @@ func FindReviews(ctx context.Context, opts FindReviewOptions) ([]*Review, error)
275275
Find(&reviews)
276276
}
277277

278+
// FindLatestReviews returns only latest reviews per user, passing FindReviewOptions
279+
func FindLatestReviews(ctx context.Context, opts FindReviewOptions) ([]*Review, error) {
280+
reviews := make([]*Review, 0, 10)
281+
cond := opts.toCond()
282+
sess := db.GetEngine(ctx).Where(cond)
283+
if opts.Page > 0 {
284+
sess = db.SetSessionPagination(sess, &opts)
285+
}
286+
287+
sess.In("id", builder.
288+
Select("max ( id ) ").
289+
From("review").
290+
Where(cond).
291+
GroupBy("reviewer_id"))
292+
293+
return reviews, sess.
294+
Asc("created_unix").
295+
Asc("id").
296+
Find(&reviews)
297+
}
298+
278299
// CountReviews returns count of reviews passing FindReviewOptions
279300
func CountReviews(opts FindReviewOptions) (int64, error) {
280301
return db.GetEngine(db.DefaultContext).Where(opts.toCond()).Count(&Review{})

models/issues/review_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,18 @@ func TestFindReviews(t *testing.T) {
7171
assert.Equal(t, "Demo Review", reviews[0].Content)
7272
}
7373

74+
func TestFindLatestReviews(t *testing.T) {
75+
assert.NoError(t, unittest.PrepareTestDatabase())
76+
reviews, err := issues_model.FindLatestReviews(db.DefaultContext, issues_model.FindReviewOptions{
77+
Type: issues_model.ReviewTypeApprove,
78+
IssueID: 11,
79+
})
80+
assert.NoError(t, err)
81+
assert.Len(t, reviews, 2)
82+
assert.Equal(t, "duplicate review from user5 (latest)", reviews[0].Content)
83+
assert.Equal(t, "singular review from user6 and final review for this pr", reviews[1].Content)
84+
}
85+
7486
func TestGetCurrentReview(t *testing.T) {
7587
assert.NoError(t, unittest.PrepareTestDatabase())
7688
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})

0 commit comments

Comments
 (0)