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
-
Introducing git-gud — the story behind this tool, by its author
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:
- Git 2.x+
- GitHub CLI (
gh) for GitHub repositories - GitLab CLI (
glab) for GitLab repositories
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.
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 setupis 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-IDs
Each stack commit carries a stable trailer, for example:
GG-ID: c-abc1234
Why GG-IDs matter:
- They keep commit-to-PR/MR mappings stable across rebases
- They let git-gud identify entries by a durable ID (not just SHA)
- They make reconcile and navigation safer 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-authnacho/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.
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
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…”.
Navigate to target commit
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
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:
- Prefer
gg syncover manualgit pushto keep mappings healthy - If mappings drift, use Reconciling Out-of-Sync Stacks
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
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 pushinstead ofgg 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:
- Add missing GG-IDs to stack commits (via rebase)
- Map existing PRs/MRs to the right GG-IDs in config
After reconciling, run:
gg ls --refresh
gg sync
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,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.
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
Examples
# Current stack status
gg ls
# All local stacks
gg ls --all
# Remote stacks you can check out
gg ls --remote
# Refresh status badges from provider
gg ls --refresh
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-f, --force: Force push even if remote is ahead--update-descriptions: Update PR/MR title/body from commit messages-l, --lint: Run lint before sync--no-lint: Disable lint before sync (overrides config default)-u, --until <UNTIL>: Sync up to target commit (position, GG-ID, or SHA)
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
# Run lint as part of sync
gg sync --lint
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
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 creatingfixup!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 reorder
Reorder commits in your stack.
gg reorder [OPTIONS]
Options
-o, --order <ORDER>: New order as positions/SHAs ("3,1,2"or"3 1 2")
Examples
# Interactive reorder
gg reorder
# Explicit reorder
gg reorder --order "3,1,2"
gg rebase
Rebase the current stack onto an updated branch.
gg rebase [TARGET]
- If
TARGETis omitted, git-gud uses the stack base branch.
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
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
gg clean
Delete merged stacks (and associated managed worktrees).
gg clean [OPTIONS]
Options
-a, --all: Clean all merged stacks without prompting
Examples
gg clean
gg clean --all
gg lint
Run configured lint commands on stack commits.
gg lint [OPTIONS]
Options
-u, --until <UNTIL>: Stop at target entry (position, GG-ID, SHA)
Examples
# Lint from bottom to current
gg lint
# Lint only a subset
gg lint --until 2
gg setup
Interactive setup for .git/gg/config.json.
gg setup
Use this when:
- Starting git-gud in a new repository
- Working with self-hosted GitHub/GitLab
- Updating defaults (base branch, username, lint config)
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
- Adds missing GG-ID trailers to stack commits
- Maps existing remote PRs/MRs back to local stack entries
Examples
# Safe preview
gg reconcile --dry-run
# Apply reconciliation
gg reconcile
Configuration
git-gud stores config per repository in .git/gg/config.json.
Initialize or update it with:
gg setup
Example config
{
"defaults": {
"provider": "gitlab",
"base": "main",
"branch_username": "your-username",
"lint": [
"cargo fmt --check",
"cargo clippy -- -D warnings"
],
"auto_add_gg_ids": true,
"land_wait_timeout_minutes": 30,
"land_auto_clean": false,
"worktree_base_path": "/tmp/gg-worktrees",
"gitlab": {
"auto_merge_on_land": false
}
}
}
defaults options
| Option | Type | What it controls | Default |
|---|---|---|---|
provider | string | Provider (github/gitlab) for self-hosted or explicit override | Auto-detected |
base | string | Default base branch for new stacks | Auto-detected |
branch_username | string | Username prefix in stack/entry branch names | Auto-detected |
lint | string[] | Commands used by gg lint / gg sync --lint | [] |
auto_add_gg_ids | boolean | Auto-add GG-ID trailers when missing | true |
land_wait_timeout_minutes | number | Timeout for gg land --wait polling | 30 |
land_auto_clean | boolean | Auto-run cleanup after full landing | false |
worktree_base_path | string | Base directory for managed worktrees | Parent of repo |
gitlab.auto_merge_on_land | boolean | Default GitLab auto-merge behavior for gg land | false |
Stack state
git-gud also stores stack-specific state in this 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:
- GitHub: https://cli.github.com/
- GitLab: https://gitlab.com/gitlab-org/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 scwhen you’re on the exact commit you want to modify. - Use
gg absorbwhen staged edits belong to multiple commits and you want git-gud to distribute them.