Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add priority to protected branch #32286

Merged
merged 51 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
cbde69f
Add test for ProtectedBranchRules.sort
6543 Oct 16, 2024
ccb2e12
optimize loadGlob for protectedBranch
6543 Oct 16, 2024
e6b9834
sort non glob protected branch roules by name
6543 Oct 16, 2024
74760d5
update comment
6543 Oct 16, 2024
3103499
clean unrelated
6543 Oct 17, 2024
0ff31b8
Backend to sort ProtectedBranch by Priority
6543 Oct 17, 2024
c6f49a7
fix misspell
6543 Oct 17, 2024
0179a77
Merge branch 'main' into add-priority-to-ProtectedBranch
6543 Oct 17, 2024
05fe316
Update/Insert protectedBranch respect priorityes and add updateFunc f…
6543 Oct 17, 2024
6c1ffb7
api
6543 Oct 17, 2024
3ed30a7
Add api to update
6543 Oct 17, 2024
e944bef
Merge branch 'main' into add-priority-to-ProtectedBranch
6543 Oct 21, 2024
5dd0623
future profe API
6543 Oct 21, 2024
bc81f75
add route for webui
6543 Oct 21, 2024
c83fce9
add grabber octicon
6543 Oct 21, 2024
ae19b8e
wip?
6543 Oct 21, 2024
19b2b7c
form expect struct
6543 Oct 21, 2024
78c9d4c
fix lints
6543 Oct 21, 2024
962ea78
fix lints
6543 Oct 21, 2024
5056af5
fix webui lint issues
6543 Oct 21, 2024
b939a8d
fix webui lint issues
6543 Oct 21, 2024
7939780
limit scope
6543 Oct 21, 2024
0336851
Merge branch 'main' into add-priority-to-ProtectedBranch
6543 Oct 23, 2024
e4af5f9
Merge branch 'main' into add-priority-to-ProtectedBranch
6543 Oct 29, 2024
7588f54
Merge branch 'main' into add-priority-to-ProtectedBranch
6543 Oct 29, 2024
af58887
Merge branch 'main' into add-priority-to-ProtectedBranch
6543 Nov 9, 2024
7f2bcf9
backend tests
6543 Nov 9, 2024
9606f7e
frontend test
6543 Nov 9, 2024
1a3872b
more test coverage
6543 Nov 9, 2024
3cf2120
fix lint
6543 Nov 9, 2024
59cc58b
make mssql work
6543 Nov 9, 2024
fca9bc1
Merge branch 'main' into add-priority-to-ProtectedBranch
6543 Nov 9, 2024
dffb634
simple api path won
6543 Nov 9, 2024
3614fad
Merge branch 'main' into add-priority-to-ProtectedBranch
6543 Nov 11, 2024
f0658d3
address TS review
6543 Nov 11, 2024
4b9f09c
sed -i 's/data-priority-url/data-update-priority-url/g'
6543 Nov 11, 2024
63ccf82
no db.WithTx
6543 Nov 11, 2024
ffff47d
toasty error messages
6543 Nov 11, 2024
4a30f74
update frontend tests
6543 Nov 11, 2024
5e7115c
Merge branch 'main' into add-priority-to-ProtectedBranch
6543 Nov 25, 2024
08df559
fix lint err
6543 Nov 25, 2024
f229044
one diff less
6543 Nov 25, 2024
c1bda05
just struct
6543 Nov 25, 2024
056a9a7
fix tmpl
6543 Nov 25, 2024
ee28d2f
simplify
6543 Nov 26, 2024
6f3308b
working
6543 Nov 26, 2024
a06d500
Merge branch 'main' into add-priority-to-ProtectedBranch
6543 Nov 26, 2024
1e543a1
Merge branch 'main' into add-priority-to-ProtectedBranch
6543 Nov 26, 2024
7aa9db1
parse
6543 Nov 26, 2024
660ca0b
Merge branch 'main' into add-priority-to-ProtectedBranch
6543 Nov 27, 2024
02756d3
nit
6543 Nov 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion models/git/protected_branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type ProtectedBranch struct {
RepoID int64 `xorm:"UNIQUE(s)"`
Repo *repo_model.Repository `xorm:"-"`
RuleName string `xorm:"'branch_name' UNIQUE(s)"` // a branch name or a glob match to branch name
Priority int64 `xorm:"NOT NULL DEFAULT 0"`
globRule glob.Glob `xorm:"-"`
isPlainName bool `xorm:"-"`
CanPush bool `xorm:"NOT NULL DEFAULT false"`
Expand Down Expand Up @@ -413,21 +414,52 @@ func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, prote
}
protectBranch.ApprovalsWhitelistTeamIDs = whitelist

