Skip to content

fix(git): wire up status_max_files and status_max_untracked config limits - #2579

Open
guyoron1 wants to merge 1 commit into
rtk-ai:developfrom
guyoron1:fix/git-status-limits
Open

guyoron1 wants to merge 1 commit into
rtk-ai:developfrom
guyoron1:fix/git-status-limits

Conversation

@guyoron1

@guyoron1 guyoron1 commented Jun 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #2437

LimitsConfig::status_max_files and status_max_untracked were parsed from config but never read by the git status formatter, so setting them did nothing. This wires them up.

  • rtk git status (and -sb / -b, and rtk pipe) now cap the listing at the configured limits. Entries past a cap are counted as ... +N more with the usual recall hint, so they stay recoverable: rtk recall <hash> returns exactly the hidden entries. With recall off it is just ... +N more. 0 means summary only.
  • Conflicted paths (DD AU UD UA DU AA UU) never count against a cap, so a large merge cannot push a conflict off the list.
  • LimitsConfig gets #[serde(default)]: a [limits] section that sets only the two status keys (the repro in [config] status_max_files and status_max_untracked are declared in LimitsConfig but never read #2437) failed to parse at all, which silently dropped the whole config and turned recall off with it.
  • Explicit user args (--porcelain, -s on its own, ...) still pass through untouched.

Defaults are unchanged: 15 tracked / 10 untracked, as documented. That does mean rtk git status now truncates by default, which is the part of #2437 worth a maintainer's call, since status completeness was made a goal in 6ebde6d and 7753e48. If you would rather default both to unlimited, or remove the knobs (option B in the issue), that is a small change.

Verified

Repo with 25 modified + 15 untracked files and [limits] status_max_files = 2, status_max_untracked = 1:

  • develop: all 41 lines (config ignored)
  • this PR: * main, 2 tracked, 1 untracked, ... +37 more [+37 hidden: rtk recall <hash>]; rtk recall <hash> returns the 37 hidden entries, byte-identical to git status --porcelain minus the 3 shown
  • no config: 15 tracked + 10 untracked + ... +15 more [...]

Tests

  • 6 unit tests: tracked cap, untracked cap counted separately, both buckets, exactly-at-cap hides nothing, zero caps, unmerged paths never hidden
  • 3 integration tests through the real binary (tests/git_status_limits_test.rs, unix): a partial [limits] caps the listing, larger caps hide nothing, and rtk recall returns exactly the hidden entries
  • test_format_status_output_shows_every_file_when_many_are_dirty is kept (same name and assertions), now calling the formatter with explicit large caps
  • The new tests fail on develop's code and when #[serde(default)] is removed
  • cargo fmt --all -- --check, cargo clippy --all-targets (0 warnings), cargo test --all (all pass)

@CLAassistant

CLAassistant commented Jun 24, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

kylehgc added a commit to kylehgc/rtk that referenced this pull request Jul 23, 2026
- Conflict list: drop rtk-ai#2579/rtk-ai#2590/rtk-ai#2615 (already flagged 07-17), add
  rtk-ai#2401/rtk-ai#2453 and the chunk-7 conflict cluster; note top-pick rtk-ai#2401 is
  a hand-port (issue #54).
- Next-wave: restore recovered candidate rtk-ai#2256 (gh prompt-injection
  scan) dropped in distillation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@guyoron1
guyoron1 force-pushed the fix/git-status-limits branch from 7be2a6e to 8b9e6b6 Compare July 24, 2026 15:36
@guyoron1
guyoron1 force-pushed the fix/git-status-limits branch from 8b9e6b6 to 53146e4 Compare August 14, 2026 19:03
@guyoron1
guyoron1 force-pushed the fix/git-status-limits branch from 53146e4 to 3f0776a Compare September 21, 2026 12:03
@guyoron1

Copy link
Copy Markdown
Contributor Author

@KuSh rebased on current develop (git.rs is git_cmd.rs now) and reworked to fit today's code. You closed #2459 in favour of this PR, so one design point for you:

Wiring the two limits with the documented defaults (15 tracked / 10 untracked) means rtk git status truncates for everyone, which reverses part of the "status completeness" work in 6ebde6d and 7753e48. I kept the documented defaults, but the alternatives are cheap: default both to unlimited (opt-in caps), or take option B from #2437 and delete the dead knobs. Which do you prefer?

What changed since the earlier push: entries past a cap are ... +N more with the usual recall hint (the old "use git status" marker would be rewritten straight back to rtk git status by the hook), conflicted paths never count against a cap, and LimitsConfig gets #[serde(default)], because a [limits] section with only the two status keys (the repro in #2437) failed to parse and silently dropped the whole config.

Checked on a 25-modified + 15-untracked repo: develop lists 41 lines; this lists 5, and rtk recall <hash> returns exactly the other 37. There are unit tests plus integration tests through the real binary.

