fix(api): honor branch protection force push and allowlist options on create and edit (#39990)

The branch protection API handled push and force push options
inconsistently. Create dropped force push unless push was enabled, and
edit ignored allowlist fields unless their parent was set to true in the
same request.

- Create treats force push independently of push, like edit and the web
UI. Enforcement is unchanged.
- Edit applies `enable_push_whitelist`, `enable_force_push_allowlist`
and both deploy key flags against the rule's resulting state, like the
deletion fields. `{"enable_push_whitelist": false}` now disables the
whitelist instead of being ignored.
- Documents that `enable_force_push_allowlist` requires
`enable_force_push` on create.

Fixes https://github.com/go-gitea/gitea/issues/35121

---------

Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
Mitrahsoft
2026-11-07 09:34:21 +00:00
committed by GitHub
parent c74f2f9216
commit b6c291f87a
6 changed files with 73 additions and 63 deletions

View File

@@ -502,8 +502,7 @@ func TestAPIBranchProtectionDeployKeyAllowlists(t *testing.T) {
t.Run("LegacyFlagsDisableDeployKeys", func(t *testing.T) {
createProtection(t, "disable-deploy-keys", true)
resp := MakeRequest(t, NewRequestWithJSON(t, "PATCH", path+"/disable-deploy-keys", map[string]any{
"enable_push": true, "enable_push_whitelist": true, "push_whitelist_deploy_keys": false,
"enable_force_push": true, "enable_force_push_allowlist": true, "force_push_allowlist_deploy_keys": false,
"push_whitelist_deploy_keys": false, "force_push_allowlist_deploy_keys": false,
"push_whitelist_usernames": []string{"user2", "gitea:deploy-key"}, "force_push_allowlist_usernames": []string{"gitea:deploy-key"},
}).AddTokenAuth(token), http.StatusOK)
bp := DecodeJSON(t, resp, &api.BranchProtection{})
@@ -529,6 +528,41 @@ func TestAPIBranchProtectionDeployKeyAllowlists(t *testing.T) {
assert.Equal(t, []string{"gitea:deploy-key"}, bp.ForcePushAllowlistUsernames)
})
t.Run("ChildFieldsFollowEffectiveParents", func(t *testing.T) {
protectionFlags := func(resp *httptest.ResponseRecorder) api.BranchProtection {
bp := DecodeJSON(t, resp, &api.BranchProtection{})
return api.BranchProtection{
EnablePush: bp.EnablePush, EnablePushWhitelist: bp.EnablePushWhitelist, PushWhitelistDeployKeys: bp.PushWhitelistDeployKeys,
EnableForcePush: bp.EnableForcePush, EnableForcePushAllowlist: bp.EnableForcePushAllowlist, ForcePushAllowlistDeployKeys: bp.ForcePushAllowlistDeployKeys,
}
}
resp := MakeRequest(t, NewRequestWithJSON(t, "POST", path, &api.CreateBranchProtectionOption{
RuleName: "child-fields", EnablePushWhitelist: true, PushWhitelistDeployKeys: true,
EnableForcePush: true, EnableForcePushAllowlist: true, ForcePushAllowlistDeployKeys: true,
}).AddTokenAuth(token), http.StatusCreated)
assert.Equal(t, api.BranchProtection{EnableForcePush: true, EnableForcePushAllowlist: true, ForcePushAllowlistDeployKeys: true}, protectionFlags(resp))
children := map[string]any{
"enable_push_whitelist": true, "push_whitelist_deploy_keys": true,
"enable_force_push_allowlist": true, "force_push_allowlist_deploy_keys": true,
}
for _, testCase := range []struct {
body map[string]any
expected api.BranchProtection
}{
{map[string]any{"enable_push": true, "enable_force_push_allowlist": false}, api.BranchProtection{EnablePush: true, EnableForcePush: true}},
{children, api.BranchProtection{
EnablePush: true, EnablePushWhitelist: true, PushWhitelistDeployKeys: true,
EnableForcePush: true, EnableForcePushAllowlist: true, ForcePushAllowlistDeployKeys: true,
}},
{map[string]any{"enable_push": false, "enable_force_push": false}, api.BranchProtection{}},
{children, api.BranchProtection{}},
} {
resp := MakeRequest(t, NewRequestWithJSON(t, "PATCH", path+"/child-fields", testCase.body).AddTokenAuth(token), http.StatusOK)
assert.Equal(t, testCase.expected, protectionFlags(resp))
}
})
t.Run("CreateWithDeployKeyUsernames", func(t *testing.T) {
resp := MakeRequest(t, NewRequestWithJSON(t, "POST", path, &api.CreateBranchProtectionOption{
RuleName: "deploy-key-usernames", EnablePush: true, EnablePushWhitelist: true,