Skip to content

Pin the bare-to-clone object hardlink with a test and state it as the design #162

Closed

Description

@blooop

Question

git clone <bare> <ws> (workspace_clone.py:265-278) shares pack files with the bare cache by git's default local-hardlink transport, but nothing asserts it, so any future file:// URL, intermediate copy, or --no-hardlinks would silently forfeit ~94% of the per-workspace .git. Measured on blooop/devlaunch: bare+clone = 2044 KB hardlinked vs 3788 KB with --no-hardlinks; each extra workspace costs 180 KB instead of 1924 KB.

Add an @pytest.mark.integration test that bare-clones a fixture, creates a workspace, and asserts st_ino equality and st_nlink >= 2 for objects/pack/*.pack between <bare>/objects/pack and <ws>/.git/objects/pack. Prove it red by flipping the clone to --no-hardlinks locally before reverting.

Add no clone flag — --local is already the default and does not even error on a file:// source, so it pins nothing. --shared/--reference were measured during the decision to leave an fsck-broken workspace after the bare's force-refspec fetch + gc, for a 2 KB saving; they are rejected.

Document in _prepare_workspace's docstring the two measured erosions:

  1. A bare repack drops an existing clone's pack to st_nlink == 1 — its private copy. That is the safety property that makes alternates unnecessary, not a bug.
  2. A cross-filesystem destination makes git silently fall back to a full copy on EXDEV (exit 0). Devlaunch's own layout makes this unreachable, since .bare and clones are siblings of one directory.

Seams: devlaunch/worktree/workspace_clone.py:249-283, devlaunch/worktree/repo_manager.py:37-52, new test in test/integration/.

Sequencing: after #150, which deletes _prepare_workspace step 4 at :298-310.

Decided on #154.

Activity

  1. self-assigned this
    on Aug 14, 2026
  2. blooop commented on Aug 14, 2026

    @blooop
    OwnerAuthor

    breadcrumb: claimed and starting. What I will build, restated: a red-provable integration test that pins the object hardlink between the bare cache and each workspace clone, plus the docstring that states it as the design rather than an accident. No clone flag is added — the ticket rejects --local (already the default, pins nothing) and --shared/--reference (fsck-broken workspace after the bare's force-refspec fetch + gc, for 2 KB).

    Seams, as I read them. The ticket names three; its line numbers have shifted since #150 landed, so I am pinning them behaviorally instead:

    1. The workspace-clone step — where the cold path clones from the bare cache into the workspace directory. This is the call whose default local transport does the hardlinking, and the docstring that must state the design.
    2. The bare-cache clone step — where a repo first arrives on disk as a bare clone. It is the other half of the pair the assertion compares, and the reason the assertion is about a sibling directory rather than about git in general.
    3. A new integration test under the integration suite, @pytest.mark.integration, over real git against the local file-path remote the suite already builds.

    One setup fact worth recording, because it decides whether the test can be vacuous. The suite's fixture repo is tiny, and a push of three objects lands under transfer.unpackLimit — so the fixture remote, the bare cache and the workspace clone all hold loose objects and zero pack files. An assertion written as "for every pack in the bare, ..." would pass over an empty set and pin nothing. Measured just now on a fixture-shaped repo: find bare/objects -type f returns three loose objects and no objects/pack directory at all. So the test repacks the bare cache before the workspace is made — which is also the honest state, since a cache cloned from a real forge arrives packed — and asserts the pack set is non-empty before comparing it.

    Red proof plan: flip the workspace clone to --no-hardlinks and watch the assertion go red. Measured on a scratch pair just now: hardlinked gives st_ino 30958156 / st_nlink 2 on both sides; --no-hardlinks gives a distinct inode and st_nlink 1.

  3. blooop commented on Aug 14, 2026

    @blooop
    OwnerAuthor

    breadcrumb: built and opened as PR #208 — the hardlink between the cache and each workspace clone is now asserted, and stated as the design at the clone site rather than inherited from a git default nobody had written down. No behaviour changed; no clone flag was added, for the reasons this ticket already settled.

    The one thing that nearly made the test vacuous, recorded because it is not obvious. The suite's fixture repository is three objects, well under git's transfer.unpackLimit, so a push explodes into loose objects and the cache ends up with no pack directory at all. "Every pack file is shared" over that is a statement about the empty set and passes on any implementation whatsoever. The test therefore packs the cache before the workspace is made — which is also the state a cache cloned from a real forge arrives in — and asserts the pack set is non-empty before it compares anything. Removing that setup step is one of the mutations checked below, and it turns the suite red rather than quietly green.

    Mutations checked, each applied then reverted. --no-hardlinks on the workspace clone: red, distinct inode and one link. A file:// source URL: red on the same assertion — and this is the case a --local flag would not have caught, which is why the ticket rejected the flag. The setup repack removed: red on the vacuity guard.

    Both erosions measured before being written down. A cache repack drops an existing clone's pack to one link and leaves it a complete copy that still passes git fsck — pinned by a second test, since it is the load-bearing half of rejecting --shared/--reference and prose alone would have left that rejection resting on an unchecked claim. A cross-filesystem destination (ext4 to tmpfs) exits 0 with no warning and produces a full copy on a different device; the sibling layout makes it unreachable, which is now said at the bare-clone site too.

    The KB figures moved and I did not overwrite them. Re-measuring on current main gives cache-plus-one at 2400 KB shared against 4472 KB unshared, and 196 KB rather than 2268 KB per further workspace — same shape as the 2044/3788 and 180/1924 decided on here, on a history that has since grown. Both sets are recorded with method, so the drift reads as a repo getting bigger rather than as a contradiction.

    Full CI green: exit 0, pylint 10.00/10, 1615 passed. Stopping at the open PR; review is a separate fresh-context stage.

  4. blooop commented on Aug 14, 2026

    @blooop
    OwnerAuthor

    breadcrumb: PR #208 checks all green — ci on py310/311/312/313 and default, e2e, prek, gate, codecov patch and project. Build stage complete; handing off to review in fresh context.

  5. blooop commented on Aug 14, 2026

    @blooop
    OwnerAuthor

    wf-review verdict on #208: Approve — both axes clean (Spec axis independently reproduced the red-proofs and the repack-erosion determinism); non-blocking riders recorded in the review (st_dev in the identity check, shared clone_manager fixture, figure consolidation). Merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions