mirror of
https://github.com/rtk-ai/rtk.git
synced 2026-09-19 07:33:17 +08:00
7931993da8
`-W`/`--function-context` does not make `git log` emit a patch -- checked against git 2.53, where it is byte-identical to plain log -- so routing it raw turned `rtk git log -W` into a 1 MB unfiltered dump of the whole history. The flags that *do* imply a patch there are `-U<n>` and the combined-diff forms `--cc`/`-c`/`--remerge-diff`, whose patch the log compaction was discarding with no tee to recover it from. That raw route is `run_passthrough`, which streams straight to the terminal, so RTK's default limit has to be in the args or it never applies at all: every one of those spellings printed the entire history, 411k lines against 50 for plain `rtk git log`. It now carries `-10` whenever the user named no limit of their own -- the limit only, since `--no-merges` would gut `--cc`/`-c`, whose whole purpose is the merge diff. `--exit-code` and `--quiet` are `diff`'s, not `log`'s. Both leave `git log`'s output byte-identical to plain log, so there is no shape for it to escape to, and claiming them cost the whole history for nothing. In `diff` they do earn the raw route: the body is replaced by a report and git exits non-zero, which RTK's stat probe inherited, its early return on failure then throwing the entire diff away. They join `--check`, whose handling already existed for the same reason. `--quiet` then splits `diff` from `show`, so each states its own raw-output grammar rather than sharing one. In `show` it is a synonym of `-s` -- exit 0, body suppressed -- so it belongs on the compact path with `-s`/`--no-patch`, where the summary is exactly what it asks for; claiming it raw-passed the very header its synonyms compact. That split's short-flag rule holds for `-p`/`-u`/`-U`, which only restate `diff`/`show`'s patch default, but not for `-c`: it is `--cc`'s combined-diff form, and `compact_diff` reads a combined diff's two marker columns as one, so `git show -c` on a merge came back as `+54 -8` against git's own 156 insertions and 0 deletions. It takes the raw route its long form already took. `--diff-merges=<format>` names those same shapes a second way, so it routes on the format rather than on the flag: every value but `off`/`none` emits a patch, which `log` cannot compact, while only `c`/`cc`/`combined`/`dense-combined` produce the two marker columns, which is all `show` needs the raw route for. Both value spellings count -- git takes the format attached or as the next token, and `git log --diff-merges c` is accepted. `--unified=5` and `-U5` are one option and now take one path. `run_show`'s format gate keeps `--oneline` out, unlike `run_log`'s: there the gate routes the whole command to a raw passthrough, and `--oneline` only means the user's one-line summary outranks RTK's own one-line summary. Claiming it gave up compaction entirely to fix that -- 116 KB against 31 KB on a real commit, on the one metric this tool exists for. `git worktree`'s write actions compact to "ok" again unless the user asked for a report with `--dry-run`/`-n` or `--verbose`/`-v`. Keeping every non-empty report was meant for `prune --dry-run`, whose list is the point of the command, but `add` also speaks on success: its two progress lines came back instead of "ok", and in the wrong order, since one is on stdout and the other on stderr. search: the `=`-unwrapping applies to the attached spelling only. Applied to a separate-token value it ate the first character of the user's pattern, so `rtk rg -e '=='` searched for `=` and matched lines the real command does not. And the filename negation is engine-specific in both directions -- grep's `-h` is `--no-filename` where rg's is `--help`, and rg's `-I` is `--no-filename` where grep's is `--binary-files` -- so rg's spelling was forwarded, beat RTK's `--with-filename`, and ran the whole search a second time. `-N` and `--no-line-number` are ripgrep's alone for the same reason: grep 3.12 exits 2 on both, so recognising them there reported a match for a command the engine refuses to run. Recursion is not a filename request. `-r`/`-R`/`--recursive` shared the last-spelling-wins slot with `-H`/`-h`, which made the result depend on typing order -- `grep -rh` dropped the prefix and `grep -hr` kept it, where real grep drops it either way, because `-r` only makes the search span several files. It now feeds the fallback that the paths already feed, and only `-H`/`-h` override. dotnet: the `-l` alias is single-dash only. `--l` is not a spelling dotnet accepts, so treating it as the logger suppressed RTK's injection for a command that then wrote no TRX at all. Two tests were asserting less than they appeared to: the stdin fixture lived inside the directory being searched, so an engine that ignored stdin and walked the cwd still produced the asserted line, and the blob-show test built its tokens with log's grammar where `run_show` uses diff's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
106 lines
4.0 KiB
Rust
106 lines
4.0 KiB
Rust
#![cfg(unix)]
|
|
//! Which stdin the engines actually read. These run in CI as integration tests rather than
|
|
//! `#[ignore]`d unit tests: `CARGO_BIN_EXE_rtk` guarantees a built binary, so the fix they
|
|
//! cover is exercised by `cargo test --all` instead of only by a flag nobody passes.
|
|
|
|
use std::process::{Command, Stdio};
|
|
|
|
fn rtk() -> Command {
|
|
let mut cmd = Command::new(env!("CARGO_BIN_EXE_rtk"));
|
|
cmd.env("LC_ALL", "C");
|
|
cmd
|
|
}
|
|
|
|
/// Each test guards on the engine it actually drives -- guarding an rg test on grep (or the
|
|
/// reverse) turns a missing tool into a silent pass rather than a skip.
|
|
fn engine_available(engine: &str) -> bool {
|
|
Command::new(engine)
|
|
.arg("--version")
|
|
.output()
|
|
.map(|o| o.status.success())
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
#[test]
|
|
fn rg_shows_filenames_when_stdin_is_not_a_pipe() {
|
|
if !engine_available("rg") {
|
|
return;
|
|
}
|
|
// `rtk rg -z foo < /dev/null` in a multi-file dir used to drop filenames: stdin being a
|
|
// non-terminal, non-pipe redirect was misread as "the engine reads stdin," routing into the
|
|
// streaming path, which can't discover "multiple files" the way the buffered path does.
|
|
// Real rg searches the cwd here, not stdin.
|
|
let dir = tempfile::tempdir().expect("test setup");
|
|
std::fs::write(dir.path().join("a.txt"), "foo one\n").expect("test setup");
|
|
std::fs::write(dir.path().join("b.txt"), "foo two\n").expect("test setup");
|
|
|
|
let output = rtk()
|
|
.args(["rg", "-z", "foo"])
|
|
.current_dir(dir.path())
|
|
.stdin(Stdio::null())
|
|
.output()
|
|
.expect("failed to run rtk rg");
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
assert!(stdout.contains("a.txt"), "filename missing: {stdout}");
|
|
assert!(stdout.contains("b.txt"), "filename missing: {stdout}");
|
|
}
|
|
|
|
#[test]
|
|
fn a_single_matching_file_still_gets_its_name() {
|
|
if !engine_available("rg") {
|
|
return;
|
|
}
|
|
// The engine walked the cwd itself, so the filename is the only way to place the match --
|
|
// real rg prints it even when exactly one file matched.
|
|
let dir = tempfile::tempdir().expect("test setup");
|
|
std::fs::write(dir.path().join("only.txt"), "foo here\n").expect("test setup");
|
|
std::fs::write(dir.path().join("other.txt"), "nothing\n").expect("test setup");
|
|
|
|
let output = rtk()
|
|
.args(["rg", "foo"])
|
|
.current_dir(dir.path())
|
|
.stdin(Stdio::null())
|
|
.output()
|
|
.expect("failed to run rtk rg");
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
assert!(stdout.contains("only.txt"), "filename missing: {stdout}");
|
|
}
|
|
|
|
#[test]
|
|
fn both_engines_read_a_redirected_file_on_stdin() {
|
|
// A regular file on stdin *is* read by both engines (rg's own is_readable_stdin counts
|
|
// files and sockets, not just FIFOs), so the search must return that file's match rather
|
|
// than the cwd's.
|
|
let dir = tempfile::tempdir().expect("test setup");
|
|
std::fs::write(dir.path().join("a.txt"), "foo from the cwd\n").expect("test setup");
|
|
// The fixture lives outside the searched directory: inside it, an engine that ignored
|
|
// stdin and walked the cwd would still print "foo from stdin" and the test would pass.
|
|
let elsewhere = tempfile::tempdir().expect("test setup");
|
|
let piped = elsewhere.path().join("piped.log");
|
|
std::fs::write(&piped, "foo from stdin\n").expect("test setup");
|
|
|
|
for engine in ["grep", "rg"] {
|
|
if !engine_available(engine) {
|
|
continue;
|
|
}
|
|
let output = rtk()
|
|
.args([engine, "foo"])
|
|
.current_dir(dir.path())
|
|
.stdin(std::fs::File::open(&piped).expect("test setup"))
|
|
.output()
|
|
.expect("failed to run rtk");
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
assert!(
|
|
stdout.contains("foo from stdin"),
|
|
"{engine} did not read stdin: {stdout}"
|
|
);
|
|
assert!(
|
|
!stdout.contains("foo from the cwd"),
|
|
"{engine} walked the cwd instead of reading stdin: {stdout}"
|
|
);
|
|
}
|
|
}
|