Skip to content

Commit 882850b

Browse files
Henry Goodmanhenrygoodman
authored andcommitted
Allow force pushes to protected branches
1 parent fce1d5d commit 882850b

File tree

11 files changed

+387
-37
lines changed

11 files changed

+387
-37
lines changed

models/git/protected_branch.go

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,15 @@ type ProtectedBranch struct {
3838
isPlainName bool `xorm:"-"`
3939
CanPush bool `xorm:"NOT NULL DEFAULT false"`
4040
EnableWhitelist bool
41-
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
42-
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
41+
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
42+
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
43+
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
44+
CanForcePush bool `xorm:"NOT NULL DEFAULT false"`
45+
EnableForcePushWhitelist bool
46+
ForcePushWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
47+
ForcePushWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
48+
ForcePushWhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
4349
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"`
44-
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
4550
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
4651
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
4752
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"`
@@ -142,6 +147,33 @@ func (protectBranch *ProtectedBranch) CanUserPush(ctx context.Context, user *use
142147
return in
143148
}
144149

150+
// CanUserForcePush returns if some user could force push to this protected branch
151+
// Since force-push extends normal push, we also check if user has regular push access
152+
func (protectBranch *ProtectedBranch) CanUserForcePush(ctx context.Context, user *user_model.User) bool {
153+
if !protectBranch.CanForcePush {
154+
return false
155+
}
156+
157+
if !protectBranch.EnableForcePushWhitelist {
158+
return protectBranch.CanUserPush(ctx, user)
159+
}
160+
161+
if base.Int64sContains(protectBranch.ForcePushWhitelistUserIDs, user.ID) {
162+
return protectBranch.CanUserPush(ctx, user)
163+
}
164+
165+
if len(protectBranch.ForcePushWhitelistTeamIDs) == 0 {
166+
return false
167+
}
168+
169+
in, err := organization.IsUserInTeams(ctx, user.ID, protectBranch.ForcePushWhitelistTeamIDs)
170+
if err != nil {
171+
log.Error("IsUserInTeams: %v", err)
172+
return false
173+
}
174+
return in && protectBranch.CanUserPush(ctx, user)
175+
}
176+
145177
// IsUserMergeWhitelisted checks if some user is whitelisted to merge to this branch
146178
func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch, userID int64, permissionInRepo access_model.Permission) bool {
147179
if !protectBranch.EnableMergeWhitelist {
@@ -303,6 +335,9 @@ type WhitelistOptions struct {
303335
UserIDs []int64
304336
TeamIDs []int64
305337

338+
ForcePushUserIDs []int64
339+
ForcePushTeamIDs []int64
340+
306341
MergeUserIDs []int64
307342
MergeTeamIDs []int64
308343

@@ -330,6 +365,13 @@ func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, prote
330365
}
331366
protectBranch.WhitelistUserIDs = whitelist
332367

368+
whitelist, err = updateUserWhitelist(ctx, repo, protectBranch.ForcePushWhitelistUserIDs, opts.ForcePushUserIDs)
369+
log.Info("%v", whitelist, err)
370+
if err != nil {
371+
return err
372+
}
373+
protectBranch.ForcePushWhitelistUserIDs = whitelist
374+
333375
whitelist, err = updateUserWhitelist(ctx, repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs)
334376
if err != nil {
335377
return err
@@ -349,6 +391,12 @@ func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, prote
349391
}
350392
protectBranch.WhitelistTeamIDs = whitelist
351393

394+
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.ForcePushWhitelistTeamIDs, opts.ForcePushTeamIDs)
395+
if err != nil {
396+
return err
397+
}
398+
protectBranch.ForcePushWhitelistTeamIDs = whitelist
399+
352400
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs)
353401
if err != nil {
354402
return err
@@ -474,13 +522,17 @@ func DeleteProtectedBranch(ctx context.Context, repo *repo_model.Repository, id
474522
func RemoveUserIDFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userID int64) error {
475523
lenIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistUserIDs), len(p.ApprovalsWhitelistUserIDs), len(p.MergeWhitelistUserIDs)
476524
p.WhitelistUserIDs = util.SliceRemoveAll(p.WhitelistUserIDs, userID)
525+
p.ForcePushWhitelistUserIDs = util.SliceRemoveAll(p.ForcePushWhitelistUserIDs, userID)
477526
p.ApprovalsWhitelistUserIDs = util.SliceRemoveAll(p.ApprovalsWhitelistUserIDs, userID)
478527
p.MergeWhitelistUserIDs = util.SliceRemoveAll(p.MergeWhitelistUserIDs, userID)
479528

480-
if lenIDs != len(p.WhitelistUserIDs) || lenApprovalIDs != len(p.ApprovalsWhitelistUserIDs) ||
529+
if lenIDs != len(p.WhitelistUserIDs) ||
530+
lenApprovalIDs != len(p.ForcePushWhitelistUserIDs) ||
531+
lenApprovalIDs != len(p.ApprovalsWhitelistUserIDs) ||
481532
lenMergeIDs != len(p.MergeWhitelistUserIDs) {
482533
if _, err := db.GetEngine(ctx).ID(p.ID).Cols(
483534
"whitelist_user_i_ds",
535+
"force_push_whitelist_user_i_ds",
484536
"merge_whitelist_user_i_ds",
485537
"approvals_whitelist_user_i_ds",
486538
).Update(p); err != nil {
@@ -502,6 +554,7 @@ func RemoveTeamIDFromProtectedBranch(ctx context.Context, p *ProtectedBranch, te
502554
lenMergeIDs != len(p.MergeWhitelistTeamIDs) {
503555
if _, err := db.GetEngine(ctx).ID(p.ID).Cols(
504556
"whitelist_team_i_ds",
557+
"force_push_whitelist_team_i_ds",
505558
"merge_whitelist_team_i_ds",
506559
"approvals_whitelist_team_i_ds",
507560
).Update(p); err != nil {

modules/structs/repo_branch.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ type BranchProtection struct {
3030
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
3131
PushWhitelistTeams []string `json:"push_whitelist_teams"`
3232
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
33+
EnableForcePush bool `json:"enable_force_push"`
34+
EnableForcePushWhitelist bool `json:"enable_force_push_whitelist"`
35+
ForcePushWhitelistUsernames []string `json:"force_push_whitelist_usernames"`
36+
ForcePushWhitelistTeams []string `json:"force_push_whitelist_teams"`
37+
ForcePushWhitelistDeployKeys bool `json:"force_push_whitelist_deploy_keys"`
3338
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
3439
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
3540
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
@@ -62,6 +67,11 @@ type CreateBranchProtectionOption struct {
6267
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
6368
PushWhitelistTeams []string `json:"push_whitelist_teams"`
6469
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
70+
EnableForcePush bool `json:"enable_force_push"`
71+
EnableForcePushWhitelist bool `json:"enable_force_push_whitelist"`
72+
ForcePushWhitelistUsernames []string `json:"force_push_whitelist_usernames"`
73+
ForcePushWhitelistTeams []string `json:"force_push_whitelist_teams"`
74+
ForcePushWhitelistDeployKeys bool `json:"force_push_whitelist_deploy_keys"`
6575
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
6676
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
6777
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
@@ -87,6 +97,11 @@ type EditBranchProtectionOption struct {
8797
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
8898
PushWhitelistTeams []string `json:"push_whitelist_teams"`
8999
PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"`
100+
EnableForcePush *bool `json:"enable_force_push"`
101+
EnableForcePushWhitelist *bool `json:"enable_force_push_whitelist"`
102+
ForcePushWhitelistUsernames []string `json:"force_push_whitelist_usernames"`
103+
ForcePushWhitelistTeams []string `json:"force_push_whitelist_teams"`
104+
ForcePushWhitelistDeployKeys *bool `json:"force_push_whitelist_deploy_keys"`
90105
EnableMergeWhitelist *bool `json:"enable_merge_whitelist"`
91106
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
92107
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`

options/locale/locale_en-US.ini

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,6 +2185,7 @@ settings.event_wiki_desc = Wiki page created, renamed, edited or deleted.
21852185
settings.event_release = Release
21862186
settings.event_release_desc = Release published, updated or deleted in a repository.
21872187
settings.event_push = Push
2188+
settings.event_force_push = Force Push
21882189
settings.event_push_desc = Git push to a repository.
21892190
settings.event_repository = Repository
21902191
settings.event_repository_desc = Repository created or deleted.
@@ -2278,8 +2279,14 @@ settings.protect_this_branch = Enable Branch Protection
22782279
settings.protect_this_branch_desc = Prevents deletion and restricts Git pushing and merging to the branch.
22792280
settings.protect_disable_push = Disable Push
22802281
settings.protect_disable_push_desc = No pushing will be allowed to this branch.
2282+
settings.protect_disable_force_push = Disable Force Push
2283+
settings.protect_disable_force_push_desc = No force pushing will be allowed to this branch.
22812284
settings.protect_enable_push = Enable Push
22822285
settings.protect_enable_push_desc = Anyone with write access will be allowed to push to this branch (but not force push).
2286+
settings.protect_enable_force_push_all = Everyone
2287+
settings.protect_enable_force_push_all_desc = Anyone with push access will be allowed to force push to this branch.
2288+
settings.protect_enable_force_push_whitelist = Whitelist Restricted Force Push
2289+
settings.protect_enable_force_push_whitelist_desc = Only whitelisted users or teams with push access will be allowed to force push to this branch.
22832290
settings.protect_enable_merge = Enable Merge
22842291
settings.protect_enable_merge_desc = Anyone with write access will be allowed to merge the pull requests into this branch.
22852292
settings.protect_whitelist_committers = Whitelist Restricted Push
@@ -2289,6 +2296,9 @@ settings.protect_whitelist_users = Whitelisted users for pushing:
22892296
settings.protect_whitelist_search_users = Search users…
22902297
settings.protect_whitelist_teams = Whitelisted teams for pushing:
22912298
settings.protect_whitelist_search_teams = Search teams…
2299+
settings.protect_force_push_whitelist_users = Whitelisted users for force pushing:
2300+
settings.protect_force_push_whitelist_teams = Whitelisted teams for force pushing:
2301+
settings.protect_force_push_whitelist_deploy_keys = Whitelist deploy keys with write access to push.
22922302
settings.protect_merge_whitelist_committers = Enable Merge Whitelist
22932303
settings.protect_merge_whitelist_committers_desc = Allow only whitelisted users or teams to merge pull requests into this branch.
22942304
settings.protect_merge_whitelist_users = Whitelisted users for merging:

routers/api/v1/repo/branch.go

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,15 @@ func CreateBranchProtection(ctx *context.APIContext) {
552552
ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
553553
return
554554
}
555+
forcePushWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.ForcePushWhitelistUsernames, false)
556+
if err != nil {
557+
if user_model.IsErrUserNotExist(err) {
558+
ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
559+
return
560+
}
561+
ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
562+
return
563+
}
555564
mergeWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
556565
if err != nil {
557566
if user_model.IsErrUserNotExist(err) {
@@ -570,7 +579,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
570579
ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
571580
return
572581
}
573-
var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
582+
var whitelistTeams, forcePushWhitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
574583
if repo.Owner.IsOrganization() {
575584
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
576585
if err != nil {
@@ -581,6 +590,15 @@ func CreateBranchProtection(ctx *context.APIContext) {
581590
ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
582591
return
583592
}
593+
forcePushWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ForcePushWhitelistTeams, false)
594+
if err != nil {
595+
if organization.IsErrTeamNotExist(err) {
596+
ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
597+
return
598+
}
599+
ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
600+
return
601+
}
584602
mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
585603
if err != nil {
586604
if organization.IsErrTeamNotExist(err) {
@@ -606,8 +624,11 @@ func CreateBranchProtection(ctx *context.APIContext) {
606624
RuleName: ruleName,
607625
CanPush: form.EnablePush,
608626
EnableWhitelist: form.EnablePush && form.EnablePushWhitelist,
609-
EnableMergeWhitelist: form.EnableMergeWhitelist,
610627
WhitelistDeployKeys: form.EnablePush && form.EnablePushWhitelist && form.PushWhitelistDeployKeys,
628+
CanForcePush: form.EnablePush && form.EnableForcePush,
629+
EnableForcePushWhitelist: form.EnablePush && form.EnableForcePush && form.EnableForcePushWhitelist,
630+
ForcePushWhitelistDeployKeys: form.EnablePush && form.EnableForcePush && form.EnableForcePushWhitelist && form.ForcePushWhitelistDeployKeys,
631+
EnableMergeWhitelist: form.EnableMergeWhitelist,
611632
EnableStatusCheck: form.EnableStatusCheck,
612633
StatusCheckContexts: form.StatusCheckContexts,
613634
EnableApprovalsWhitelist: form.EnableApprovalsWhitelist,
@@ -624,6 +645,8 @@ func CreateBranchProtection(ctx *context.APIContext) {
624645
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
625646
UserIDs: whitelistUsers,
626647
TeamIDs: whitelistTeams,
648+
ForcePushUserIDs: forcePushWhitelistUsers,
649+
ForcePushTeamIDs: forcePushWhitelistTeams,
627650
MergeUserIDs: mergeWhitelistUsers,
628651
MergeTeamIDs: mergeWhitelistTeams,
629652
ApprovalsUserIDs: approvalsWhitelistUsers,
@@ -754,6 +777,27 @@ func EditBranchProtection(ctx *context.APIContext) {
754777
}
755778
}
756779

780+
if form.EnableForcePush != nil {
781+
if !*form.EnableForcePush {
782+
protectBranch.CanForcePush = false
783+
protectBranch.EnableForcePushWhitelist = false
784+
protectBranch.ForcePushWhitelistDeployKeys = false
785+
} else {
786+
protectBranch.CanForcePush = true
787+
if form.EnableForcePushWhitelist != nil {
788+
if !*form.EnableForcePushWhitelist {
789+
protectBranch.EnableForcePushWhitelist = false
790+
protectBranch.ForcePushWhitelistDeployKeys = false
791+
} else {
792+
protectBranch.EnableForcePushWhitelist = true
793+
if form.ForcePushWhitelistDeployKeys != nil {
794+
protectBranch.ForcePushWhitelistDeployKeys = *form.ForcePushWhitelistDeployKeys
795+
}
796+
}
797+
}
798+
}
799+
}
800+
757801
if form.EnableMergeWhitelist != nil {
758802
protectBranch.EnableMergeWhitelist = *form.EnableMergeWhitelist
759803
}
@@ -802,7 +846,7 @@ func EditBranchProtection(ctx *context.APIContext) {
802846
protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
803847
}
804848

805-
var whitelistUsers []int64
849+
var whitelistUsers, forcePushWhitelistUsers, mergeWhitelistUsers, approvalsWhitelistUsers []int64
806850
if form.PushWhitelistUsernames != nil {
807851
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
808852
if err != nil {
@@ -816,7 +860,19 @@ func EditBranchProtection(ctx *context.APIContext) {
816860
} else {
817861
whitelistUsers = protectBranch.WhitelistUserIDs
818862
}
819-
var mergeWhitelistUsers []int64
863+
if form.ForcePushWhitelistUsernames != nil {
864+
forcePushWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.ForcePushWhitelistUsernames, false)
865+
if err != nil {
866+
if user_model.IsErrUserNotExist(err) {
867+
ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
868+
return
869+
}
870+
ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
871+
return
872+
}
873+
} else {
874+
forcePushWhitelistUsers = protectBranch.ForcePushWhitelistUserIDs
875+
}
820876
if form.MergeWhitelistUsernames != nil {
821877
mergeWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
822878
if err != nil {
@@ -830,7 +886,6 @@ func EditBranchProtection(ctx *context.APIContext) {
830886
} else {
831887
mergeWhitelistUsers = protectBranch.MergeWhitelistUserIDs
832888
}
833-
var approvalsWhitelistUsers []int64
834889
if form.ApprovalsWhitelistUsernames != nil {
835890
approvalsWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false)
836891
if err != nil {
@@ -845,7 +900,7 @@ func EditBranchProtection(ctx *context.APIContext) {
845900
approvalsWhitelistUsers = protectBranch.ApprovalsWhitelistUserIDs
846901
}
847902

848-
var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
903+
var whitelistTeams, forcePushWhitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
849904
if repo.Owner.IsOrganization() {
850905
if form.PushWhitelistTeams != nil {
851906
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
@@ -860,6 +915,19 @@ func EditBranchProtection(ctx *context.APIContext) {
860915
} else {
861916
whitelistTeams = protectBranch.WhitelistTeamIDs
862917
}
918+
if form.ForcePushWhitelistTeams != nil {
919+
forcePushWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ForcePushWhitelistTeams, false)
920+
if err != nil {
921+
if organization.IsErrTeamNotExist(err) {
922+
ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
923+
return
924+
}
925+
ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
926+
return
927+
}
928+
} else {
929+
forcePushWhitelistTeams = protectBranch.ForcePushWhitelistTeamIDs
930+
}
863931
if form.MergeWhitelistTeams != nil {
864932
mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
865933
if err != nil {
@@ -891,6 +959,8 @@ func EditBranchProtection(ctx *context.APIContext) {
891959
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
892960
UserIDs: whitelistUsers,
893961
TeamIDs: whitelistTeams,
962+
ForcePushUserIDs: forcePushWhitelistUsers,
963+
ForcePushTeamIDs: forcePushWhitelistTeams,
894964
MergeUserIDs: mergeWhitelistUsers,
895965
MergeTeamIDs: mergeWhitelistTeams,
896966
ApprovalsUserIDs: approvalsWhitelistUsers,

0 commit comments

Comments
 (0)