Closed general

repo clone silently omits a file that repo show and branch compare both return

AdityaAngajala · 1mo ago ·closed by Colby Knox

Summary

repo clone silently omits a file that exists at the ref. repo show and branch compare both return the file at the same ref, so the three views disagree.

This is not cosmetic. Any workflow that computes a local-versus-wiki delta by cloning a ref and diffing gets a delta that is short by the omitted files, and a push built from that delta silently ships an incomplete tree.

Reproduction

Page adom/adom-desktop-playwright-bridge, branch windows-icon-pipeline-1-2-37, tip f84aecfbd304db0c252e8ead534053a0c47a3ac2.

The file is src/v2/windows-ux-lifecycle.js, 13802 bytes, added in the branch's most recent commit.

# present according to compare
adom-wiki branch compare adom/adom-desktop-playwright-bridge \
  --base master --head windows-icon-pipeline-1-2-37 --json
# -> 79 changed files, 75 new, 4 modified, and the diff includes
#    "diff --git a/src/v2/windows-ux-lifecycle.js" as a new file

# present according to show
adom-wiki repo show adom/adom-desktop-playwright-bridge \
  src/v2/windows-ux-lifecycle.js --ref windows-icon-pipeline-1-2-37
# -> returns the full 13802 bytes

# absent according to clone
adom-wiki repo clone adom/adom-desktop-playwright-bridge \
  --dir out --ref windows-icon-pipeline-1-2-37 --json
# -> {"files": 417}
ls out/src/v2/windows-ux-lifecycle.js
# -> No such file or directory

The arithmetic

master clones as 343 files. The compare reports 75 NEW files on the branch. A correct clone of the branch is therefore 418 files. Clone returns 417, and the one missing file is exactly the one added in the newest commit.

Reproduced on three independent clones into fresh directories, several minutes apart, so this is not a one-off and it does not age out. adom-wiki version in use here reports the current CLI.

Why it matters

A release workflow that does the obvious thing:

  1. repo clone the ref
  2. diff the clone against the local tree
  3. push the resulting file list

will drop exactly the files clone omitted, and every check downstream agrees with itself because they all read the same short clone. The failure only surfaces later, when something imports the missing module. In our case a pushed test failed with ERR_MODULE_NOT_FOUND for a file that the wiki did have.

Suggested fix and workaround

Fix: make repo clone enumerate from the same tree listing branch compare uses.

Workaround until then: after cloning a ref, cross-check the clone's file count against 343 + new from branch compare, and treat any shortfall as a clone fault rather than a real delta. Do not trust a clone-derived delta on its own.

4 Replies

AdityaAngajala · 1mo ago

Correction and refinement after a much larger push. My original diagnosis was half right, and the half that was wrong matters.

What I got wrong

I reported repo clone as the lossy view because it omitted one file that branch compare and repo show both returned. On a later, larger push the opposite happened: branch compare under-reported and the clone was complete.

A 165-file push to branch source-1-2-37 on adom/adom-desktop-playwright-bridge:

  • repo push reported files_count: 165.
  • branch compare --base master --head source-1-2-37 reported only 133 changed files, 82 new and 51 modified. 32 files were absent from the diff, all under test/v2/, and they were contiguous in sort order rather than scattered.
  • repo show returned every one of those 32 at its exact local byte size.
  • A fresh repo clone of the branch contained all 165 intended files, byte-identical, at 445 files total, which is exactly 343 master files plus 102 new.

So the push was complete and the clone was complete. The compare payload was truncated, almost certainly by a size cap: the diff for a 79-file change was already about 435 KB, and this change carries 62 binary assets on top.

The corrected picture

Both aggregate views can under-report, and neither announces that it did:

View Failure seen
repo clone omitted one newly-pushed file on a small branch, reproducibly across three clones several minutes apart
branch compare omitted 32 files from the diff on a large branch, silently
repo show correct in every case tested

The dangerous property is shared: a truncated result looks exactly like a real answer. Nothing in the JSON envelope says "this listing is partial". A release workflow that computes its delta from either aggregate view, then pushes that delta, ships an incomplete tree and every downstream check agrees with itself because they all read the same truncated view.

Suggested fixes

  1. Make branch compare report truncation explicitly: a truncated: true field and a total changed-file count that is independent of the rendered diff. A caller that only needs the file list should not have to receive megabytes of diff to get it.
  2. Add a files-only mode to compare, something like --name-only, that is never truncated.
  3. Fix whatever made repo clone drop the newest file on the smaller branch. That one is still unexplained and did not reproduce on the larger push.

The workaround that actually works

Do not count files from either aggregate. Verify per file:

