Skip to content

Commit 148e1d6

Browse files
Move official reviews to db instead of counting approvals each time
1 parent 9d3da22 commit 148e1d6

File tree

5 files changed

+164
-64
lines changed

5 files changed

+164
-64
lines changed

models/branches.go

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,38 @@ func (protectBranch *ProtectedBranch) CanUserMerge(userID int64) bool {
111111
return in
112112
}
113113

114+
// IsUserOfficialReviewer check if user is official reviewer for the branch (counts towards required approvals)
115+
func (protectBranch *ProtectedBranch) IsUserOfficialReviewer(user *User) (bool, error) {
116+
return protectBranch.isUserOfficialReviewer(x, user)
117+
}
118+
119+
func (protectBranch *ProtectedBranch) isUserOfficialReviewer(e Engine, user *User) (bool, error) {
120+
repo, err := getRepositoryByID(e, protectBranch.RepoID)
121+
if err != nil {
122+
return false, err
123+
}
124+
125+
if !protectBranch.EnableApprovalsWhitelist {
126+
// Anyone with write access is considered official reviewer
127+
writeAccess, err := hasAccessUnit(e, user, repo, UnitTypeCode, AccessModeWrite)
128+
if err != nil {
129+
return false, err
130+
}
131+
return writeAccess, nil
132+
}
133+
134+
if base.Int64sContains(protectBranch.ApprovalsWhitelistUserIDs, user.ID) {
135+
return true, nil
136+
}
137+
138+
inTeam, err := isUserInTeams(e, user.ID, protectBranch.ApprovalsWhitelistTeamIDs)
139+
if err != nil {
140+
return false, err
141+
}
142+
143+
return inTeam, nil
144+
}
145+
114146
// HasEnoughApprovals returns true if pr has enough granted approvals.
115147
func (protectBranch *ProtectedBranch) HasEnoughApprovals(pr *PullRequest) bool {
116148
if protectBranch.RequiredApprovals == 0 {
@@ -121,46 +153,16 @@ func (protectBranch *ProtectedBranch) HasEnoughApprovals(pr *PullRequest) bool {
121153

122154
// GetGrantedApprovalsCount returns the number of granted approvals for pr. A granted approval must be authored by a user in an approval whitelist.
123155
func (protectBranch *ProtectedBranch) GetGrantedApprovalsCount(pr *PullRequest) int64 {
124-
reviews, err := GetReviewersByPullID(pr.IssueID)
156+
approvals, err := x.Where("issue_id = ?", pr.Issue.ID).
157+
And("type = ?", ReviewTypeApprove).
158+
And("official = ?", true).
159+
Count(new(Review))
125160
if err != nil {
126-
log.Error("GetReviewersByPullID: %v", err)
161+
log.Error("GetGrantedApprovalsCount: %v", err)
127162
return 0
128163
}
129164

130-
repo, err := GetRepositoryByID(protectBranch.RepoID)
131-
if err != nil {
132-
log.Error("GetRepositoryByID: %v", err)
133-
return 0
134-
}
135-
136-
approvals := int64(0)
137-
userIDs := make([]int64, 0)
138-
for _, review := range reviews {
139-
if review.Type != ReviewTypeApprove {
140-
continue
141-
}
142-
if !protectBranch.EnableApprovalsWhitelist {
143-
if user, err := GetUserByID(review.ID); err != nil {
144-
log.Error("GetUserByID: %v", err)
145-
} else if writeAccess, err := HasAccessUnit(user, repo, UnitTypeCode, AccessModeWrite); err != nil {
146-
log.Error("HasAccessUnit: %v", err)
147-
} else if writeAccess {
148-
approvals++
149-
}
150-
continue
151-
}
152-
if base.Int64sContains(protectBranch.ApprovalsWhitelistUserIDs, review.ID) {
153-
approvals++
154-
continue
155-
}
156-
userIDs = append(userIDs, review.ID)
157-
}
158-
approvalTeamCount, err := UsersInTeamsCount(userIDs, protectBranch.ApprovalsWhitelistTeamIDs)
159-
if err != nil {
160-
log.Error("UsersInTeamsCount: %v", err)
161-
return 0
162-
}
163-
return approvalTeamCount + approvals
165+
return approvals
164166
}
165167

166168
// GetProtectedBranchByRepoID getting protected branch by repo ID
@@ -171,8 +173,12 @@ func GetProtectedBranchByRepoID(repoID int64) ([]*ProtectedBranch, error) {
171173

172174
// GetProtectedBranchBy getting protected branch by ID/Name
173175
func GetProtectedBranchBy(repoID int64, branchName string) (*ProtectedBranch, error) {
176+
return getProtectedBranchBy(x, repoID, branchName)
177+
}
178+
179+
func getProtectedBranchBy(e Engine, repoID int64, branchName string) (*ProtectedBranch, error) {
174180
rel := &ProtectedBranch{RepoID: repoID, BranchName: branchName}
175-
has, err := x.Get(rel)
181+
has, err := e.Get(rel)
176182
if err != nil {
177183
return nil, err
178184
}

models/migrations/v110.go

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
package migrations
66

77
import (
8+
"code.gitea.io/gitea/models"
9+
10+
"xorm.io/core"
811
"xorm.io/xorm"
912
)
1013

@@ -17,16 +20,74 @@ func addBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
1720
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
1821
}
1922

23+
type Review struct {
24+
ID int64 `xorm:"pk autoincr"`
25+
Official bool `xorm:"NOT NULL DEFAULT false"`
26+
}
27+
2028
sess := x.NewSession()
2129
defer sess.Close()
2230

23-
if err := x.Sync2(new(ProtectedBranch)); err != nil {
31+
if err := sess.Sync2(new(ProtectedBranch)); err != nil {
2432
return err
2533
}
2634

27-
if _, err := x.Exec("UPDATE `protected_branch` SET `can_push` = `enable_whitelist`"); err != nil {
35+
if err := sess.Sync2(new(Review)); err != nil {
36+
return err
37+
}
38+
39+
if _, err := sess.Exec("UPDATE `protected_branch` SET `can_push` = `enable_whitelist`"); err != nil {
40+
return err
41+
}
42+
if _, err := sess.Exec("UPDATE `protected_branch` SET `enable_approvals_whitelist` = ? WHERE `required_approvals` > ?", true, 0); err != nil {
2843
return err
2944
}
30-
_, err := x.Exec("UPDATE `protected_branch` SET `enable_approvals_whitelist` = ? WHERE `required_approvals` > ?", true, 0)
31-
return err
45+
46+
// Find latest review of each user in each pull request, and set official field if appropriate
47+
reviews := []*models.Review{}
48+
if x.Dialect().DBType() == core.MSSQL {
49+
if err := x.SQL(`SELECT *, max(review.updated_unix) as review_updated_unix FROM review WHERE (review.type = ? OR review.type = ?)
50+
GROUP BY review.id, review.issue_id, review.reviewer_id, review.type, ) as review
51+
ORDER BY review_updated_unix DESC`,
52+
models.ReviewTypeApprove, models.ReviewTypeReject).
53+
Find(&reviews); err != nil {
54+
return err
55+
}
56+
} else {
57+
if err := x.Select("review.*, max(review.updated_unix) as review_updated_unix").
58+
Table("review").
59+
Join("INNER", "`user`", "review.reviewer_id = `user`.id").
60+
Where("(review.type = ? OR review.type = ?)",
61+
models.ReviewTypeApprove, models.ReviewTypeReject).
62+
GroupBy("review.issue_id, review.reviewer_id, review.type").
63+
OrderBy("review_updated_unix DESC").
64+
Find(&reviews); err != nil {
65+
return err
66+
}
67+
}
68+
69+
// We need to group our results by user id _and_ review type, otherwise the query fails when using postgresql.
70+
usersInArray := make(map[int64]map[int64]bool)
71+
for _, review := range reviews {
72+
if usersInArray[review.IssueID] == nil {
73+
usersInArray[review.IssueID] = make(map[int64]bool)
74+
}
75+
if !usersInArray[review.IssueID][review.ReviewerID] {
76+
if err := review.LoadAttributes(); err != nil {
77+
return err
78+
}
79+
official, err := models.IsOfficialReviewer(review.Issue, review.Reviewer)
80+
if err != nil {
81+
return err
82+
}
83+
review.Official = official
84+
85+
if _, err := sess.ID(review.ID).Cols("official").Update(review); err != nil {
86+
return err
87+
}
88+
usersInArray[review.IssueID][review.ReviewerID] = true
89+
}
90+
}
91+
92+
return sess.Commit()
3293
}

models/org_team.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,11 @@ func RemoveTeamMember(team *Team, userID int64) error {
914914

915915
// IsUserInTeams returns if a user in some teams
916916
func IsUserInTeams(userID int64, teamIDs []int64) (bool, error) {
917-
return x.Where("uid=?", userID).In("team_id", teamIDs).Exist(new(TeamUser))
917+
return isUserInTeams(x, userID, teamIDs)
918+
}
919+
920+
func isUserInTeams(e Engine, userID int64, teamIDs []int64) (bool, error) {
921+
return e.Where("uid=?", userID).In("team_id", teamIDs).Exist(new(TeamUser))
918922
}
919923

920924
// UsersInTeamsCount counts the number of users which are in userIDs and teamIDs

models/pull.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,16 +160,20 @@ func (pr *PullRequest) loadIssue(e Engine) (err error) {
160160

161161
// LoadProtectedBranch loads the protected branch of the base branch
162162
func (pr *PullRequest) LoadProtectedBranch() (err error) {
163+
return pr.loadProtectedBranch(x)
164+
}
165+
166+
func (pr *PullRequest) loadProtectedBranch(e Engine) (err error) {
163167
if pr.BaseRepo == nil {
164168
if pr.BaseRepoID == 0 {
165169
return nil
166170
}
167-
pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
171+
pr.BaseRepo, err = getRepositoryByID(e, pr.BaseRepoID)
168172
if err != nil {
169173
return
170174
}
171175
}
172-
pr.ProtectedBranch, err = GetProtectedBranchBy(pr.BaseRepo.ID, pr.BaseBranch)
176+
pr.ProtectedBranch, err = getProtectedBranchBy(e, pr.BaseRepo.ID, pr.BaseBranch)
173177
return
174178
}
175179

models/review.go

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ type Review struct {
5353
Issue *Issue `xorm:"-"`
5454
IssueID int64 `xorm:"index"`
5555
Content string
56+
// Official is a review made by an assigned approver (counts towards approval)
57+
Official bool `xorm:"NOT NULL DEFAULT false"`
5658

5759
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
5860
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
@@ -122,23 +124,6 @@ func GetReviewByID(id int64) (*Review, error) {
122124
return getReviewByID(x, id)
123125
}
124126

125-
func getUniqueApprovalsByPullRequestID(e Engine, prID int64) (reviews []*Review, err error) {
126-
reviews = make([]*Review, 0)
127-
if err := e.
128-
Where("issue_id = ? AND type = ?", prID, ReviewTypeApprove).
129-
OrderBy("updated_unix").
130-
GroupBy("reviewer_id").
131-
Find(&reviews); err != nil {
132-
return nil, err
133-
}
134-
return
135-
}
136-
137-
// GetUniqueApprovalsByPullRequestID returns all reviews submitted for a specific pull request
138-
func GetUniqueApprovalsByPullRequestID(prID int64) ([]*Review, error) {
139-
return getUniqueApprovalsByPullRequestID(x, prID)
140-
}
141-
142127
// FindReviewOptions represent possible filters to find reviews
143128
type FindReviewOptions struct {
144129
Type ReviewType
@@ -182,14 +167,40 @@ type CreateReviewOptions struct {
182167
Reviewer *User
183168
}
184169

170+
// IsOfficialReviewer check if reviewer can make official reviews in issue (counts towards required approvals)
171+
func IsOfficialReviewer(issue *Issue, reviewer *User) (bool, error) {
172+
return isOfficialReviewer(x, issue, reviewer)
173+
}
174+
175+
func isOfficialReviewer(e Engine, issue *Issue, reviewer *User) (bool, error) {
176+
pr, err := getPullRequestByIssueID(e, issue.ID)
177+
if err != nil {
178+
return false, err
179+
}
180+
if err = pr.loadProtectedBranch(e); err != nil {
181+
return false, err
182+
}
183+
if pr.ProtectedBranch == nil {
184+
return false, nil
185+
}
186+
187+
return pr.ProtectedBranch.isUserOfficialReviewer(e, reviewer)
188+
}
189+
185190
func createReview(e Engine, opts CreateReviewOptions) (*Review, error) {
191+
official, err := isOfficialReviewer(e, opts.Issue, opts.Reviewer)
192+
if err != nil {
193+
return nil, err
194+
}
195+
186196
review := &Review{
187197
Type: opts.Type,
188198
Issue: opts.Issue,
189199
IssueID: opts.Issue.ID,
190200
Reviewer: opts.Reviewer,
191201
ReviewerID: opts.Reviewer.ID,
192202
Content: opts.Content,
203+
Official: official,
193204
}
194205
if _, err := e.Insert(review); err != nil {
195206
return nil, err
@@ -250,6 +261,11 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content strin
250261
return nil, nil, err
251262
}
252263

264+
// Only reviewers latest review shall count as "official", so existing reviews needs to be cleared
265+
if _, err := sess.Exec("UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_id=?", false, issue.ID, doer.ID); err != nil {
266+
return nil, nil, err
267+
}
268+
253269
review, err := getCurrentReview(sess, doer, issue)
254270
if err != nil {
255271
if !IsErrReviewNotExist(err) {
@@ -278,10 +294,18 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content strin
278294
return nil, nil, ContentEmptyErr{}
279295
}
280296

297+
// Official status of review updated at every submit in case settings changed
298+
official, err := isOfficialReviewer(sess, issue, doer)
299+
if err != nil {
300+
return nil, nil, err
301+
}
302+
review.Official = official
303+
281304
review.Issue = issue
282305
review.Content = content
283306
review.Type = reviewType
284-
if _, err := sess.ID(review.ID).Cols("content, type").Update(review); err != nil {
307+
308+
if _, err := sess.ID(review.ID).Cols("content, type, official").Update(review); err != nil {
285309
return nil, nil, err
286310
}
287311
}
@@ -307,22 +331,23 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content strin
307331
type PullReviewersWithType struct {
308332
User `xorm:"extends"`
309333
Type ReviewType
334+
Official bool
310335
ReviewUpdatedUnix timeutil.TimeStamp `xorm:"review_updated_unix"`
311336
}
312337

313338
// GetReviewersByPullID gets all reviewers for a pull request with the statuses
314339
func GetReviewersByPullID(pullID int64) (issueReviewers []*PullReviewersWithType, err error) {
315340
irs := []*PullReviewersWithType{}
316341
if x.Dialect().DBType() == core.MSSQL {
317-
err = x.SQL(`SELECT [user].*, review.type, review.review_updated_unix FROM
342+
err = x.SQL(`SELECT [user].*, review.type, review.official, review.review_updated_unix FROM
318343
(SELECT review.id, review.type, review.reviewer_id, max(review.updated_unix) as review_updated_unix
319344
FROM review WHERE review.issue_id=? AND (review.type = ? OR review.type = ?)
320345
GROUP BY review.id, review.type, review.reviewer_id) as review
321346
INNER JOIN [user] ON review.reviewer_id = [user].id ORDER BY review_updated_unix DESC`,
322347
pullID, ReviewTypeApprove, ReviewTypeReject).
323348
Find(&irs)
324349
} else {
325-
err = x.Select("`user`.*, review.type, max(review.updated_unix) as review_updated_unix").
350+
err = x.Select("`user`.*, review.type, review.official, max(review.updated_unix) as review_updated_unix").
326351
Table("review").
327352
Join("INNER", "`user`", "review.reviewer_id = `user`.id").
328353
Where("review.issue_id = ? AND (review.type = ? OR review.type = ?)",

0 commit comments

Comments
 (0)