# fuckgit A binary literally named `git` that, when run inside a [jj][] (Jujutsu) repo, refuses to actually run git and instead tells you to use `jj`. [jj]: https://github.com/jj-vcs/jj ## Why AI coding agents (Claude Code, Cursor, et al.) keep running `git` inside jj-managed repos despite being told to use `jj`. In a colocated jj+git layout, that corrupts state. `fuckgit` shadows the real `git` on `PATH` and traps these invocations, redirecting them with the equivalent `jj` subcommand and inline `jj help ` output. It works on humans too, when they ask for it. ## Install ```sh cargo install fuckgit ``` This installs a binary named **`git`** (yes, really) to `~/.cargo/bin/git`. Make sure `~/.cargo/bin` is ahead of `/usr/bin` on `PATH`. Linux and macOS only. ## Behavior By default, `fuckgit` is a silent passthrough — every `git` invocation is forwarded to the real `git` on `PATH`. The redirect *only* fires when **all** of these are true: 1. You're inside a jj worktree (a `.jj` directory exists in the cwd or an ancestor — honoring `git -C `). 2. The intercept gate is open — at least one of: - `FUCKGIT_INTERCEPT=1` is set, OR - an AI-agent marker env var is set (currently just `CLAUDECODE`). 3. The subcommand isn't on the plumbing allowlist (`rev-parse`, `cat-file`, `for-each-ref`, …). 4. None of the safety env vars are set: `GIT_DIR`, `GIT_EXEC_PATH`, `CARGO`. 5. No top-level `--git-dir=` or `--work-tree=` flag is present. If any check fails, `git` behaves exactly as if `fuckgit` weren't installed. When the redirect *does* fire, you'll see something like: ``` Don't use git. Use jj. git status → jj st jj has no staging area; `jj st` shows the working-copy commit's diff. --- jj help status --- Show high-level repo status [default alias: st] … Override with FUCKGIT_PASSTHROUGH=1 or git --fucking-git ``` …and exit `2`. ## Escape hatches When you need real `git` and the intercept is firing: - `FUCKGIT_PASSTHROUGH=1 git ` — env var override - `git --fucking-git ` — flag override (the flag is stripped before exec'ing real git) ## Configuration | Variable | Effect | |-------------------------|------------------------------------------------------------------| | `FUCKGIT_INTERCEPT=1` | Open the intercept gate (opt in for your own jj repos). | | `FUCKGIT_PASSTHROUGH=1` | Force passthrough even with the gate open. | | `CLAUDECODE` | Set by Claude Code; opens the gate automatically. | To extend the AI-marker list, edit `AI_MARKERS` in `src/main.rs` and rebuild. ## Supported subcommands `fuckgit` knows the `jj` equivalent for these `git` subcommands: `status`, `log`, `commit`, `add`, `checkout`, `switch`, `restore`, `branch`, `merge`, `rebase`, `pull`, `push`, `fetch`, `clone`, `init`, `diff`, `show`, `reset`, `revert`, `cherry-pick`, `stash`, `tag`, `blame`, `remote`, `bisect`, `config`, `reflog`. Unknown subcommands fall through to a generic "use jj instead" + `jj help` output. ## License MIT — see [LICENSE](LICENSE).