Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

git-gud (gg) is a stacked-diffs CLI for GitHub and GitLab.

It helps you split large changes into a sequence of small commits that reviewers can understand quickly. In git-gud, each commit in your stack maps to its own PR/MR, and dependencies are wired automatically.

Why this workflow exists

Stacked diffs solve a common problem: features are often too large for a single review, but splitting work manually into many dependent branches is painful.

With git-gud, you can:

  • Keep reviews small and focused
  • Keep moving while earlier changes are in review
  • Preserve clean, logical commit history
  • Land big projects incrementally without long-lived feature branches

Learn more about stacked diffs

Provider support

git-gud supports:

  • GitHub through gh
  • GitLab through glab

Provider selection is auto-detected from your remote URL (github.com / gitlab.com). For self-hosted instances, run gg setup and select the provider explicitly.

Getting Started

Installation

Homebrew (macOS/Linux)

brew install mrmans0n/tap/gg-stack

crates.io

cargo install gg-stack

From source

cargo install --path .

Prerequisites

Before using git-gud, make sure you have:

Authentication

Authenticate with your provider CLI first:

# GitHub
gh auth login

# GitLab
glab auth login

If authentication is missing, gg sync and gg land cannot create or merge PRs/MRs.

Note: If the auth check fails due to a network error (e.g., DNS resolution failure, connection timeout), gg will print a warning and continue. The operation may still fail later if authentication is actually required.

Initial setup

After installing, run the setup wizard in any git repository to configure git-gud:

gg setup

This interactively sets your base branch, username for branch naming, provider (auto-detected for github.com/gitlab.com), and optional lint commands. Configuration is stored per-repo in .git/gg/config.json.

Tip: gg setup is optional — git-gud auto-detects sensible defaults. But it’s useful for setting lint commands, customizing your username prefix, or configuring self-hosted GitHub Enterprise / GitLab instances.

See Configuration for all available options.

Quick start: first stack in 2 minutes

# 1) Create a stack
gg co my-feature

# 2) Commit in small slices
git add . && git commit -m "Add data model"
git add . && git commit -m "Add API endpoint"
git add . && git commit -m "Add UI"

# 3) Inspect current stack
gg ls

# 4) Push branches and create PRs/MRs
gg sync --draft

# 5) Navigate to edit an earlier commit
gg mv 1
# ...make changes...
gg sc

# 6) Re-sync after changes
gg sync

# 7) Land approved changes
gg land --all

# 8) Clean merged stack
gg clean

For a full walkthrough with expected outputs and decision points, see Your First Stack.

Core Concepts

Stacked diffs in one sentence

A stack is a series of commits where each commit is reviewed as its own PR/MR, and each PR/MR depends on the previous one.

The git-gud model: one commit = one PR/MR

In git-gud, each commit is an “entry” in the stack:

  • Entry 1 targets your base branch (for example, main)
  • Entry 2 targets entry 1’s branch
  • Entry 3 targets entry 2’s branch
  • …and so on

That gives reviewers small units, while preserving execution order.

GG metadata trailers

Each stack commit carries stable trailers, for example:

GG-ID: c-abc1234
GG-Parent: c-1234567
  • GG-ID identifies the commit itself.
  • GG-Parent points to the previous stack entry’s GG-ID.
  • The first stack entry has no GG-Parent.

Why this matters:

  • Commit-to-PR/MR mappings stay stable across rebases
  • Stack topology is recoverable from commit-local metadata
  • gg sync and gg reconcile can auto-heal metadata drift after history edits

Branch naming convention

git-gud uses predictable branch names:

  • Stack branch: <username>/<stack-name>
  • Entry branch: <username>/<stack-name>--<gg-id>

Example:

  • nacho/user-auth
  • nacho/user-auth--c-abc1234

This convention is what makes remote discovery (gg ls --remote) and reconciliation possible.

PR/MR dependency chains

Dependency chaining is automatic during gg sync:

  • First PR/MR targets main (or your configured base)
  • Next PR/MR targets previous entry branch
  • This continues until stack head

Result: reviewers can review from bottom to top, and gg land can merge safely in order.

Immutable commits

Some commits should not be casually rewritten. History-rewriting commands — gg squash, gg drop, gg reorder, gg split, gg unstack, gg absorb, and gg rebase — refuse to touch the following by default:

  • Merged PR/MR commits. If an entry’s PR/MR state is Merged, rewriting it locally produces a duplicate of something already upstream. This is the only rule that catches squash-merged PRs, because their merge commit on origin/<base> has a brand-new SHA that doesn’t share ancestry with your local commit.
  • Base-ancestor commits. Any commit already reachable from origin/<base> via plain merge or rebase falls in the same bucket. When no origin/<base> ref exists, gg falls back to the local base branch.

Exception: gg rebase silently skips both merged-PR commits and base-ancestor commits instead of blocking, because git rebase naturally drops them via patch-id matching. An info line (→ Skipping N merged commit(s) already on <base>) is printed when this happens.

Running one of those commands on an immutable target prints a clear error like:

error: cannot rewrite immutable commits (pass --force / --ignore-immutable to override):
  #2  abc1234  Fix typo in parser  (merged as !123)
  #3  def5678  Bump dependency     (already in origin/main)

If you genuinely want to rewrite history anyway, pass --force (or --ignore-immutable if you prefer the longer, self-describing name). Every rewrite command accepts both spellings. The guard still emits a warning so scripts see that they are bypassing a safety check.

Keeping PR state fresh

Each rewrite command runs a best-effort PR-state refresh against the configured provider just before the immutability check, so the merged-PR rule fires even when nothing in the session has touched provider state yet. The refresh is silent when offline / no auth is configured, in which case the base-ancestor rule remains the only protection. Note that base-ancestor does not catch squash-merges (those produce a new SHA on origin/<base>), so working offline against a repo that uses squash-merge is the one case where you can still rewrite a “merged” commit without --force. A working provider closes that gap automatically.

Guides

This section is practical and task-oriented.

If you’re new to stacked diffs, start with Your First Stack. If you’re already using git-gud day-to-day, jump to the workflow you need (editing, remote collaboration, worktrees, landing, linting, reconcile).

Your First Stack

This walkthrough covers the full lifecycle: create → commit → sync → edit → land → clean.

1) Create a stack

gg co user-auth

This creates/switches to stack branch your-user/user-auth.

2) Build the feature in reviewable commits

git add . && git commit -m "Add user model"
git add . && git commit -m "Add auth endpoints"
git add . && git commit -m "Add login UI"

Keep each commit small and self-contained.

3) Inspect stack structure

gg ls

You should see ordered entries, each with a GG-ID.

4) Publish review chain

gg sync --draft

This pushes one branch per entry and creates one PR/MR per commit, chained by dependencies.

5) Address feedback in an older commit

gg mv 1
# edit files
git add .
gg sc

gg sc amends the current entry and rebases subsequent entries automatically.

6) Update remote PRs/MRs

gg sync

If your stack base is behind origin/<base>, gg sync warns and suggests running gg rebase first. Use --no-rebase-check to skip this check once.

7) Land approved entries

gg land --all

Use --wait if you want git-gud to wait for CI/approvals:

gg land --all --wait

8) Cleanup

gg clean

Editing Commits in a Stack

The most common stacked-diff operation is “change commit N without losing N+1, N+2…”.

gg mv 2
# or use gg first / gg next / gg prev / gg last

Make changes and fold them in

# after editing files
git add .
gg sc

Use gg sc --all to include unstaged changes too.

Reorder commits

Interactive:

gg reorder

Explicit order:

gg reorder --order "3,1,2"

Absorb scattered staged edits automatically

gg absorb

Useful flags:

  • --dry-run: preview only
  • --and-rebase: absorb and rebase in one step
  • --whole-file: match whole-file changes instead of hunks
  • --squash: squash fixups directly

After major edits, run:

gg sync

Working with Remote Stacks

Use this when a stack exists on origin but not in your local checkout (new machine, pairing, takeover).

Discover remote-only stacks

gg ls --remote

Active stacks are shown first. Stacks whose PRs/MRs have all been merged appear in a separate “Landed” section at the bottom, so you can focus on work that still needs attention.

Check out a remote stack

gg co user-auth

If a local stack doesn’t exist, git-gud can reconstruct it from remote entry branches and mappings.

Typical collaboration loop

gg co teammate-feature
gg ls
# make changes
gg sync

Tips:

Using Worktrees

Worktrees let you keep your main checkout clean while developing a stack in a dedicated directory.

Create stack in a managed worktree

gg co user-auth --worktree

Short flag:

gg co user-auth -w

Why use worktrees

  • Keep your main checkout untouched
  • Work on multiple stacks side by side
  • Avoid stashing/switching overhead

Default path behavior