// Make sure protectBranch.ID is not 0 for whitelists
// Looks like it's a new rule
if protectBranch.ID == 0 {
// as it's a new rule and if priority was not set, we need to calc it.
if protectBranch.Priority == 0 {
var lowestPrio int64
// because of mssql we can not use builder or save xorm syntax, so raw sql it is
if _, err := db.GetEngine(ctx).SQL(`SELECT MAX(priority) FROM protected_branch WHERE repo_id = ?`, protectBranch.RepoID).
Get(&lowestPrio); err != nil {
return err
}
log.Trace("Create new ProtectedBranch at repo[%d] and detect current lowest priority '%d'", protectBranch.RepoID, lowestPrio)
protectBranch.Priority = lowestPrio + 1
}

if _, err = db.GetEngine(ctx).Insert(protectBranch); err != nil {
return fmt.Errorf("Insert: %v", err)
}
return nil
}

// update the rule
if _, err = db.GetEngine(ctx).ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil {
return fmt.Errorf("Update: %v", err)
}

return nil
}

func UpdateProtectBranchPriorities(ctx context.Context, repo *repo_model.Repository, ids []int64) error {
prio := int64(1)
return db.WithTx(ctx, func(ctx context.Context) error {
for _, id := range ids {
if _, err := db.GetEngine(ctx).
ID(id).Where("repo_id = ?", repo.ID).
Cols("priority").
Update(&ProtectedBranch{
Priority: prio,
}); err != nil {
return err
}
prio++
}
return nil
})
}

// updateApprovalWhitelist checks whether the user whitelist changed and returns a whitelist with
// the users from newWhitelist which have explicit read or write access to the repo.
func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
Expand Down
7 changes: 7 additions & 0 deletions models/git/protected_branch_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ func (rules ProtectedBranchRules) sort() {
sort.Slice(rules, func(i, j int) bool {
rules[i].loadGlob()
rules[j].loadGlob()

// if priority differ, use that to sort
if rules[i].Priority != rules[j].Priority {
return rules[i].Priority < rules[j].Priority
}

// now we sort the old way
if rules[i].isPlainName != rules[j].isPlainName {
return rules[i].isPlainName // plain name comes first, so plain name means "less"
}
Expand Down
36 changes: 35 additions & 1 deletion models/git/protected_branch_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestBranchRuleMatchPriority(t *testing.T) {
}
}

func TestBranchRuleSort(t *testing.T) {
func TestBranchRuleSortLegacy(t *testing.T) {
in := []*ProtectedBranch{{
RuleName: "b",
CreatedUnix: 1,
Expand Down Expand Up @@ -103,3 +103,37 @@ func TestBranchRuleSort(t *testing.T) {
}
assert.Equal(t, expect, got)
}

func TestBranchRuleSortPriority(t *testing.T) {
in := []*ProtectedBranch{{
RuleName: "b",
CreatedUnix: 1,
Priority: 4,
}, {
RuleName: "b/*",
CreatedUnix: 3,
Priority: 2,
}, {
RuleName: "a/*",
CreatedUnix: 2,
Priority: 1,
}, {
RuleName: "c",
CreatedUnix: 0,
Priority: 0,
}, {
RuleName: "a",
CreatedUnix: 4,
Priority: 3,
}}
expect := []string{"c", "a/*", "b/*", "a", "b"}

pbr := ProtectedBranchRules(in)
pbr.sort()

var got []string
for i := range pbr {
got = append(got, pbr[i].RuleName)
}
assert.Equal(t, expect, got)
}
78 changes: 78 additions & 0 deletions models/git/protected_branch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import (
"fmt"
"testing"

"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"

"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -76,3 +80,77 @@ func TestBranchRuleMatch(t *testing.T) {
)
}
}

func TestUpdateProtectBranchPriorities(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())

repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})

// Create some test protected branches with initial priorities
protectedBranches := []*ProtectedBranch{
{
RepoID: repo.ID,
RuleName: "master",
Priority: 1,
},
{
RepoID: repo.ID,
RuleName: "develop",
Priority: 2,
},
{
RepoID: repo.ID,
RuleName: "feature/*",
Priority: 3,
},
}

for _, pb := range protectedBranches {
_, err := db.GetEngine(db.DefaultContext).Insert(pb)
assert.NoError(t, err)
}

// Test updating priorities
newPriorities := []int64{protectedBranches[2].ID, protectedBranches[0].ID, protectedBranches[1].ID}
err := UpdateProtectBranchPriorities(db.DefaultContext, repo, newPriorities)
assert.NoError(t, err)

// Verify new priorities
pbs, err := FindRepoProtectedBranchRules(db.DefaultContext, repo.ID)
assert.NoError(t, err)

expectedPriorities := map[string]int64{
"feature/*": 1,
"master": 2,
"develop": 3,
}

for _, pb := range pbs {
assert.Equal(t, expectedPriorities[pb.RuleName], pb.Priority)
}
}

