feat(repo): allow deleting protected branches (#39243)

This adds a Branch Deletion setting with three modes: disabled by
default, anyone with push access, or a restricted user/team/deploy-key
allowlist. The policy applies to Git pushes, API/UI deletion, and
post-merge cleanup; the default branch and the pull request target
branch remain undeletable.

Closes https://github.com/go-gitea/gitea/issues/33305

---------

Co-authored-by: Enoque Duarte <12515551+enoquefcd@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-10-14 07:19:49 +00:00
1 parent aebdbd85fb
commit b015cd67e2
21 files changed
+600 -59

No files matched your search

+9 -7
View File
@@ -192,14 +192,16 @@
{{svg "octicon-pencil"}}
</button>
{{end}}
{{if and $.IsWriter $.Repository.CanContentChange (not .IsProtected)}}
{{if and $.IsWriter $.Repository.CanContentChange}}
{{if .DBBranch.IsDeleted}}
<button type="button" class="btn interact-bg tw-p-2 link-action restore-branch-button" data-url="{{$.Link}}/restore?branch_id={{.DBBranch.ID}}&name={{.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.restore" (.DBBranch.Name)}}">
<span class="tw-text-blue">
{{svg "octicon-reply"}}
</span>
</button>
{{else}}
{{if not .IsProtected}}
<button type="button" class="btn interact-bg tw-p-2 link-action restore-branch-button" data-url="{{$.Link}}/restore?branch_id={{.DBBranch.ID}}&name={{.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.restore" (.DBBranch.Name)}}">
<span class="tw-text-blue">
{{svg "octicon-reply"}}
</span>
</button>
{{end}}
{{else if .CanDelete}}
<button type="button" class="btn interact-bg tw-p-2 show-modal delete-branch-button tw-text-red" data-modal="#delete-branch-modal" data-modal-form.url="{{$.Link}}/delete?name={{.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.delete" (.DBBranch.Name)}}" data-modal-name="{{.DBBranch.Name}}">
{{svg "octicon-trash"}}
</button>
@@ -106,6 +106,42 @@
{{end}}
</div>
</div>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.event_branch_delete"}}</h5>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="enable_deletion" value="none" class="toggle-target-disabled" data-target="#deletion_allowlist_box" {{if not .Rule.CanDelete}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.protect_disable_deletion"}}</label>
<p class="help">{{ctx.Locale.Tr "repo.settings.protect_disable_deletion_desc"}}</p>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="enable_deletion" value="all" class="toggle-target-disabled" data-target="#deletion_allowlist_box" {{if and (.Rule.CanDelete) (not .Rule.EnableDeletionAllowlist)}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.protect_enable_deletion_all"}}</label>
<p class="help">{{ctx.Locale.Tr "repo.settings.protect_enable_deletion_all_desc"}}</p>
</div>
</div>
<div class="grouped fields">
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="enable_deletion" value="whitelist" class="toggle-target-enabled" data-target="#deletion_allowlist_box" {{if and (.Rule.CanDelete) (.Rule.EnableDeletionAllowlist)}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.protect_enable_deletion_allowlist"}}</label>
<p class="help">{{ctx.Locale.Tr "repo.settings.protect_enable_deletion_allowlist_desc"}}</p>
</div>
</div>
<div id="deletion_allowlist_box" class="grouped fields {{if not .Rule.EnableDeletionAllowlist}}disabled{{end}}">
<div class="checkbox-sub-item field">
<label>{{ctx.Locale.Tr "repo.settings.protect_deletion_allowlist_users"}}</label>
{{template "repo/settings/user_dropdown" (dict "Name" "deletion_allowlist_users" "Value" .deletion_allowlist_users "Users" .DeletionUsers)}}
</div>
{{if .Owner.IsOrganization}}
<div class="checkbox-sub-item field">
<label>{{ctx.Locale.Tr "repo.settings.protect_deletion_allowlist_teams"}}</label>
{{template "repo/settings/team_dropdown" (dict "Name" "deletion_allowlist_teams" "Value" .deletion_allowlist_teams "Teams" .Teams)}}
</div>
{{end}}
</div>
</div>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.event_pull_request_approvals"}}</h5>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.protect_required_approvals"}}</label>
+66
View File
@@ -2917,6 +2917,20 @@
"type": "string",
"x-go-name": "Created"
},
"deletion_allowlist_teams": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "DeletionAllowlistTeams"
},
"deletion_allowlist_usernames": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "DeletionAllowlistUsernames"
},
"dismiss_stale_approvals": {
"type": "boolean",
"x-go-name": "DismissStaleApprovals"
@@ -2929,6 +2943,14 @@
"type": "boolean",
"x-go-name": "EnableBypassAllowlist"
},
"enable_deletion": {
"type": "boolean",
"x-go-name": "EnableDeletion"
},
"enable_deletion_allowlist": {
"type": "boolean",
"x-go-name": "EnableDeletionAllowlist"
},
"enable_force_push": {
"type": "boolean",
"x-go-name": "EnableForcePush"
@@ -3817,6 +3839,20 @@
"type": "array",
"x-go-name": "BypassAllowlistUsernames"
},
"deletion_allowlist_teams": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "DeletionAllowlistTeams"
},
"deletion_allowlist_usernames": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "DeletionAllowlistUsernames"
},
"dismiss_stale_approvals": {
"type": "boolean",
"x-go-name": "DismissStaleApprovals"
@@ -3829,6 +3865,14 @@
"type": "boolean",
"x-go-name": "EnableBypassAllowlist"
},
"enable_deletion": {
"type": "boolean",
"x-go-name": "EnableDeletion"
},
"enable_deletion_allowlist": {
"type": "boolean",
"x-go-name": "EnableDeletionAllowlist"
},
"enable_force_push": {
"type": "boolean",
"x-go-name": "EnableForcePush"
@@ -5372,6 +5416,20 @@
"type": "array",
"x-go-name": "BypassAllowlistUsernames"
},
"deletion_allowlist_teams": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "DeletionAllowlistTeams"
},
"deletion_allowlist_usernames": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "DeletionAllowlistUsernames"
},
"dismiss_stale_approvals": {
"type": "boolean",
"x-go-name": "DismissStaleApprovals"
@@ -5384,6 +5442,14 @@
"type": "boolean",
"x-go-name": "EnableBypassAllowlist"
},
"enable_deletion": {
"type": "boolean",
"x-go-name": "EnableDeletion"
},
"enable_deletion_allowlist": {
"type": "boolean",
"x-go-name": "EnableDeletionAllowlist"
},
"enable_force_push": {
"type": "boolean",
"x-go-name": "EnableForcePush"
+66
View File
@@ -25987,6 +25987,20 @@
"format": "date-time",
"x-go-name": "Created"
},
"deletion_allowlist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "DeletionAllowlistTeams"
},
"deletion_allowlist_usernames": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "DeletionAllowlistUsernames"
},
"dismiss_stale_approvals": {
"type": "boolean",
"x-go-name": "DismissStaleApprovals"
@@ -25999,6 +26013,14 @@
"type": "boolean",
"x-go-name": "EnableBypassAllowlist"
},
"enable_deletion": {
"type": "boolean",
"x-go-name": "EnableDeletion"
},
"enable_deletion_allowlist": {
"type": "boolean",
"x-go-name": "EnableDeletionAllowlist"
},
"enable_force_push": {
"type": "boolean",
"x-go-name": "EnableForcePush"
@@ -26870,6 +26892,20 @@
},
"x-go-name": "BypassAllowlistUsernames"
},
"deletion_allowlist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "DeletionAllowlistTeams"
},
"deletion_allowlist_usernames": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "DeletionAllowlistUsernames"
},
"dismiss_stale_approvals": {
"type": "boolean",
"x-go-name": "DismissStaleApprovals"
@@ -26882,6 +26918,14 @@
"type": "boolean",
"x-go-name": "EnableBypassAllowlist"
},
"enable_deletion": {
"type": "boolean",
"x-go-name": "EnableDeletion"
},
"enable_deletion_allowlist": {
"type": "boolean",
"x-go-name": "EnableDeletionAllowlist"
},
"enable_force_push": {
"type": "boolean",
"x-go-name": "EnableForcePush"
@@ -28451,6 +28495,20 @@
},
"x-go-name": "BypassAllowlistUsernames"
},
"deletion_allowlist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "DeletionAllowlistTeams"
},
"deletion_allowlist_usernames": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "DeletionAllowlistUsernames"
},
"dismiss_stale_approvals": {
"type": "boolean",
"x-go-name": "DismissStaleApprovals"
@@ -28463,6 +28521,14 @@
"type": "boolean",
"x-go-name": "EnableBypassAllowlist"
},
"enable_deletion": {
"type": "boolean",
"x-go-name": "EnableDeletion"
},
"enable_deletion_allowlist": {
"type": "boolean",
"x-go-name": "EnableDeletionAllowlist"
},
"enable_force_push": {
"type": "boolean",
"x-go-name": "EnableForcePush"