返回 CodeWhale
branch-hygiene.test.sh
根目录 / scripts / release / branch-hygiene.test.sh
1 #!/usr/bin/env bash
2 # Hermetic test for scripts/release/branch-hygiene.sh.
3 #
4 # Builds a throwaway git repo with a known branch topology and asserts that the
5 # hygiene script:
6 # * marks branches whose tip is contained in main/the release branch as
7 # "safe to delete",
8 # * keeps a branch with unique commits from a non-Hunter contributor as
9 # contributor work (never a safe delete),
10 # * flags a branch with only unmerged maintainer commits as needs-review,
11 # * detects a working checkout parked on an already-merged scratch branch,
12 # * honors --remote when the canonical release refs live outside origin,
13 # * actually deletes only safe branches under --prune --yes and never the
14 # contributor branch.
15 #
16 # Run: bash scripts/release/branch-hygiene.test.sh
17 set -euo pipefail
18
19 script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
20 hygiene="${script_dir}/branch-hygiene.sh"
21
22 work="$(mktemp -d)"
23 cleanup() { rm -rf "${work}"; }
24 trap cleanup EXIT
25
26 fail=0
27 check() {
28 # check <description> <expected-substring> <<<haystack-on-stdin>
29 local desc="$1" needle="$2" hay
30 hay="$(cat)"
31 if grep -qF -- "${needle}" <<<"${hay}"; then
32 echo "ok - ${desc}"
33 else
34 echo "FAIL - ${desc}"
35 echo " expected to find: ${needle}"
36 echo "------ output ------"
37 echo "${hay}"
38 echo "--------------------"
39 fail=1
40 fi
41 }
42 refute() {
43 # refute <description> <forbidden-substring> <<<haystack>
44 local desc="$1" needle="$2" hay
45 hay="$(cat)"
46 if grep -qF -- "${needle}" <<<"${hay}"; then
47 echo "FAIL - ${desc}"
48 echo " did NOT expect to find: ${needle}"
49 fail=1
50 else
51 echo "ok - ${desc}"
52 fi
53 }
54
55 # The script resolves its repo root as <script>/../.. and operates on *that*
56 # repo, not the current directory. So copy it into the throwaway repo at the
57 # same relative path and invoke the copy; that makes the temp repo its root.
58 mkdir -p "${work}/scripts/release"
59 cp "${hygiene}" "${work}/scripts/release/branch-hygiene.sh"
60 hygiene="${work}/scripts/release/branch-hygiene.sh"
61
62 cd "${work}"
63 export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null
64 git init -q -b main .
65 git config user.name "Hunter Bown"
66 git config user.email "hmbown@gmail.com"
67 # Mirror the real repo's .mailmap canonicalization for Hunter.
68 cat >.mailmap <<'EOF'
69 Hunter Bown <hmbown@gmail.com> Claude <noreply@anthropic.com>
70 EOF
71
72 commit() {
73 # commit <file> <content> <author-name> <author-email>
74 echo "$2" >"$1"
75 git add -A
76 git -c user.name="$3" -c user.email="$4" \
77 commit -q --author="$3 <$4>" -m "touch $1"
78 }
79
80 H_NAME="Hunter Bown"; H_EMAIL="hmbown@gmail.com"
81
82 # main: base commit by Hunter.
83 commit base "v0" "${H_NAME}" "${H_EMAIL}"
84
85 # release branch sits at main for this test.
86 git branch codex/v0.8.61 main
87
88 # merged-scratch: branched and merged back into main (tip contained in main).
89 git switch -q -c merged-scratch
90 commit feat-a "a" "${H_NAME}" "${H_EMAIL}"
91 git switch -q main
92 git merge -q --no-ff merged-scratch -m "merge merged-scratch"
93 # fast-forward the release branch to include the merge too.
94 git branch -f codex/v0.8.61 main
95
96 # contributor-branch: unique commit by a NON-Hunter contributor (must be kept).
97 git switch -q -c contributor-branch main
98 commit feat-contrib "c" "Jane Contributor" "jane@example.com"
99
100 # maintainer-scratch: unique commit by Hunter, not merged (needs review).
101 git switch -q -c maintainer-scratch main
102 commit feat-h "h" "${H_NAME}" "${H_EMAIL}"
103
104 # bot-folded: unique commit by Claude, which .mailmap folds into Hunter, so it
105 # must be treated as maintainer-only (needs review, NOT contributor work).
106 git switch -q -c bot-folded main
107 commit feat-bot "b" "Claude" "noreply@anthropic.com"
108
109 # Park the working checkout on an already-merged scratch branch to exercise the
110 # "parked checkout" warning. Point HEAD at the merged-scratch tip but on a
111 # fresh non-release branch name.
112 git switch -q -c renovate/parked merged-scratch
113
114 # --- Dry-run report ----------------------------------------------------------
115 report="$(bash "${hygiene}" --release-branch codex/v0.8.61 --main-ref main 2>&1)"
116
117 check "merged scratch branch is a safe delete" \
118 "local : merged-scratch" <<<"${report}"
119 check "contributor branch is kept as contributor work" \
120 "[local] contributor-branch:" <<<"${report}"
121 check "contributor branch names the contributor author" \
122 "Jane Contributor" <<<"${report}"
123 check "contributor branch reason is KEEP" \
124 "KEEP - unique contributor work" <<<"${report}"
125 check "maintainer-only scratch is flagged for review" \
126 "[local] maintainer-scratch:" <<<"${report}"
127 check "maintainer-only scratch reason is REVIEW" \
128 "REVIEW -" <<<"${report}"
129 check "mailmap-folded bot commit is treated as maintainer (review, not keep)" \
130 "[local] bot-folded:" <<<"${report}"
131 check "parked working checkout warning fires" \
132 "working checkout is parked on 'renovate/parked'" <<<"${report}"
133
134 # The contributor branch must NEVER appear in the safe-delete list.
135 safe_section="$(awk '/^-- Safe to delete/{f=1;next} /^-- Keep/{f=0} f' <<<"${report}")"
136 refute "contributor branch is not in the safe-delete list" \
137 "contributor-branch" <<<"${safe_section}"
138 refute "maintainer-only scratch is not in the safe-delete list" \
139 "maintainer-scratch" <<<"${safe_section}"
140
141 # --- Prune (local) -----------------------------------------------------------
142 prune_out="$(bash "${hygiene}" --release-branch codex/v0.8.61 --main-ref main --prune --yes 2>&1)"
143 check "prune deletes the merged scratch branch" \
144 "deleted local merged-scratch" <<<"${prune_out}"
145
146 # After prune: contributor + maintainer + bot branches still exist; merged one
147 # is gone.
148 remaining="$(git for-each-ref --format='%(refname:short)' refs/heads/)"
149 check "contributor branch survives prune" "contributor-branch" <<<"${remaining}"
150 check "maintainer-only scratch survives prune" "maintainer-scratch" <<<"${remaining}"
151 refute "merged scratch branch is gone after prune" "merged-scratch" <<<"${remaining}"
152
153 # --- Custom remote name ------------------------------------------------------
154 git switch -q main
155 git switch -q -c remote-main-tip
156 commit remote-main-only "remote" "${H_NAME}" "${H_EMAIL}"
157 git branch "upstream/main" main^1
158 git update-ref "refs/remotes/upstream/main" "$(git rev-parse remote-main-tip)"
159 git update-ref "refs/remotes/upstream/codex/v0.8.61" "$(git rev-parse codex/v0.8.61)"
160 git update-ref "refs/remotes/upstream/merged-remote" "$(git rev-parse main)"
161 git update-ref "refs/remotes/upstream/remote-main-only" "$(git rev-parse remote-main-tip)"
162 upstream_report="$(bash "${hygiene}" --remote upstream --release-branch codex/v0.8.61 2>&1)"
163 check "custom remote release tip is reported" \
164 "upstream" <<<"${upstream_report}"
165 check "custom remote default main ref is fully qualified" \
166 "Main ref : refs/remotes/upstream/main" <<<"${upstream_report}"
167 check "custom remote safe-delete command uses the selected remote" \
168 "remote: upstream/merged-remote" <<<"${upstream_report}"
169 check "custom remote main ref is not confused with a same-named local branch" \
170 "remote: upstream/remote-main-only" <<<"${upstream_report}"
171 refute "custom remote report does not hard-code origin in safe deletes" \
172 "remote: origin/merged-remote" <<<"${upstream_report}"
173
174 # --- State inconsistency: diverged local vs remote release branch ------------
175 git switch -q main
176 # Simulate a remote release branch that has diverged from local.
177 git update-ref "refs/remotes/origin/codex/v0.8.61" "$(git rev-parse maintainer-scratch)"
178 git branch -f codex/v0.8.61 bot-folded
179 set +e
180 diverged_out="$(bash "${hygiene}" --release-branch codex/v0.8.61 --main-ref main 2>&1)"
181 diverged_ec=$?
182 set -e
183 check "divergence between local and remote release branch is reported" \
184 "have DIVERGED" <<<"${diverged_out}"
185 if [[ "${diverged_ec}" -ne 1 ]]; then
186 echo "FAIL - diverged state should exit 1, got ${diverged_ec}"
187 fail=1
188 else
189 echo "ok - diverged state exits non-zero"
190 fi
191
192 echo
193 if [[ "${fail}" -eq 0 ]]; then
194 echo "branch-hygiene.test.sh: all checks passed"
195 else
196 echo "branch-hygiene.test.sh: FAILURES above"
197 fi
198 exit "${fail}"
199
199 lines BASH