Expand description
The removal of a clean subvolume worktree by one ioctl, and the plan of the git worktree remove it serves.
What:
The removal of a clean linked worktree that is a btrfs subvolume, by one subvolume destroy call and the
deletion of its administrative directory, in place of Git’s file-by-file removal.
Why:
Git unlinks every file of the worktree (0.7 s without ignored state, 3.6 s with a large ignored tree in the
measurements of doc/planning/cli-git-btrfs-worktree.md), and the subvolume call takes milliseconds. The call
cannot be undone, so the wrapper takes it only when the same conditions hold that make Git remove without
--force: the worktree is registered in the repository the command runs in, under the name Git would pick, is not
locked, is clean (worktree_clean.rs), and holds no submodule. The mount must also allow the call to an
unprivileged user (user_subvol_rm_allowed). Every other case, and every call that the kernel refuses, is left to
Git, which then words the refusal itself.
The check and the destroy are not one step, as Git’s check and its deletion are not. Three things keep the
window small and its worst case no worse than Git’s: the directories are held open from before the proof of
cleanliness to the destroy and the destroy names the subvolume by id (or by name below the held parent), the lock,
the identity of the worktree directory and of the registration are looked at again immediately before the destroy,
and the registration is emptied through its held descriptor, never by a path that could name another directory by
then. What remains is a write or a lock made by another process in the milliseconds between the last look and the
destroy; Git has the same window for its whole deletion, which takes seconds (SPEC.md).
In TS you’d write (pseudocode):
const outcome = await removeSubvolumeWorktree(request); // 'left-to-git': run git worktree removeStructs§
- Held
- What: The directories one removal holds open from before the proof of cleanliness to the destroy, and the identities they had when they were looked at. Why: A descriptor names the object it was opened on, whatever happens to the path afterwards; the identities let the last look before the destroy tell a swapped name from the held object.
- Linux
Operations - The kernel and Git, as they are.
- Remove
Request - What: What one removal is asked to do and in which repository. Why: The repository that was invoked decides which registrations count; the worktree’s own pointers never do.
Enums§
- Remove
Outcome - What: How one attempt to remove a worktree with a subvolume destroy ended. Why: Three endings need three different next steps: Git removes it (nothing changed), the work is done, or the files are gone and the registration is not, which only the person can finish and which must never reach Git.
Constants§
- DESTROY_
NOTICE - What the command says on standard error whenever it destroys a subvolume.
Traits§
- Remove
Operations - What:
The operations of one removal that touch the kernel or start Git, behind one trait.
&selfborrows the implementation read-only;dyn RemoveOperationsis how a caller holds “any implementation” (a TS interface). Why: The ordering of the checks is the same on any filesystem; a stand-in lets the controls run it on a plain directory tree, and swap objects between two steps to prove the identity checks.
Functions§
- remove_
subvolume_ worktree - What:
Remove the worktree if every condition holds, with the kernel and Git as they are.
Why:
See
remove_with. - remove_
with - What:
Remove the worktree if every condition holds; report how it ended.
Why:
LeftToGitmeans Git has to do the removal, and nothing was changed.