KIT

Reference snapshot · source revision 7a2e36f2b250934a095e542ae20e62dcfb0d5f70

SPEC-WORKTREE — linked working trees

Status: Normative for the state/locking/discovery model specified here; Draft because §5's out-of-scope surface (worktree move/lock/repair) is not yet covered by this document. Scope: the common-dir / per-worktree state split, the linked-tree pointer-file format, the worktrees/ registry, repository discovery, cross-worktree locking, and gc root-collection semantics.

Reference implementation: kit-core/src/layout.rs (the RepoLayout type is the single path-resolution authority; every repo-state API takes it).


1. State classification

Every piece of repository state under .kit/ belongs to exactly one of two classes:

Class Contents
common dir (shared by all trees) objects/, format, refs/ (heads, tags, remotes), shallow, config, keys/, history/, history-v1/, recovery-log, attestations/, applied-packs/, upload-parts/, git/, sparse/, worktrees/, refs-history-<branch>.lock, refs-<ref>.lock, worktrees.lock, serve.lock (SPEC-CONCURRENCY §2)
worktree state dir (private to one tree) HEAD, index, ORIG_HEAD, MERGE_HEAD/MERGE_MSG, CHERRY_PICK_HEAD/CHERRY_PICK_MSG, REVERT_HEAD/REVERT_MSG, kit-conflicts, KIT_OP_RESULT, rebase-apply/, bisect, stash, sparse-checkout, worktree.lock

In the classic single-worktree layout both directories are the same <root>/.kit/; the two-directory model is byte-identical there.

Divergences from git, both deliberate:

Relative signing-key paths in config (.kit/keys/…) resolve their .kit/ prefix against the common dir, so every tree signs with the one shared repo key store.

The disposable applied-packs/.heads/ subdirectory stores native fetch checkpoints shared with the object store and remote-tracking refs. Each cache key binds the remote, branch, repository identity, and origin. A checkpoint follows verified object application and durable tracking publication under the existing worktree lock. It is never a GC root. Its advisory format and recovery rules are documented in the sync README. No wire, ref, or authoritative repository format changes.

2. On-disk model

<main>/.kit/                       # common dir
  worktrees/<id>/                   # one per linked tree (the registry)
    commondir                       # path to the common dir
    kitdir                         # abs path of the tree's pointer file
    HEAD, index, ORIG_HEAD, ...     # that tree's per-worktree state
<linked-tree>/.kit                 # pointer FILE (not a directory)

2.1 Pointer file

A linked tree's .kit is a regular file containing exactly one line:

kitdir: <path>\n

2.2 State-dir metadata files

2.3 Worktree ids

The registry directory name <id> MUST match: non-empty, ≤ 255 bytes, ASCII alphanumeric plus ., _, -, and not . or .. (validate_worktree_id). Ids derive from the target path's basename, sanitized (invalid bytes → -) and uniquified git-style (name, name-1, name-2, …).

3. Discovery

Given a working directory W (kit treats the invocation directory as the worktree root; there is no upward search):

  1. If W/.kit is a directory or absent → the single-worktree layout (common dir = state dir = W/.kit). Absence is not a discovery error; "not a repository" surfaces from the store open, unchanged from pre-worktree behavior.
  2. If W/.kit is a file → parse it as a pointer file. Then:
    • the state dir is the pointer target (resolved against W if relative); it MUST exist and is canonicalized;
    • the common dir comes from <state>/commondir (default ../..), resolved against the state dir, canonicalized; it MUST exist.
  3. Every failure in step 2 is fatal (typed DiscoverError): malformed/oversized/non-UTF-8/multi-line pointer, dangling state dir, unreadable or dangling commondir. A broken linked tree MUST NOT silently degrade to operating on some other directory.

A registry entry is prunable when its back-pointer is missing or unreadable, its tree is gone, or the tree's pointer no longer names that state dir (a moved or re-created tree MUST NOT be claimed by a stale entry). worktree list reports prunable entries; only worktree prune deletes them.

