fuckgit installs as a Cargo bin literally named `git` (via `[[bin]] name = "git"`). When invoked inside a jj worktree by an AI agent, it prints the equivalent `jj` subcommand plus the live `jj help <sub>` output to stderr and exits 2 — the AI sees a clear failure and the right correction. Outside a jj repo, or with no intercept marker, it is a transparent passthrough. The intercept gate fires only when: - effective cwd (after honoring `git -C`) is inside a `.jj` worktree - FUCKGIT_INTERCEPT=1 or an AI marker (CLAUDECODE) is set - subcommand is not on the plumbing allowlist (rev-parse, cat-file, for-each-ref, ...) - GIT_DIR / GIT_EXEC_PATH / CARGO are not set - no top-level --git-dir= / --work-tree= flag is present Otherwise we walk PATH, skip self by canonicalized current_exe(), and exec() the first other `git` we find — preserving signals, exit code, and TTY ownership. Escape hatches: FUCKGIT_PASSTHROUGH=1 env var, --fucking-git flag (stripped before exec). FUCKGIT_DEPTH guard aborts at depth >= 2 to prevent self-recursion. Linux and macOS, zero runtime dependencies.
164 lines
4.0 KiB
Rust
164 lines
4.0 KiB
Rust
use std::env;
|
|
use std::ffi::OsString;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
pub struct ParsedArgv {
|
|
pub effective_cwd: PathBuf,
|
|
pub has_git_dir_or_work_tree: bool,
|
|
pub subcommand: Option<String>,
|
|
pub is_version_query: bool,
|
|
}
|
|
|
|
const BOOL_TOP_FLAGS: &[&str] = &[
|
|
"-p",
|
|
"-P",
|
|
"--paginate",
|
|
"--no-pager",
|
|
"--bare",
|
|
"--no-replace-objects",
|
|
"--html-path",
|
|
"--man-path",
|
|
"--info-path",
|
|
];
|
|
|
|
pub fn parse_top_level(argv: &[OsString]) -> ParsedArgv {
|
|
let mut effective_cwd = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
|
let mut has_git_dir_or_work_tree = false;
|
|
let mut subcommand: Option<String> = None;
|
|
let mut is_version_query = false;
|
|
|
|
let mut i = 0;
|
|
while i < argv.len() {
|
|
let arg = &argv[i];
|
|
|
|
if arg == "-C" {
|
|
if let Some(p) = argv.get(i + 1) {
|
|
effective_cwd = effective_cwd.join(Path::new(p));
|
|
i += 2;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if arg == "-c" {
|
|
i += 2;
|
|
continue;
|
|
}
|
|
|
|
if arg == "--git-dir" || arg == "--work-tree" {
|
|
has_git_dir_or_work_tree = true;
|
|
i += 2;
|
|
continue;
|
|
}
|
|
|
|
let Some(s) = arg.to_str() else { break };
|
|
|
|
if s.starts_with("--git-dir=") || s.starts_with("--work-tree=") {
|
|
has_git_dir_or_work_tree = true;
|
|
i += 1;
|
|
continue;
|
|
}
|
|
|
|
if s.starts_with("--namespace=")
|
|
|| s.starts_with("--config-env=")
|
|
|| s.starts_with("--exec-path=")
|
|
|| s == "--exec-path"
|
|
{
|
|
i += 1;
|
|
continue;
|
|
}
|
|
|
|
if BOOL_TOP_FLAGS.contains(&s) {
|
|
i += 1;
|
|
continue;
|
|
}
|
|
|
|
if s == "--version" || s == "-v" {
|
|
is_version_query = true;
|
|
i += 1;
|
|
continue;
|
|
}
|
|
|
|
if s == "--help" || s == "-h" {
|
|
subcommand = Some("help".to_string());
|
|
break;
|
|
}
|
|
|
|
if !s.starts_with('-') {
|
|
subcommand = Some(s.to_string());
|
|
break;
|
|
}
|
|
|
|
// Unknown leading flag — bail out; it presumably belongs to a subcommand
|
|
// we couldn't identify.
|
|
break;
|
|
}
|
|
|
|
ParsedArgv {
|
|
effective_cwd,
|
|
has_git_dir_or_work_tree,
|
|
subcommand,
|
|
is_version_query,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn parse(args: &[&str]) -> ParsedArgv {
|
|
let owned: Vec<OsString> = args.iter().map(OsString::from).collect();
|
|
parse_top_level(&owned)
|
|
}
|
|
|
|
#[test]
|
|
fn bare_subcommand() {
|
|
let p = parse(&["status"]);
|
|
assert_eq!(p.subcommand.as_deref(), Some("status"));
|
|
assert!(!p.is_version_query);
|
|
assert!(!p.has_git_dir_or_work_tree);
|
|
}
|
|
|
|
#[test]
|
|
fn version_query() {
|
|
let p = parse(&["--version"]);
|
|
assert!(p.is_version_query);
|
|
let p = parse(&["-v"]);
|
|
assert!(p.is_version_query);
|
|
}
|
|
|
|
#[test]
|
|
fn dash_capital_c_changes_cwd() {
|
|
let p = parse(&["-C", "/tmp", "status"]);
|
|
assert_eq!(p.subcommand.as_deref(), Some("status"));
|
|
assert_eq!(p.effective_cwd, PathBuf::from("/tmp"));
|
|
}
|
|
|
|
#[test]
|
|
fn git_dir_forces_passthrough_flag() {
|
|
let p = parse(&["--git-dir=/foo", "status"]);
|
|
assert!(p.has_git_dir_or_work_tree);
|
|
|
|
let p = parse(&["--work-tree", "/foo", "status"]);
|
|
assert!(p.has_git_dir_or_work_tree);
|
|
}
|
|
|
|
#[test]
|
|
fn config_kv_consumes_two_args() {
|
|
let p = parse(&["-c", "user.name=Foo", "log"]);
|
|
assert_eq!(p.subcommand.as_deref(), Some("log"));
|
|
}
|
|
|
|
#[test]
|
|
fn paginate_skipped() {
|
|
let p = parse(&["--paginate", "log"]);
|
|
assert_eq!(p.subcommand.as_deref(), Some("log"));
|
|
}
|
|
|
|
#[test]
|
|
fn help_short_and_long() {
|
|
assert_eq!(parse(&["--help"]).subcommand.as_deref(), Some("help"));
|
|
assert_eq!(parse(&["-h"]).subcommand.as_deref(), Some("help"));
|
|
}
|
|
}
|