By default git-gud creates:

../<repo-name>.<stack-name>

You can change this with defaults.worktree_base_path in .git/gg/config.json.

Cleanup behavior

gg clean removes merged stacks and associated managed worktrees.

Landing and Cleanup

Use gg land to merge entries in order, from the bottom of the stack upward.

Land one approved entry

gg land

Land the whole stack

gg land --all

Wait for CI and approvals

gg land --all --wait

Land only part of a stack

gg land --until 2
# or by GG-ID / SHA

Merge strategy and provider-specific behavior

gg land --no-squash

GitLab auto-merge queue:

gg land --auto-merge

Auto-clean after landing

One-off:

gg land --all --clean

Or make it default with land_auto_clean in config.

Manual cleanup remains available:

gg clean

Linting Your Stack

gg lint runs your configured lint commands commit-by-commit across the stack.

Configure lint commands

In .git/gg/config.json:

{
  "defaults": {
    "lint": [
      "cargo fmt --check",
      "cargo clippy -- -D warnings"
    ]
  }
}

Run lint manually

gg lint

Run only up to a specific entry:

gg lint --until 2

Run lint during sync

gg sync --lint

If lint fails during gg sync --lint, sync is aborted and git-gud restores repository state to the pre-sync snapshot.

Skip lint for one sync (even if enabled by default):

gg sync --no-lint

Reconciling Out-of-Sync Stacks

Use reconcile when stack metadata and remote state diverge.

Common causes:

  • Someone pushed with git push instead of gg sync
  • A stack was edited across machines and mappings got stale
  • Commits exist without GG-ID trailers

Preview changes safely

gg reconcile --dry-run

Apply reconciliation

gg reconcile

Reconcile can:

  1. Add missing GG-IDs to stack commits (via rebase)
  2. Map existing PRs/MRs to the right GG-IDs in config

After reconciling, run:

gg ls --refresh
gg sync

Agent Skills

git-gud follows the open Agent Skills standard. AI coding agents with shell access — including Codex, Claude Code, Cursor, Gemini CLI, VS Code integrations, and others — can use gg for stacked-diff workflows.

What’s included

The integration provides one unified skill:

SkillDescription
ggUse gg with GitHub PRs (gh CLI) or GitLab MRs (glab CLI, merge trains)

Each skill includes:

  • SKILL.md — concise instructions with agent operating rules
  • reference.md — command reference and JSON schemas
  • examples/ — step-by-step workflow walkthroughs

Installation

npx skills add mrmans0n/git-gud

The skills CLI installs the skill into the agent setup it detects. If it cannot decide, it prompts you to choose where to install it.

2) Install for a specific agent

Use --agent when you already know the host you want to target:

npx skills add mrmans0n/git-gud --agent codex
npx skills add mrmans0n/git-gud --agent claude-code
npx skills add mrmans0n/git-gud --agent cursor
npx skills add mrmans0n/git-gud --agent gemini-cli

For a shared repository setup, run the command from the project root. For a user-level install, add --global.

3) Claude Code marketplace

Claude Code users can also install git-gud as a plugin:

claude plugin marketplace add https://github.com/mrmans0n/git-gud
claude plugin install git-gud

4) Claude Code local checkout

Use this when launching Claude Code directly from a local git-gud checkout:

claude --plugin-dir /path/to/git-gud

5) Claude Code project-level config (.claude/settings.json)

Use this when you want the local checkout enabled by default for a repository:

{
  "plugins": [
    {
      "name": "git-gud",
      "path": "/path/to/git-gud"
    }
  ]
}

6) Manual setup for compatible tools

Tools that support Agent Skills can also load the repo’s skills/gg/ directory directly. Use this fallback if your agent does not use the skills CLI yet, or if you want to manage skill files yourself.

How agents typically use gg

A practical AI-assisted stacked-diff workflow looks like this:

  1. Agent creates or switches to a stack (gg co ..., ideally with a worktree)
  2. Agent makes small commits and keeps each commit focused
  3. Agent syncs the stack (gg sync) so PRs/MRs are created/updated in order
  4. Agent iterates on review feedback (amend/reorder/re-sync)
  5. Agent asks for explicit user confirmation before gg land

This keeps work reviewable while preserving user control over merges.

JSON output for tool-driven agents

For machine-readable parsing, gg supports --json on key commands:

  • gg ls --json
  • gg sync --json
  • gg land --json
  • gg clean --json
  • gg lint --json

Use these outputs in agents and automation for reliable state checks and decisions. For full response schemas, see each skill’s reference.md.

Safety model (required behavior)

When using AI agents with gg, keep these rules:

  1. Never land without explicit user confirmation
  2. Never run git add -A blindly (stage only reviewed/intended files)
  3. Prefer worktrees for isolation (gg co --wt)
  4. Use structured output (--json) when automation must parse command results

Skill references

For full operational details, prompts, and examples:

File structure

.claude-plugin/
  plugin.json           # Plugin manifest
skills/
  gg/
    SKILL.md            # Unified GitHub + GitLab skill
    reference.md        # Command reference + JSON schemas
    examples/
      basic-flow.md     # Provider-agnostic feature workflow
      multi-commit.md   # Absorb, reorder, lint
      merge-train.md    # GitLab merge train workflow

Command Reference

This section is a command-by-command reference based on gg --help and gg <command> --help.

Unlike the guides, this section is organized by command surface and flags. Each page still includes practical examples.

Command groups

  • Stack lifecycle: co, ls, sync, land, clean
  • Editing: mv, first, last, prev, next, sc, absorb, reorder, split, unstack, rebase
  • Utilities: lint, setup, reconcile, continue, abort, completions

gg co

Create a new stack, switch to an existing local stack, or check out a remote stack by name.

gg co [OPTIONS] [STACK_NAME]

Options

  • -b, --base <BASE>: Base branch to use (default auto-detected: main/master/trunk)
  • -w, --worktree: Create or reuse a managed worktree for this stack

Examples

# Create/switch stack
gg co user-auth

# Create stack based on a specific branch
gg co user-auth --base develop

# Create stack in worktree
gg co user-auth --worktree

gg ls

List the current stack, all local stacks, or remote-only stacks.

When the stack base is behind origin/<base>, output includes a ↓N indicator (N = commits behind).

gg ls [OPTIONS]

Options

  • -a, --all: Show all local stacks
  • -r, --refresh: Refresh PR/MR status from remote
  • --remote: List remote stacks not checked out locally. Stacks whose PRs/MRs are all merged are shown in a separate “Landed” section at the bottom with a marker
  • --json: Print structured JSON output (for scripts and automation). Automatically performs a best-effort refresh of PR/MR state from the provider API, so pr_state and ci_status fields are populated without needing --refresh.

Examples

# Current stack status
gg ls

# All local stacks
gg ls --all

# Remote stacks (active first, then landed)
gg ls --remote

# Refresh status badges from provider
gg ls --refresh

# Structured JSON for automation
gg ls --json
gg ls --all --json
gg ls --remote --json

gg log

Show a smartlog-style view of the current stack.

gg log is stack-scoped: it renders just the current stack as a tree, with each commit’s position, short SHA, title, PR/MR state, CI badge, and a <- HEAD marker on the currently-checked-out commit. For a cross-stack overview use gg ls --all.

gg log [OPTIONS]

Options

  • -r, --refresh: Refresh PR/MR status from the remote before rendering.
  • --json: Print structured JSON output (for scripts and automation). Automatically performs a best-effort refresh of PR/MR state from the provider API, so pr_state and ci_status fields are populated without needing --refresh.

Example output

my-feature (3 commits, base: main)

  ├── [1] abc1234 feat: add parser open #101 ✓
  │        #101
  ├── [2] def5678 feat: wire CLI flag open #102 ●
  │        #102
  └── [3] 9abcdef test: coverage for edge cases not pushed  <- HEAD

JSON shape

{
  "version": 1,
  "log": {
    "stack": "my-feature",
    "base": "main",
    "current_position": 3,
    "entries": [
      {
        "position": 1,
        "sha": "abc1234",
        "title": "feat: add parser",
        "gg_id": "c-abc1234",
        "gg_parent": null,
        "pr_number": 101,
        "pr_state": "open",
        "approved": false,
        "ci_status": "success",
        "is_current": false,
        "in_merge_train": false,
        "merge_train_position": null
      }
    ]
  }
}

Entry fields match gg ls --json so consumers can share parsers across both commands.

See also

  • gg ls — current stack details with more summary metrics, plus --all and --remote modes.

gg inbox

gg inbox shows an actionable repository-wide triage view for all local stacks. Instead of inspecting stacks one by one, it groups PRs or MRs by what they need right now.

