Repository navigation
fix(history): --limit selects the newest commits; --since windows work - #28
Conversation
Post-open corrections & one observationrustfmt follow-up ( Clippy is now eligible to stop being advisory. Not touched on purpose: |
`--limit N` streamed the N OLDEST commits, inherited from the retired Go reference's `initStreamingIterator` (`logOpts.Reverse = true` + CollectN). Measured on the pinned reference binary: `--limit 3` on kubernetes emits `2c4b3a562ce3` (2014-06-06) -- the repository's first commits -- and the same bytes are frozen in tests/golden/run/burndown.ndjson. No user means "the ten first commits ever made" when they type `--limit 10`. A partial `--since` window was worse: the planner counted the in-window commits, then the oldest-first loading pass applied the author-time stop filter and stopped at the first too-old commit, so the run aborted with `expected N commits, got 0: EOF`, empty stdout, exit 1 -- the documented `--since 2025-01-01` path never worked. Both bounds are now taken from ONE newest-first pass (cap at `--limit`, then reverse to the chronological order the analyzers require), which deletes the planner/loader two-pass and the abort. `--since` additionally gains git revision specs (`v1.2.0`, `HEAD~100`, a SHA) and CODEFANG_NOW-aware durations by sharing cf-gitlib's single parser; the CLI's duplicate ~130-line parser is deleted, not adapted. An unresolvable spec now reports `Error: cannot resolve --since "<value>"` instead of the generic tier-8 dispatch placeholder (same rc=1, still no stdout). Commits are still delivered oldest-first, so tick, burndown line survival and identity semantics are unchanged -- only the selected window moves. This is a deliberate divergence from the retired reference: superseded `--limit`-bounded captures are annotated (not recaptured) in tests/golden/MANIFEST.json and tests/compat/matrix.toml. The `cf-gitlib` `testutil` feature its module docs already promised makes the deterministic fixture repo usable from these tests. See specs/frds/FRD-newest-commit-window.md. Tests: 10 unit (`history_window_tests`) + 7 CLI-level e2e (`bins/codefang/tests/history_window.rs`) driving the real binary over a real repo, incl. the `--head` == `--limit 1` byte-identity invariant. Written red-first against the old behavior. `make test`: 2455 passed, 0 failed.
…ir backlog AGENTS.md mandates both gates, but neither target existed -- and `cargo clippy` could not pass anyway: it failed on deny-by-default lints (clippy::approx_constant in cf-reportutil, cf-gojson and cf-goyaml; clippy::never_loop in bins/uast), so the two pre-flight PR checkboxes were untickable by construction. - Makefile: `lint` (clippy --workspace --all-targets -- -D warnings) and `deadcode` (RUSTFLAGS="-D dead_code" cargo check --workspace --all-targets). The limitation of the latter -- rustc treats any `pub` item in a library crate as reachable, so it proves "no private code is unreachable", not "no exported function is unused" -- is documented at the target instead of glossed over. - Clear the workspace clippy backlog with real simplifications where the lint is right (single_match -> if let, if_not_else, get().is_none() -> assert against None, field_reassign_with_default -> struct update syntax, redundant closure, a no-op `& 0xFF` after `as u8`, doc backticks), and documented `#[allow]`s only where the flagged literal is a captured Go reference fixture: substituting f64::consts::PI for `3.14`/`3.14159` there would change the fixture under test, not the style. bins/uast's `for filename in files` could never iterate twice (it returns on the first pass while grammar wiring is pending), so it is now an explicit first-file bail with identical sentinel strings. - cf-uast: drop the never-read `InitState.rule_index` field. The local is still used for rule resolution; the map no longer stays alive inside every parser's init state. `make lint` and `make deadcode` both exit 0. The changelog entry for this work landed with the previous commit. No behavior or output-byte change: the workspace test suite is unchanged at 2455 passed / 0 failed.
CI's `Format check` step (`cargo fmt --all -- --check`) is a hard gate, and the two files the previous commits added/extended were not rustfmt-clean. `main` was clean, so `cargo fmt --all` touched only these two files -- verified before committing. Behavior is unchanged: `cargo fmt --all -- --check` exits 0 and `make test` is still 2455 passed / 0 failed.
The aggregate-int test helper in `mod history_filter_tests` (added by f54f1bb / #26) carries two clippy findings that the advisory CI Clippy step never failed on, but that `make lint` (`clippy --all-targets -- -D warnings`) does: * clippy::useless_format -- format!(r#""aggregate":{{"#) has no placeholders, so the brace escape was both pointless and a latent trap for whoever edits the literal next; * clippy::needless_borrows_for_generic_args -- once the literal is a &str, `json.find(&agg)` borrows a value that already satisfies the Pattern bound. Both are test-only code paths; behaviour is unchanged. Verified with `make lint`, `make deadcode`, `cargo fmt --all -- --check` (all clean) and `make test` (2480 passed, 0 failed).
d6396d8 to
8caae0c
Compare
Rebased onto
|
| File | Resolution |
|---|---|
crates/cf-commands/Cargo.toml |
kept #26's git2 dev-dep and this PR's cf-gitlib (testutil) dev-dep |
crates/cf-commands/src/lib.rs |
kept both validation blocks; pre-dispatch order is now static filter → history filter → --since |
crates/cf-commands/src/handlers.rs |
kept both tail test modules (history_filter_tests / dispatch_flag_tests from #26, history_window_tests from this PR) |
One finding the new make lint gate caught in #26
clippy --workspace --all-targets -- -D warnings fails on two lints in the
aggregate-int test helper that #26 added (format!(r#""aggregate":{{"#) has no
placeholders, and the follow-on json.find(&agg) borrows a value that already
satisfies Pattern). CI's Clippy step is continue-on-error: true, so nothing
ever reported them. 8caae0c fixes both; test-only, behavior unchanged.
Now that the backlog is clear, the Clippy step could stop being advisory — left as
a maintainer decision, since it is repo policy rather than part of this fix.
Post-rebase gates (re-measured, not carried over)
make test 2480 passed / 0 failed across 164 result groups (2455 before the
rebase; #26 added the rest), make lint exit 0, make deadcode exit 0,
cargo fmt --all -- --check exit 0. Focused suites re-run because #26 touched
history.rs/uast_walk.rs: history_window_tests 10/10,
bins/codefang/tests/history_window.rs 7/7.
CI is green on
|
| Step | Outcome |
|---|---|
Format check (cargo fmt --all -- --check) |
success — hard gate |
Clippy (--workspace --all-targets -- -D warnings) |
success |
Test (cargo test --workspace) |
success |
So the workspace is clippy-clean on the pinned toolchain on CI's own runner, not
just on this machine: CI executes the same -D warnings command and passes it. That
is the evidence for the note in the description — continue-on-error: true on that
step is no longer load-bearing, and flipping it off would have caught the two #26
lints before they landed.
Deploy Documentation run
35678954461:
Build Documentation, markdownlint, lychee pass; Vale and cspell (advisory) pass.
Deploy to GitHub Pages is skipped, as expected for a pull_request event.
PR state: mergeable=MERGEABLE, mergeStateStatus=CLEAN — ready for review.
Scope
Two history-window bugs, both measured rather than theorized.
--limit Nanalyzed the N oldest commits — inherited from the retired Goreference's
initStreamingIterator(logOpts.Reverse = true+CollectN).Verified two independent ways:
build/bin/codefang):--limit 3on kubernetes emits2c4b3a562ce3(2014-06-06) — the repository's first commits;tests/golden/run/burndown.ndjson(Go-captured at--limit 10) line 0 isthat same commit.
--sincewindow aborted the run. The planner counted thein-window commits, then the oldest-first loading pass applied the author-time
stop filter and halted at the first too-old commit, so the run died with
expected N commits, got 0: EOF— empty stdout, exit 1. The primarydocumented path (
--since 2025-01-01) had never worked.Both bounds now come from one newest-first pass (cap at
--limit, thenreverse to the chronological order analyzers need), which deletes the
planner/loader two-pass and the under-fill abort.
--sinceadditionally acceptsgit revisions (
v1.2.0,HEAD~100, a SHA) and its durations now honorCODEFANG_NOW, because the CLI's duplicate ~130-line time parser was deleted infavour of
cf-gitlib's single one.Also fixed, on the same path:
--sincesaidcommand dispatch is blocked on cf-commands (tier 8)— blaming the port for the user's typo. It now saysError: cannot resolve --since "<value>"(same rc=1, still no stdout).site/guide/data-analytics.mdtold users--since 6mmeant six months;mis minutes. Docs now state the supported units (h/m/s, nod/w)and use
4380h.cf_gitlib::testutildocumented atestutilcargo feature that did not exist,so no other crate could build a deterministic fixture repo. It exists now.
make lint/make deadcodedid not exist althoughAGENTS.mdand thisPR template mandate them — and
cargo clippyfailed outright on deny-by-defaultlints. Second commit adds both targets and clears the backlog (incl. a dead
InitState.rule_indexfield incf-uast). Rebased onto fix(static+history): flag/language filters for every analyzer; root-cause oracle timeout #26, the gate caught twofurther lints in the test helper fix(static+history): flag/language filters for every analyzer; root-cause oracle timeout #26 added (
clippy::useless_format,clippy::needless_borrows_for_generic_args) that the advisory CI Clippy step hadnever reported;
8caae0cclears them.Measured behavior (built binary, 5-commit fixture dated 2021-01-01…05):
--limit 274d76e4b,f5846e20— newest two, oldest-first--since 2021-01-03--since <sha>/HEAD~2--since 2030-01-01--since nonsenseError: cannot resolve --since "nonsense"--headvs--limit 1Test matrix
Written red first against the old behavior, then green.
crates/cf-commands/src/handlers.rs::history_window_tests(10):newest-N selection;
--limit 0/negative = unlimited; partial window; emptywindow (cutoff after HEAD); cutoff at/before root;
--limitcapping the newestend of a
--sincewindow; revision spec;CODEFANG_NOW-injected duration;unresolvable spec aborts; a self-check tying the cutoff literal to the fixture
clock so expectations cannot drift.
bins/codefang/tests/history_window.rs(7): real binary over areal on-disk repo, machine formats only (the ones the golden harness treats as
binding): newest-N, user-visible
total_commitsgrowth with--limit, partial--sinceexits 0 with the right hashes,--since <sha>, empty window exits 0,bad
--since→ rc 1 + empty stdout + flag named in stderr, and the--head≡--limit 1byte-identity invariant (which also proves--headignores
--limit).Gates, re-measured after the rebase onto
f54f1bb(#26):make test2480 passed / 0 failed across 164 result groups,
make lint(=clippy --workspace --all-targets -- -D warnings) exit 0,make deadcodeexit 0,cargo fmt --all -- --checkexit 0. No flakes across repeated runs.Link to FRD/BUG
specs/frds/FRD-newest-commit-window.md(includes the measured before/afterevidence and the residuality analysis).
specs/is gitignored in this repo, sothe FRD is not in the diff — content is reproduced in this description.
Breaking changes
Yes — output-changing for
--limitand--sinceruns, by owner ruling:--limit Nnow analyzes the N newest commits (was: N oldest). This is adeliberate divergence from the retired Go reference; the golden harness is
unaffected (it skips every
runcapture today —IMPLEMENTED_PREFIXESisversiononly), so no bytes were rewritten undertests/golden/. Instead,tests/golden/MANIFEST.jsonandtests/compat/matrix.tomlnow record that--limit/--sincecells encode the superseded reference and must not be readas port defects. A future re-capture must come from the Rust binary.
--sincein the middle of a history now produces a report instead of exiting 1.line survival and identity ids behave as before;
--head,--first-parentandall static analyzers are untouched.
SinceSpec::Active(i64)→Active(String)(a rawspec string, resolved once by
cf_gitlib::Repository::resolve_time). It hasthree production sites —
history_since_spec, the cutoff resolution inload_history_commit_hashes, and the new pre-dispatch check — all insidecf-commands; every other mention is this PR's tests.Risks / known limits
--sincecompares author time, so ahistory with bulk
git commit --daterewrites can be cut short. Inherited fromthe reference, documented at the function; mitigation is a skip-filter (costs a
full walk).
h/m/s(GoParseDurationhas nod/w), so--since -24hnow resolves to a future cutoff (empty window) instead oferroring, and
1.5his rejected.make deadcodeproves no private code is unreachable; apub-APIreachability audit needs external tooling and is out of scope.
Rollback — revert the four commits
(
git revert --no-commit fe4455b^..8caae0c && git commit, newest-first by design);no data/migration/format change is involved. The
testutilfeatureis additive and off by default, so it is inert for shipped binaries.
Before opening this PR
make test) — 2480 passed, 0 failed (post-rebase)make lint) — added by this PR, exit 0make deadcode) — added by this PR, exit 0site/guide/cli-reference.md,site/guide/data-analytics.md,site/getting-started/first-analysis.md,llms-full.txt,CHANGELOG.md)CI status
CI is executing for this PR now: push
8caae0ctriggeredCIrun35678954520and
Deploy Documentationrun35678954461(Build Documentation, markdownlint, Vale, cspell, lychee all pass). Earlier in this
PR
gh pr checks 28reported no checks at all, and the newest run anywhere inthe repo predated #24/#25 — a repo-wide runner/Actions stall, not something caused
by this branch. That earlier state is why the local gates below were measured
instead of deferred to CI.
Local gates, all re-run after the rebase:
cargo fmt --all -- --checkexit 0,make lintexit 0,make deadcodeexit 0,make test2480 passed / 0 failed.The
Testjob outcome is recorded in the issue comments once it reports.Note for maintainers: CI's Clippy step is
continue-on-error: true, and thatadvisory setting is exactly why the two #26 lints above reached
mainunnoticed.The backlog is now empty, so it can become a blocking gate — deliberately left out
of this PR, as that is repo policy rather than part of the fix.