Repository navigation
feat: add wt prune - #3
Merged
Merged
Conversation
A worktree outlives its branch. The merge lands, the branch goes away on
origin, and the directory stays until you name it in `wt delete`. `wt prune`
removes each worktree whose work is finished, and deletes its branch.
Two conditions make a worktree finished. The base branch contains the
branch and origin still has it, which is a merge commit whose branch is not
deleted yet. Or origin no longer has the upstream branch, which is a squash
merge or a rebase merge whose branch is deleted. The second condition is the
one that matters for a squash merge: such a merge writes a new commit, so
the branch is not an ancestor of the base branch, and the merge is complete
nonetheless.
Each condition needs proof that the branch reached origin. This is the rule
that makes the command safe. A new branch from `wt add` holds no commit of
its own, so the base branch contains it, and the test for a merge alone
cannot tell `wt add eng-1234` from finished work. The two conditions prove
the push in two ways, because a branch that origin lost leaves no ref
behind: a merge needs the remote-tracking ref, and a loss needs the upstream
branch that the config names. The config is what `git push -u` and `wt add
<remote branch>` write, and `git fetch --prune` keeps it after it removes
the ref. Reading the config and not `@{upstream}` is deliberate:
`@{upstream}` fails once the ref is absent, which is the condition the
command looks for.
The remote-tracking ref is the proof of a merge, and not the config, because
`git clone --bare` copies refs/heads. Each branch that origin had at the
time of `wt clone` is therefore a local branch with no upstream branch, and
the config would miss every one of them.
The command keeps a worktree that has a modified file or an untracked file,
which --force overrides. It keeps the worktree that you are in, which
--force does not override, because a removal would leave your shell in a
path that is absent. It says nothing about a worktree on the base branch or
on no branch, because there is no branch to test.
`git branch -d` refuses a branch that a squash merge left behind, so the
command deletes it with -D and writes the reason. --dry-run reads the list
and changes nothing, which shows that step before it runs.
The command also removes the administrative files of a worktree whose
directory you deleted by hand. It compares the list before with the list
after: `git worktree prune -v` writes its report to the error output, which
this tool reserves for messages in its own form, and git keeps a worktree
that a lock holds.
`verdict` takes the facts and returns the decision, so the rules above are
a table of tests and no test needs a repository.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A worktree outlives its branch. The merge lands, the branch goes away on origin, and the directory stays until you name it in
wt delete.wt pruneremoves each worktree whose work is finished, and deletes its branch.What counts as finished
The second condition is the one that matters for a squash merge: such a merge writes a new commit, so the branch is never an ancestor of the base branch, and the merge is complete nonetheless.
The safety rule
Each condition needs proof that the branch reached origin. This is the rule that makes the command safe, and it is the design problem the feature turns on.
A new branch from
wt addholds no commit of its own, so the base branch trivially contains it. The test for a merge alone therefore cannot tellwt add eng-1234from finished work, and would remove a worktree you had just made.The two conditions prove the push in two ways, because a branch that origin lost leaves no ref behind:
refs/remotes/origin/<branch>.git push -uandwt add <remote branch>write, and whatgit fetch --prunekeeps after it removes the ref.Reading the config and not
@{upstream}is deliberate:@{upstream}fails once the ref is absent, which is exactly the condition the command looks for. And the remote-tracking ref is the proof of a merge, and not the config, becausegit clone --barecopiesrefs/heads. Every branch that origin had at the time ofwt cloneis a local branch with no upstream branch, and the config would miss all of them.wt pruneremoves no branch that is only on your machine.What it keeps
--forceoverrides.--forcedoes not override: a removal would leave your shell in a path that is absent.git branch -drefuses a branch that a squash merge left behind, so the command deletes it with-Dand writes the reason.--dry-runreads the list and changes nothing, which shows that step before it runs.--no-fetchstays offline and uses the refs already present.The command also removes the administrative files of a worktree whose directory you deleted by hand. It compares the list before with the list after, because
git worktree prune -vwrites its report to the error output, which this tool reserves for messages in its own form, and because git keeps a worktree that a lock holds.Shape
verdicttakes the facts and returns the decision, so the rules above are a table of tests and no test needs a repository.factsis the only part that reads git.Checks
The four nix gates pass locally:
wt,wt-clippy,wt-test,wt-fmt. 34 tests, 13 of them new.Exercised against a real repository in the
.barelayout: merged and still on origin, merged and deleted, squash-merged, a dirty candidate, an active branch, a never-pushed branch, an unstarted branch, a detached HEAD, a worktree deleted by hand,--dry-run,--force,--no-fetch, running from inside a candidate, and an unreachable origin.