Use it when you want quick answers to questions like:

  • which PRs are ready to land
  • which ones are blocked on CI
  • where changes were requested
  • which stacks have fallen behind their base

Usage

gg inbox
gg inbox --all
gg inbox --json

Buckets

gg inbox classifies each PR or MR into exactly one bucket, in priority order:

  1. ready_to_land
  2. changes_requested
  3. blocked_on_ci
  4. awaiting_review
  5. behind_base
  6. draft
  7. merged (only with --all)

Classification notes

  • A canceled CI run counts as blocked_on_ci.
  • If remote refresh fails transiently, the entry stays visible instead of disappearing, so the inbox does not look empty because of a temporary provider error.
  • behind_base is computed from the real stack tip versus origin/<base>, not from the state of your local base branch.

Example human output

The labels and number prefixes adapt to the detected provider.

GitHub:

Inbox (3 items across 2 stacks)

Ready to land (1):
  auth #2  abc1234  Add login button  stack/auth  PR #41

Blocked on CI (1):
  auth #3  def5678  Add login API  stack/auth  PR #42 ⏳

Awaiting review (1):
  billing #1  9876abc  Add invoice export  stack/billing  PR #51

GitLab:

Inbox (2 items across 1 stack)

Ready to land (1):
  auth #2  abc1234  Add login button  stack/auth  MR !41

Awaiting review (1):
  auth #3  def5678  Add login API  stack/auth  MR !42

JSON

With --json, gg inbox returns a versioned response designed for automation and MCP.

Example:

{
  "version": 1,
  "total_items": 2,
  "buckets": {
    "ready_to_land": [
      {
        "stack_name": "auth",
        "position": 1,
        "sha": "abc1234",
        "title": "Add login",
        "pr_number": 42,
        "pr_url": "https://github.com/org/repo/pull/42",
        "ci_status": "success",
        "behind_base": null
      }
    ],
    "blocked_on_ci": [
      {
        "stack_name": "auth",
        "position": 2,
        "sha": "def5678",
        "title": "Add login API",
        "pr_number": 43,
        "pr_url": "https://github.com/org/repo/pull/43",
        "ci_status": "running",
        "behind_base": 2
      }
    ]
  }
}

Per-entry fields

  • stack_name: stack name
  • position: commit position inside the stack
  • sha: short SHA
  • title: commit title
  • pr_number: PR or MR number
  • pr_url: PR or MR URL
  • ci_status: pending, running, success, failed, canceled, unknown, or omitted
  • behind_base: number of commits behind origin/<base>, or null

Flags

  • --all: include items already marked as merged
  • --json: emit structured output for tooling and MCP

Relationship to other commands

  • gg ls shows detailed status for the current stack
  • gg log gives you a smartlog view of the current stack
  • gg inbox is for cross-stack triage across multiple stacks

gg sync

Push entry branches and create/update PRs/MRs for the current stack.

gg sync [OPTIONS]

Options

  • -d, --draft: Create new PRs/MRs as draft (does not affect existing PRs/MRs)
  • -f, --force: Force push even if remote is ahead
  • --update-descriptions: Update PR/MR descriptions from commit messages
  • --update-title: Update PR/MR titles from commit messages
  • -l, --lint: Run lint before sync (aborts sync on lint failure and restores repository state to the pre-sync snapshot)
  • --no-lint: Disable lint before sync (overrides config default)
  • --no-rebase-check: Skip checking whether your stack base is behind origin/<base>
  • --no-verify: Skip the pre-push hook for pushes performed by this sync (forwards git push --no-verify). Opt-in per invocation; does not affect other hooks.
  • -u, --until <UNTIL>: Sync up to target commit (position, GG-ID, or SHA)
  • --json: Output structured JSON for automation (suppresses human/progress output)

Before pushing, gg sync checks whether your stack base is behind origin/<base>. If it is behind by at least the configured threshold, git-gud warns and suggests rebasing first (gg rebase).

When you run gg sync --lint, lint runs before any push/PR updates. If lint fails, sync aborts immediately and git-gud restores your repository to the pre-sync snapshot.

Before pushing, gg sync also normalizes commit metadata (GG-ID and GG-Parent) for the whole stack. This normalization is always enforced during sync (including adding missing GG-ID trailers) to keep stack identity and PR/MR mappings stable.

If an existing mapped PR/MR is attached to the wrong source branch (for example after moving commits into a new stack with gg unstack), providers cannot retarget that source branch in place. gg sync creates a replacement PR/MR with the correct branch, updates the local mapping, comments on the old PR/MR, and closes it. In JSON output that entry uses action "recreated".

You can control this behavior with config:

  • defaults.sync_auto_rebase (sync.auto_rebase): automatically run gg rebase before sync when behind threshold is reached
  • defaults.sync_behind_threshold (sync.behind_threshold): minimum number of commits behind before warning/rebase logic applies (0 disables the check)

Examples

# First publish as drafts
gg sync --draft

# Sync only first two entries
gg sync --until 2

# Refresh PR/MR descriptions after commit message edits
gg sync --update-descriptions

# Also update PR/MR titles to match commit subjects
gg sync --update-title

# Run lint as part of sync
gg sync --lint

# Skip behind-base check once
gg sync --no-rebase-check

# Machine-readable output
# (useful in scripts/agents)
gg sync --json

# Skip pre-push hooks for this sync only
gg sync --no-verify

Target Branch Resolution

When computing the target branch for each PR/MR, gg sync walks backwards through predecessor entries and skips any that are already merged or closed. If all predecessors have been merged, the target falls back to stack.base. This ensures downstream MRs are correctly retargeted after an upstream MR is merged — whether merged via gg land or directly in the provider UI.

PR/MR Body Ownership

When gg sync creates a new PR/MR, the generated description is wrapped in invisible HTML comment markers:

<!-- gg:managed:start -->
(generated content from commit message / template)
<!-- gg:managed:end -->

On subsequent syncs with --update-descriptions (or when sync_update_descriptions is enabled in config), only the content inside the managed block is regenerated. Any text you add above or below the markers on GitHub/GitLab is preserved across syncs.

This means you can safely:

  • Add review checklists above or below the managed block
  • Write reviewer notes that survive re-syncs
  • Check/uncheck task boxes outside the managed section

Content inside the managed block (the generated description) is regenerated on every sync. If your PR template includes a checklist, place persistent checklists outside the markers after creation.

Legacy PRs (created before this feature) have no managed markers. gg sync will skip body updates for these PRs and log a warning, to avoid overwriting manual edits.

Stack navigation comments

If defaults.stack_nav_comments is enabled in .git/gg/config.json, every full gg sync (no --until) reconciles a managed comment on each PR/MR in the stack. The comment shows all entries in the stack in bottom-up order, with a 👉 marker on the entry that PR corresponds to — letting reviewers see where they are in the chain and click through to siblings.

The comment is identified by a hidden HTML marker (<!-- gg:stack-nav -->) and never touches comments git-gud didn’t create. Disabling the setting and re-syncing cleans up any previously-posted comments automatically.

Merged or closed PRs are left alone — gg sync never modifies comments on historical PRs.

When running with --json, each entry includes an optional nav_comment_action field (one of "created", "updated", "unchanged", "deleted", "error") when a reconcile decision was made.

Example JSON (shape):

{
  "version": 1,
  "sync": {
    "stack": "my-stack",
    "base": "main",
    "rebased_before_sync": false,
    "metadata": {
      "gg_ids_added": 0,
      "gg_parents_updated": 1,
      "gg_parents_removed": 0
    },
    "entries": [
      {
        "position": 1,
        "sha": "abc1234",
        "title": "Add feature",
        "gg_id": "c-abc1234",
        "branch": "user/my-stack--c-abc1234",
        "action": "created",
        "pr_number": 42,
        "pr_url": "https://github.com/org/repo/pull/42",
        "draft": false,
        "pushed": true,
        "error": null
      }
    ]
  }
}

Navigation (mv, first, last, prev, next)

These commands move HEAD within your stack without manual rebase gymnastics.

gg mv <TARGET>

Move to a specific entry by:

  • Position (1-indexed)
  • GG-ID (c-...)
  • Commit SHA
gg mv 1
gg mv c-abc1234
gg mv a1b2c3d

Relative navigation

gg first   # first entry
gg last    # stack head
gg prev    # previous entry
gg next    # next entry

gg sc

Squash local changes into the current stack commit.

gg sc [OPTIONS]

Options

  • -a, --all: Include staged and unstaged changes
  • -f, --force (alias --ignore-immutable): Override the immutability guard. By default gg sc refuses to amend a commit whose PR is merged or which is already reachable from origin/<base>. See Core concepts · Immutable commits.

