git-surgeon 0.1.0

Surgical git hunk control for AI agents
# git-surgeon

Surgical, non-interactive git hunk control for AI agents. Think `git add -p` for
agents.

Without hunk-level staging, an AI agent that needs to commit two independent
changes in the same file has to edit one change out, commit, then restore it.
git-surgeon lets the agent stage each hunk separately — no gymnastics.

## Installation

### Cargo

Requires Rust. Install via [rustup](https://rustup.rs/) if you don't have it.

```bash
cargo install git-surgeon
```

## Quick start

```bash
# List all unstaged hunks with IDs and previews
git-surgeon hunks

# Stage specific hunks
git-surgeon stage a1b2c3d e4f5678

# Commit as usual
git commit -m "partial changes"
```

## Commands

- [`hunks`]#hunks — List hunks in the diff
- [`show`]#show — Show full diff for a specific hunk
- [`stage`]#stage — Stage hunks by ID
- [`unstage`]#unstage — Unstage hunks by ID
- [`discard`]#discard — Discard working tree changes for hunks
- [`fixup`]#fixup — Fold staged changes into an earlier commit
- [`undo`]#undo — Reverse-apply hunks from a commit

---

### `hunks`

Lists all hunks with their IDs, file paths, function context, change counts, and
a preview of changed lines.

```bash
# List unstaged hunks
git-surgeon hunks

# List staged hunks
git-surgeon hunks --staged

# Filter to a specific file
git-surgeon hunks --file src/main.rs

# List hunks from a specific commit
git-surgeon hunks --commit HEAD
git-surgeon hunks --commit abc1234
```

#### Example output

```
a1b2c3d src/main.rs fn handle_request (+3 -1)
  -    let result = process(input);
  +    let result = match process(input) {
  +        Ok(v) => v,
  +        Err(e) => return Err(e),
  +    };

e4f5678 src/lib.rs (+1 -0)
  +use std::collections::HashMap;
```

Each line shows: `<hunk-id> <file> [function context] (+additions -deletions)`

---

### `show`

Shows the full diff (header + all lines) for a single hunk.

```bash
git-surgeon show a1b2c3d

# Show a hunk from a specific commit
git-surgeon show a1b2c3d --commit HEAD
```

Searches both unstaged and staged diffs when no `--commit` is specified.

---

### `stage`

Stages one or more hunks by ID. Equivalent to selectively answering "y" in
`git add -p`.

```bash
git-surgeon stage a1b2c3d
git-surgeon stage a1b2c3d e4f5678
```

---

### `unstage`

Unstages one or more previously staged hunks, moving them back to the working
tree.

```bash
git-surgeon unstage a1b2c3d
git-surgeon unstage a1b2c3d e4f5678
```

---

### `discard`

Discards working tree changes for specific hunks. This reverse-applies the
hunks, effectively running `git checkout -p` non-interactively.

```bash
git-surgeon discard a1b2c3d
```

**Warning:** This permanently removes uncommitted changes for the specified
hunks.

---

### `fixup`

Folds currently staged changes into an earlier commit. Uses `git commit --amend`
for HEAD, or an autosquash rebase for older commits. Unstaged changes are
preserved via `--autostash`.

```bash
# Stage some hunks, then fixup an earlier commit
git-surgeon stage a1b2c3d
git-surgeon fixup abc1234

# Fixup HEAD (equivalent to git commit --amend --no-edit)
git-surgeon fixup HEAD
```

If the rebase hits a conflict, the repo is left in the conflict state for manual
resolution (`git rebase --continue` or `git rebase --abort`).

---

### `undo`

Reverse-applies hunks from a specific commit onto the working tree. Useful for
selectively reverting parts of a previous commit without reverting the entire
commit.

```bash
# List hunks from the commit to find IDs
git-surgeon hunks --commit HEAD

# Undo specific hunks
git-surgeon undo a1b2c3d --from HEAD
git-surgeon undo a1b2c3d e4f5678 --from HEAD~3
```

The changes appear as unstaged modifications in the working tree. Fails
gracefully if context lines have changed since the commit (the patch no longer
applies cleanly).

## How hunk IDs work

IDs are 7-character hex strings derived from SHA-1 of the file path and hunk
content (the actual `+`/`-`/context lines, excluding the `@@` header). This
means:

- IDs are stable across line shifts — adding lines above a hunk doesn't change
  its ID
- IDs are deterministic — the same content always produces the same ID
- Collisions get a `-2`, `-3` suffix (e.g., `a1b2c3d-2`)

## Typical AI agent workflow

```bash
# 1. Agent makes changes to multiple files
# 2. Review what changed
git-surgeon hunks

# 3. Stage only the hunks related to feature A
git-surgeon stage a1b2c3d e4f5678

# 4. Commit feature A
git commit -m "implement feature A"

# 5. Stage remaining hunks for feature B
git-surgeon stage f6g7h8i
git commit -m "implement feature B"
```

## Requirements

- Git 2.0+
- Rust (for building from source)