Skip to content

Fix the assumed main base in git bclean and bdone - #84

Open
mattmenefee wants to merge 1 commit into
mainfrom
fix-bclean-base-branch
Open

Fix the assumed main base in git bclean and bdone#84
mattmenefee wants to merge 1 commit into
mainfrom
fix-bclean-base-branch

Conversation

@mattmenefee

Copy link
Copy Markdown
Owner

Fixes #83.

git cleanup was fixed in #82, but bclean still carried the identical bug and bdone avoided it only by accident. Run bare in a master-based repository, git bclean force-deleted every local branch — the same incident #82 describes, one alias over.

Summary

  • Add a git base alias that resolves the repository's base branch from origin/HEAD, falling back to whichever of main or master exists locally or on origin, and fails loudly when none does
  • Give cleanup an optional base argument and have it consult git base when given none; bclean becomes a thin wrapper around cleanup and bdone resolves its checkout target the same way, so the dangerous comparison has one implementation rather than three
  • Pass fully qualified refs to git cherry — a tag sharing the base branch's name shadowed the branch and made unmerged work compare clean under a bare main, which the exit-code guard could not catch because the command succeeds
  • Skip branches carrying merge commits, since git cherry ignores merges and a conflict resolution living only inside one would read as merged and be deleted
  • Never delete the repository's own base branch, alongside the existing current-branch protection — git cleanup develop previously ate a main that develop contained
  • Iterate with git for-each-ref refs/heads/, dropping the (HEAD detached at …) pseudo-entry that produced a confusing skip message
  • Expand the comments to record why each guard is load-bearing, so none of them gets "simplified" back into the original bug

git bclean "" is fixed as a side effect of the rewrite: ${1-main} substituted only when the argument was unset, so an explicit empty string became a base named "".

Test plan

Verified with a harness that extracts only the [alias] section into a throwaway GIT_CONFIG_GLOBAL (with GIT_CONFIG_SYSTEM=/dev/null and a scratch HOME) and exercises each scenario against real bare-origin clones. 23 assertions, all passing.

  • git bclean in a master-based repository resolves master and keeps unmerged branches (the filed bug)
  • git bclean "" falls through to resolution instead of treating the empty string as a base
  • git bclean typo-brnch aborts with exit 1 and deletes nothing
  • A tag named main no longer masks unmerged work, and no longer produces a spurious base-branch error
  • git bdone resolves master in a master-based repository and checks it out
  • git bdone develop honors the explicit base and leaves main intact
  • Regressions from Resolve the base branch in git cleanup instead of assuming main #82 all still pass: normal main-based repo, no origin remote, base only on origin, no base resolvable (aborts, exit 1), detached HEAD, current branch never deleted, worktree-held branch survives while the loop continues
  • A branch whose only unique content is a conflict-resolution merge is skipped rather than deleted
  • A refs/heads/-D branch reaches no git branch -D unquoted — no option injection
  • git base prints the base on stdout and fails silently there when none resolves
  • All four alias bodies parse under dash -n and sh -n; shellcheck -s sh is clean apart from two SC2120 false positives (it does not know git appends "$@" to ! aliases)

Not covered: the exit status on the delete path is still the last loop iteration's, since the while remains the last stage of a pipeline. It is unchanged by this PR and noted in #83 as non-blocking.

`git bclean` still compared every branch against a hardcoded `main` and
read `git cherry`'s empty output as "already merged" — the defect fixed
in `cleanup` but left in place here. Run bare in a master-based
repository it force-deleted every local branch, unmerged work included.
`git bclean typo` and `git bclean ""` failed the same way, the latter
because `${1-main}` substitutes only when the argument is unset, not
when it is empty. `bdone` escaped the fault only because its
`git checkout main` failed first and short-circuited the chain.

Base resolution now lives in a single `git base` alias that reads
origin/HEAD and falls back to whichever of main or master exists locally
or on origin. `cleanup` takes an optional base and consults `git base`
when given none, `bclean` is a thin wrapper around it, and `bdone`
resolves the branch it checks out the same way, so the dangerous part
has one implementation rather than three.

Two further ways to lose work are closed in the same pass. `git cherry`
is handed fully qualified refs, since a tag sharing the base branch's
name shadows the branch and makes unmerged work compare clean under a
bare `main`. Branches carrying merge commits are skipped, because
`git cherry` ignores merges and a conflict resolution living only inside
one would read as merged. The repository's own base branch joins the
current branch as never deletable, so cleaning up against a longer-lived
base no longer eats a main that it contains.

Fixes #83.
@mattmenefee mattmenefee self-assigned this Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Resolve the base branch in git bclean and bdone instead of assuming main

1 participant