func TestNewProtectBranchPriority(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})

err := UpdateProtectBranch(db.DefaultContext, repo, &ProtectedBranch{
RepoID: repo.ID,
RuleName: "branch-1",
Priority: 1,
}, WhitelistOptions{})
assert.NoError(t, err)

newPB := &ProtectedBranch{
RepoID: repo.ID,
RuleName: "branch-2",
// Priority intentionally omitted
}

err = UpdateProtectBranch(db.DefaultContext, repo, newPB, WhitelistOptions{})
assert.NoError(t, err)

savedPB2, err := GetFirstMatchProtectedBranchRule(db.DefaultContext, repo.ID, "branch-2")
assert.NoError(t, err)
assert.Equal(t, int64(2), savedPB2.Priority)
}
1 change: 1 addition & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ func prepareMigrationTasks() []*migration {
newMigration(307, "Fix milestone deadline_unix when there is no due date", v1_23.FixMilestoneNoDueDate),
newMigration(308, "Add index(user_id, is_deleted) for action table", v1_23.AddNewIndexForUserDashboard),
newMigration(309, "Improve Notification table indices", v1_23.ImproveNotificationTableIndices),
newMigration(310, "Add Priority to ProtectedBranch", v1_23.AddPriorityToProtectedBranch),
}
return preparedMigrations
}
Expand Down
16 changes: 16 additions & 0 deletions models/migrations/v1_23/v310.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_23 //nolint

import (
"xorm.io/xorm"
)

