Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions src/cmds/git/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,13 @@ fn run_log(
verbose: u8,
global_args: &[String],
) -> Result<i32> {
if requests_raw_log_output(args) {
let passthrough_args: Vec<OsString> = std::iter::once(OsString::from("log"))
.chain(args.iter().map(OsString::from))
.collect();
return run_passthrough(&passthrough_args, global_args, verbose);
}

let timer = tracking::TimedExecution::start();

let mut cmd = git_cmd(global_args);
Expand Down Expand Up @@ -510,6 +517,17 @@ fn run_log(
Ok(0)
}

fn requests_raw_log_output(args: &[String]) -> bool {
args.iter()
.take_while(|arg| *arg != "--")
.any(|arg| {
matches!(
arg.as_str(),
"-p" | "-u" | "--patch" | "--patch-with-raw" | "--patch-with-stat"
)
})
}

/// Filter git log output: truncate long messages, cap lines
/// Parse the user-specified limit from git log args.
/// Handles: -20, -n 20, --max-count=20, --max-count 20
Expand Down Expand Up @@ -2731,6 +2749,36 @@ A added.rs
assert_eq!(parse_user_limit(&args), None);
}

#[test]
fn test_patch_log_flags_request_raw_output() {
for flag in ["-p", "-u", "--patch", "--patch-with-raw", "--patch-with-stat"] {
let args = vec![flag.to_string()];
assert!(requests_raw_log_output(&args), "{flag} should pass through");
}
}

#[test]
fn test_patch_flag_after_pathspec_separator_is_ignored() {
// `git log -- -p` means "show history for a path literally named -p",
// not "show patches" — the flag lookalike appears after `--`.
let args = vec!["--".to_string(), "-p".to_string()];
assert!(
!requests_raw_log_output(&args),
"-p after -- is a pathspec, not a patch flag, and should stay on the filtered path"
);
}

#[test]
fn test_non_patch_log_flags_remain_filtered() {
for flag in ["--no-patch", "--stat", "--oneline", "--format=%H"] {
let args = vec![flag.to_string()];
assert!(
!requests_raw_log_output(&args),
"{flag} should remain on the filtered log path"
);
}
}

#[test]
fn test_filter_log_output_token_savings() {
fn count_tokens(text: &str) -> usize {
Expand Down
26 changes: 26 additions & 0 deletions tests/guard_integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,32 @@ fn git_stash_list_no_stashes_emits_empty() {
assert_eq!(code, Some(0));
}

#[test]
fn git_log_patch_output_matches_raw_git() {
let dir = init_git_repo();
std::fs::write(
dir.path().join("history.txt"),
"STRIPE_KEY=sk_live_FAKE1234567890\n",
)
.expect("write history fixture");
git_in_dir(dir.path(), &["add", "history.txt"]);
git_in_dir(dir.path(), &["commit", "-q", "-m", "add history fixture"]);

let raw = Command::new("git")
.args(["log", "-p", "--all"])
.current_dir(dir.path())
.output()
.expect("spawn raw git log");
assert!(raw.status.success());

let (rtk_stdout, rtk_stderr, rtk_code) =
rtk_output_in_dir(dir.path(), &["git", "log", "-p", "--all"]);

assert_eq!(rtk_code, Some(0), "rtk stderr: {rtk_stderr}");
assert_eq!(rtk_stdout.as_bytes(), raw.stdout.as_slice());
assert!(rtk_stdout.contains("STRIPE_KEY=sk_live_FAKE1234567890"));
}

#[test]
fn git_stash_show_no_stash_emits_empty_and_propagates_failure() {
// Regression: previously printed "Empty stash" and returned Ok(0), masking
Expand Down
Loading