Skip to main content

Module worktree

Module worktree 

Source
Expand description

Worktree commands: add, remove, clean, convert, trash, snapshots and the placement ladder. What: Worktree commands: add, remove, clean, convert, trash, snapshots and the placement ladder. Why: Worktree lifecycle code shares plans and journals.

In TS you’d write (pseudocode):

// worktree/index.ts: one `export * from './file'` per module below.

Modules§

add_timing
The record of where one wrapped git worktree add spent its time, step by step, appended beside the commit timing record. What: The record of where one wrapped git worktree add spent its time, step by step, appended to a size-capped file in the common Git directory beside the commit timing record. Why: An add that took thirteen seconds could not be split afterwards without a strace that distorted it. Each step is a stretch of the command (a walk of the source, a Git run, the snapshot call, a durable write), timed on the monotonic clock; a step inside another is taken out of the outer one, so the steps are disjoint and what none covers is reported as otherMs. The record goes to the file only, in one append when the command ends, with no flush to disk, and it never changes what the add does.
checkout_workers
Whether the checkout of a new worktree may be given Git’s parallel checkout, and the option that does it. What: Whether the checkout of a new worktree may be given Git’s parallel checkout, and the option that does it. Why: Git’s checkout.workers defaults to one worker, and a checkout of twenty thousand files measured several times faster with checkout.workers=0 (one worker per logical CPU). The owner decided on 2026-11-03 “Pass only if unset”: a value the person set anywhere Git reads (a configuration file of any scope, GIT_CONFIG_*, GIT_CONFIG_PARAMETERS, a -c or --config-env they typed) is theirs and stands alone; only where nothing sets the key does the wrapper put -c checkout.workers=0 before the subcommand of its own checkout run. The files are read through the one configuration read the wrapper already makes, so no extra Git start is added.
inner_git_words
Where the words of a Git run the wrapper or the service started on its own go, now that none carries --quiet. What: Where the words of a Git run the wrapper or the service started on its own go, now that none of those runs carries --quiet or -q: one journal entry, from the service for a run it made and from the wrapper for a run it made while carrying out a person’s command. Why: The owner’s rule is “No the wrapper should never append –quiet. Letting native git emit whatever it wants to is expected”, and for the wrapper’s and the service’s own runs the answer was “Drop it everywhere (Recommended)”. Every such run is captured (run_git pipes both streams into GitOutput), so Git’s words would be discarded with the capture; this keeps them where a failure of the same run is already recorded (journalctl). A hook’s standard output and the service’s protocol replies are never the destination, and no other option or redirect hides the words in the meantime.
shell_quote
Quoting of bytes printed inside a shell command for a person to run. What: Quoting of bytes that are printed inside a shell command for a person to copy and run. Why: A path or a kernel argument can hold spaces, quotes, ;, $(...) or bytes that are not UTF-8. Printed bare, a pasted command runs whatever follows the separator or substitution, or passes the wrong arguments. A value of safe characters is printed as it is; any other value is wrapped in single quotes (each single quote inside closed, escaped and opened again); a value with a control character or a byte that is not UTF-8 is spelled in the ANSI-C form $'...' of bash, zsh and ksh with those bytes written as \xHH escapes, so the printed text holds no control character and no line break. The same rules as package/wg/cli-quicker/src/shell-quote.ts.
worktree_add_binding
The binding of a git worktree add to the directories Git made for it, its record, and the check that the binding holds. What: The binding of one git worktree add to the objects real Git made for it (the destination directory and the administrative directory that registers it), the record that carries the binding, and the check that the binding still holds. Why: The wrapper completes an add in place, through the bound objects, and never deletes or unregisters a worktree: a path can be changed by another process between any check and any act, so no pathname or identity recorded earlier proves the right to remove something later. The binding is what a fast rung and the fallback checkout verify before they write into the worktree, and what the report of an interrupted add names. It is captured right after Git returned, and an add whose pair cannot be proven is left exactly as Git made it.
worktree_add_plan
The plan for one git worktree add that the cheap-worktree ladder serves: its destination and insertion point. What: The plan for one git worktree add the cheap-worktree ladder may serve: where the new worktree goes and where in the argument list a rung inserts --no-checkout, or nothing when the invocation uses a form the ladder does not know. Why: The ladder runs real Git once with the person’s arguments, so it only needs the destination path and a promise that every other word means what the table says. An option the table does not list, a bundled short flag, a global option that changes how Git reads configuration, or a destination whose parent does not exist makes the whole invocation a plain Git run, which is always correct.
worktree_attributes
What: Git’s attribute-file lexer and pattern matcher over raw bytes, as far as a refusal needs them: which lines of an attributes file set an attribute that can change a file’s bytes on the way into the index, and whether the pattern of such a line may apply to a path. Why: Paths and patterns are byte strings to Git, and attr.c splits a line on the four blank bytes space, tab, carriage return and line feed only, ends a line’s text at a NUL, and skips a UTF-8 byte order mark on the first line of a worktree file. Any other reading of the same bytes (Unicode whitespace, lossy decoding, Unicode case folding) can miss a line Git applies, and a miss accepts a file whose bytes Git would convert. Every spelling the matcher does not model answers that the line may apply, so the error is always a refusal.
worktree_clean
Whether a worktree is clean enough to copy from or to remove without asking Git, proven in process. What: Whether a worktree is clean enough to be copied from, or removed without asking Git: no operation in progress, an index equal to the tree of HEAD, every tracked file unchanged by its recorded size, times and inode, and no untracked file that Git does not ignore. Why: A snapshot or a clone carries the bytes of the source’s files, so any change the source has not committed would reach the new worktree, and a removal by the subvolume ioctl cannot be undone. The tracked-file checks read the index in process (no Git process) with the same comparison Git’s fast path makes, and refuse every shape they cannot prove: a split or sparse index, a skip-worktree or assume-valid entry, a submodule, an entry as new as the index file (Git re-hashes those, so their stat fields prove nothing). The one question the index cannot answer, whether an untracked file is ignored, goes to one git ls-files run. Every refusal returns its reason in words for the log.
worktree_clean_content
What: The decision, by content and without a Git process, whether a tracked file that its index entry’s stat data cannot vouch for (a racily clean entry, a stale stat) still equals the committed blob: hash the file as a blob and compare it with the entry’s object name, refusing where a content conversion could make the two differ. Why: Git settles those entries by hashing the file; stat data alone cannot tell a modified file from a copied or cloned one that kept the index’s old inode and times. The hash of the raw bytes is conclusive only when Git’s own conversion on the way into the index (filter, ident, working-tree-encoding, or a line-ending conversion that strips a carriage return) leaves the bytes alone, so a path such an attribute applies to, and a file with a carriage return, are refused with that reason instead of guessed. The file is read through a handle opened below the worktree root without following a link, its type and mode are judged on that handle, and its state is compared before and after the read, so the bytes that were hashed are the bytes of the one file that was judged.
worktree_clone
The clone rung: tracked files cloned into the new worktree, the source’s index installed with fresh stat fields. What: Rung 2 of the cheap-worktree ladder: each tracked file of the clean source is cloned (FICLONE) into the new worktree with the source’s mode and times, and the source’s index is installed with fresh stat fields. Why: A clone shares every data block with the source on btrfs, on XFS with reflink and inside an overlay, which the snapshot rung cannot reach (overlayfs and XFS have reflink but no subvolumes). It takes no less time than Git’s checkout on btrfs (doc/planning/cli-git-btrfs-worktree.md, option B), so it saves disk, not time, and it is the accelerated rung where a snapshot is impossible. The wrapper’s own index writer is rewrite_index_stat_with: with each file’s size and modification time kept, only the inode, change time and device of every entry change. The first failure ends the rung with its reason, the entries this run created stay (a name cannot be checked and unlinked in one step), and Git’s own checkout completes the worktree and overwrites the tracked ones. Every source file is opened below a held handle of the source root without following a link, and its descriptor’s metadata, not a path, is compared with the frozen index entry before and after the clone; every destination entry is created below a held handle of the destination. A directory swapped for a link while the run goes on is an error, never a detour.
worktree_completion
Bringing added paths’ worktree copies to their landed bytes. What: Bringing each added path’s worktree copy to the landed bytes after a commit landed, with descriptor-bound checks that never overwrite a concurrent edit. Why: An added path’s committed bytes differ from its worktree copy (a policy corrected it); the copy is replaced only while it still holds the original bytes, through a same-directory temporary file. A copy holding neither is kept and reported, since the commit already landed (src/policy-engine/commit-transaction-added-paths.ts, -worktree-check.ts, -worktree-replace.ts).
worktree_convert
Converting a linked worktree that is an ordinary directory into a btrfs subvolume at the same path, resumably. What: Convert one registered linked worktree that is an ordinary directory into a btrfs subvolume at the same path, resumably: quiesce it, copy it with cloned data into <path>.cli-git-converting, prove the copy equals the original, swap the two with one atomic exchange, refresh the index stat fields, and remove the old tree only after the swap is verified. Why: A snapshot of the main checkout shows a nested worktree that is a subvolume as an empty directory, and one that is an ordinary directory as a full copy that must then be removed (0.5 s each). Converting each ordinary worktree once makes every later snapshot skip it for nothing. The registration is untouched: the path and the bytes of the .git file do not change, so the administrative directory’s back-pointer stays valid. A journal in the administrative directory records the phase, so a rerun completes the conversion in place; nothing is deleted that the journal does not prove this command created.
worktree_convert_arguments
The grammar of git cli-git convert-worktree-to-subvolume. What: The grammar of git cli-git convert-worktree-to-subvolume. Why: The grammar is parsed on every platform by the management namespace; only the run needs Linux and btrfs.
worktree_convert_command
git cli-git convert-worktree-to-subvolume: the worktrees to cover and the report. What: git cli-git convert-worktree-to-subvolume: parse the options, find the worktrees to convert, run the conversion on each, and turn the results into lines and an exit status. Why: A conversion takes seconds to minutes per worktree and is bound by the filesystem’s metadata work, not by any one worktree’s state, so the worktrees are converted side by side within a budget the host allows (parallel_budget.rs); progress lines go to standard error as they happen for the first unfinished worktree, and every worktree’s lines come in input order (parallel_runner.rs). Exit status 0 means every worktree is converted, already a subvolume, previewed, or skipped under --all; 1 means a named worktree was refused before anything changed; 2 means a step failed after the journal began, with the way forward. One worktree’s failure never stops another: each has its own journal and claim.
worktree_convert_compare
The read-only comparison of the old tree of a conversion with the installed one. What: A read-only comparison of the old tree of a conversion with the installed one: the first entry of the old tree that the installed tree does not hold in the same form, or that this command does not delete. Why: The old tree is deleted only after the installed copy is known to hold everything in it. The comparison changes neither tree and copies nothing: a difference keeps the old tree and says which entry differs, and whoever knows which version is wanted settles it. An entry on another device (a nested subvolume or a mount point) is reported the same way, because deleting it is not this command’s to do.
worktree_convert_journal
The journal of a conversion and the retirement of the kernel lock file older releases left. What: The journal of one conversion of a worktree into a subvolume. Why: The journal is the only proof that the converting subvolume and the index lock are this command’s, and it names the identity of the old root and of the subvolume it created, which is what every later phase checks before it reuses, exchanges, destroys or deletes anything. The journal is written through exclusively created files and never replaces a file it did not make. One command at a time owns a conversion through a numbered claim file (single_owner_claim.rs) taken before the journal is read, so no kernel lock is held.
worktree_convert_remove
The removal of the old tree of a conversion beneath a pinned descriptor. What: Remove the old tree of a conversion, but only the directory the journal recorded, pinned by a descriptor from the moment its identity is checked until its last entry is gone. Why: A device and inode read from a path say nothing about what the path names a moment later: another process can rename the recorded directory away and put an unrelated one at the name before a recursive delete resolves it again. The directory is opened below its parent without following a link, its identity is read from that descriptor, its entries are removed through the descriptor’s own path, and the last step removes the name only while it still names the same object and only if that object is empty (a directory removal cannot take a tree).
worktree_dirty_restore
The restore of a snapshot of a main checkout with uncommitted changes, and its proof. What: The restore of a snapshot of a main checkout that had uncommitted changes: make the copy’s index and files equal the tree of the new worktree’s HEAD with git read-tree --reset -u and git clean -f -d, then prove it in the stage. Why: git worktree add from a checkout with changes makes a worktree that holds the committed files and nothing of the changes. The snapshot carries the changes, so the copy is reset the way Git’s own reset --hard would reset it, from the index the snapshot carries: that index still records the files as they were before they were edited, so the reset rewrites exactly the edited, deleted and staged paths and leaves the rest. Untracked files that no ignore rule covers go, ignored files stay. Both Git runs are the ladder’s own steps on the new worktree (its Git directory and its stage), started as real Git children by the lifecycle’s runner, so no policy of the wrapper that guards a person’s reset or clean applies to them. The proof runs before the stage is swapped in; a proof that fails ends the rung and Git’s own checkout makes the worktree. The main checkout is read only to be snapshotted and listed, and is never written.
worktree_identity
Repository and worktree identity as reported by real Git. What: Ask real Git which repository and worktree an invocation selects. Why: Configuration lives at the worktree top level, and worktree policies need to know main versus linked. Git applies -C, --git-dir, --work-tree and its environment itself, so the wrapper never re-implements that selection.
worktree_ladder
The cheap-worktree ladder around one git worktree add: what runs before real Git and after it. What: The cheap-worktree ladder around one git worktree add: what runs before real Git and after it, on Linux; on other systems the ladder does nothing and Git’s own checkout stands. Why: doc/planning/cli-git-btrfs-worktree.md designs the ladder and measures it: a snapshot of a clean subvolume worktree, then clones of its tracked files, then Git’s own checkout. The lifecycle of the copy (worktree_copy.rs) calls this one module, so the rungs stay out of its settlement and recovery code.
worktree_ladder_finish
The ladder’s steps after real Git returned: the snapshot rung and the fall back to Git’s checkout. What: What the ladder does after real Git returned: bind the add to the directories Git made, the snapshot rung’s steps (read the new HEAD, snapshot, apply the tree difference, swap into place, print Git’s checkout line), the clone rung’s steps, and the fall back to Git’s own reset --hard in the bound worktree when a step cannot finish. Why: Every step before the swap leaves the destination exactly as Git’s --no-checkout run made it, so a failure there completes the worktree the way Git would have. The ladder never deletes or unregisters a worktree: when the fallback fails, or the bound objects are no longer what they were, the worktree and its registration stay exactly as they are and one loud report names what is incomplete and the command that removes it. The only deletions are of objects this process made and still checks: the stage it took and never exchanged. The line that Git’s own checkout would have printed is printed here, so the person cannot tell the rungs apart except by the time they take. No hook runs on any rung: every real-Git start of the wrapper switches hooks off (hooks_off.rs).
worktree_ladder_run
The ladder’s choice of rung before real Git runs. What: The ladder around one git worktree add on Linux: the choice of rung before real Git runs, and, after it returned, the snapshot rung’s steps with a fall back to Git’s own checkout whenever one cannot finish. Why: doc/planning/cli-git-btrfs-worktree.md designs it. With a snapshot source Git runs with --no-checkout (branch validation, administrative files, configuration copy and every message stay Git’s), the wrapper snapshots the source into a stage name beside the destination, makes it the worktree, applies the difference between the source’s tree and the new HEAD with git read-tree -m -u, and swaps it into place. Any failure before the swap destroys the stage and completes the worktree the way Git does, with git reset --hard; the person only ever sees Git’s result.
worktree_ladder_target
What both filling rungs read from Git’s --no-checkout result, and the one step that applies the tree difference. What: What both rungs that fill a worktree themselves need to know about the target of a git worktree add once real Git ran without a checkout: the administrative directory, Git’s pointer, the commit and tree HEAD names, whether the tree differs from the source’s, the line Git’s checkout would print, and the step that applies the difference. Why: The snapshot and clone rungs differ in how the source’s files reach the destination and agree on everything else, so the shared reading of Git’s result and the single git read-tree -m -u step live in one place. The first version keeps Git for the two-tree update because it owns the conversion filters (doc/planning/cli-git-btrfs-worktree.md).
worktree_removal_rescue
What becomes of the unshown push notices and unfinished pushes of a linked worktree that Git removes. What: What happens to the unshown push-failure notices and the unfinished backup pushes of a linked worktree when Git removes that worktree: before git worktree remove or git worktree prune runs, the wrapper copies those records into a snapshot under the common Git directory, and a later wrapper command settles each snapshot, turning it into notices that any worktree shows when the worktree is gone and discarding it when the removal did not happen. Why: A worktree keeps its records in its own Git directory, which Git deletes with the worktree. Without this a failed push nobody had seen yet, or a push the service had not run yet, would vanish with no word, and the person would never learn that a branch is not on the remote. The wrapper hands the process over to Git for the removal, so it cannot look afterwards; the snapshot is taken for every linked worktree that has records and settled later. A Git directory that is gone proves the removal. A Git directory that is still there proves a refusal only once the command that took the snapshot has ended (the process keeps its identity when it becomes Git, so the snapshot names it) and the worktree’s checkout is intact; while that command lives, or a checkout is missing from a Git directory that is still registered, the removal may be under way or cut short, and the snapshot is kept. A snapshot that cannot be fully promoted, or fully taken, is kept as far as it goes and reported on standard error, because a line in the diagnostic log is not a word to the person.
worktree_remove
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.
worktree_remove_plan
The plan of a git worktree remove that names one directory and no option. What: The plan for one git worktree remove that the wrapper may serve with a subvolume destroy: the one directory it names. Why: Only the plain form (one directory, no option, a non-empty name) is served; --force and every other option change what Git checks, and a name Git resolves by its last path component is not a directory here, so those stay with Git.
worktree_remove_registry
What: Which registration of the invoking repository a worktree directory belongs to, and whether Git would pick that same worktree for the name the person typed. Why: Git removes only a worktree that its own list for the repository it runs in contains, found first by the last components of every registered path and only then by the resolved path (find_worktree in Git’s worktree.c). The .git file of a worktree and the gitdir file of an administrative directory that name each other prove only that the pair is consistent, not that the repository the command runs in registered it, and a directory that exists under the typed name proves nothing about the worktree Git would remove for that name. Anything but a registration of the invoking repository that Git would also pick is left to Git, which words its own answer.
worktree_slow_route
The plain sentence that says why an add was not made as a snapshot, and where it goes. What: The plain sentence that tells a person why a git worktree add was not made as a snapshot, and the places it goes: one line on standard error unless --quiet was given, one warning entry in the journal, and the debug log that already holds the detail. Why: A snapshot of the main checkout takes seconds and Git’s checkout with the ignored-state copy takes minutes. The choice between them was made silently, so two slow adds could not be explained afterwards. The sentence names the cause in the person’s words (a change, a running Git command, a stale index, a missing subvolume) and what makes the next add quick, without the names of the ladder’s steps. A filesystem that is not btrfs gets no sentence: nothing the person does there would make the add a snapshot, so the line would only repeat itself.
worktree_snapshot
The snapshot rung’s mechanics: snapshot to a stage name, make it a worktree, swap it into place. What: The mechanics of rung 1 of the cheap-worktree ladder: a snapshot of the source subvolume taken at a stage name beside the destination, made into a worktree there (its own .git pointer, no stubs of nested worktrees, the source’s index with refreshed stat fields), and swapped with the destination in one atomic step. Why: The snapshot shares every data block with the source and carries its ignored state at no cost (doc/planning/cli-git-btrfs-worktree.md). It is made at a name Git never hands out, so the destination is complete or untouched at every instant: RENAME_EXCHANGE puts the finished subvolume at the destination and the empty directory real Git made at the stage name, which is then removed. A source that changed while the snapshot was taken is caught by comparing the snapshot’s own files with the index entries copied from the source.
worktree_snapshot_source
The snapshot source: whether the creating worktree can be snapshotted for a new worktree, and what that needs. What: Whether the creating worktree can be the source of a snapshot for a new worktree, decided before real Git runs, and everything the snapshot needs to know about it. Why: A snapshot copies the whole subvolume, so the source must be a subvolume root on the same filesystem as the destination, clean, and free of every feature whose result would differ from Git’s checkout: a sparse checkout, a worktree-specific configuration, a submodule, and a nested linked worktree that is an ordinary directory (a snapshot carries its files, and removing them one by one costs more than the checkout saves). Each refusal returns its reason in words; the caller logs it and falls to the next rung without a word to the person.
worktree_subvolume
The empty subvolume made at the destination of a git worktree add before Git runs, and its cleanup. What: The empty btrfs subvolume the wrapper makes at the destination of a git worktree add, before real Git runs, and the cleanup of it when Git leaves it empty. Why: A worktree that is a subvolume is a source of a later snapshot with no migration, and it is removed in one call. Git accepts an existing empty directory as the destination and fills it, and an empty subvolume is an empty directory (doc/planning/cli-git-btrfs-worktree.md, “Creating new worktrees as subvolumes”). Git removes the destination again only when it created it and failed late; a failure before that point (a branch that exists, a name Git rejects) leaves the directory, so the wrapper removes the empty subvolume itself. A destination that is not on btrfs, or a subvolume the kernel refuses, leaves Git to make its ordinary directory without a word to the person, with one diagnostic line for the log.
worktree_trash
Setting aside what a snapshot of the main checkout leaves out, and having the commit service empty it. What: The leave-out step of a snapshot of the main checkout: set aside the nested worktrees that are ordinary directories, the other checkouts, the caches and the run records that the main-checkout rule leaves out, into a trash directory inside the new subvolume, and write down what was made and moved; and, once the worktree is in place, hand the trash to the commit service as a work item that empties it (worktree_trash_dispose.rs). Why: A snapshot carries everything, so the leave-out is a removal. Unlinking about 495 thousand entries takes 14 s, which the person should not wait for; renaming 480 directories into one trash directory takes 1.6 s, 1.2 s of which is the walk. The rename keeps the new worktree final at once. The .git pointer of a nested worktree that was copied names another worktree’s administrative directory, so it is unlinked before anything else and before the add returns. Nothing the main checkout tracks is ever moved, and every move and removal names one plain entry below a held directory handle.
worktree_trash_dispose
Disposal of a snapshot’s trash: the work item, the foreground fallback and the service’s handler. What: The disposal of the trash a snapshot’s leave-out made: hand it to the commit service as a work item, or empty it here when the service cannot be reached, and the service’s handler. Both delete only the directory the leave-out recorded and only the entries it recorded, found by walking down from the worktree’s handle without following a link. Why: A name of forty hexadecimal digits proves nothing about who made a directory, and a path string is resolved again by every call that takes it. The manifest says which objects the leave-out made and moved; the disposal opens each directory on the way by one plain name below the handle of the one above, compares identities, and leaves anything else alone with a reason. A record stays until the trash is gone or the service has taken it, so a failure is never a silent success.
worktree_trash_entries
The removal of the entries a snapshot’s leave-out moved into its trash, side by side. What: The removal of the entries a snapshot’s leave-out moved into its trash, side by side. Why: The entries are separate directories that share only the held trash handle, each opened by its own name and removed only when it is the recorded object, so they are independent items: a reason for one entry never stops the others. They run within the budget the host allows (parallel_budget.rs), which inside the commit service’s control group is the two threads and the memory that unit is given, and the reasons come back in entry order (parallel_runner.rs).
worktree_trash_manifest
What a snapshot’s leave-out made and moved, with identities, as the text a work item carries. What: What a snapshot’s leave-out made and moved, with identities: the trash directory it created and each directory it renamed into it, as the text a work item carries and the handler reads back. Why: A name of forty hexadecimal digits proves nothing about who made a directory. Only what the leave-out itself created or moved may be deleted, so the leave-out writes down the identity of every object it put in the trash and the disposal deletes an object only when it is still the one written down.
worktree_trash_remove
Removal of a directory tree beneath a held parent handle, never following a link. What: Remove a directory tree beneath a held parent directory: every directory is opened by one plain name below the handle of its parent without following a link, and every entry is unlinked from the handle of the directory that holds it. Why: A path string is resolved again by every call that takes it, so a directory replaced by a link between a check and the removal redirects the removal. Here no call resolves a path: a link, a mount point or a directory on another device is an error that names it, and the identity of the top directory is compared with the recorded one on the object that is opened, so a replacement is never emptied. A directory the owner cannot write to is given the owner’s permission bits through its descriptor on the way down, as subvolume_remove.rs does for the old tree of a conversion.