Skip to content

Retire Branch

Retire Branch #7

Workflow file for this run

name: Retire Branch
# Removes Dependabot entries for a branch that is no longer maintained and
# locks the branch so no further commits can be pushed to it.
#
# Requires a token with:
# contents:write — to update dependabot.yml on the default branch
# administration:write — to set branch protection / lock the branch
on:
workflow_dispatch:
inputs:
repo:
description: 'Repository (format: org/repo-name)'
required: true
type: string
branch:
description: 'Branch to retire'
required: true
type: string
token:
description: 'GitHub token with contents:write and administration:write on the target repository. Falls back to GH_ACTIONS_REPO_TOKEN.'
required: false
type: string
default: ''
workflow_call:
inputs:
repo:
description: 'Repository (format: org/repo-name)'
required: true
type: string
branch:
description: 'Branch to retire'
required: true
type: string
secrets:
token:
description: 'GitHub token with contents:write and administration:write on the target repository. Falls back to GH_ACTIONS_REPO_TOKEN.'
required: false
permissions:
contents: read
jobs:
retire:
runs-on: ubuntu-latest
steps:
# -----------------------------------------------------------------------
# 1. Remove Dependabot entries targeting the retiring branch.
# Dependabot configuration lives on the default branch of the repo.
# -----------------------------------------------------------------------
- name: Remove Dependabot entries for retiring branch
shell: bash
env:
GH_TOKEN: ${{ inputs.token || secrets.token || secrets.GH_ACTIONS_REPO_TOKEN }}
run: |
REPO="${{ inputs.repo }}"
BRANCH="${{ inputs.branch }}"
echo "=== Remove Dependabot Entries ==="
echo "Repository: $REPO"
echo "Branch: $BRANCH"
echo ""
# Clone the default branch (where dependabot.yml lives).
git clone --depth 1 \
"https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" _retire_repo
cd _retire_repo
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
DEPBOT_FILE=""
if [[ -f ".github/dependabot.yml" ]]; then
DEPBOT_FILE=".github/dependabot.yml"
elif [[ -f ".github/dependabot.yaml" ]]; then
DEPBOT_FILE=".github/dependabot.yaml"
fi
if [[ -z "$DEPBOT_FILE" ]]; then
echo "No dependabot.yml found on the default branch — nothing to remove."
else
echo "Found $DEPBOT_FILE"
BEFORE=$(yq e '.updates | length' "$DEPBOT_FILE")
yq e "del(.updates[] | select(.target-branch == \"${BRANCH}\"))" -i "$DEPBOT_FILE"
AFTER=$(yq e '.updates | length' "$DEPBOT_FILE")
REMOVED=$((BEFORE - AFTER))
if [[ $REMOVED -eq 0 ]]; then
echo "No Dependabot entries found for branch '${BRANCH}' — nothing to remove."
else
echo "Removed ${REMOVED} Dependabot entr$([ $REMOVED -eq 1 ] && echo 'y' || echo 'ies') targeting '${BRANCH}'."
if [[ $AFTER -eq 0 ]]; then
echo "No entries remain — removing ${DEPBOT_FILE}."
rm "$DEPBOT_FILE"
fi
git add .
git commit -m "Remove Dependabot entries for retired branch ${BRANCH}"
git push origin HEAD
echo "Dependabot configuration updated."
fi
fi
# -----------------------------------------------------------------------
# 2. Lock the branch so no further commits can be pushed to it.
# Existing branch protection rules are preserved; only lock_branch
# is added/set to true.
# -----------------------------------------------------------------------
- name: Lock branch
shell: bash
env:
GH_TOKEN: ${{ inputs.token || secrets.token || secrets.GH_ACTIONS_REPO_TOKEN }}
# Python script to merge lock_branch=true into existing branch protection settings.
# Stored here as an env var to avoid unindented heredoc content breaking YAML parsing.
LOCK_SCRIPT: |
import json
with open('/tmp/existing_protection.json') as f:
p = json.load(f)
body = {}
rsc = p.get('required_status_checks')
if rsc:
entry = {'strict': rsc.get('strict', False)}
if 'checks' in rsc:
entry['checks'] = rsc['checks']
else:
entry['contexts'] = rsc.get('contexts', [])
body['required_status_checks'] = entry
else:
body['required_status_checks'] = None
ea = p.get('enforce_admins', {})
body['enforce_admins'] = ea.get('enabled', False) if isinstance(ea, dict) else bool(ea)
rprr = p.get('required_pull_request_reviews')
if rprr:
rev = {
'dismiss_stale_reviews': rprr.get('dismiss_stale_reviews', False),
'require_code_owner_reviews': rprr.get('require_code_owner_reviews', False),
'required_approving_review_count': rprr.get('required_approving_review_count', 0),
}
if rprr.get('dismissal_restrictions'):
dr = rprr['dismissal_restrictions']
rev['dismissal_restrictions'] = {
'users': [u['login'] for u in dr.get('users', [])],
'teams': [t['slug'] for t in dr.get('teams', [])],
}
if rprr.get('bypass_pull_request_allowances'):
bpa = rprr['bypass_pull_request_allowances']
rev['bypass_pull_request_allowances'] = {
'users': [u['login'] for u in bpa.get('users', [])],
'teams': [t['slug'] for t in bpa.get('teams', [])],
'apps': [a['slug'] for a in bpa.get('apps', [])],
}
body['required_pull_request_reviews'] = rev
else:
body['required_pull_request_reviews'] = None
r = p.get('restrictions')
body['restrictions'] = {
'users': [u['login'] for u in r.get('users', [])],
'teams': [t['slug'] for t in r.get('teams', [])],
'apps': [a['slug'] for a in r.get('apps', [])],
} if r else None
for flag in (
'required_linear_history',
'allow_force_pushes',
'allow_deletions',
'block_creations',
'required_conversation_resolution',
'allow_fork_syncing',
):
val = p.get(flag, {})
body[flag] = val.get('enabled', False) if isinstance(val, dict) else bool(val)
body['lock_branch'] = True
print(json.dumps(body))
run: |
REPO="${{ inputs.repo }}"
BRANCH="${{ inputs.branch }}"
echo "=== Lock Branch ==="
echo "Repository: $REPO"
echo "Branch: $BRANCH"
echo ""
# Fetch any existing branch protection settings.
EXISTING=$(gh api "repos/${REPO}/branches/${BRANCH}/protection" 2>/dev/null) || EXISTING=''
if [[ -z "$EXISTING" ]]; then
echo "No existing branch protection. Creating minimal protection with lock_branch=true."
PAYLOAD='{"required_status_checks":null,"enforce_admins":false,"required_pull_request_reviews":null,"restrictions":null,"lock_branch":true}'
else
echo "Existing protection found. Merging lock_branch=true into current settings."
# Write to a temp file so Python can consume it cleanly.
echo "$EXISTING" > /tmp/existing_protection.json
PAYLOAD=$(python3 -c "$LOCK_SCRIPT")
fi
echo "$PAYLOAD" \
| gh api "repos/${REPO}/branches/${BRANCH}/protection" \
--method PUT \
--header "Content-Type: application/json" \
--input -
echo ""
echo "Branch '${BRANCH}' is now locked — no further commits can be pushed to it."