Back to Gitbutler

GitButler CLI Workflow Examples

crates/but/skill/references/examples.md

0.21.213.0 KB
Original Source

GitButler CLI Workflow Examples

Real-world examples of common workflows.

Note on CLI IDs: Examples below use illustrative IDs like bu, nn, a1 to keep commands readable. In practice, always read actual IDs from but status -fv — they are generated per-session and will differ from these examples. Branch IDs are derived from unique substrings of the branch name (e.g., fe from feature-x), commit IDs are short change-ID prefixes that stay stable across history edits (e.g., kyn; commits without a change ID fall back to a sha prefix), and file/hunk/stack IDs are auto-generated (e.g., r, r:c, h0). All IDs are unique across entity types.

Example 1: Starting Independent Parallel Work

Scenario: Need to work on two independent features: a new API endpoint and UI styling updates.

bash
# 1. Check current state
but status -fv

# 2. Create two independent (parallel) branches
but branch new api-endpoint
but branch new ui-styling

# 3. Make changes to multiple files
# (edit api/users.js and components/Button.svelte)

# 4. Check what's uncommitted
but status -fv

# 5. Commit specific files directly using --changes (recommended for agents)
# Use CLI ID values from but status -fv output (e.g., branch IDs and file IDs)
# For multiple IDs, use one comma-separated argument or repeat --changes.
but commit <api-branch-id> -m "Add user details endpoint" --changes <api-file-id>
but commit <ui-branch-id> -m "Update button hover styles" --changes <ui-file-id>

# Follow-up fix that belongs in a commit you just made? Amend it in.
# Each mutation returns updated workspace state — take any new IDs you need from it.
# but amend <api-commit-id> --changes <api-fix-file-id>,<api-fix-hunk-id>

# 6. Create pull requests (auto-pushes the branches)
but pr new <api-branch-id>
but pr new <ui-branch-id>

Why parallel branches? The API endpoint and UI styling are independent - neither depends on the other. They can be reviewed and merged separately.

Example 2: Building Stacked Features

Scenario: Need to add authentication, then build a user profile page that requires auth.

bash
# 1. Check current state and update
but pull
but status -fv

# 2. Create base branch for authentication
but branch new add-authentication

# 3. Implement auth and commit
# (edit auth/login.js, auth/middleware.js)
but status -fv
but commit bu -m "Add JWT authentication" --changes <file-ids>

# 4. Create stacked branch anchored on authentication
but branch new user-profile -a bu

# 5. Implement profile page (depends on auth)
# (edit pages/profile.js)
but status -fv
but commit bv -m "Add user profile page" --changes <file-ids>

# 6. Create stacked pull requests through GitButler (auto-pushes the stack)
but pr new bv -t

Result: Two PRs where user-profile targets add-authentication, with GitButler stack information in the PR descriptions.

Example 3: Amending Fixes Into Existing Commits

Scenario: Made a small typo fix that should be part of an existing commit, not a new commit.

bash
# 1. Check current commits and uncommitted changes
but status -fv

# Output shows:
# Branch: feature-x (bu)
# Commits:
#   nn: Implement feature logic
#   mm: Add feature tests
# Uncommitted:
#   a1: fix-typo.js

# 2. Decide which commit the fix belongs to
# (the typo is in code introduced by nn, so it belongs in nn)

# 3. Amend the file into that commit
but amend nn --changes a1    # Amend just this file + get updated status

Why amend? Keeps history clean. Small fixes belong in the commits they fix, not as separate "fix typo" commits. You know what you changed and why — pick the target commit yourself.

Example 4: Reorganizing Commit History

Scenario A: Squashing Commits

Situation: Made 5 small WIP commits, want to combine into one logical commit.

bash
# Before (newest first):
# rr: More tweaks
# pp: Fix another thing
# nn: Fix tests
# mm: Adjust logic
# kk: Initial implementation

# Squash all commits in branch
but squash bu

# Or squash specific range
but squash mm..rr    # Squashes mm, nn, pp, rr into one

# Or squash specific commits
but squash mm nn pp    # Squashes these three

Scenario B: Moving Files Between Commits

Situation: A file was committed in the wrong commit, need to move it.

bash
# 1. See which files are in which commits
but status -fv

# Output shows:
# nn: api.js, utils.js
# mm: config.js