@KuSh KuSh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 1 of 3 — CHANGES REQUESTED

Claim: [limits] status_max_files / status_max_untracked now bound the compact rtk git status listing (the rest stays recoverable through recall, conflicts never counted), and a partial [limits] section no longer drops the whole config.
Scope: accept if narrowed to "the two caps apply when set; unset by default, so rtk git status stays complete (#991)", plus the #[serde(default)] fix as is. (round 1, frozen)
Ran: rtk rewrite "git status" → rtk git status on real repos (25 modified + 15 untracked + an untracked dir; a 20-file merge with 3 conflicts; detached HEAD), develop merge-base 0c279f5 vs 3f0776a vs the merge into current develop, each binary built separately. Cases: no config, [limits] 2/1, 0/0, 100/100; -sb, -b, --short --branch, -s, --porcelain; rtk pipe -f git-status; git -C <other>; RTK_RECALL=0; sqlite and tee recovery modes, each hint followed through rtk rewrite. Invariants: under the hook | over-cap + accounting (shown + recalled = git status --porcelain, both modes) | non-default target (-C) | every decision (tracked / untracked / unmerged bucket, cap hit / exact / zero). Savings vs raw git status (964 B): develop 56%, this PR with defaults 67%, caps 2/1 91% — all above the 20% floor (CONTRIBUTING.md).
Matrix: never run before — executed this round
CI @3f0776a4: all green (ubuntu, macos, windows, clippy, fmt, benchmark)

Blocks merge (1, frozen at round 1)

  1. src/core/config.rs:238 — the documented defaults now apply to everyone, so a stock install hides dirty paths that develop lists. Evidence, no config.toml, 25 modified + 16 untracked: develop lists all 41; this PR lists 25 and prints ... +16 more [+16 hidden: rtk recall …]. With RTK_RECALL=0 or [retriever] mode = "disabled" the note is just ... +16 more and the paths cannot be recovered from rtk at all. This is exactly what #991 removed (its test is kept here, but now run with caps of 100). The store write also costs time on every such status: hyperfine 6.5 ms (develop) → 9.8 ms (this PR, defaults), and 6.6 ms for both when nothing is hidden. See "Your questions" for the change that clears this.

Optional — will not hold merge

  • The listing tests that call format_status_output now read the developer's own ~/.config/rtk/config.toml: with status_max_files = 0 set locally, test_format_status_output_{thai_filename,mixed_changes,preserves_rename_and_conflict_lines,preserves_nested_untracked_paths} fail. A clean CI runner never sees it. Routing those seven callers through a small helper that calls format_status_inner with usize::MAX caps keeps them independent of local config.
  • rtk pipe -f git-status fed --porcelain=v2 output puts u UU … into the tracked bucket, so under a cap a v2 conflict can be hidden (still recallable). The formatter only speaks v1, and nothing in rtk sends it v2, so this is only a note.
  • When never_worse falls back to raw (short headerless input through rtk pipe), the recall row or tee file has already been written for a hint that is never printed. Harmless apart from one orphan row.

Filed as follow-ups

  • #3431 (existing) — tee files are named {epoch_seconds}_{slug}.log, so two capped statuses in the same second share one file and the first hint returns the second repo's entries. Reproduced with rtk git status in repo A, then rtk git -C B status: A's tail -n +3 …_git-status.log printed B_f3…B_f6. Pre-existing for every tee caller (find, ls, search, …). This PR adds one caller, reached only with caps set and tee mode, so it's absorbed there (comment with the repro added) rather than held here.
  • Partial [tracking] / [display] / [filters] sections still fail to parse and drop the whole config, [limits] included — pre-existing, covered by open #1483. Both PRs add the same #[serde(default)] line to LimitsConfig; whichever lands second rebases trivially.

Checked and correct — no need to re-verify

  • Sqlite recall returns exactly the hidden entries; shown + recalled = git status --porcelain, with no duplicates (38 hidden at 2/1, 16 at defaults). Tee mode's tail -n +N <file> is left native by rtk rewrite and returns the hidden tail.
  • A real merge with 20 staged files and 3 UU entries under caps 2/1: all three conflicts shown, only staged/untracked entries counted.
  • -sb, -b, --short --branch take the capped path; -s and --porcelain pass through untouched. git -C <other> status, detached HEAD and rtk pipe -f git-status apply the same caps.
  • #[serde(default)] on LimitsConfig: on develop, [limits] with only the two status keys makes rtk config fail (missing field … in 'limits') and drops the config; here it parses and keeps the other defaults.
  • cargo fmt --check, cargo clippy --all-targets (0 warnings), cargo test --all (3936 passed) on this branch merged into current develop.