4. Semantics

4.1 Sharing

Linked trees share the object store and every common-dir source of truth: refs, config, keys, history MMB, recovery log, attestations, transport caches. An object written from any tree is immediately visible to all; a ref moved from any tree moves for all.

4.2 Single writer per branch

A branch may be checked out in at most one worktree. worktree add, checkout/switch (including -b/-B), branch -d/-D, and branch -m MUST refuse (naming the holding tree) when the branch is checked out in another tree. Rationale: branch moves flow through the history-MMB ref-write path (SPEC-HISTORY-PROOF), which assumes one writer per branch. Detached HEADs are unconstrained.

The guard reads sibling HEADs via the registry and MUST fail closed if the registry cannot be enumerated.

4.3 Locks

This document originates the worktree.lock/worktrees.lock primitives and their per-command holder details below; the total lock order across all kit locks (including refs-history-<branch>.lock and the file-transport's refs/.lock), and the enumeration of every writer, is owned by SPEC-CONCURRENCY — see that document for the authoritative order. This section states only the two locks this document defines and how the commands specified here use them.

Lock Location Guards
worktree.lock each tree's state dir that tree's worktree/index read-modify-write
worktrees.lock common dir registry mutations (worktree add/remove/prune)

Per-command holders:

Per-tree locking is insufficient to serialize two linked trees mutating the same branch/tag/remote ref. Each ref mutation additionally takes the shared full-ref guard specified in SPEC-CONCURRENCY §4, after any history guard. Unconditional operations and deletes participate in this same order.

4.4 gc root collection

Shared sources are read once: the strict refs walk, attestations, and the recovery log. Per-tree sources are unioned over the main tree and every registry entry whose state dir exists — including prunable entries (until worktree prune reaps a state dir, whatever it pins stays pinned): HEAD, staging index, ORIG_HEAD, in-progress merge/cherry-pick/revert/rebase state, conflict sidecars, and the tree-local stash.

Fail closed: a registry enumeration error or an unreadable sibling source aborts collection; gc never prunes on a partial view. The applied-packs record is a redownload-avoidance cache and MUST NOT be a root source; gc may delete it freely. The upload-parts/ receipt store is likewise a deletable cache and MUST NOT be a GC root. It is distinct from receipts/, reserved for signed storage receipts. Part upload resume works only while the push plan regenerates the same pack bytes.

4.5 worktree add write ordering (crash safety)

  1. state dir fully populated (commondir, kitdir back-pointer, HEAD) — before any pointer to it exists;
  2. branch ref creation (new-branch form), through the history-recording ref path;
  3. the tree: pointer file, then materialization via the shared checkout/restore path.

A crash between (1) and (3) leaves a prunable registry orphan, never a live tree pointing at half-built state. worktree remove deletes the tree before the state dir for the same reason.

4.6 Stash manifest

The stash file in a worktree state dir holds the stash stack. It is a binary manifest. All integers are little-endian.

magic   : 4 bytes   "KSTS"
count   : u32
entries : count entries, newest first
  commit_hash : 32 bytes
  parent_hash : 32 bytes
  timestamp   : u32   Unix seconds, saturating
  msg_len     : u16
  message     : msg_len bytes of UTF-8

KSTS is a protocol constant. No other magic, tag or domain-separation value may equal it. A new stash is prepended, so the newest entry comes first.

A writer MUST reject a message longer than 65535 bytes. A reader MUST reject the file with an invalid-format error in each of these cases:

A reader MUST NOT allocate from count before the count check passes. Each commit_hash and parent_hash is a gc root (SPEC-GC).

Reference implementation: kit-core/src/ops/stash.rs (serialize_list, deserialize_list). The uniqueness test in kit-core/tests/unique_tags.rs covers the KSTS constant.

5. Out of scope (v1)

worktree move/lock/repair (follow-ups), submodule interplay (submodules are a non-goal), and sharing a repository between kit versions with and without worktree support while linked trees exist.

6. Test anchors