# 2. Move utils.js from nn to mm
but rub a2 mm    # File a2 (utils.js) → commit mm + get updated status

Scenario C: Moving Commit to Different Branch

Situation: Committed to wrong branch, need to move commit.

bash
# 1. Check current state
but status -fv

# Output:
# Branch: feature-a (bu)
#   nn: This should be in feature-b!
#   mm: Correct commit

# 2. Create or identify target branch
but branch new feature-b    # Creates branch bv

# 3. Move the commit
but move nn bv    # Move nn to top of branch bv

Example 5: Stacking Existing Branches

Scenario: Two independent branches exist, but one now depends on the other. Stack them.

bash
# 1. Check current state — two independent branches in separate stacks
but status -fv

# Output:
# Stack 1: feature/backend (bu) — 2 commits
# Stack 2: feature/frontend (bv) — 1 commit

# 2. Frontend now depends on backend API — stack frontend on backend
#    IMPORTANT: Prefer full branch NAMES here; branch CLI IDs are also accepted
but move feature/frontend feature/backend

# Result: Both branches are now in the same stack:
# Stack 1: feature/backend → feature/frontend (stacked)

# 3. Continue working — commits go to the right branch
but status -fv
but commit bu -m "Add caching layer" --changes <id>   # To backend
but commit bv -m "Add dialog component" --changes <id> # To frontend

Key point: branch stack moves use branch names (like feature/frontend) or branch CLI IDs. Commit reordering still uses commit IDs.

Example 6: Conflict Resolution

Scenario: After but pull, conflicts appear in a commit.

bash
# 1. Pull updates
but pull

# Output:
# Summary
# ────────
#   feature-x - conflicted
#       nn Add validation

# 2. Enter resolution mode using the commit ID from the pull output
but resolve nn

# Output:
# Checking out conflicted commit nn
# Conflicted files remaining:
#   ✗ api/users.js
#      12│<<<<<<< New base: ...
#      ...conflict regions with line numbers...

# 3. Edit each conflicted file to resolve
# IMPORTANT: You MUST edit the files — do NOT just run `but resolve finish`
# NEVER use `git add`, `git checkout --theirs/--ours`, or any git write command — just edit the files directly with the Edit tool, then `but resolve finish`
# (edit to remove <<<<<<< ======= >>>>>>> markers and keep correct content;
#  with several conflicted files, `but resolve status` re-lists what remains)

# 4. Finalize
but resolve finish

# Output:
# ✓ Conflict resolution finalized successfully!
# No conflict markers remain in the resolved files.
# Workspace restored; uncommitted changes intact: ...
# No follow-up status or marker scan needed — finish already reports both.

Example 7: Complete Feature Development Workflow

Scenario: Building a complete feature from start to finish.

bash
# 1. Update to latest
but pull

# 2. Create branch for feature
but branch new user-dashboard

# 3. Make initial changes
# (create dashboard.js, add routes)

# 4. Check status and gather file IDs
but status -fv

# 5. First commit
but commit bu -m "Add dashboard route and basic layout" --changes <file-ids>

# 6. Continue iterating
# (add widgets, styling)
but commit bu -m "Add dashboard widgets" --changes <file-ids>
but commit bu -m "Style dashboard components" --changes <file-ids>

# 7. Make small fix
# (fix typo in widget)
but amend <commit-id> --changes a1    # Amend fix into the commit it belongs to

# 8. Clean up if needed
but squash bu    # Combine all commits (optional)

# 9. Create pull request (auto-pushes the branch)
but pr new bu

# Output:
# Created PR #123: https://github.com/org/repo/pull/123

# 10. After PR is merged, update
but pull

Example 8: Working with Applied/Unapplied Branches

Scenario: Have 3 branches, but two are causing conflicts. Temporarily unapply them.

bash
# 1. Check active branches
but status -fv

# Output:
# Applied branches:
#   bu: feature-a
#   bv: feature-b
#   bw: feature-c

# 2. Conflicts between feature-b and feature-c
# Unapply them temporarily
but unapply bv
but unapply bw

# 3. Focus on feature-a
# (make changes, commit)
but commit bu -m "Complete feature-a" --changes <file-ids>

# 4. Create PR for feature-a (auto-pushes)
but pr new bu