Hypotheses built and dropped

  • "config::limits() re-parses config.toml on the hot path": measured, develop 6.6 ms vs this PR 6.6 ms with caps set high enough that nothing is hidden. The time difference in the blocker comes from the store write, not the parse.
  • "One combined +N more count across both buckets hides which kind was cut": wording preference; recall shows it.
  • "TEST_ENV_LOCK.lock().unwrap() in the new helper breaks the test rule": it's the established idiom in the existing tee.rs and retriever.rs tests.

Your questions

  • "I kept the documented defaults, but the alternatives are cheap: default both to unlimited (opt-in caps), or take option B from #2437 and delete the dead knobs. Which do you prefer?" → Opt-in: unset by default. Option B would throw away a feature this PR makes work, and the documented defaults undo #991 for everyone (blocker 1). One constraint I checked: usize::MAX as the "unlimited" default breaks rtk config and rtk config --create (rtk: out-of-range value for u64 type, since TOML integers are i64), so the fields need to become Option<usize> with skip_serializing_if = "Option::is_none". The docs that name the defaults (src/core/README.md, src/cmds/git/README.md, docs/contributing/CODING_PRACTICES.md) change with them. I checked that shape locally: it passes fmt, clippy and cargo test --all, and on the same repo gives: no config → all 41 entries, byte-identical to develop; caps 2/1 → capped with a working recall; status_max_files = 0 alone → honoured; rtk config / --create serialize and round-trip.

Next

Make the two caps opt-in (Option<usize>, unset by default). After that this merges.
Rounds: 1/3. Threads: 0 open, 0 resolved this round.

