LANDED eec94934 (2026-06-06). Fixed via the absorbed-tree (theirs)
ULOG cursor described in §"Root cause / fix"; repro test/post/28 green and
off WILL_FAIL; full ctest 298/298.
HIGH — silent data inclusion. In commit-all (implicit) mode, be post
commits an untracked file the user never staged, purely because the file's
mtime collides with an in-scope put / patch row's timestamp. be post
classifies each on-disk file by looking its mtime up in the wtlog via
SNIFFAtRowAtTs (sniff/AT.c), which resolves a row by timestamp alone and
never checks the row's path. An untracked sibling whose mtime aliases a
put/patch/mod stamp is therefore misattributed as that row's work and
ADDed to the commit (sniff/POST.c post_classify_step, the
SNIFFAtKnown → SNIFFAtRowAtTs → POST_V_ADD branch).
Two variants, one root cause:
feature.c
(PATCH stamps it with the patch row's ts); an untracked junk.txt whose
mtime equals that ts is swept into the same commit.be post is now
commit-all (SUBS-001); the recursion's be put <sub> writes a sub-bump
put <sub>#<40-hex> row. Any untracked file actively written during the
post (e.g. a 2> file-in-the-wt stderr redirect) aliases that put stamp
and is swept — and, because the post keeps writing it after hashing, lands
committed-but-dirty, breaking the next branch switch. This is what blocks
test/patch/28 (SUBS-002's repro) at its setup step.tracked.c.b: add feature.c, commit; back to trunk.be patch ?./b — absorbs feature.c, stamps it with the patch row ts.printf ... > junk.txt; touch -r feature.c junk.txt — alias the stamp.be post '#absorb b' — commit-all (a patch row is in scope).be tree:?<tip> shows BOTH feature.c (correct) AND junk.txt (BUG).Commit-all = git commit -a: every dirty tracked file plus files genuinely
introduced by an absorbed patch land; an untracked sibling never does unless
explicitly be put-staged. feature.c (in the absorbed tree) commits;
junk.txt (in no absorbed tree) does not.
junk.txt is committed because its mtime aliases the patch stamp and POST
trusts the ts→row lookup without verifying the row pertains to this path.
SNIFFAtRowAtTs is a ts-keyed lookup; the stamp model assumes mtime uniquely
identifies the owning row, but ms-granularity mtimes alias (same DIS-023
class). The clean fix procures the absorbed (theirs) tree(s) as a ULOG from
keeper — KEEPCommitTreeSha on each in-scope SNIFFAtPatchEntries sha, then
KEEPTreeULog — and feeds it into the existing N-way classify merge as a 5th
cursor (a thr verb, exactly like the baseline base cursor bu). An
untracked file (!src_base) reaching the stamp-owned ADD branch is then
committed only if it carries a src_thr record (i.e. it is genuinely in an
absorbed tree); otherwise it is an alias and is ignored. No separate
lookups, no parallel path-set — membership rides the merge. Covers both
variants: the put-alias post has no patch rows, so the theirs cursor is
empty and the untracked file is never ADDed.
test/post/28-untracked-alias-not-swept — deterministic local patch-alias
repro (touch -r); WILL_FAIL until fixed. The put-alias variant is covered
end-to-end once SUBS-002 unblocks test/patch/28.