run_diff, run_show and run_log decide between three output routes — raw passthrough, RTK's compacted diff, and summary-only — by asking a set of per-token predicates "does any token match", at three separate call sites. git does not work that way, and the mismatch has been the single most productive source of regressions in the arg_tokenizer work (#3681): most review rounds there found at least one, and they were all the same shape — a flag combination taking the wrong route.
Filing the measurements rather than the fix, because the fix belongs on develop after #3681 lands (that PR is what introduced most of these predicates, so doing it before would mean rewriting code that does not exist here yet).
Measured semantics
Probed against git 2.53, LC_ALL=C, in a repo with a modification, a staged create, a rename, a delete, a binary file and a merge commit. Every flag singly across diff/show/log, then all 272 ordered pairs per subcommand.
Result 1 — only suppressors are order-sensitive. 40 ordered pairs per subcommand change shape when reversed, and every one of them has -s or --no-patch on one side. Identical across all three subcommands:
-s -p = PATCH -p -s = NONE
-s --stat = STAT --stat -s = NONE
-s --numstat = NUMSTAT --numstat -s = NONE
-s --raw = RAW --raw -s = NONE
--no-patch -U0 = PATCH -U0 --no-patch = NONE
Result 2 — --quiet is not one of them. Zero order-sensitive pairs. It loses to any shape request from either side and suppresses only when nothing else asked for output. Modelling it as a third spelling of -s silently dropped patches; fixed in #3681, but it is the clearest example of the class.
Result 3 — "last flag wins" is wrong for shape-vs-shape. I tested that model against 343 random 2–4 flag combinations: it predicts 207. Since two shape flags are never order-sensitive (Result 1), what git applies among them is a fixed precedence, not recency:
--name-only --compact-summary -> NAMES (not STAT)
--patch-with-stat --numstat --compact-… -> PATCH (not STAT)
--numstat -W --compact-summary -> NUMSTAT (not STAT)
--check --raw --shortstat -> NONE (not STAT)
So the real model is two rules, not one:
- suppressor vs shape → last one wins
- shape vs shape → fixed precedence, order-independent
Neither is expressible as "does any token match this predicate", which is why each new flag combination has had to be discovered empirically.
Suggested shape of the fix
Fold the tokens once into an output shape, and let the three call sites read the result instead of each re-deriving it:
enum OutputShape { Patch { unified: Option<u32> }, Stat, NumStat, Raw, Names, None, Unparseable }
fn output_shape(sub: Subcommand, tokens: &[Token]) -> OutputShape
Each flag classifies as Suppressor, Sets(shape) or Inert — data, not control flow — and the precedence lattice among Sets is a table derived from the probe above rather than from reading documentation. Unparseable covers the flags that re-shape the columns the compaction keys on (--line-prefix, --output-indicator-*, word diff) and therefore have to take the raw route; note the probe classifier independently rediscovers this, since --line-prefix=X stops matching as a patch.
Before implementing, the precedence lattice needs measuring properly — the four rows above are a refutation of the simple model, not a complete ordering. That measurement is most of the work and is what makes this worth doing as its own change with its own verification budget.
Verification available
The differential harness used for #3681 compares real git / develop / branch across randomised flag combinations on exit code, first stderr line, and body presence, and it is what caught the --quiet case. Any implementation here should have to pass it with zero divergences that are not improvements — that is a much stronger gate than unit tests over the individual predicates, which reported green for every one of these regressions.
run_diff,run_showandrun_logdecide between three output routes — raw passthrough, RTK's compacted diff, and summary-only — by asking a set of per-token predicates "does any token match", at three separate call sites. git does not work that way, and the mismatch has been the single most productive source of regressions in thearg_tokenizerwork (#3681): most review rounds there found at least one, and they were all the same shape — a flag combination taking the wrong route.Filing the measurements rather than the fix, because the fix belongs on
developafter #3681 lands (that PR is what introduced most of these predicates, so doing it before would mean rewriting code that does not exist here yet).Measured semantics
Probed against git 2.53,
LC_ALL=C, in a repo with a modification, a staged create, a rename, a delete, a binary file and a merge commit. Every flag singly acrossdiff/show/log, then all 272 ordered pairs per subcommand.Result 1 — only suppressors are order-sensitive. 40 ordered pairs per subcommand change shape when reversed, and every one of them has
-sor--no-patchon one side. Identical across all three subcommands:Result 2 —
--quietis not one of them. Zero order-sensitive pairs. It loses to any shape request from either side and suppresses only when nothing else asked for output. Modelling it as a third spelling of-ssilently dropped patches; fixed in #3681, but it is the clearest example of the class.Result 3 — "last flag wins" is wrong for shape-vs-shape. I tested that model against 343 random 2–4 flag combinations: it predicts 207. Since two shape flags are never order-sensitive (Result 1), what git applies among them is a fixed precedence, not recency:
So the real model is two rules, not one:
Neither is expressible as "does any token match this predicate", which is why each new flag combination has had to be discovered empirically.
Suggested shape of the fix
Fold the tokens once into an output shape, and let the three call sites read the result instead of each re-deriving it:
Each flag classifies as
Suppressor,Sets(shape)orInert— data, not control flow — and the precedence lattice amongSetsis a table derived from the probe above rather than from reading documentation.Unparseablecovers the flags that re-shape the columns the compaction keys on (--line-prefix,--output-indicator-*, word diff) and therefore have to take the raw route; note the probe classifier independently rediscovers this, since--line-prefix=Xstops matching as a patch.Before implementing, the precedence lattice needs measuring properly — the four rows above are a refutation of the simple model, not a complete ordering. That measurement is most of the work and is what makes this worth doing as its own change with its own verification budget.
Verification available
The differential harness used for #3681 compares real git /
develop/ branch across randomised flag combinations on exit code, first stderr line, and body presence, and it is what caught the--quietcase. Any implementation here should have to pass it with zero divergences that are not improvements — that is a much stronger gate than unit tests over the individual predicates, which reported green for every one of these regressions.