When unstaged changes are present, behavior is controlled by defaults.unstaged_action in .git/gg/config.json:

  • ask (default): prompt to stage all, stash, continue, or abort
  • add: auto-stage all changes (git add -A) and continue
  • stash: auto-stash and continue
  • continue: continue without including unstaged changes
  • abort: fail immediately

Examples

# Standard amend-like flow
git add .
gg sc

# Include unstaged changes too
gg sc --all

gg absorb

Automatically distribute staged changes to the most appropriate commits in your stack.

gg absorb [OPTIONS]

Options

  • --dry-run: Preview actions without changing commits
  • -a, --and-rebase: Rebase automatically after creating fixups
  • -w, --whole-file: Match and absorb by whole file rather than hunks
  • --one-fixup-per-commit: At most one fixup per commit
  • -n, --no-limit: Search all commits in the stack (not just last 10)
  • -s, --squash: Squash directly instead of creating fixup! commits
  • -f, --force (alias --ignore-immutable): Override the immutability guard. By default, gg absorb refuses to run if any commit in the stack is merged or reachable from origin/<base> — because it cannot tell ahead of time whether git-absorb will target those commits. --dry-run skips the guard so you can preview safely. See Core concepts · Immutable commits.

Examples

# Preview before applying
gg absorb --dry-run

# Absorb and finish with rebase
gg absorb --and-rebase

# Heavy refactor across many files
gg absorb --whole-file --no-limit

gg drop

Remove one or more commits from the stack.

Alias: gg abandon

gg drop <TARGET>... [OPTIONS]

Arguments

  • <TARGET>...: One or more commits to drop. Each target can be:
    • Position (1-indexed): 1, 3
    • Short SHA: abc1234
    • GG-ID: c-abc1234

Options

  • -f, --force (alias --ignore-immutable): Skip the confirmation prompt and override the immutability guard. gg drop refuses by default to drop or rewrite commits whose PR is merged or which are already reachable from origin/<base>; this flag bypasses both safeties. See Core concepts · Immutable commits.
  • --json: Output result as JSON

Behavior

  1. Validates the working directory is clean
  2. Resolves each target to a commit in the stack
  3. Shows which commits will be dropped and asks for confirmation (unless --force)
  4. Performs a git rebase -i that omits the dropped commits
  5. Cleans up per-commit branches for dropped commits
  6. Prints a summary of what was dropped

At least one commit must remain in the stack after dropping.

Examples

# Drop the second commit in the stack
gg drop 2

# Drop multiple commits at once
gg drop 1 3

# Drop by GG-ID, skip confirmation
gg drop c-abc1234 --force

# Drop with JSON output
gg drop 2 --force --json

# Use the 'abandon' alias (inspired by jj)
gg abandon 2

JSON Output

{
  "version": 1,
  "drop": {
    "dropped": [
      {"position": 2, "sha": "abc1234", "title": "Fix typo"}
    ],
    "remaining": 3
  }
}

Edge Cases

  • Dropping all commits produces an error — at least one commit must remain
  • Invalid position shows the valid range
  • Rebase conflicts are handled the same as gg reorder — resolve with gg continue or gg abort

gg reorder / gg arrange

Reorder and/or drop commits in your stack.

gg arrange is an alias for gg reorder — they share the same implementation.

gg reorder [OPTIONS]
gg arrange [OPTIONS]

Options

  • -o, --order <ORDER>: New order as positions/SHAs ("3,1,2" or "3 1 2")
  • --no-tui: Disable the interactive TUI and use a text editor instead
  • -f, --force (alias --ignore-immutable): Override the immutability guard. Reordering or dropping a commit that is already merged or reachable from origin/<base> is refused by default. See Core concepts · Immutable commits.

Interactive TUI

When run without --order, opens an interactive TUI where you can visually rearrange and drop commits:

┌─ Arrange Stack ──────────────────────────────────┐
│  1  abc1234  feat: add login page                │
│▸ 2  def5678  fix: handle empty input     ↕       │
│  [DROP] 3  ghi9012  refactor: extract validator   │
│  4  jkl3456  test: add integration tests          │
└──────────────────────────────────────────────────┘
 j/k:navigate  J/K:move commit  d:drop/undrop  Enter/s:confirm  q/Esc:cancel

Keyboard shortcuts

KeyAction
j / Move cursor down
k / Move cursor up
J / Shift+↓Move commit down
K / Shift+↑Move commit up
d / DeleteToggle drop mark on commit
Enter / sConfirm new order
q / EscCancel (no changes)

Position 1 is the bottom of the stack (closest to the base branch).

Dropped commits appear in red with strikethrough and a [DROP] prefix. You can still move dropped commits (e.g., to undrop them later). At least one commit must remain — you cannot drop all commits.

The TUI requires a TTY. In non-interactive environments (pipes, CI), gg reorder falls back to the text editor automatically. Use --no-tui to force the editor fallback.

Editor Fallback

When using the editor fallback (--no-tui or non-TTY), you can:

  • Reorder commits by rearranging lines
  • Drop commits by deleting their lines

At least one commit must remain.

Examples

# Interactive reorder/drop with TUI
gg reorder
gg arrange

# Explicit reorder by position (no dropping)
gg reorder --order "3,1,2"

# Use text editor instead of TUI
gg arrange --no-tui

gg split

Split a commit in the stack into two commits. The selected hunks become a new commit inserted before the original in the stack, while the remaining changes stay in the original commit.

gg split [OPTIONS] [FILES...]

Options

  • -c, --commit <TARGET>: Target commit — position (1-indexed), short SHA, or GG-ID. Defaults to the current commit (HEAD).
  • -m, --message <MESSAGE>: Commit message for the new (first) commit. Skips the editor prompt.
  • --no-edit: Keep the original message for the remainder commit without prompting.
  • --no-tui: Disable TUI, use sequential prompt instead (legacy git add -p style).
  • -f, --force (alias --ignore-immutable): Override the immutability guard. Splitting a merged or base-ancestor commit is refused by default. See Core concepts · Immutable commits.
  • FILES...: Files to include in the new commit. When provided, all hunks from those files are auto-selected (skips the interactive picker).

How It Works

When you split commit K into two:

  1. New commit (K’) — Contains only the selected hunks. Inserted before K in the stack. Gets a new GG-ID.
  2. Remainder (K’’) — Contains the remaining hunks. Stays in K’s original position. Keeps the original GG-ID (preserving PR association).

All descendant commits are automatically rebased onto the remainder.

BEFORE                    AFTER
  4: "Fix tests"            5: "Fix tests"       (rebased)
  3: "Add auth+logging"     4: "Add logging"      ← remainder (keeps GG-ID)
  2: "Setup DB"             3: "Add auth"         ← NEW commit (selected changes)
  1: "Init project"         2: "Setup DB"
                             1: "Init project"

Interactive Hunk Selection

Running gg split without file arguments opens the interactive hunk picker:

# Split the current commit — opens hunk selector
gg split

# Split a specific commit in the stack
gg split -c 3

TUI Mode (Default)

When run with a TTY, gg split opens a two-panel TUI for hunk selection:

┌── Files (1/3 width) ──┬── Diff (2/3 width) ──────────────┐
│ [✓] src/auth.rs (3)   │ @@ -10,6 +10,12 @@               │
│ [ ] src/logging.rs (1)│ +  // Validate token              │
│ [~] src/tests.rs (2)  │ +  if token.is_empty() {          │
│                        │ +      return false;               │
│                        │ +  }                               │
├────────────────────────┴────────────────────────────────────┤
│ 5/12 hunks selected │ [Space] toggle · [Tab] switch panel │
└─────────────────────────────────────────────────────────────┘

TUI Keyboard Shortcuts

KeyIn File PanelIn Diff Panel
↑/↓ or j/kNavigate filesNavigate hunks
SpaceToggle all hunks for fileToggle current hunk
aSelect all hunks (all files)Select all hunks (this file)
nDeselect all hunks (all files)Deselect all hunks (this file)
sSplit current hunk into sub-hunks
Tab / ← / →Switch to diff panelSwitch to file panel
EnterEnter commit messageEnter commit message
q / EscAbort (cancel split)Abort (cancel split)

Inline Commit Message

After pressing Enter to confirm your hunk selection, an inline text input appears at the bottom of the TUI for the commit message. It’s pre-filled with Split from: <original commit title>.

KeyAction
EnterConfirm message and create the split
EscGo back to hunk selection
← / →Move cursor
Home / EndJump to beginning/end
Backspace / DeleteDelete characters
Ctrl+A / Ctrl+EJump to beginning/end (emacs-style)
Ctrl+UClear from cursor to beginning
Ctrl+KClear from cursor to end

After confirming the new commit message, a second inline input appears for the remainder commit message (pre-filled with the original commit’s message). This replaces the external editor for both messages, keeping the entire split workflow inside the TUI.

