feat: add delete whitelist to branch protection rules #696

Closed
opened 2026-06-25 13:20:46 +00:00 by jmiller · 2 comments
Owner

Problem

Branch protection currently blocks all deletion of protected branches with no whitelist mechanism. The pre-receive hook unconditionally blocks deletion. This prevents workflows like auto-release from cleaning up ephemeral branches (e.g. rc).

Solution

Add a delete whitelist following the same pattern as force-push allowlist:

  • CanDelete, EnableDeleteAllowlist, DeleteAllowlistUserIDs, DeleteAllowlistTeamIDs, DeleteAllowlistDeployKeys, DeleteAllowlistActionsUser
  • CanUserDelete() method on ProtectedBranch
  • Migration v361
  • API structs, pre-receive hook, branch service, web UI, locale strings

Use Case

The rc branch is ephemeral -- created by promote-rc, deleted by auto-release. It needs push protection while alive but must be deletable by CI.

## Problem Branch protection currently blocks all deletion of protected branches with no whitelist mechanism. The pre-receive hook unconditionally blocks deletion. This prevents workflows like auto-release from cleaning up ephemeral branches (e.g. rc). ## Solution Add a delete whitelist following the same pattern as force-push allowlist: - CanDelete, EnableDeleteAllowlist, DeleteAllowlistUserIDs, DeleteAllowlistTeamIDs, DeleteAllowlistDeployKeys, DeleteAllowlistActionsUser - CanUserDelete() method on ProtectedBranch - Migration v361 - API structs, pre-receive hook, branch service, web UI, locale strings ## Use Case The rc branch is ephemeral -- created by promote-rc, deleted by auto-release. It needs push protection while alive but must be deletable by CI.
Author
Owner

Implementation complete in PR #706 (feature/delete-whitelistdev).

What was added:

  • 6 new model fields on ProtectedBranch: CanDelete, EnableDeleteAllowlist, DeleteAllowlistUserIDs, DeleteAllowlistTeamIDs, DeleteAllowlistDeployKeys, DeleteAllowlistActionsUser
  • CanUserDelete() method — defaults to admin access when no allowlist is enabled (higher threshold than force-push which defaults to write)
  • Migration v361 adds columns to protected_branch table
  • Pre-receive hook: replaced unconditional deletion block with CanUserDelete check (handles both deploy key and user scenarios)
  • CanDeleteBranch service: uses GetFirstMatchProtectedBranchRule + CanUserDelete instead of IsBranchProtected
  • Full API support (create/edit branch protection endpoints)
  • Web UI settings page with radio buttons (none/all/whitelist) and user/team/deploy-key dropdowns
  • 12 new locale strings

Files changed (13):
models/git/protected_branch.go, models/migrations/migrations.go, models/migrations/v1_27/v361.go, modules/structs/repo_branch.go, options/locale/locale_en-US.json, routers/api/v1/repo/branch.go, routers/private/hook_pre_receive.go, routers/web/repo/setting/protected_branch.go, services/convert/convert.go, services/forms/repo_form.go, services/repository/branch.go, templates/repo/settings/protected_branch.tmpl, CHANGELOG.md

Implementation complete in PR #706 (`feature/delete-whitelist` → `dev`). **What was added:** - 6 new model fields on `ProtectedBranch`: `CanDelete`, `EnableDeleteAllowlist`, `DeleteAllowlistUserIDs`, `DeleteAllowlistTeamIDs`, `DeleteAllowlistDeployKeys`, `DeleteAllowlistActionsUser` - `CanUserDelete()` method — defaults to admin access when no allowlist is enabled (higher threshold than force-push which defaults to write) - Migration v361 adds columns to `protected_branch` table - Pre-receive hook: replaced unconditional deletion block with `CanUserDelete` check (handles both deploy key and user scenarios) - `CanDeleteBranch` service: uses `GetFirstMatchProtectedBranchRule` + `CanUserDelete` instead of `IsBranchProtected` - Full API support (create/edit branch protection endpoints) - Web UI settings page with radio buttons (none/all/whitelist) and user/team/deploy-key dropdowns - 12 new locale strings **Files changed (13):** `models/git/protected_branch.go`, `models/migrations/migrations.go`, `models/migrations/v1_27/v361.go`, `modules/structs/repo_branch.go`, `options/locale/locale_en-US.json`, `routers/api/v1/repo/branch.go`, `routers/private/hook_pre_receive.go`, `routers/web/repo/setting/protected_branch.go`, `services/convert/convert.go`, `services/forms/repo_form.go`, `services/repository/branch.go`, `templates/repo/settings/protected_branch.tmpl`, `CHANGELOG.md`
Author
Owner

Implemented in PR #706 (merged to dev).

Changes (13 files, +345/-20):

  • 6 new fields on ProtectedBranch model: CanDelete, EnableDeleteAllowlist, DeleteAllowlistUserIDs/TeamIDs, DeleteAllowlistDeployKeys, DeleteAllowlistActionsUser
  • CanUserDelete() method — defaults to admin access when no allowlist (higher threshold than force-push which uses write access)
  • Migration v361 adds columns to protected_branch table
  • Pre-receive hook checks CanUserDelete instead of unconditionally blocking deletion
  • Branch service CanDeleteBranch uses allowlist-aware check
  • Full API support (create/edit branch protection endpoints)
  • Web UI with radio pattern (disable/enable all/allowlist) matching force-push section
  • 12 locale strings added
Implemented in PR #706 (merged to dev). **Changes (13 files, +345/-20):** - 6 new fields on `ProtectedBranch` model: `CanDelete`, `EnableDeleteAllowlist`, `DeleteAllowlistUserIDs/TeamIDs`, `DeleteAllowlistDeployKeys`, `DeleteAllowlistActionsUser` - `CanUserDelete()` method — defaults to admin access when no allowlist (higher threshold than force-push which uses write access) - Migration v361 adds columns to `protected_branch` table - Pre-receive hook checks `CanUserDelete` instead of unconditionally blocking deletion - Branch service `CanDeleteBranch` uses allowlist-aware check - Full API support (create/edit branch protection endpoints) - Web UI with radio pattern (disable/enable all/allowlist) matching force-push section - 12 locale strings added
Sign in to join this conversation.