Skip to content

Conversation

@davidsvantesson
Copy link
Contributor

@davidsvantesson davidsvantesson commented Nov 17, 2019

fixes #8288. Related to #5731 (does not solve exactly what poster wants)

Gives three options for pushing to protected branch:

  • disabled
  • anyone with write access
  • only whitelisted

For approvals a new checkbox is added to enable whitelist. Otherwise anyone with write access is considered as a valid approval.

Screenshot

image

@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Nov 17, 2019
@codecov-io
Copy link

codecov-io commented Nov 17, 2019

Codecov Report

Merging #9055 into master will decrease coverage by 0.05%.
The diff coverage is 17.97%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #9055      +/-   ##
==========================================
- Coverage   41.08%   41.03%   -0.06%     
==========================================
  Files         555      556       +1     
  Lines       72403    72535     +132     
==========================================
+ Hits        29750    29764      +14     
- Misses      38912    39028     +116     
- Partials     3741     3743       +2
Impacted Files Coverage Δ
models/migrations/migrations.go 1.3% <ø> (ø) ⬆️
modules/auth/repo_form.go 43.47% <ø> (ø) ⬆️
models/migrations/v111.go 0% <0%> (ø)
services/pull/review.go 0% <0%> (ø) ⬆️
routers/private/hook.go 50.24% <0%> (ø) ⬆️
models/org_team.go 51.47% <100%> (+0.15%) ⬆️
models/pull.go 55.26% <100%> (+0.12%) ⬆️
models/review.go 46.15% <20.68%> (-7.08%) ⬇️
routers/repo/setting_protected_branch.go 40.29% <38.46%> (+0.6%) ⬆️
routers/repo/issue.go 36.36% <50%> (ø) ⬆️
... and 8 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6460284...93ede5a. Read the comment docs.

@guillep2k
Copy link
Member

guillep2k commented Nov 17, 2019

Sorry if I didn't get this correctly; the original issue was kind of unclear, but what I understood is that the OP asked for something like...?:

  • Allow forced pushs (-f)

This PR seems very useful nonetheless.

@davidsvantesson
Copy link
Contributor Author

Reading the issue again (and also the comment that was in Chinese), I see I misunderstood what the original poster wanted. I though the problem was that you wanted to easily disable only force pushing for everyone but allowing pushing (without having to enter a whitelist), and maybe also that it shall be enforced for all branches (which can be solved by wildcard protected branches).
So yes, enabling force pushing for some users is not solved by this PR.

@GiteaBot GiteaBot added lgtm/need 1 This PR needs approval from one additional maintainer to be merged. and removed lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. labels Nov 17, 2019
@lunny
Copy link
Member

lunny commented Nov 18, 2019

Please resolve conflicts.

@lunny lunny added the type/enhancement An improvement of existing functionality label Nov 19, 2019
@lunny lunny added this to the 1.11.0 milestone Nov 19, 2019
@6543
Copy link
Member

6543 commented Nov 21, 2019

Migration number has changed +1

@davidsvantesson davidsvantesson force-pushed the branch-protection-anyone branch from 0fa8144 to d48fcb4 Compare November 21, 2019 06:48
# Conflicts:
#	public/js/index.js
#	public/js/index.js.map
if review.Type != ReviewTypeApprove {
continue
}
if !protectBranch.EnableApprovalsWhitelist {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO approvals should "remember" if the users were whitelisted at the moment they approved and this value should not be recalculated. Two reasons for this:

  • This is not "list friendly"; it's very difficult to optimize as we want to have the PR list showing the number of approvals. If possible, we should resolve the list with a simple join/select.
  • If rules change, history should not be rewritten. Non-counting approvals should remain non-counting and vice-versa.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your argumentation makes sense, but that would require quite large code change (need new database columns). I think it should be a different PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's that big of a change. I think you only need a new "Verified bool" column to the review table and move this check to the code that inserts the review. One day that "verified" column can reflect a 2FA, for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it be called OfficialReviewer (bool)?
You mean there will be an option to only accept 2FA users as reviewers?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it can be called OfficialReviewer, although that would lock the meaning. What I mean is that what today is a bool (verified/not verified) tomorrow can be an enum (not verified/verified/verified+signed, etc.). But whatever we may want in the future can be solved with a migration no problem, so feel free to choose whatever you feel that fits best. 👍

# Conflicts:
#	public/js/index.js
#	public/js/index.js.map
@davidsvantesson davidsvantesson force-pushed the branch-protection-anyone branch from 128f1ca to b63974e Compare November 26, 2019 07:52
}

var pageSize int64 = 20
qresult, err := sess.QueryInterface("SELECT max(id) as max_id FROM pull_request")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try:

	var max_id int64
	if _, err = sess.Select("max(id) as `max_id`").Table("pull_request").Get(&max_id); err != nil {
		return err
	}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current query seem to work, although your suggestion maybe looks a bit simpler.