# 5. Reapply other branches
but apply feature-b
but apply feature-c

# 6. Deal with their conflicts now
but resolve ...

Example 9: Fixing History Before Pushing

Scenario: Made several commits, realized you need to reword messages and reorder.

bash
# 1. Current state
but status -fv

# Output (newest first):
# Branch: feature-x (bu)
#   rr: final commit
#   pp: WIP
#   nn: Fix stuff
#   mm: Another fix
#   kk: Initial

# 2. Reword commit messages — commit refs are change-ID based and stay
#    valid across rewords and other history edits
but reword pp -m "Add validation logic"
but reword nn -m "Fix edge case in parser"
but reword mm -m "Update error messages"

# 3. Move rr to be earlier
but move rr nn    # Move rr before nn

# 4. Squash similar commits
but squash mm nn    # Combine error handling commits; target nn keeps its ref

# Output (newest first):
# Branch: feature-x (bu)
#   pp: Add validation logic
#   nn: Fix edge case in parser and update error messages
#   rr: final commit
#   kk: Initial

# 5. Push clean history
but push feature-x

Example 10: Daily Development Workflow

Typical day working with GitButler:

bash
# Morning: Start day
but pull    # Get latest from team

# Start new task
but branch new fix-auth-bug  # Create branch for today's work

# Work and commit iteratively
# (make changes)
but status -fv              # Check changes
but commit bu -m "Identify auth bug source" --changes <file-ids>
# (make more changes)
but commit bu -m "Fix token expiration handling" --changes <file-ids>
# (small fix to existing code)
but amend <commit-id> --changes a1  # Amend fix into the commit it belongs to

# Mid-day: Start urgent fix on different branch
but branch new hotfix-login  # Parallel branch for urgent work
# (make fix)
but commit bv -m "Fix login redirect loop" --changes <file-ids>
but pr new bv      # Push and create PR immediately

# Back to original work
# (continue working on bu, auth bug fix)
but commit bu -m "Add tests for token handling" --changes <file-ids>

# End of day: Clean up and create PR
but squash bu    # Combine into clean history
but pr new bu      # Push and create PR

# After PR review: Make requested changes
# (make changes based on feedback)
but amend <commit-id> --changes <file-id>  # Amend each fix into the commit it belongs to
but push fix-auth-bug   # Push updated history

Example 11: Recovering from Mistakes

Scenario: Made changes you didn't mean to, need to undo.

Undo Last Operation

bash
# Made a mistake
but squash bu    # Oops! Didn't mean to squash

# Undo it
but undo         # Reverts the squash

Restore to Earlier Point

bash
# View operation history
but oplog

# Output:
# s5: squash branch bu
# s4: commit bu "message"
# s3: amend a1 into mm
# s2: create branch bu
# s1: pull from remote

# Restore to before squash
but oplog restore s4

Discard Uncommitted Changes

bash
# Changed a file but want to discard
but status -fv

# Output:
# Uncommitted:
#   a1: bad-changes.js

# Discard it
but discard a1

Tips and Tricks

Quick Status Check

bash
but status -fv    # File-centric view for quick overview

Preview Before Doing

bash
but push my-feature --dry-run   # See what would be pushed

Multiple Commits From One Diff

File/hunk IDs copied from the original output generally remain usable across commits. Chain but commit calls to split a dirty diff into several commits in one go:

bash
but diff   # read the file/hunk IDs once

but commit my-branch -m "Add parser" --changes qs:5,qs:2 \
  && but commit my-branch -m "Add tests" --changes uo:d

The commits stack in the order you write them, so Add parser ends up below (older than) Add tests. Chain these commit commands when each references uncommitted IDs (plus the stable branch ID). If an ID stops resolving, re-read the diff and continue. History edits — amend, squash, move, uncommit, reword — may also run in sequence off one status read when every commit ref involved is a change-ID ref; those stay stable across the edits. Run them one at a time when a ref is sha-based or #N-suffixed, or when the next command needs IDs the previous one prints, and take follow-up refs from the returned workspace state.

Auto-completion

bash
eval "$(but completions zsh)"     # Add to ~/.zshrc
eval "$(but completions bash)"    # Add to ~/.bashrc

Viewing History

bash
but show bu       # Show all commits in branch
git log bu               # Traditional git log (read-only, still works)