func AddPriorityToProtectedBranch(x *xorm.Engine) error {
type ProtectedBranch struct {
Priority int64 `xorm:"NOT NULL DEFAULT 0"`
}

return x.Sync(new(ProtectedBranch))
}
8 changes: 8 additions & 0 deletions modules/structs/repo_branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type BranchProtection struct {
// Deprecated: true
BranchName string `json:"branch_name"`
RuleName string `json:"rule_name"`
Priority int64 `json:"priority"`
EnablePush bool `json:"enable_push"`
EnablePushWhitelist bool `json:"enable_push_whitelist"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
Expand Down Expand Up @@ -64,6 +65,7 @@ type CreateBranchProtectionOption struct {
// Deprecated: true
BranchName string `json:"branch_name"`
RuleName string `json:"rule_name"`
Priority int64 `json:"priority"`
EnablePush bool `json:"enable_push"`
EnablePushWhitelist bool `json:"enable_push_whitelist"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
Expand Down Expand Up @@ -96,6 +98,7 @@ type CreateBranchProtectionOption struct {

// EditBranchProtectionOption options for editing a branch protection
type EditBranchProtectionOption struct {
Priority *int64 `json:"priority"`
EnablePush *bool `json:"enable_push"`
EnablePushWhitelist *bool `json:"enable_push_whitelist"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
Expand Down Expand Up @@ -125,3 +128,8 @@ type EditBranchProtectionOption struct {
UnprotectedFilePatterns *string `json:"unprotected_file_patterns"`
BlockAdminMergeOverride *bool `json:"block_admin_merge_override"`
}

// UpdateBranchProtectionPriories a list to update the branch protection rule priorities
type UpdateBranchProtectionPriories struct {
IDs []int64 `json:"ids"`
}
1 change: 1 addition & 0 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,7 @@ func Routes() *web.Router {
m.Patch("", bind(api.EditBranchProtectionOption{}), mustNotBeArchived, repo.EditBranchProtection)
m.Delete("", repo.DeleteBranchProtection)
})
m.Post("/priority", bind(api.UpdateBranchProtectionPriories{}), mustNotBeArchived, repo.UpdateBranchProtectionPriories)
}, reqToken(), reqAdmin())
m.Group("/tags", func() {
m.Get("", repo.ListTags)
Expand Down
56 changes: 52 additions & 4 deletions routers/api/v1/repo/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
protectBranch = &git_model.ProtectedBranch{
RepoID: ctx.Repo.Repository.ID,
RuleName: ruleName,
Priority: form.Priority,
CanPush: form.EnablePush,
EnableWhitelist: form.EnablePush && form.EnablePushWhitelist,
WhitelistDeployKeys: form.EnablePush && form.EnablePushWhitelist && form.PushWhitelistDeployKeys,
Expand All @@ -640,7 +641,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
BlockAdminMergeOverride: form.BlockAdminMergeOverride,
}

err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
if err := git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
UserIDs: whitelistUsers,
TeamIDs: whitelistTeams,
ForcePushUserIDs: forcePushAllowlistUsers,
Expand All @@ -649,14 +650,13 @@ func CreateBranchProtection(ctx *context.APIContext) {
MergeTeamIDs: mergeWhitelistTeams,
ApprovalsUserIDs: approvalsWhitelistUsers,
ApprovalsTeamIDs: approvalsWhitelistTeams,
})
if err != nil {
}); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
return
}

if isBranchExist {
if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, ruleName); err != nil {
if err := pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, ruleName); err != nil {
ctx.Error(http.StatusInternalServerError, "CheckPRsForBaseBranch", err)
return
}
Expand Down Expand Up @@ -796,6 +796,10 @@ func EditBranchProtection(ctx *context.APIContext) {
}
}

if form.Priority != nil {
protectBranch.Priority = *form.Priority
}

if form.EnableMergeWhitelist != nil {
protectBranch.EnableMergeWhitelist = *form.EnableMergeWhitelist
}
Expand Down Expand Up @@ -1080,3 +1084,47 @@ func DeleteBranchProtection(ctx *context.APIContext) {

ctx.Status(http.StatusNoContent)
}

// UpdateBranchProtectionPriories updates the priorities of branch protections for a repo
func UpdateBranchProtectionPriories(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/branch_protections/priority repository repoUpdateBranchProtectionPriories
// ---
// summary: Update the priorities of branch protections for a repository.
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/UpdateBranchProtectionPriories"
// responses:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
// "423":
// "$ref": "#/responses/repoArchivedError"
form := web.GetForm(ctx).(*api.UpdateBranchProtectionPriories)
repo := ctx.Repo.Repository

if err := git_model.UpdateProtectBranchPriorities(ctx, repo, form.IDs); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateProtectBranchPriorities", err)
return
}

ctx.Status(http.StatusNoContent)
}
3 changes: 3 additions & 0 deletions routers/api/v1/swagger/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ type swaggerParameterBodies struct {
// in:body
EditBranchProtectionOption api.EditBranchProtectionOption

// in:body
UpdateBranchProtectionPriories api.UpdateBranchProtectionPriories

// in:body
CreateOAuth2ApplicationOptions api.CreateOAuth2ApplicationOptions

Expand Down
10 changes: 10 additions & 0 deletions routers/web/repo/setting/protected_branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,16 @@ func DeleteProtectedBranchRulePost(ctx *context.Context) {
ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
}

func UpdateBranchProtectionPriories(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.ProtectBranchPriorityForm)
repo := ctx.Repo.Repository

if err := git_model.UpdateProtectBranchPriorities(ctx, repo, form.IDs); err != nil {
ctx.ServerError("UpdateProtectBranchPriorities", err)
return
}
}

// RenameBranchPost responses for rename a branch
func RenameBranchPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.RenameBranchForm)
Expand Down
Loading
Loading