models/review.go Outdated
if x.Dialect().DBType() == core.MSSQL {
err = x.SQL(`SELECT [user].*, review.type, review.official, review.review_updated_unix FROM
(SELECT review.id, review.type, review.reviewer_id, max(review.updated_unix) as review_updated_unix
(SELECT review.id, review.type, review.reviewer_id, review.official, max(review.updated_unix) as review_updated_unix
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line I don't understand. If you have review.id in the row, all lines are already unique (unless there were multiple users with the same id which should never happen). A MAX(updated_unix) will be the same as updated_unix without the Group By. 🤔

What is it that you are trying to accomplish? Bring up the latest review? The query from the migration should achieve that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function shall return the latest review of each reviewer (not necessary official reviewers). I see that I actually don't use the change in this function, probably rewrote something. I think later on the information on which reviews are "official" should be shown on the pull request page.

The max(review.updated_unix) as review_updated_unix is used to sort the list and ensure the group by gets the latest review. However it might have been enough to get the max(id) instead, I don't see how reviews from same reviewer could change order.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function shall return the latest review of each reviewer (not necessary official reviewers). I see that I actually don't use the change in this function, probably rewrote something. I think later on the information on which reviews are "official" should be shown on the pull request page.

The max(review.updated_unix) as review_updated_unix is used to sort the list and ensure the group by gets the latest review. However it might have been enough to get the max(id) instead, I don't see how reviews from same reviewer could change order.

This kind of query will not work. If you have two different review.type (e.g. comment + approval) from the same user, you'll get two rows because Group By will see them as different values. To get the latest record you need to do a select in select like the one used in the migration:

if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND type in (?, ?) GROUP BY issue_id, reviewer_id)",
			issueID, models.ReviewTypeApprove, models.ReviewTypeReject).
			Find(&reviews); err != nil {
			return err
		}

Of course the example above requires some adaptation to get the JOIN and the columns you're expecting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is actually solved further down in the function (I don't say it is a good solution 😄)

	// We need to group our results by the fields we want, otherwise some databases will fail (only_full_group_by).
	// But becaus we're doing this, we need to manually filter out multiple reviews of different types by the
	// same person because we only want to show the newest review grouped by user. Thats why we're using a map here.

Copy link
Contributor Author

@davidsvantesson davidsvantesson Nov 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your query might work... although it doesn't work to just remove some of the group by fields for pgsql and mysql (due to some only_full_group_by setting).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I didn't want to touch this function more than needed (and actually I can revert the changes I done). But the only thing I added was the Official field to the results, and hence added in SQL where necessary. The multiple rows is handled by the code later already.

I think maybe this function tries to do too much in SQL which makes it a bit complex.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To exemplify this, let me give you one example. Suppose you have this in the review table:

review ID user_id review_type review_date
1 1 comment Jan 1
2 2 request-changes Jan 2
3 1 comment Jan 3
4 3 comment Jan 4
5 2 approval Jan 5

If you do:

select max(review_id), user_id, review_type, max(review_date)
from review
group by user_id, review_type;

This is what you will get:

review ID user_id review_type review_date
2 2 request-changes Jan 2
3 1 comment Jan 3
4 3 comment Jan 4
5 2 approval Jan 5

As you see, the two reviews from user #1 were consolidated in one row, as the fields user_id and review_type are the same for all records; user #3 is not a problem, but the two reviews from user #2 show up as different rows.

That's why the only way to get this SQL right is with a sub-query:

select max(id), user_id from review group by user_id;

Will give you:

review ID user_id
3 1
4 3
5 2

So, these are the records that you're looking for:

select review_id, user_id, review_type, review_date
from `review`
where review_id in (
        select max(id), user_id from `review` as `review_inner` group by user_id
    )

returns:

review ID user_id review_type date
3 1 comment Jan 3
4 3 comment Jan 4
5 2 approval Jan 5

which is what you want. With this method you can perform joins too:

select review_id, user_id, review_type, review_date, `user`.*
from `review`
INNER JOIN `user` ON `user`.`id` = `review`.`user_id`
where review_id in (
        select max(id), user_id from `review` as `review_inner` group by user_id
    )

There's probably a nice way to write all of this with a couple of xorm directives (e.g. I think you can write the subquery in builder and use it in the In() operator, but I'm not sure), so I think it will be just quicker to do it with sess.SQL() like you did in migrations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very long informative response 😄.
I just wanted to point out that the existing code actually works, although not very efficient or elegant. I will see if I can rewrite the SQL query in a better way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry! I didn't realize that the code wasn't yours. It's my tendency of checking the right column more thoroughly than the left one. 😋

If you just don't feel like doing that change just let me know, because that's the only thing I was waiting for to give my 👍.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rewrote the code and think it is easier to understand now although there will be a few more sql queries, but normally there will not be that many reviews for a PR.

@davidsvantesson davidsvantesson force-pushed the branch-protection-anyone branch from 8423e2f to b1537d4 Compare November 27, 2019 07:12
@davidsvantesson davidsvantesson force-pushed the branch-protection-anyone branch from 9d7d184 to 72044ff Compare November 27, 2019 07:53
@davidsvantesson davidsvantesson force-pushed the branch-protection-anyone branch from 72044ff to 8198531 Compare November 27, 2019 07:54
@davidsvantesson
Copy link
Contributor Author

So finally I think this PR is ready....

@davidsvantesson davidsvantesson force-pushed the branch-protection-anyone branch from 4515ccd to 6ca746a Compare November 28, 2019 08:47
Copy link
Member

@guillep2k guillep2k left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the good work!

@GiteaBot GiteaBot added lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. and removed lgtm/need 1 This PR needs approval from one additional maintainer to be merged. labels Nov 28, 2019
techknowlogick and others added 3 commits November 30, 2019 21:46
# Conflicts:
#	models/migrations/migrations.go
#	models/migrations/v110.go
#	models/review.go
#	public/js/index.js
#	public/js/index.js.map
@techknowlogick techknowlogick merged commit bac4b78 into go-gitea:master Dec 4, 2019
@go-gitea go-gitea locked and limited conversation to collaborators Nov 24, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. type/enhancement An improvement of existing functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow Collaborators with Write Access to Give Approving PR Reviews

8 participants