KeyAction
EnterConfirm remainder message and complete the split
EscGo back to the new commit message input

The -m flag still works and bypasses the TUI input for the new commit. The --no-edit flag skips the remainder message input entirely, keeping the original message as-is.

File Panel Indicators

  • [✓] — All hunks selected (green)
  • [~] — Some hunks selected (yellow)
  • [ ] — No hunks selected

Sequential Prompt Mode (--no-tui)

Use --no-tui to fall back to the legacy git add -p style sequential prompt:

gg split --no-tui

This mode is automatically used when no TTY is available (e.g., in CI pipelines or when piping).

For each hunk, you’ll see the diff with colored output and a prompt:

--- a/src/auth.rs
+++ b/src/auth.rs
@@ -10,6 +10,12 @@ fn authenticate(user: &str) -> bool {
+    // Validate token
+    if token.is_empty() {
+        return false;
+    }

Include this hunk? [y]es/[n]o/[a]ll file/[d]one file/[s]plit/[q]uit/?help:

Sequential Mode Actions

KeyActionDescription
yYesInclude this hunk in the new commit
nNoSkip this hunk (stays in remainder)
aAll fileInclude all remaining hunks from this file
dDone fileSkip all remaining hunks from this file
sSplitSplit this hunk into smaller hunks
qQuitStop; all remaining hunks stay in remainder
?HelpShow this help

File-Based Splitting

When file arguments are provided, all hunks from those files are auto-selected without opening the interactive picker:

# Move auth files to a new commit before the current one
gg split -m "Add authentication" src/auth.rs src/auth_test.rs

# Split a specific commit with explicit files
gg split -c 3 src/config.rs

# Non-interactive with both messages
gg split -c 2 -m "Extract helpers" --no-edit helpers.rs utils.rs

# Split by GG-ID
gg split -c c-abc1234 src/config.rs

Hunk Splitting

If a hunk contains multiple logical changes separated by unchanged lines, pressing s will break it into smaller hunks. You can then select each sub-hunk individually. If the hunk is already atomic (contiguous changes), you’ll see “This hunk cannot be split further.”

Edge Cases

  • All changes selected — Warning: the original commit will be empty.
  • No changes selected — Error.
  • Dirty working directory — Error. Commit or stash changes first.
  • Merge conflicts during rebase — Split is aborted, original state restored.

gg unstack

Split the current stack into two independent stacks.

gg unstack [--target <TARGET>] [--name <STACK_NAME>] [--no-tui] [-f] [--json] [-w]

The selected entry becomes the root of a new stack, and all entries above it move with it. Entries below the selected point remain in the original stack.

The command is called unstack because gg split already means splitting one commit into two commits.

Examples

# Pick the split point interactively
gg unstack

# Move entries 3 and above into a new stack named auth-followup
gg unstack --target 3 --name auth-followup --no-tui

# Use a GG-ID or SHA prefix instead of a position
gg unstack --target c-abc1234 --name auth-followup --no-tui

# Machine-readable output
gg unstack --target 3 --json --no-tui

# Put the new upper stack in a managed worktree
gg unstack --target 3 --name upper-auth --wt

Behavior

Given a stack:

1  Add schema
2  Add API
3  Add UI
4  Add tests

Running gg unstack --target 3 --name ui-work leaves:

original stack: 1 Add schema, 2 Add API
new stack:      1 Add UI, 2 Add tests

GG-ID trailers are preserved. GG-Parent trailers are rewritten so the first entry in each resulting stack has no parent and later entries point at the previous entry in that same stack.

Local PR/MR mappings in .git/gg/config.json move with their entries. Stale local entry branches under the old stack name are removed for moved entries. If moved entries had review mappings, run gg sync afterwards to recreate or update review branches for the new stack.

Without --worktree, the current directory switches to the new upper stack. With --worktree, the current directory stays on the lower stack and the new upper stack is checked out in its managed worktree.

Options

  • --target <TARGET>: first entry for the new stack. Accepts a 1-indexed position, GG-ID, or SHA prefix.
  • --name <STACK_NAME>: name for the new stack. If omitted, gg generates a unique name such as <old-stack>-2.
  • --no-tui: disable the interactive picker. Use this with --target in scripts and tests.
  • -f, --force: bypass the immutability guard for merged/base commits.
  • --json: emit structured JSON.
  • -w, --worktree: create or reuse a managed worktree for the new stack (--wt also works).

Position 1 is rejected because it would leave the original stack empty. The last position is allowed and creates a one-entry new stack.

gg rebase

Rebase the current stack onto an updated branch.

gg rebase [TARGET]
  • If TARGET is omitted, git-gud uses the stack base branch.

Options

  • -f, --force (alias --ignore-immutable): Override the immutability guard. Rebase rewrites the parent of every commit in the stack; merged commits (including squash-merged PRs) and commits already reachable from origin/<base> are silently skipped — git rebase drops them automatically via patch-id matching, so --force is not required for these. See Core concepts · Immutable commits.

Examples

# Rebase onto configured base
gg rebase

# Rebase onto specific branch
gg rebase main

gg land

Merge approved PRs/MRs from the bottom of your stack upward.

gg land [OPTIONS]

Options

  • -a, --all: Land all approved entries in sequence
  • --auto-merge: (GitLab only) Request auto-merge instead of immediate merge
  • --no-squash: Disable squash merge (squash is default)
  • -w, --wait: Wait for CI and approvals before merging
  • -u, --until <UNTIL>: Land up to a target entry (position, GG-ID, SHA)
  • -c, --clean: Clean stack automatically after landing all
  • --no-clean: Disable auto-clean for this run
  • --admin: (GitHub only) Use admin privileges to bypass branch protection requirements (see Admin Override below)
  • --json: Emit machine-readable JSON output (no human logs)

Examples

# Land one approved entry
gg land

# Land complete stack, waiting for readiness
gg land --all --wait

# Land part of stack
gg land --until 2

# GitLab auto-merge queue
gg land --all --auto-merge

# JSON output for automation
gg land --all --json

# Bypass approval requirements (GitHub admin)
gg land --admin

# Land full stack with admin override
gg land --all --wait --admin

Admin Override

The --admin flag (or land_admin config default) passes --admin to gh pr merge, which uses GitHub’s API-level admin merge. This bypasses all branch protection rules the merging user has permission to override, which may include both review approvals and required status checks depending on your repository settings.

Use --wait --admin if you want to wait for CI to pass before merging while still bypassing approval requirements. Without --wait, no client-side CI validation is performed.

On GitLab, --admin is a no-op — glab mr merge has no equivalent flag. A warning is printed and the merge proceeds normally.

A warning (⚠ Merging with admin override) is printed before each admin-elevated merge.

Downstream MR Retargeting

After landing an entry, gg land automatically retargets the next MR in the stack so it no longer points at the now-merged intermediate branch:

  • Single entry (gg land): retargets the immediate next MR to stack.base.
  • All entries (gg land --all): retargets all remaining MRs to stack.base as each entry is landed.

This applies to both GitHub PRs and GitLab MRs. No manual retargeting in the provider UI is needed after landing.

Merge Trains (GitLab)

When merge trains are enabled on the target branch, gg land automatically adds MRs to the merge train instead of merging directly.

Approval is always required before an MR can enter the merge train queue — even with --all. If using --wait, the command will show “Waiting for approval…” until a reviewer approves the MR.

CI Failure Details

When using --wait, if CI fails on an MR the command stops and shows which jobs failed:

OK Landed 1 MR(s)

⚠ Landed 1 MR(s), but encountered an error:

Error: MR !7621 CI failed
  Failed jobs: lint (stage: test), build-android (stage: build)

This helps diagnose CI issues without having to open the GitLab UI. Failed job names and stages are fetched from the MR’s head pipeline.

JSON Output

Example JSON response:

{
  "version": 1,
  "land": {
    "stack": "my-stack",
    "base": "main",
    "landed": [
      {
        "position": 1,
        "sha": "abc1234",
        "title": "feat: add parser",
        "gg_id": "c-abc1234",
        "pr_number": 42,
        "action": "merged",
        "error": null
      }
    ],
    "remaining": 0,
    "cleaned": false,
    "warnings": [],
    "error": null
  }
}

gg clean

Delete merged stacks (and associated managed worktrees).

gg clean [OPTIONS]

Options

  • -a, --all: Clean all merged stacks without prompting
  • --json: Emit machine-readable JSON output

Examples

gg clean
gg clean --all
gg clean --json

--json prints:

  • version: output schema version
  • clean.cleaned: stacks that were cleaned
  • clean.skipped: stacks skipped (unmerged or declined in interactive mode)

gg lint

Run configured lint commands on stack commits.

gg lint [OPTIONS]

Options

  • -u, --until <UNTIL>: Stop at target entry (position, GG-ID, SHA)
  • --json: Emit structured JSON output

Examples

# Lint from bottom to current
gg lint

# Lint only a subset
gg lint --until 2

gg run

Run an arbitrary command on each commit in the stack. Like jj run in Jujutsu — useful for validating changes (build, test), applying auto-fixers (formatters, codemods), or any per-commit shell command.

gg run [OPTIONS] -- <COMMAND>...

Arguments after -- are passed through as-is. Use -- whenever your command has its own flags so clap knows where gg run’s options end.

Modes

gg run has three modes that control what happens to files the command modifies:

  • Read-only (default): if the command modifies tracked files, the commit is marked failed. Ideal for build/test/lint validation.
  • --amend: stage and fold any modifications into the current commit, then rebase the rest of the stack on top. Ideal for formatters and codemods (cargo fmt, prettier, ruff --fix).
  • --discard: revert any working-tree changes after each commit. Ideal for commands with known side effects you want to ignore.

Options

  • -u, --until <UNTIL>: stop at this commit position (default: current).
  • --amend: fold file changes into each commit (see above).
  • --discard: discard file changes after each commit.
  • --keep-going: continue on command failure instead of stopping at the first failed commit (default is to stop).
  • -j, --jobs <N>: number of parallel workers. 0 = auto-detect CPUs, 1 = sequential (default). Parallel mode only applies to read-only runs and uses isolated temporary worktrees, one per commit.
  • --json: emit structured JSON output instead of human-readable text.

Examples

# Read-only validation across the whole stack
gg run -- cargo test

# Apply a formatter and fold changes into each commit
gg run --amend -- cargo fmt

# Run across up to commit 2 only
gg run --until 2 -- cargo check

# Parallel read-only run with 4 workers
gg run -j 4 -- cargo test

# Continue past failing commits (useful for auditing)
gg run --keep-going -- cargo clippy

# Pass a command with quoted arguments — argv boundaries are preserved
gg run -- sh -c 'echo "$GG_CURRENT_COMMIT" && cargo test --test integration'

JSON output

With --json, gg run emits a RunResponse:

{
  "version": 1,
  "run": {
    "results": [
      {
        "position": 1,
        "sha": "abc1234",
        "title": "Add feature",
        "passed": true,
        "commands": [
          {"command": "cargo test", "passed": true, "output": null}
        ]
      }
    ],
    "all_passed": true
  }
}

The command field is a copy-pasteable shell form: arguments containing whitespace or special characters are single-quoted. It reflects your input, not the actual argv.

Notes

  • Shell aliases (alias gw=./gradlew) are not expanded. Use the real command path.
  • .git/ paths in the command (e.g. .git/gg/lint.sh) are rewritten to the real git commondir so they resolve inside linked worktrees and inside parallel worker worktrees.
  • Parallel mode creates temporary worktrees under $TMPDIR/gg-run-<pid>/, cleaned up automatically when the run finishes.
  • Under --amend + default stop-on-error, if a later commit fails, the already-amended commits above are preserved — the branch is not force-reset. Use gg continue / gg abort to resume if a rebase conflict happened instead.

gg setup

Interactive setup for .git/gg/config.json.

gg setup        # Quick mode: essential settings only
gg setup --all  # Full mode: all settings organized by category

Use this when:

  • Starting git-gud in a new repository
  • Working with self-hosted GitHub/GitLab
  • Updating defaults (base branch, username, lint config)

Quick Mode (default)

Quick mode prompts for only the essential settings:

  • Provider: GitHub or GitLab
  • Base branch: Default base branch (main/master/trunk)
  • Username: Username for branch naming

After completing quick setup, you’ll see:

Tip: Run 'gg setup --all' to configure advanced options (sync, land, lint, etc.)

Full Mode (--all)

Full mode organizes all settings into logical groups:

General

FieldTypeDefaultDescription
providerselectauto-detectGitHub or GitLab (auto-detects github.com and gitlab.com remotes; self-hosted instances require manual selection)
basestringauto-detectDefault base branch (main/master/trunk)
branch_usernamestringfrom CLI authUsername for branch naming
unstaged_actionselectaskAction for gg amend with unstaged changes

Sync

FieldTypeDefaultDescription
sync_auto_rebaseboolfalseAuto-rebase when base is behind origin
sync_behind_thresholdnumber1Commits behind origin before warning/rebase
sync_draftboolfalseCreate new PRs/MRs as drafts by default
sync_update_descriptionsbooltrueUpdate PR/MR descriptions on re-sync
sync_update_titleboolfalseUpdate PR/MR titles on re-sync
stack_nav_commentsboolfalsePost a managed navigation comment linking all PRs/MRs in the stack

Land

FieldTypeDefaultDescription
land_auto_cleanboolfalseAuto-clean stack after landing all
land_wait_timeout_minutesnumber30Timeout for gg land --wait

Lint

FieldTypeDefaultDescription
lintlistemptyLint commands to run per commit
sync_auto_lintboolfalseRun lint automatically before sync

Worktrees

FieldTypeDefaultDescription
worktree_base_pathstringemptyTemplate for stack worktrees ({repo}, {stack})

GitLab (only shown if provider is GitLab)

FieldTypeDefaultDescription
gitlab.auto_merge_on_landboolfalseUse “merge when pipeline succeeds”

Global Config

git-gud supports global configuration at ~/.config/gg/config.json. When running gg setup:

  • If no local config exists, global defaults are shown in prompts
  • Local config always takes precedence over global config

This allows you to set organization-wide defaults while allowing per-repo overrides.

All fields are written to config.json after setup, making it easy to review and edit configuration manually.

Note: auto_add_gg_ids is deprecated. Existing configs that include it are still read, but setup no longer prompts for it and runtime behavior always treats it as enabled.

gg continue / gg abort

Control paused operations (typically rebases with conflicts).

gg continue
gg abort

Use gg continue after resolving conflicts and staging files. Use gg abort when you want to stop and roll back the in-progress operation.

gg reconcile

Repair stack metadata when branches/PRs were manipulated outside gg sync.

gg reconcile [OPTIONS]

Options

  • -n, --dry-run: Preview only; make no changes

What it does

  • Normalizes GG metadata trailers on stack commits (GG-ID + GG-Parent)
  • Maps existing remote PRs/MRs back to local stack entries

Examples

# Safe preview
gg reconcile --dry-run

# Apply reconciliation
gg reconcile

gg restack

Repair stack ancestry after manual history changes (amend, cherry-pick, upstream rebase).

gg restack [OPTIONS]

Options

  • -n, --dry-run: Show what would be done without making changes
  • --from <TARGET>: Repair only from this commit upward (position, SHA, or GG-ID)
  • --json: Output result as JSON

Behavior

  1. Validates no rebase is already in progress
  2. Validates the working directory is clean
  3. Requires all commits to have GG-IDs (directs to gg reconcile if missing)
  4. Compares each entry’s GG-Parent trailer against the expected parent
  5. If all parents match, reports “Stack is already consistent”
  6. If --dry-run, displays the plan and exits
  7. Performs a single git rebase -i to realign the chain
  8. Normalizes GG metadata after rebase
  9. Prints a summary with a hint to run gg sync

Examples

# Check if the stack needs restacking (no changes)
gg restack --dry-run

# Repair the full stack
gg restack

# Repair only from position 3 upward
gg restack --from 3

# Repair from a specific GG-ID upward
gg restack --from c-abc1234

# Get JSON output
gg restack --json

# Combine dry-run and JSON
gg restack --dry-run --json

JSON Output

{
  "version": 1,
  "restack": {
    "stack_name": "my-feature",
    "total_entries": 4,
    "entries_restacked": 2,
    "entries_ok": 2,
    "dry_run": false,
    "steps": [
      {
        "position": 1,
        "gg_id": "c-abc1234",
        "title": "Add login form",
        "action": "ok",
        "current_parent": null,
        "expected_parent": null
      },
      {
        "position": 2,
        "gg_id": "c-def5678",
        "title": "Add validation",
        "action": "reattach",
        "current_parent": "c-old1111",
        "expected_parent": "c-abc1234"
      }
    ]
  }
}

Edge Cases

  • Empty stack produces an error
  • Missing GG-IDs directs to gg reconcile
  • Rebase in progress blocks with a clear error message
  • Rebase conflicts are handled the same as gg reorder — resolve with gg continue or gg abort
  • --from 1 is equivalent to a full restack

gg undo

Reverse the local ref/HEAD effects of the most recent mutating gg command, backed by a per-repo operation log.

gg undo [OPERATION_ID] [--json]
gg undo --list [--limit N] [--json]

gg undo only moves refs and HEAD — it never modifies your working tree, working-copy files, or the index. The log lives at <commondir>/gg/operations/*.json and keeps the last 100 records; Pending records (operations interrupted by a crash, Ctrl-C, or a long-running conflict) are never pruned.

Options

  • OPERATION_ID: Target a specific record (op_…). When omitted, undoes the most recent locally-undoable operation.
  • --list: Show recent operations with id, kind, status, timestamp, and undoability marker.
  • --limit N: Cap --list output (default: 20).
  • --json: Emit machine-readable JSON.

How it works

Every mutating gg command (sc, drop, split, unstack, rebase, reorder, absorb, reconcile, checkout, mv/first/last/prev/next, clean, sync, land, and run --amend) now snapshots the refs it will touch before mutating and finalises the record on success. gg undo replays the refs_before snapshot of the target record, moving refs back to where they were.

A second gg undo redoes the first — because undo is itself recorded as an operation, running it twice reverses the reversal. Entries created by gg undo appear in --list with a marker and an undoes field pointing at the original operation id.

Refusal modes

gg undo refuses (exit 1, no refs touched) when:

ReasonConditionWhat to do
remoteThe target op pushed/merged/closed/created a PR or MR.Use the printed provider hint (gh pr close <n>, glab mr close <n>, git push --delete …). Local state is unchanged.
interruptedThe op crashed or was Ctrl-C’d mid-flight and has status: Interrupted.Fix the underlying state manually; the stale Pending record is swept into Interrupted on the next lock-acquiring op.
staleRefs have moved since the target op finalised.Run gg undo --list and target a more recent record instead. The error names the ref, the expected OID, and the actual OID.
unsupported_schemaThe record was written by a newer gg with a schema version this binary does not understand.Upgrade gg or delete the offending record.

Examples

# Reverse the last local operation
gg undo

# See what's on the log (newest first)
gg undo --list
gg undo --list --limit 5

# Target a specific record from --list
gg undo op_0000001750000000_018f…

# Redo: undo twice in a row
gg undo
gg undo

# Scripting
gg undo --list --json | jq '.operations[] | select(.is_undoable)'

JSON output

Schema versioning: all gg JSON responses share the top-level version field (OUTPUT_VERSION). The undo types are additive — new optional fields may be added in future releases without bumping the version. Forward-compatible consumers should ignore unknown fields.

gg undo --json

{
  "version": 1,
  "status": "succeeded",
  "undone": {
    "id": "op_0000001750000000_018f…",
    "kind": "drop",
    "status": "committed",
    "created_at_ms": 1750000000000,
    "args": ["drop", "3"],
    "stack_name": "feat/login",
    "touched_remote": false,
    "is_undoable": true,
    "is_undo": false,
    "remote_effects": []
  }
}

On refusal:

{
  "version": 1,
  "status": "refused",
  "refusal": {
    "reason": "remote",
    "message": "Cannot locally undo 'sync': it touched a remote.",
    "target": { "id": "op_…", "kind": "sync", "touched_remote": true, "…": "…" },
    "hints": [
      "Close PR #42: gh pr close 42",
      "Delete remote branch: git push --delete origin nacho/feat/1"
    ]
  }
}

refusal.reason is one of remote, interrupted, stale, unsupported_schema.

gg undo --list --json

{
  "version": 1,
  "operations": [
    {
      "id": "op_…",
      "kind": "undo",
      "status": "committed",
      "created_at_ms": 1750000100000,
      "args": ["undo"],
      "stack_name": "feat/login",
      "touched_remote": false,
      "is_undoable": true,
      "is_undo": true,
      "undoes": "op_previous…"
    },
    { "…": "…" }
  ]
}

Operations are returned newest-first. Use is_undoable to gate UI/agent actions; use is_undo + undoes to render redo markers.

What gg undo does NOT do

  • It does not restore working-tree files or the index. If you amended a commit and want the old source back, use git reflog or git stash.
  • It does not touch remotes. Operations that pushed, merged, closed, or created PRs/MRs are recorded (so you can see them in --list) but refused for local replay.
  • It does not guarantee atomicity of the replay. If the process dies mid-replay, a second gg undo will finish the job — the working tree is clean throughout, so only refs move.
  • It does not support an --all / --range mode. Each call reverses exactly one operation.

See also

  • gg log — smartlog view of the current stack
  • MCP serverstack_undo and stack_undo_list tools for agentic workflows

MCP Server

git-gud includes an MCP (Model Context Protocol) server that allows AI assistants like Claude Desktop, Cursor, and other MCP-compatible tools to interact with your stacked-diffs workflows programmatically.

Installation

The gg-mcp binary is distributed alongside gg. If you installed via Homebrew or cargo-dist, it should already be available.

Configuration

Claude Desktop

Add this to your Claude Desktop MCP configuration (~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "git-gud": {
      "command": "gg-mcp",
      "env": {
        "GG_REPO_PATH": "/path/to/your/repo"
      }
    }
  }
}

Cursor / Other MCP Clients

Configure gg-mcp as a stdio-based MCP server. Set GG_REPO_PATH to point to your repository.

Environment Variables

VariableDescriptionDefault
GG_REPO_PATHPath to the git repositoryCurrent working directory

Available Tools

stack_list

List the current stack with commit entries and PR/MR status.

Parameters:

  • refresh (boolean, optional): Refresh PR/MR status from remote before listing. Default: false.

Returns: Stack name, base branch, commit entries with positions, SHAs, titles, GG-IDs, PR numbers, states, CI status, and approval status.

stack_log

Render the current stack as a smartlog-style view (stack-scoped). Mirrors the CLI gg log --json output.

Parameters:

  • refresh (boolean, optional): Refresh PR/MR status from remote before rendering. Default: false.

Returns: { stack, base, current_position, entries: [...] }. Entry fields match stack_list. Use stack_list_all when you need a cross-stack overview.

stack_list_all

List all stacks in the repository with summary information.

Parameters: None.

Returns: Current stack name and a list of all stacks with name, base branch, commit count, and whether each is the current stack.

stack_inbox

Show actionable triage across local stacks. Mirrors gg inbox --json and groups PRs/MRs into action buckets like ready to land, blocked on CI, awaiting review, behind base, draft, and optionally merged.

Parameters:

  • all (boolean, optional): Include merged items as well.

Returns: A versioned JSON payload with total_items and buckets, where each bucket contains entries with stack name, position, SHA, title, PR/MR number, URL, CI status, and optional behind-base count.

stack_status

Get a quick status summary of the current stack.

Parameters: None.

Returns: Stack name, base branch, total commits, synced commits, current position, and how many commits behind the base branch.

pr_info

Get detailed information about a specific PR/MR by number.

Parameters:

  • number (integer, required): The PR/MR number to look up.

Returns: PR number, title, state (open/merged/closed/draft), URL, draft status, approval status, mergeability, and CI status.

config_show

Show the current git-gud configuration for this repository.

Parameters: None.

Returns: Provider, base branch, branch username, lint commands, and boolean settings (including compatibility field auto_add_gg_ids, which is always returned as true).

Write Tools

stack_checkout

Create a new stack or switch to an existing one.

Parameters:

  • name (string, optional): Stack name.
  • base (string, optional): Base branch (default: main/master).
  • worktree (boolean, optional): Use a git worktree for isolation.

stack_sync

Push branches and create/update PRs/MRs for the current stack.

Parameters:

  • draft (boolean, optional): Create PRs as draft.
  • force (boolean, optional): Force-push branches.
  • update_descriptions (boolean, optional): Update PR descriptions from commit messages.
  • update_title (boolean, optional): Update PR titles from commit messages.
  • no_rebase_check (boolean, optional): Skip rebase-needed check.
  • lint (boolean, optional): Run lint before syncing.
  • until (string, optional): Only sync up to this position/GG-ID/SHA.

stack_land

Merge approved PRs/MRs from the current stack.

Parameters:

  • all (boolean, optional): Land all approved PRs.
  • squash (boolean, optional): Use squash merge.
  • auto_clean (boolean, optional): Auto-clean the stack after landing.
  • until (string, optional): Only land up to this position/GG-ID/SHA.

stack_clean

Clean up stacks whose PRs have been merged.

Parameters:

  • all (boolean, optional): Clean all merged stacks.

stack_rebase

Rebase the current stack onto the latest base branch.

Parameters:

  • target (string, optional): Target branch to rebase onto.

stack_squash

Squash (amend) staged changes into the current commit.

Parameters:

  • all (boolean, optional): Stage all changes first.

stack_absorb

Auto-absorb staged changes into the correct commits.

Parameters:

  • dry_run (boolean, optional): Show what would be absorbed.
  • and_rebase (boolean, optional): Rebase after absorbing.
  • whole_file (boolean, optional): Absorb whole files.
  • one_fixup_per_commit (boolean, optional): One fixup per target commit.
  • squash (boolean, optional): Squash fixups immediately.

stack_reconcile

Reconcile out-of-sync branches pushed outside of gg.

Parameters:

  • dry_run (boolean, optional): Show what would change.

stack_move

Move to a specific commit in the stack.

Parameters:

  • target (string, required): Position number, GG-ID, or SHA prefix.

stack_navigate

Navigate within the stack.

Parameters:

  • direction (string, required): "first", "last", "prev", or "next".

stack_lint

Run configured lint commands on each commit.

Parameters:

  • until (integer, optional): Only lint up to this position.

stack_drop

Remove commits from the stack.

Parameters:

  • targets (array of strings, required): Commits to drop—positions (1-indexed), short SHAs, or GG-IDs.

Notes: Always uses --force (the agent is expected to confirm with the user before calling). Returns JSON with dropped commits.

stack_split

Split a commit into two by moving specified files to a new commit.

Parameters:

  • commit (string, optional): Target commit—position (1-indexed), short SHA, or GG-ID. Defaults to the current commit.
  • files (array of strings, required): Files to include in the new commit.
  • message (string, optional): Message for the new (first) commit.
  • no_edit (boolean, optional): Don’t prompt for the remainder commit message.

Notes: File-level only (no hunk selection via MCP). The new commit is inserted before the original.

stack_reorder

Reorder commits in the stack with an explicit order.

Parameters:

  • order (string, required): New order as positions (1-indexed), e.g., "3,1,2" or "3 1 2".

Notes: No TUI via MCP. The order specifies the new bottom-to-top arrangement of commits.

stack_undo

Reverse the local ref/HEAD effects of the most recent mutating gg command (or a specific operation by id). Shell-out wrapper around gg undo --json.

Parameters:

  • operation_id (string, optional): Target a specific record (see stack_undo_list). Defaults to the most-recent-undoable operation.

Notes: Refuses on remote-touching operations (sync, land) — the returned payload includes a refusal.reason of remote plus a provider-specific revert hint (e.g. gh pr close <n>, git push --delete …). Agents must surface the hint to the user rather than attempt silent remote rollback. Also refuses on interrupted, stale, and unsupported_schema records; the working tree is never modified.

stack_undo_list

List recent operations from the per-repo operation log (newest-first). Shell-out wrapper around gg undo --list --json.

Parameters:

  • limit (integer, optional): Cap the number of records returned (default: 20).

Notes: Each entry carries is_undoable (gate for safe local replay) and, for undo records, is_undo + undoes (the operation id being reversed). Remote-touching ops appear with is_undoable: false and touched_remote: true.

Transport

The MCP server uses stdio transport (JSON-RPC over stdin/stdout), which is the standard for local MCP tools. No network configuration is needed.

Configuration

git-gud uses a layered configuration system:

  1. Global config: ~/.config/gg/config.json — shared defaults across all repos
  2. Local config: .git/gg/config.json — per-repository settings

Local config always takes precedence over global config.

Setup

Initialize or update local config with:

gg setup        # Quick mode: essential settings only
gg setup --all  # Full mode: all settings organized by category

For global config, manually create ~/.config/gg/config.json with your preferred defaults.

Example config

{
  "defaults": {
    "provider": "gitlab",
    "base": "main",
    "branch_username": "your-username",
    "lint": [
      "cargo fmt --check",
      "cargo clippy -- -D warnings"
    ],
    "auto_add_gg_ids": true,
    "unstaged_action": "ask",
    "land_wait_timeout_minutes": 30,
    "land_admin": false,
    "land_auto_clean": false,
    "sync_auto_lint": false,
    "sync_auto_rebase": false,
    "sync_behind_threshold": 1,
    "sync_draft": false,
    "sync_update_descriptions": true,
    "sync_update_title": false,
    "worktree_base_path": "/tmp/gg-worktrees",
    "gitlab": {
      "auto_merge_on_land": false
    }
  }
}

defaults options

OptionTypeWhat it controlsDefault
providerstringProvider (github/gitlab) for self-hosted or explicit overrideAuto-detected
basestringDefault base branch for new stacksAuto-detected
branch_usernamestringUsername prefix in stack/entry branch namesAuto-detected
lintstring[]Commands used by gg lint / gg sync --lint[]
auto_add_gg_idsbooleanDeprecated compatibility field. gg always enforces GG metadata normalization, regardless of this value.true
unstaged_actionstringDefault behavior for gg sc/gg amend when unstaged changes exist: ask, add, stash, continue, or abortask
land_wait_timeout_minutesnumberTimeout for gg land --wait polling30
land_adminbooleanUse admin privileges to bypass approval requirements on land (GitHub only)false
land_auto_cleanbooleanAuto-run cleanup after full landingfalse
sync_auto_lintbooleanAutomatically run gg lint before gg syncfalse
sync_auto_rebasebooleanAutomatically run gg rebase before gg sync when behind threshold is reachedfalse
sync_behind_thresholdnumberWarn/rebase in gg sync when base is at least this many commits behind origin/<base> (0 disables check)1
sync_draftbooleanCreate new PRs/MRs as drafts by defaultfalse
sync_update_descriptionsbooleanUpdate PR/MR descriptions on re-synctrue
sync_update_titlebooleanUpdate PR/MR titles on re-syncfalse
stack_nav_commentsbooleanPost a managed navigation comment on each open PR/MR in a multi-entry stack, listing all entries with a 👉 marker on the current one. When set back to false, the next gg sync removes any previously-posted managed comments. Skipped for single-entry stacks and when --until limits a sync.false
worktree_base_pathstringBase directory for managed worktreesParent of repo
gitlab.auto_merge_on_landbooleanDefault GitLab auto-merge behavior for gg landfalse

Global Config

Store shared defaults in ~/.config/gg/config.json. This is useful for:

  • Organization-wide settings (e.g., always use drafts)
  • Personal preferences that apply to all your repos
  • Reducing repetitive setup across multiple repositories

Example global config:

{
  "defaults": {
    "sync_draft": true,
    "land_auto_clean": true,
    "sync_behind_threshold": 5
  }
}

When gg setup runs in a new repo, these global defaults will be shown in prompts. You can accept them or override per-repo.

Stack state

git-gud also stores stack-specific state in the local config file (for example PR/MR mappings by GG-ID). This is how it remembers which commit corresponds to which PR/MR over time.

PR/MR templates

You can customize descriptions by creating .git/gg/pr_template.md.

Supported placeholders:

  • {{title}}
  • {{description}}
  • {{stack_name}}
  • {{commit_sha}}

Example:

## Summary

{{description}}

---

**Stack:** `{{stack_name}}`
**Commit:** `{{commit_sha}}`

Shell Completions

Generate completions with:

gg completions <shell>

Supported shells include: bash, zsh, fish, elvish, powershell.

Bash

mkdir -p ~/.local/share/bash-completion/completions
gg completions bash > ~/.local/share/bash-completion/completions/gg

Zsh

mkdir -p ~/.zfunc
gg completions zsh > ~/.zfunc/_gg

Then in ~/.zshrc:

fpath=(~/.zfunc $fpath)
autoload -Uz compinit && compinit

Fish

mkdir -p ~/.config/fish/completions
gg completions fish > ~/.config/fish/completions/gg.fish

Troubleshooting / FAQ

gh or glab is missing

Install the provider CLI:

Not authenticated with provider

gh auth login
glab auth login

“Not on a stack branch”

You’re on a branch that doesn’t match the stack naming scheme.

gg co <stack-name>

I pushed with git push and now mappings are wrong

Run reconcile:

gg reconcile --dry-run
gg reconcile

Merge commits are not supported

Stacks require linear history. Rebase your branch:

git rebase main

gg land --wait times out

Increase timeout in config:

{
  "defaults": {
    "land_wait_timeout_minutes": 60
  }
}

When should I use gg absorb vs gg sc?

  • Use gg sc when you’re on the exact commit you want to modify.
  • Use gg absorb when staged edits belong to multiple commits and you want git-gud to distribute them.

How do I undo a gg command?

Run gg undo. It reverses the local ref/HEAD effects of the most recent mutating gg command (drop, squash, split, unstack, rebase, reorder, absorb, reconcile, checkout, nav, clean, sync, land, or run --amend). Working-tree changes are not touched.

gg undo              # reverse the last local operation
gg undo --list       # see the recent operation log
gg undo <op_id>      # target a specific record
gg undo; gg undo     # undo then redo — a second undo reverses the first

Remote-touching operations (sync, land) are recorded but refused for local replay. gg undo prints a provider-specific revert hint (e.g. gh pr close <n>, git push --delete …) instead of silently rewriting published history. See gg undo for the full refusal matrix and JSON schema.