Skip to content
Draft
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
98 changes: 59 additions & 39 deletions src/cmds/git/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,43 @@ fn run_log(
let mut cmd = git_cmd(global_args);
cmd.arg("log");

let (log_args, limit, user_set_limit, has_format_flag) = build_log_args(args);
cmd.args(log_args);

let result = exec_capture(&mut cmd).context("Failed to run git log")?;

if !result.success() {
eprintln!("{}", result.stderr);
return Ok(result.exit_code);
}

if verbose > 0 {
eprintln!("Git log output:");
}

// Post-process: truncate long messages, cap lines only if RTK set the default
let filtered = filter_log_output(&result.stdout, limit, user_set_limit, has_format_flag);
let filtered = never_worse(&result.stdout, &filtered).to_string();
println!("{}", filtered);

timer.track(
&format!("git log {}", args.join(" ")),
&format!("rtk git log {}", args.join(" ")),
&result.stdout,
&filtered,
);

Ok(0)
}

/// Build the arguments that RTK adds around the user's git log arguments.
///
/// Merge commits are deliberately left in the history. They carry topology and
/// often identify the repository's actual integration state, so silently adding
/// --no-merges would make the compact output semantically incorrect.
fn build_log_args(args: &[String]) -> (Vec<String>, usize, bool, bool) {
let mut log_args = Vec::new();

// Check if user provided format flags
let has_format_flag = args.iter().any(|arg| {
arg.starts_with("--oneline") || arg.starts_with("--pretty") || arg.starts_with("--format")
Expand All @@ -452,7 +489,7 @@ fn run_log(
// Use %b (body) to preserve first line of commit body for agent context
// (BREAKING CHANGE, Closes #xxx, design notes)
if !has_format_flag {
cmd.args(["--pretty=format:%h %s (%ar) <%an>%n%b%n---END---"]);
log_args.push("--pretty=format:%h %s (%ar) <%an>%n%b%n---END---".to_string());
}

// Determine limit: respect user's explicit -N flag, use sensible defaults otherwise
Expand All @@ -462,52 +499,18 @@ fn run_log(
(n, true)
} else if has_format_flag {
// --oneline / --pretty without -N: user wants compact output, allow more
cmd.arg("-50");
log_args.push("-50".to_string());
(50, false)
} else {
// No flags at all: default to 10
cmd.arg("-10");
log_args.push("-10".to_string());
(10, false)
};

// Only add --no-merges if user didn't explicitly request merge commits
let wants_merges = args
.iter()
.any(|arg| arg == "--merges" || arg == "--min-parents=2" || arg == "--no-merges");
// Don't add --no-merges if user explicitly requested merges or an exact count (-n N / --max-count)
if !wants_merges && !has_limit_flag {
cmd.arg("--no-merges");
}

// Pass all user arguments
for arg in args {
cmd.arg(arg);
}

let result = exec_capture(&mut cmd).context("Failed to run git log")?;

if !result.success() {
eprintln!("{}", result.stderr);
return Ok(result.exit_code);
}

if verbose > 0 {
eprintln!("Git log output:");
}

// Post-process: truncate long messages, cap lines only if RTK set the default
let filtered = filter_log_output(&result.stdout, limit, user_set_limit, has_format_flag);
let filtered = never_worse(&result.stdout, &filtered).to_string();
println!("{}", filtered);
log_args.extend(args.iter().cloned());

timer.track(
&format!("git log {}", args.join(" ")),
&format!("rtk git log {}", args.join(" ")),
&result.stdout,
&filtered,
);

Ok(0)
(log_args, limit, user_set_limit, has_format_flag)
}

/// Filter git log output: truncate long messages, cap lines
Expand Down Expand Up @@ -2731,6 +2734,23 @@ A added.rs
assert_eq!(parse_user_limit(&args), None);
}

#[test]
fn test_build_log_args_preserves_merge_commits() {
let (args, limit, user_set_limit, user_format) = build_log_args(&[]);

assert_eq!(limit, 10);
assert!(!user_set_limit);
assert!(!user_format);
assert!(
!args.iter().any(|arg| arg == "--no-merges"),
"default git log must not hide merge commits"
);
assert!(
args.iter().any(|arg| arg.starts_with("--pretty=format:")),
"default git log should still use RTK's compact format"
);
}

#[test]
fn test_filter_log_output_token_savings() {
fn count_tokens(text: &str) -> usize {
Expand Down