Comment thread src/cmds/git/git_cmd.rs Outdated
format_status_inner(
porcelain,
None,
limits.status_max_files,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocks merge (1). With the documented defaults, this line caps every stock rtk git status at 15 tracked / 10 untracked: a repo with 25 modified + 16 untracked files goes from all 41 entries on develop to 25 plus ... +16 more, and with recall off the hidden paths cannot be recovered from rtk. That reverses #991. Making the caps Option<usize>, unset by default, clears it; details in the review body.

…mits

`limits.status_max_files` and `limits.status_max_untracked` were parsed and
documented, but nothing read them, so `rtk git status` listed every entry
whatever the config said.

The status formatter now applies both caps, one per bucket (tracked and
untracked). Entries past a cap are counted as `... +N more`, followed by the
recall hint so the hidden entries stay recoverable. Conflicted paths are
never counted against a cap, so a merge that stages many files cannot push
the conflicts off the listing.

`LimitsConfig` also gains `#[serde(default)]`. A `[limits]` section that
sets only the two status keys, as in the issue, used to fail to parse, and
the failed load silently dropped the whole config (recall included).

Fixes rtk-ai#2437
@guyoron1
guyoron1 force-pushed the fix/git-status-limits branch from 3f0776a to 28b13bd Compare September 27, 2026 03:28
@guyoron1

Copy link
Copy Markdown
Contributor Author

Made both caps opt-in as you suggested — status_max_files/status_max_untracked are now Option<usize> (#[serde(default, skip_serializing_if = "Option::is_none")]), unset by default, unwrap_or(usize::MAX) at the two format_status_inner call sites in git_cmd.rs. Updated the doc comments and the three READMEs/CODING_PRACTICES.md that named the numeric defaults.

Verified the four cases you listed, on a real repo (5 tracked modified + 4 untracked):

  • No [limits] section at all → all 9 entries, nothing hidden (new test no_limits_section_leaves_the_listing_unbounded, plus manual check with a fresh rtk config --create'd config: the two fields are simply absent from the written TOML, no crash).
  • status_max_files = 2 / status_max_untracked = 1 → still capped, unchanged (existing the_configured_caps_bound_the_listing).
  • status_max_files = 0 alone, status_max_untracked unset → tracked capped at 0, untracked stays unbounded (checked directly: one untracked file showed, zero tracked shown, ... +1 more).
  • rtk config / rtk config --create round-trip cleanly with the fields unset — no out-of-range value for u64 error.

Rebased onto current develop (45b0b4c) since this was 41 commits behind. cargo fmt --check, cargo clippy --all-targets (0 warnings), cargo test --all (4043 passed, 8 ignored) all green on the rebased branch. Same commit, amended in place: 28b13bd.

@KuSh KuSh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 2 of 3 — CHANGES REQUESTED

Claim: [limits] status_max_files / status_max_untracked now bound the compact rtk git status listing (the rest stays recoverable through recall, conflicts never counted), and a partial [limits] section no longer drops the whole config.
Scope: accept if narrowed to "the two caps apply when set; unset by default, so rtk git status stays complete (#991)", plus the #[serde(default)] fix as is. (round 1, frozen)
Ran: develop 45b0b4c vs 28b13bd (the head is on the develop tip, so it is also the merge check), each binary built separately. Same real repos as round 1 (25 modified + 15 untracked + an untracked dir; a 20-file merge with 3 conflicts; detached HEAD), each command through rtk rewrite first. Cases: no config file; a config.toml written by develop's rtk telemetry disable; [limits] 2/1, 0/0, status_max_files = 0 alone; -sb, -b, --short --branch, -s, --porcelain; rtk pipe -f git-status; git -C <other>; RTK_RECALL=0; sqlite and tee recovery; rtk config / rtk config --create. Invariants: under the hook | over-cap + accounting (shown + recalled = git status --porcelain) | non-default target (-C) | every decision (tracked / untracked / unmerged bucket, cap set / unset / zero). Mutation: defaults set back to Some(15) / Some(10) against the test suite. Savings unchanged from round 1 (no-config output is develop's).
Matrix: executed round 1 @3f0776a4; rerun this round on 28b13bd
CI @28b13bd6: all green (ubuntu, macos, windows, clippy, fmt, benchmark)

Thanks for the quick turnaround. The opt-in change does what it says for a fresh install. The remaining blocker is about configs rtk already wrote, and part of it is mine: in round 1 I said Option<usize> would clear it, and that was incomplete.

Blocks merge (1, frozen at round 1 — still open)

  1. src/core/config.rs:228 — rtk has been writing these two keys into users' config.toml since v0.30.1, so the caps switch on for existing installs. Config::save() writes the whole config. It runs on rtk config --create, and since v0.36.0 on every telemetry consent write too: the prompt during rtk init, and rtk telemetry enable|disable|forget. On develop, rtk telemetry disable with no prior config produces [limits] … status_max_files = 15 / status_max_untracked = 10. On that exact file, the 25 + 15 repo gives 42 lines on develop, but 27 lines ending ... +16 more [+16 hidden: rtk recall …] on this head. So for anyone who answered the telemetry prompt, this is still the round-1 regression. The PR cannot tell those auto-written values from a 15/10 a user chose, so reading the same keys with a new meaning is what has to change.
    What a fix has to satisfy (the rows I will rerun):
    • a config.toml written by develop (rtk telemetry disable or rtk config --create) → rtk git status output byte-identical to develop, with 25 + 15 entries and no ... +N more
    • a cap the user sets on purpose still applies, and recall still returns exactly the hidden entries (the 2/1 row)
    • rtk config / rtk config --create still round-trip
    • a test that fails if the defaults go back to 15/10 and one that fails on a develop-written config. Today, with the defaults set back to Some(15) / Some(10), all 4 tests in tests/git_status_limits_test.rs and the core::config unit tests still pass: the sandbox has 5 tracked + 4 untracked files, under both caps.
      One shape that meets all four: give the caps key names rtk has never written (for example git_status_max_files / git_status_max_untracked). The old keys then parse as unknown and are ignored, as other unknown keys are today. No user loses anything, because the old keys never did anything before this PR. Your call on the name, or on a different mechanism that meets the rows.

Optional — will not hold merge

  • Still open from round 1: the listing tests that call format_status_output read the developer's own config.toml. With status_max_files = 0 set locally, test_format_status_output_{thai_filename,mixed_changes,preserves_rename_and_conflict_lines,preserves_nested_untracked_paths} fail on this head. Calling format_status_inner with usize::MAX caps in those tests removes the dependency.
  • The commit message still describes the caps as applied without saying they are opt-in; one sentence saying the keys are unset by default would keep the changelog accurate.

Filed as follow-ups

  • none new this round (#3431 and #1483 from round 1 stand)

Checked and correct — no need to re-verify

  • No config file: output byte-identical to develop for plain rtk git status, -sb, -b, --short --branch.
  • rtk config with no file and rtk config --create: no out-of-range error, and neither status key is written. A set cap shows up in rtk config.
  • Caps 2/1: shown + recalled = git status --porcelain exactly. Tee hint tail -n +N stays native under the hook and returns the hidden tail. RTK_RECALL=0 gives the bare count. -C, detached HEAD and rtk pipe -f git-status apply the same caps. -s / --porcelain pass through.
  • status_max_files = 0 alone: tracked entries hidden, all 16 untracked shown. Real merge under 2/1: all 3 UU shown.
  • Every reader of the two fields was updated (config.rs, both call sites in git_cmd.rs, the three docs). The old 15/10 is claimed nowhere else.
  • Local gate on 28b13bd: fmt, clippy (0 warnings), cargo test --all 4043 passed.

Your questions

  • none this round.

Next

Stop rtk-written configs from turning the caps on (the four rows under blocker 1); after that this merges. Round 3 is the last one, so if anything small is left then, I'll fix it on the branch rather than send another list.
Rounds: 2/3. Threads: 1 open (blocker 1, waiting on the fix above), 0 resolved this round.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[config] status_max_files and status_max_untracked are declared in LimitsConfig but never read

3 participants