From e9b02fe47556d35d4485726d50b31d52de05139e Mon Sep 17 00:00:00 2001 From: PascalCADET Date: Tue, 17 Feb 2026 13:36:11 +0100 Subject: [PATCH 1/2] feat: support git -C flag for cross-directory operations Add support for the git global flag -C which allows running git commands in a different directory. This is commonly used by Claude Code when working across multiple repositories. Changes: - main.rs: Add -C option to Git command variant, pass to git module - git.rs: Add git_command() helper that prepends -C , thread git_dir parameter through all run_* functions - rtk-rewrite.sh: Extract -C flags before subcommand matching, inject them into rewritten commands via _rtk_git_rewrite() helper --- .claude/hooks/rtk-rewrite.sh | 69 ++++++++++----- src/git.rs | 160 +++++++++++++++++++++++------------ src/main.rs | 104 +++++++++++++---------- 3 files changed, 214 insertions(+), 119 deletions(-) diff --git a/.claude/hooks/rtk-rewrite.sh b/.claude/hooks/rtk-rewrite.sh index 5c8bad0..b5ad3b2 100755 --- a/.claude/hooks/rtk-rewrite.sh +++ b/.claude/hooks/rtk-rewrite.sh @@ -58,29 +58,54 @@ fi REWRITTEN="" +# --- Extract git global flags (-C ) before subcommand matching --- +GIT_GLOBAL_FLAGS="" +GIT_SUBCMD="$MATCH_CMD" +GIT_SUBCMD_BODY="$CMD_BODY" +if echo "$MATCH_CMD" | grep -qE '^git[[:space:]]+-C[[:space:]]'; then + # Extract all -C flags (may be repeated) + GIT_GLOBAL_FLAGS=$(echo "$MATCH_CMD" | sed -E 's/^git[[:space:]]+((-C[[:space:]]+[^[:space:]]+[[:space:]]+)+).*/\1/') + # Rebuild MATCH_CMD/CMD_BODY without the global flags for subcommand matching + GIT_SUBCMD="git $(echo "$MATCH_CMD" | sed -E 's/^git[[:space:]]+(-C[[:space:]]+[^[:space:]]+[[:space:]]+)+//')" + GIT_SUBCMD_BODY="git $(echo "$CMD_BODY" | sed -E 's/^git[[:space:]]+(-C[[:space:]]+[^[:space:]]+[[:space:]]+)+//')" +fi + +# Helper: rewrite git command preserving -C flags +# Usage: _rtk_git_rewrite +# Reads GIT_SUBCMD_BODY, GIT_GLOBAL_FLAGS, ENV_PREFIX from outer scope +_rtk_git_rewrite() { + local subcmd="$1" + local rest="${GIT_SUBCMD_BODY#git ${subcmd}}" + if [ -n "$GIT_GLOBAL_FLAGS" ]; then + REWRITTEN="${ENV_PREFIX}rtk git ${GIT_GLOBAL_FLAGS}${subcmd}${rest}" + else + REWRITTEN="${ENV_PREFIX}rtk git ${subcmd}${rest}" + fi +} + # --- Git commands --- -if echo "$MATCH_CMD" | grep -qE '^git[[:space:]]+status([[:space:]]|$)'; then - REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^git status/rtk git status/')" -elif echo "$MATCH_CMD" | grep -qE '^git[[:space:]]+diff([[:space:]]|$)'; then - REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^git diff/rtk git diff/')" -elif echo "$MATCH_CMD" | grep -qE '^git[[:space:]]+log([[:space:]]|$)'; then - REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^git log/rtk git log/')" -elif echo "$MATCH_CMD" | grep -qE '^git[[:space:]]+add([[:space:]]|$)'; then - REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^git add/rtk git add/')" -elif echo "$MATCH_CMD" | grep -qE '^git[[:space:]]+commit([[:space:]]|$)'; then - REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^git commit/rtk git commit/')" -elif echo "$MATCH_CMD" | grep -qE '^git[[:space:]]+push([[:space:]]|$)'; then - REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^git push/rtk git push/')" -elif echo "$MATCH_CMD" | grep -qE '^git[[:space:]]+pull([[:space:]]|$)'; then - REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^git pull/rtk git pull/')" -elif echo "$MATCH_CMD" | grep -qE '^git[[:space:]]+branch([[:space:]]|$)'; then - REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^git branch/rtk git branch/')" -elif echo "$MATCH_CMD" | grep -qE '^git[[:space:]]+fetch([[:space:]]|$)'; then - REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^git fetch/rtk git fetch/')" -elif echo "$MATCH_CMD" | grep -qE '^git[[:space:]]+stash([[:space:]]|$)'; then - REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^git stash/rtk git stash/')" -elif echo "$MATCH_CMD" | grep -qE '^git[[:space:]]+show([[:space:]]|$)'; then - REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^git show/rtk git show/')" +if echo "$GIT_SUBCMD" | grep -qE '^git[[:space:]]+status([[:space:]]|$)'; then + _rtk_git_rewrite "status" +elif echo "$GIT_SUBCMD" | grep -qE '^git[[:space:]]+diff([[:space:]]|$)'; then + _rtk_git_rewrite "diff" +elif echo "$GIT_SUBCMD" | grep -qE '^git[[:space:]]+log([[:space:]]|$)'; then + _rtk_git_rewrite "log" +elif echo "$GIT_SUBCMD" | grep -qE '^git[[:space:]]+add([[:space:]]|$)'; then + _rtk_git_rewrite "add" +elif echo "$GIT_SUBCMD" | grep -qE '^git[[:space:]]+commit([[:space:]]|$)'; then + _rtk_git_rewrite "commit" +elif echo "$GIT_SUBCMD" | grep -qE '^git[[:space:]]+push([[:space:]]|$)'; then + _rtk_git_rewrite "push" +elif echo "$GIT_SUBCMD" | grep -qE '^git[[:space:]]+pull([[:space:]]|$)'; then + _rtk_git_rewrite "pull" +elif echo "$GIT_SUBCMD" | grep -qE '^git[[:space:]]+branch([[:space:]]|$)'; then + _rtk_git_rewrite "branch" +elif echo "$GIT_SUBCMD" | grep -qE '^git[[:space:]]+fetch([[:space:]]|$)'; then + _rtk_git_rewrite "fetch" +elif echo "$GIT_SUBCMD" | grep -qE '^git[[:space:]]+stash([[:space:]]|$)'; then + _rtk_git_rewrite "stash" +elif echo "$GIT_SUBCMD" | grep -qE '^git[[:space:]]+show([[:space:]]|$)'; then + _rtk_git_rewrite "show" # --- GitHub CLI (added: api, release) --- elif echo "$MATCH_CMD" | grep -qE '^gh[[:space:]]+(pr|issue|run|api|release)([[:space:]]|$)'; then diff --git a/src/git.rs b/src/git.rs index 8f8d891..34a4f41 100644 --- a/src/git.rs +++ b/src/git.rs @@ -19,24 +19,46 @@ pub enum GitCommand { Worktree, } -pub fn run(cmd: GitCommand, args: &[String], max_lines: Option, verbose: u8) -> Result<()> { +/// Create a git Command with optional -C flag +fn git_command(git_dir: Option<&str>) -> Command { + let mut cmd = Command::new("git"); + if let Some(dir) = git_dir { + cmd.arg("-C").arg(dir); + } + cmd +} + +pub fn run( + cmd: GitCommand, + args: &[String], + max_lines: Option, + git_dir: Option<&str>, + verbose: u8, +) -> Result<()> { match cmd { - GitCommand::Diff => run_diff(args, max_lines, verbose), - GitCommand::Log => run_log(args, max_lines, verbose), - GitCommand::Status => run_status(args, verbose), - GitCommand::Show => run_show(args, max_lines, verbose), - GitCommand::Add => run_add(args, verbose), - GitCommand::Commit { message } => run_commit(&message, verbose), - GitCommand::Push => run_push(args, verbose), - GitCommand::Pull => run_pull(args, verbose), - GitCommand::Branch => run_branch(args, verbose), - GitCommand::Fetch => run_fetch(args, verbose), - GitCommand::Stash { subcommand } => run_stash(subcommand.as_deref(), args, verbose), - GitCommand::Worktree => run_worktree(args, verbose), + GitCommand::Diff => run_diff(args, max_lines, git_dir, verbose), + GitCommand::Log => run_log(args, max_lines, git_dir, verbose), + GitCommand::Status => run_status(args, git_dir, verbose), + GitCommand::Show => run_show(args, max_lines, git_dir, verbose), + GitCommand::Add => run_add(args, git_dir, verbose), + GitCommand::Commit { message } => run_commit(&message, git_dir, verbose), + GitCommand::Push => run_push(args, git_dir, verbose), + GitCommand::Pull => run_pull(args, git_dir, verbose), + GitCommand::Branch => run_branch(args, git_dir, verbose), + GitCommand::Fetch => run_fetch(args, git_dir, verbose), + GitCommand::Stash { subcommand } => { + run_stash(subcommand.as_deref(), args, git_dir, verbose) + } + GitCommand::Worktree => run_worktree(args, git_dir, verbose), } } -fn run_diff(args: &[String], max_lines: Option, verbose: u8) -> Result<()> { +fn run_diff( + args: &[String], + max_lines: Option, + git_dir: Option<&str>, + verbose: u8, +) -> Result<()> { let timer = tracking::TimedExecution::start(); // Check if user wants stat output @@ -49,7 +71,7 @@ fn run_diff(args: &[String], max_lines: Option, verbose: u8) -> Result<() if wants_stat || !wants_compact { // User wants stat or explicitly no compacting - pass through directly - let mut cmd = Command::new("git"); + let mut cmd = git_command(git_dir); cmd.arg("diff"); for arg in args { cmd.arg(arg); @@ -77,7 +99,7 @@ fn run_diff(args: &[String], max_lines: Option, verbose: u8) -> Result<() } // Default RTK behavior: stat first, then compacted diff - let mut cmd = Command::new("git"); + let mut cmd = git_command(git_dir); cmd.arg("diff").arg("--stat"); for arg in args { @@ -95,7 +117,7 @@ fn run_diff(args: &[String], max_lines: Option, verbose: u8) -> Result<() println!("{}", stat_stdout.trim()); // Now get actual diff but compact it - let mut diff_cmd = Command::new("git"); + let mut diff_cmd = git_command(git_dir); diff_cmd.arg("diff"); for arg in args { diff_cmd.arg(arg); @@ -123,7 +145,12 @@ fn run_diff(args: &[String], max_lines: Option, verbose: u8) -> Result<() Ok(()) } -fn run_show(args: &[String], max_lines: Option, verbose: u8) -> Result<()> { +fn run_show( + args: &[String], + max_lines: Option, + git_dir: Option<&str>, + verbose: u8, +) -> Result<()> { let timer = tracking::TimedExecution::start(); // If user wants --stat or --format only, pass through @@ -136,7 +163,7 @@ fn run_show(args: &[String], max_lines: Option, verbose: u8) -> Result<() .any(|arg| arg.starts_with("--pretty") || arg.starts_with("--format")); if wants_stat_only || wants_format { - let mut cmd = Command::new("git"); + let mut cmd = git_command(git_dir); cmd.arg("show"); for arg in args { cmd.arg(arg); @@ -161,7 +188,7 @@ fn run_show(args: &[String], max_lines: Option, verbose: u8) -> Result<() } // Get raw output for tracking - let mut raw_cmd = Command::new("git"); + let mut raw_cmd = git_command(git_dir); raw_cmd.arg("show"); for arg in args { raw_cmd.arg(arg); @@ -172,7 +199,7 @@ fn run_show(args: &[String], max_lines: Option, verbose: u8) -> Result<() .unwrap_or_default(); // Step 1: one-line commit summary - let mut summary_cmd = Command::new("git"); + let mut summary_cmd = git_command(git_dir); summary_cmd.args(["show", "--no-patch", "--pretty=format:%h %s (%ar) <%an>"]); for arg in args { summary_cmd.arg(arg); @@ -187,7 +214,7 @@ fn run_show(args: &[String], max_lines: Option, verbose: u8) -> Result<() println!("{}", summary.trim()); // Step 2: --stat summary - let mut stat_cmd = Command::new("git"); + let mut stat_cmd = git_command(git_dir); stat_cmd.args(["show", "--stat", "--pretty=format:"]); for arg in args { stat_cmd.arg(arg); @@ -200,7 +227,7 @@ fn run_show(args: &[String], max_lines: Option, verbose: u8) -> Result<() } // Step 3: compacted diff - let mut diff_cmd = Command::new("git"); + let mut diff_cmd = git_command(git_dir); diff_cmd.args(["show", "--pretty=format:"]); for arg in args { diff_cmd.arg(arg); @@ -295,10 +322,15 @@ pub(crate) fn compact_diff(diff: &str, max_lines: usize) -> String { result.join("\n") } -fn run_log(args: &[String], _max_lines: Option, verbose: u8) -> Result<()> { +fn run_log( + args: &[String], + _max_lines: Option, + git_dir: Option<&str>, + verbose: u8, +) -> Result<()> { let timer = tracking::TimedExecution::start(); - let mut cmd = Command::new("git"); + let mut cmd = git_command(git_dir); cmd.arg("log"); // Check if user provided format flags @@ -523,12 +555,13 @@ fn filter_status_with_args(output: &str) -> String { } } -fn run_status(args: &[String], verbose: u8) -> Result<()> { +fn run_status(args: &[String], git_dir: Option<&str>, verbose: u8) -> Result<()> { let timer = tracking::TimedExecution::start(); // If user provided flags, apply minimal filtering if !args.is_empty() { - let output = Command::new("git") + let mut cmd = git_command(git_dir); + let output = cmd .arg("status") .args(args) .output() @@ -557,13 +590,13 @@ fn run_status(args: &[String], verbose: u8) -> Result<()> { // Default RTK compact mode (no args provided) // Get raw git status for tracking - let raw_output = Command::new("git") + let raw_output = git_command(git_dir) .args(["status"]) .output() .map(|o| String::from_utf8_lossy(&o.stdout).to_string()) .unwrap_or_default(); - let output = Command::new("git") + let output = git_command(git_dir) .args(["status", "--porcelain", "-b"]) .output() .context("Failed to run git status")?; @@ -585,10 +618,10 @@ fn run_status(args: &[String], verbose: u8) -> Result<()> { Ok(()) } -fn run_add(args: &[String], verbose: u8) -> Result<()> { +fn run_add(args: &[String], git_dir: Option<&str>, verbose: u8) -> Result<()> { let timer = tracking::TimedExecution::start(); - let mut cmd = Command::new("git"); + let mut cmd = git_command(git_dir); cmd.arg("add"); // Pass all arguments directly to git (flags like -A, -p, --all, etc.) @@ -614,7 +647,7 @@ fn run_add(args: &[String], verbose: u8) -> Result<()> { if output.status.success() { // Count what was added - let status_output = Command::new("git") + let status_output = git_command(git_dir) .args(["diff", "--cached", "--stat", "--shortstat"]) .output() .context("Failed to check staged files")?; @@ -657,14 +690,14 @@ fn run_add(args: &[String], verbose: u8) -> Result<()> { Ok(()) } -fn run_commit(message: &str, verbose: u8) -> Result<()> { +fn run_commit(message: &str, git_dir: Option<&str>, verbose: u8) -> Result<()> { let timer = tracking::TimedExecution::start(); if verbose > 0 { eprintln!("git commit -m \"{}\"", message); } - let output = Command::new("git") + let output = git_command(git_dir) .args(["commit", "-m", message]) .output() .context("Failed to run git commit")?; @@ -721,14 +754,14 @@ fn run_commit(message: &str, verbose: u8) -> Result<()> { Ok(()) } -fn run_push(args: &[String], verbose: u8) -> Result<()> { +fn run_push(args: &[String], git_dir: Option<&str>, verbose: u8) -> Result<()> { let timer = tracking::TimedExecution::start(); if verbose > 0 { eprintln!("git push"); } - let mut cmd = Command::new("git"); + let mut cmd = git_command(git_dir); cmd.arg("push"); for arg in args { cmd.arg(arg); @@ -782,14 +815,14 @@ fn run_push(args: &[String], verbose: u8) -> Result<()> { Ok(()) } -fn run_pull(args: &[String], verbose: u8) -> Result<()> { +fn run_pull(args: &[String], git_dir: Option<&str>, verbose: u8) -> Result<()> { let timer = tracking::TimedExecution::start(); if verbose > 0 { eprintln!("git pull"); } - let mut cmd = Command::new("git"); + let mut cmd = git_command(git_dir); cmd.arg("pull"); for arg in args { cmd.arg(arg); @@ -867,14 +900,14 @@ fn run_pull(args: &[String], verbose: u8) -> Result<()> { Ok(()) } -fn run_branch(args: &[String], verbose: u8) -> Result<()> { +fn run_branch(args: &[String], git_dir: Option<&str>, verbose: u8) -> Result<()> { let timer = tracking::TimedExecution::start(); if verbose > 0 { eprintln!("git branch"); } - let mut cmd = Command::new("git"); + let mut cmd = git_command(git_dir); cmd.arg("branch"); // If user passes flags like -d, -D, -m, pass through directly @@ -995,14 +1028,14 @@ fn filter_branch_output(output: &str) -> String { result.join("\n") } -fn run_fetch(args: &[String], verbose: u8) -> Result<()> { +fn run_fetch(args: &[String], git_dir: Option<&str>, verbose: u8) -> Result<()> { let timer = tracking::TimedExecution::start(); if verbose > 0 { eprintln!("git fetch"); } - let mut cmd = Command::new("git"); + let mut cmd = git_command(git_dir); cmd.arg("fetch"); for arg in args { cmd.arg(arg); @@ -1039,7 +1072,12 @@ fn run_fetch(args: &[String], verbose: u8) -> Result<()> { Ok(()) } -fn run_stash(subcommand: Option<&str>, args: &[String], verbose: u8) -> Result<()> { +fn run_stash( + subcommand: Option<&str>, + args: &[String], + git_dir: Option<&str>, + verbose: u8, +) -> Result<()> { let timer = tracking::TimedExecution::start(); if verbose > 0 { @@ -1048,7 +1086,7 @@ fn run_stash(subcommand: Option<&str>, args: &[String], verbose: u8) -> Result<( match subcommand { Some("list") => { - let output = Command::new("git") + let output = git_command(git_dir) .args(["stash", "list"]) .output() .context("Failed to run git stash list")?; @@ -1067,7 +1105,7 @@ fn run_stash(subcommand: Option<&str>, args: &[String], verbose: u8) -> Result<( timer.track("git stash list", "rtk git stash list", &raw, &filtered); } Some("show") => { - let mut cmd = Command::new("git"); + let mut cmd = git_command(git_dir); cmd.args(["stash", "show", "-p"]); for arg in args { cmd.arg(arg); @@ -1090,7 +1128,7 @@ fn run_stash(subcommand: Option<&str>, args: &[String], verbose: u8) -> Result<( } Some("pop") | Some("apply") | Some("drop") | Some("push") => { let sub = subcommand.unwrap(); - let mut cmd = Command::new("git"); + let mut cmd = git_command(git_dir); cmd.args(["stash", sub]); for arg in args { cmd.arg(arg); @@ -1121,7 +1159,7 @@ fn run_stash(subcommand: Option<&str>, args: &[String], verbose: u8) -> Result<( } _ => { // Default: git stash (push) - let mut cmd = Command::new("git"); + let mut cmd = git_command(git_dir); cmd.arg("stash"); for arg in args { cmd.arg(arg); @@ -1177,7 +1215,7 @@ fn filter_stash_list(output: &str) -> String { result.join("\n") } -fn run_worktree(args: &[String], verbose: u8) -> Result<()> { +fn run_worktree(args: &[String], git_dir: Option<&str>, verbose: u8) -> Result<()> { let timer = tracking::TimedExecution::start(); if verbose > 0 { @@ -1190,7 +1228,7 @@ fn run_worktree(args: &[String], verbose: u8) -> Result<()> { }); if has_action { - let mut cmd = Command::new("git"); + let mut cmd = git_command(git_dir); cmd.arg("worktree"); for arg in args { cmd.arg(arg); @@ -1225,7 +1263,7 @@ fn run_worktree(args: &[String], verbose: u8) -> Result<()> { } // Default: list mode - let output = Command::new("git") + let output = git_command(git_dir) .args(["worktree", "list"]) .output() .context("Failed to run git worktree list")?; @@ -1268,13 +1306,13 @@ fn filter_worktree_list(output: &str) -> String { } /// Runs an unsupported git subcommand by passing it through directly -pub fn run_passthrough(args: &[OsString], verbose: u8) -> Result<()> { +pub fn run_passthrough(args: &[OsString], git_dir: Option<&str>, verbose: u8) -> Result<()> { let timer = tracking::TimedExecution::start(); if verbose > 0 { eprintln!("git passthrough: {:?}", args); } - let status = Command::new("git") + let status = git_command(git_dir) .args(args) .status() .context("Failed to run git")?; @@ -1515,6 +1553,24 @@ no changes added to commit (use "git add" and/or "git commit -a") assert!(result.contains("ทดสอบ.rs")); } + #[test] + fn test_git_command_without_dir() { + let cmd = git_command(None); + assert_eq!(cmd.get_program(), "git"); + let args: Vec<_> = cmd.get_args().collect(); + assert!(args.is_empty()); + } + + #[test] + fn test_git_command_with_dir() { + let cmd = git_command(Some("/tmp/repo")); + assert_eq!(cmd.get_program(), "git"); + let args: Vec<_> = cmd.get_args().collect(); + assert_eq!(args.len(), 2); + assert_eq!(args[0], "-C"); + assert_eq!(args[1], "/tmp/repo"); + } + #[test] fn test_format_status_output_emoji_filename() { let porcelain = "## main\nA 🎉-party.txt\n M 日本語ファイル.rs\n"; diff --git a/src/main.rs b/src/main.rs index 5bec4da..8170d45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -121,6 +121,10 @@ enum Commands { /// Git commands with compact output Git { + /// Run as if git was started in + #[arg(short = 'C', allow_hyphen_values = true)] + directory: Option, + #[command(subcommand)] command: GitCommands, }, @@ -850,52 +854,62 @@ fn main() -> Result<()> { local_llm::run(&file, &model, force_download, cli.verbose)?; } - Commands::Git { command } => match command { - GitCommands::Diff { args } => { - git::run(git::GitCommand::Diff, &args, None, cli.verbose)?; - } - GitCommands::Log { args } => { - git::run(git::GitCommand::Log, &args, None, cli.verbose)?; - } - GitCommands::Status { args } => { - git::run(git::GitCommand::Status, &args, None, cli.verbose)?; - } - GitCommands::Show { args } => { - git::run(git::GitCommand::Show, &args, None, cli.verbose)?; - } - GitCommands::Add { args } => { - git::run(git::GitCommand::Add, &args, None, cli.verbose)?; - } - GitCommands::Commit { message } => { - git::run(git::GitCommand::Commit { message }, &[], None, cli.verbose)?; - } - GitCommands::Push { args } => { - git::run(git::GitCommand::Push, &args, None, cli.verbose)?; - } - GitCommands::Pull { args } => { - git::run(git::GitCommand::Pull, &args, None, cli.verbose)?; - } - GitCommands::Branch { args } => { - git::run(git::GitCommand::Branch, &args, None, cli.verbose)?; - } - GitCommands::Fetch { args } => { - git::run(git::GitCommand::Fetch, &args, None, cli.verbose)?; - } - GitCommands::Stash { subcommand, args } => { - git::run( - git::GitCommand::Stash { subcommand }, - &args, - None, - cli.verbose, - )?; - } - GitCommands::Worktree { args } => { - git::run(git::GitCommand::Worktree, &args, None, cli.verbose)?; - } - GitCommands::Other(args) => { - git::run_passthrough(&args, cli.verbose)?; + Commands::Git { directory, command } => { + let git_dir = directory.as_deref(); + match command { + GitCommands::Diff { args } => { + git::run(git::GitCommand::Diff, &args, None, git_dir, cli.verbose)?; + } + GitCommands::Log { args } => { + git::run(git::GitCommand::Log, &args, None, git_dir, cli.verbose)?; + } + GitCommands::Status { args } => { + git::run(git::GitCommand::Status, &args, None, git_dir, cli.verbose)?; + } + GitCommands::Show { args } => { + git::run(git::GitCommand::Show, &args, None, git_dir, cli.verbose)?; + } + GitCommands::Add { args } => { + git::run(git::GitCommand::Add, &args, None, git_dir, cli.verbose)?; + } + GitCommands::Commit { message } => { + git::run( + git::GitCommand::Commit { message }, + &[], + None, + git_dir, + cli.verbose, + )?; + } + GitCommands::Push { args } => { + git::run(git::GitCommand::Push, &args, None, git_dir, cli.verbose)?; + } + GitCommands::Pull { args } => { + git::run(git::GitCommand::Pull, &args, None, git_dir, cli.verbose)?; + } + GitCommands::Branch { args } => { + git::run(git::GitCommand::Branch, &args, None, git_dir, cli.verbose)?; + } + GitCommands::Fetch { args } => { + git::run(git::GitCommand::Fetch, &args, None, git_dir, cli.verbose)?; + } + GitCommands::Stash { subcommand, args } => { + git::run( + git::GitCommand::Stash { subcommand }, + &args, + None, + git_dir, + cli.verbose, + )?; + } + GitCommands::Worktree { args } => { + git::run(git::GitCommand::Worktree, &args, None, git_dir, cli.verbose)?; + } + GitCommands::Other(args) => { + git::run_passthrough(&args, git_dir, cli.verbose)?; + } } - }, + } Commands::Gh { subcommand, args } => { gh_cmd::run(&subcommand, &args, cli.verbose, cli.ultra_compact)?; From 7d849036430fab331a433ca334fdf350eeaeaa6d Mon Sep 17 00:00:00 2001 From: PascalCADET Date: Tue, 17 Feb 2026 14:37:20 +0100 Subject: [PATCH 2/2] docs: update documentation for git -C flag support Add PR #171 feature to CLAUDE.md (implementation details + fork-specific features), README.md (usage example), and CHANGELOG.md (release entry). --- CHANGELOG.md | 1 + CLAUDE.md | 8 ++++++++ README.md | 1 + 3 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1b0c3d..ae0dd69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features * add hook audit mode for verifiable rewrite metrics ([#151](https://github.com/rtk-ai/rtk/issues/151)) ([70c3786](https://github.com/rtk-ai/rtk/commit/70c37867e7282ee0ccf200022ecef8c6e4ab52f4)) +* support git `-C ` flag for cross-directory operations ([#171](https://github.com/rtk-ai/rtk/pull/171)) ([e9b02fe](https://github.com/rtk-ai/rtk/commit/e9b02fe)) ## [0.19.0](https://github.com/rtk-ai/rtk/compare/v0.18.1...v0.19.0) (2026-02-16) diff --git a/CLAUDE.md b/CLAUDE.md index fc8002e..d28b665 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -192,6 +192,8 @@ rtk gain --history | grep proxy - Uses `trailing_var_arg = true` + `allow_hyphen_values = true` to properly handle git flags - Auto-detects `--merges` flag to avoid conflicting with `--no-merges` injection - Propagates git exit codes for CI/CD reliability (PR #5 fix) +- Supports `git -C ` for cross-directory operations via `git_command()` helper +- `git_dir` parameter threaded through all `run_*` functions to prepend `-C ` to git invocations **Output Filtering Strategy** - Compact mode: Show only summary/failures @@ -378,6 +380,12 @@ pub fn execute_with_filter(cmd: &str, args: &[&str]) -> Result<()> { - **Features**: Exit code preservation, error grouping, consistent formatting - **Testing**: Validated on production T3 Stack project (methode-aristote/app) +### PR #171: Git `-C ` Flag Support (2026-02-17) +- **Feature**: Support `git -C ` flag for cross-directory git operations +- **Implementation**: `git_command()` helper prepends `-C ` to all git invocations, `git_dir` threaded through all 12 `run_*` functions +- **Hook**: `_rtk_git_rewrite()` helper extracts and preserves `-C` flags before subcommand matching +- **Impact**: All git commands can now operate on repos outside the current directory (e.g., `rtk git -C /other/repo status`) + ### Python & Go Support (2026-02-12) - **Python Commands**: 3 commands for Python development workflows - `rtk ruff check/format`: Ruff linter/formatter with JSON (check) and text (format) parsing (80%+ reduction) diff --git a/README.md b/README.md index 4960e85..4ff8b19 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,7 @@ rtk git add # → "ok ✓" rtk git commit -m "msg" # → "ok ✓ abc1234" rtk git push # → "ok ✓ main" rtk git pull # → "ok ✓ 3 files +10 -2" +rtk git -C /path/to/repo status # Cross-directory operations ``` ### Commands