Skip to content

Commit e832767

Browse files
Merge pull request #170 from stuggi/delete_branch
Add tools/delete-branch.sh for org-wide branch cleanup
2 parents 6674da4 + 9c44107 commit e832767

1 file changed

Lines changed: 116 additions & 0 deletions

File tree

tools/delete-branch.sh

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/bin/bash
2+
# Delete a branch from all repos in the openstack-k8s-operators org.
3+
# Records the commit SHA of each branch before deletion for recovery.
4+
#
5+
# Usage:
6+
# ./delete-branch.sh <branch> # dry-run (default)
7+
# ./delete-branch.sh <branch> --execute # actually delete branches
8+
#
9+
# Prerequisites:
10+
# - gh CLI installed and authenticated (https://cli.github.com/)
11+
# - Authenticated user must have push access to the target repos
12+
#
13+
# To restore a deleted branch from the log file:
14+
# gh api --method POST "repos/openstack-k8s-operators/REPO/git/refs" \
15+
# -f ref="refs/heads/BRANCH" -f sha="COMMIT_SHA"
16+
17+
set -euo pipefail
18+
19+
if [[ $# -lt 1 ]]; then
20+
echo "Usage: $0 <branch-name> [--execute]"
21+
echo ""
22+
echo " <branch-name> Branch to delete from all org repos"
23+
echo " --execute Actually delete (default is dry-run)"
24+
exit 1
25+
fi
26+
27+
ORG="openstack-k8s-operators"
28+
BRANCH="$1"
29+
30+
if [[ $# -ge 2 && "$2" != "--execute" ]]; then
31+
echo "Unknown option: $2" >&2
32+
exit 1
33+
fi
34+
35+
EXECUTE=false
36+
[[ "${2:-}" == "--execute" ]] && EXECUTE=true
37+
LOGFILE="deleted-branch-${BRANCH}-$(date +%Y%m%d-%H%M%S).log"
38+
39+
# Protected branches — to delete these, edit this list in the script.
40+
PROTECTED_PATTERNS=("main" "master" "[0-9]*.0-fr*")
41+
42+
for PATTERN in "${PROTECTED_PATTERNS[@]}"; do
43+
# shellcheck disable=SC2053
44+
if [[ "$BRANCH" == $PATTERN ]]; then
45+
echo "ERROR: '$BRANCH' matches protected pattern '$PATTERN'. Refusing to delete."
46+
echo "If you really need to delete it, remove it from PROTECTED_PATTERNS in this script."
47+
exit 1
48+
fi
49+
done
50+
51+
if ! $EXECUTE; then
52+
echo "=== DRY RUN MODE (pass --execute to actually delete) ==="
53+
echo ""
54+
else
55+
read -rp "Delete branch '$BRANCH' from ALL repos in $ORG? [y/N] " CONFIRM
56+
if [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]]; then
57+
echo "Aborted."
58+
exit 0
59+
fi
60+
fi
61+
62+
echo "Checking all repos in $ORG for branch '$BRANCH'..."
63+
echo "# branch deletion log - $(date -Iseconds)" > "$LOGFILE"
64+
echo "# repo,branch,commit_sha" >> "$LOGFILE"
65+
66+
FOUND=0
67+
DELETED=0
68+
ERRORS=0
69+
70+
REPOS=$(gh repo list "$ORG" --no-archived --limit 500 --json name --jq '.[].name' | sort)
71+
TOTAL=$(echo "$REPOS" | wc -w)
72+
CURRENT=0
73+
74+
for REPO in $REPOS; do
75+
CURRENT=$((CURRENT + 1))
76+
printf "\r Checking repo %d/%d..." "$CURRENT" "$TOTAL"
77+
78+
if ! SHA=$(gh api "repos/$ORG/$REPO/branches/$BRANCH" --jq '.commit.sha' 2>/dev/null); then
79+
continue
80+
fi
81+
82+
if [[ -z "$SHA" || "$SHA" == "null" ]]; then
83+
continue
84+
fi
85+
86+
printf "\r\033[K"
87+
88+
FOUND=$((FOUND + 1))
89+
echo "$ORG/$REPO,$BRANCH,$SHA" >> "$LOGFILE"
90+
91+
if $EXECUTE; then
92+
if ERR=$(gh api --method DELETE "repos/$ORG/$REPO/git/refs/heads/$BRANCH" 2>&1); then
93+
echo " DELETED $ORG/$REPO (was $SHA)"
94+
DELETED=$((DELETED + 1))
95+
else
96+
echo " ERROR $ORG/$REPO (failed to delete, was $SHA): $ERR"
97+
ERRORS=$((ERRORS + 1))
98+
fi
99+
else
100+
echo " would delete $ORG/$REPO/$BRANCH ($SHA)"
101+
fi
102+
done
103+
printf "\r\033[K"
104+
105+
echo ""
106+
echo "Found: $FOUND repos with '$BRANCH' branch"
107+
if $EXECUTE; then
108+
echo "Deleted: $DELETED, Errors: $ERRORS"
109+
else
110+
echo "Dry run — no branches deleted. Run with --execute to delete."
111+
fi
112+
echo "Commit log: $LOGFILE"
113+
114+
if [[ "$ERRORS" -gt 0 ]]; then
115+
exit 1
116+
fi

0 commit comments

Comments
 (0)