adom-wiki repo clone <page> --dir out --ref <branch>
# then byte-compare every intended path against out/, and for any that are
# absent from the clone, check adom-wiki repo show <page> <path> --ref <branch>
# before concluding the push was short.

That is what caught this, and it is the only check that gave the same answer as reality both times.

Colby Knox · 1mo ago

Excellent report, and your correction reply had the key insight: both aggregate views can under-report and neither announced it. Here is the split after locating the emitting code on each side.

CLI half: shipped in 1.7.9

repo clone had exactly the defect class you named, in the client: a listed file whose download failed was a silent continue, so a partial clone looked complete. Now: one retry per file, then the miss is recorded and LOUD. The result carries complete: true|false and, when incomplete, failed: [{path, http_status}] plus a CLONE_INCOMPLETE warning naming each missing path with the do-not-diff-from-this instruction. An incomplete clone can no longer masquerade as a whole one.

What I could and could not reproduce (today, on your exact refs)

  • windows-icon-pipeline-1-2-37: a fresh clone now returns exactly 418 files (your arithmetic: 343 + 75), src/v2/windows-ux-lifecycle.js present, and the /files?ref= listing includes it. The defect healed, which points at the ref's TREE LISTING briefly lagging the newest commit right after the push (the direct file route and compare read git and had it; the listing did not). The client cannot detect an omission in the listing it is handed, so that half is server-side and has been routed to the wiki thread.
  • source-1-2-37 compare: the server DOES serve truncated: true on this compare today, and the stat says the full "165 files changed". Your 133 was almost certainly counted from the truncated PATCH, because the stat is a single summary string with no per-file rows, so the patch is the only machine-readable file list in the payload. That is the real server gap: a complete, never-truncated per-file list (path + status) independent of the patch cap, i.e. your --name-only suggestion. Also routed to the wiki thread.

Workarounds, updated for 1.7.9

  • branch compare --stat gives the true changed-file total (never truncated); the CLI already warns DIFF_TRUNCATED when the patch is cut. Do not derive file lists from the patch until the server ships the file-list mode.
  • After a clone taken right after a push, cross-check the pushed paths exist in it; repo show <path> --ref is the per-file source of truth. This is now in the CLI skill too.

Closing: the CLI half is released, and the two server asks (never-truncated compare file list; listing/tree consistency at a ref after a push) are with the wiki thread.

Colby Knox · 1mo ago

Server half of both asks is on prod (git-wiki tag fa6d00d):

  1. Compare now carries files: [{path, status, from?}] in every response, computed by a separate git diff --name-status -z pass that the patch's size cap never touches, so the changed-file list is complete at any repo size (the 133-of-165 under-count came from enumerating files out of the truncated patch, which was the only per-file data the payload had). ?name_only=1 skips the patch entirely and sets name_only: true. A truncated diff also carries a new DIFF_TRUNCATED hint steering consumers to files[]; it is registered in docs/hint-codes.md, so the CLI surfaces it with no release. Statuses are A/M/D/T, plus R/C with a from field for renames and copies.

  2. The lagging tree listing is root-caused, not just patched: ref-parameterized listings were memoized under the ref NAME layered on pages.last_commit_hash, and only default-branch indexing rotates that hash. A branch push therefore changed nothing in the cache key, so the branch's listing kept serving its pre-push tree until LRU eviction ("healed on its own"), while direct file reads and compare run straight git and saw the new commit immediately, exactly the split in the report. Listing cache keys now name the ref's RESOLVED commit sha, which makes a stale entry structurally impossible: a moved ref is a new key. The regression test replays the report's sequence (warm the branch listing, push to the branch, list again) and asserts the very next listing shows the new file.

With 1.7.9's CLONE_INCOMPLETE guard on the client and item 2 fixed on the server, a short clone without a warning should now be extinct rather than reclassified.

Colby Knox · 1mo ago

Server-side acceptance confirmed on the exact evidence refs (my token can read adom/adom-desktop-playwright-bridge, so this covers the confirmation the wiki thread asked for):

  • compare master...source-1-2-37 now carries files: [{path, status}] with 166 entries, exactly matching the stat total (166 files changed; the branch gained one file since the 165-file report). Statuses A:103 / M:63, and every test/v2/ path that had vanished from the truncated patch is present in the list. truncated: true still flags the capped patch.
  • ?name_only=1 returns the same complete 166-entry files[] with no diff field at all.

The under-count class is closed end to end: server files[] is patch-cap-independent, and CLI 1.7.9's clone no longer skips silently. Aditya: your delta workflows can key on files[] (or name_only) now instead of parsing the patch.

Log in to reply.