version-control
This skill should be used when working with GitButler, virtual branches, `but` commands, or when `--gitbutler` or `--but` flags are mentioned.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install outfitter-dev-agents-version-control
Repository
Skill path: gitbutler/skills/version-control
This skill should be used when working with GitButler, virtual branches, `but` commands, or when `--gitbutler` or `--but` flags are mentioned.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: outfitter-dev.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install version-control into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/outfitter-dev/agents before adding version-control to shared team environments
- Use version-control for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: version-control
version: 2.0.0
description: This skill should be used when working with GitButler, virtual branches, `but` commands, or when `--gitbutler` or `--but` flags are mentioned.
---
# GitButler Version Control
Virtual branches → parallel development → post-hoc organization.
<when_to_use>
- Multiple unrelated features in same workspace simultaneously
- Multi-agent concurrent development (agents in same repo)
- Exploratory coding where organization comes after writing
- Post-hoc commit reorganization needed
- Visual organization preferred (GUI + CLI)
NOT for: projects using Graphite (incompatible models), simple linear workflows (use plain git), when PR submission automation required end-to-end (use Graphite instead)
</when_to_use>
<core_concepts>
| Concept | Description |
|---------|-------------|
| Virtual branches | Multiple branches applied simultaneously to working directory |
| Integration branch | `gitbutler/workspace` tracks virtual branch state — never touch directly |
| Target branch | Base branch (e.g., `origin/main`) all work diverges from |
| File assignment | Assign file hunks to branches with `but rub` |
| Stacks | Dependent branches via `--anchor` flag |
| Oplog | Operations log for undo/restore — your safety net |
**Key difference from Git**: All branches visible at once. Organize files to branches after editing. No checkout.
</core_concepts>
<workflow>
## Quick Start
```bash
# Initialize (one time)
but init
# Create branch
but branch new feature-auth
# Make changes, check status for file IDs
but status
# ╭┄00 [Unassigned Changes]
# │ m6 A src/auth.ts
# Assign file to branch using ID
but rub m6 feature-auth
# Commit
but commit feature-auth -m "feat: add authentication"
```
## Core Loop
1. **Create**: `but branch new <name>`
2. **Edit**: Make changes in working directory
3. **Check**: `but status` to see file IDs
4. **Assign**: `but rub <file-id> <branch-name>`
5. **Commit**: `but commit <branch> -m "message"`
6. **Repeat**: Continue with other features in parallel
## The Power of `but rub`
Swiss Army knife — combines entities to perform operations:
| Source | Target | Operation |
|--------|--------|-----------|
| File ID | Branch | Assign file to branch |
| File ID | Commit | Amend commit with file |
| Commit SHA | Branch | Move commit between branches |
| Commit SHA | Commit SHA | Squash (newer into older) |
</workflow>
<parallel_development>
## Parallel Feature Development
```bash
# Create two independent features
but branch new feature-a
but branch new feature-b
# Edit files for both (same workspace!)
echo "Feature A" > feature-a.ts
echo "Feature B" > feature-b.ts
# Assign to respective branches
but rub <id-a> feature-a
but rub <id-b> feature-b
# Commit independently
but commit feature-a -m "feat: implement feature A"
but commit feature-b -m "feat: implement feature B"
# Both branches exist, zero conflicts, same directory
```
## Multi-Agent Workflows
Multiple AI agents working concurrently in same repo:
```bash
# Agent 1
but branch new agent-1-feature
# ... make changes ...
but commit agent-1-feature -m "feat: add feature X"
# Agent 2 (simultaneously, same workspace)
but branch new agent-2-bugfix
# ... make changes ...
but commit agent-2-bugfix -m "fix: resolve issue Y"
```
**See [multi-agent skill](../multi-agent/SKILL.md) for advanced patterns**
</parallel_development>
<completion>
## Completing Work
**CRITICAL**: GitButler CLI lacks native commands for merging to main or creating PRs. Use git for integration.
```bash
# 1. Snapshot for safety
but snapshot --message "Before integrating feature-auth"
# 2. Switch to main
git checkout main
# 3. Update main
git pull origin main
# 4. Merge virtual branch
git merge --no-ff refs/gitbutler/feature-auth -m "feat: add auth"
# 5. Push
git push origin main
# 6. Clean up and return
but branch rm feature-auth
git checkout gitbutler/workspace
```
**See [complete-branch skill](../complete-branch/SKILL.md) for full guided workflow**
</completion>
<commands>
## Essential Commands
| Command | Purpose |
|---------|---------|
| `but init` | Initialize GitButler in repository |
| `but status` | View changes and file IDs |
| `but log` | View commits on active branches |
| `but branch new <name>` | Create virtual branch |
| `but branch new <name> --anchor <parent>` | Create stacked branch |
| `but rub <source> <target>` | Assign/move/squash/amend |
| `but commit <branch> -m "msg"` | Commit to branch |
| `but commit <branch> -o -m "msg"` | Commit only assigned files |
| `but publish` | Publish branches to forge (GitHub) |
| `but forge auth` | Authenticate with GitHub |
| `but absorb` | Amend uncommitted changes |
| `but oplog` | Show operation history |
| `but undo` | Undo last operation |
| `but snapshot --message "msg"` | Create manual snapshot |
| `but base update` | Update workspace with latest base |
| `but .` | Open GitButler GUI for current repo |
**Global flags come first**: `but --json status` ✓ | `but status --json` ✗
</commands>
<ai_integration>
## AI Agent Integration
Three integration methods:
**1. Agents Tab** (Claude Code, recommended)
- GUI-based launcher tied to branches
- Automatic commit management per session
- Parallel agent execution with branch isolation
**2. Lifecycle Hooks**
```json
{
"hooks": {
"PreToolUse": [{"matcher": "Edit|MultiEdit|Write", "hooks": [{"type": "command", "command": "but claude pre-tool"}]}],
"PostToolUse": [{"matcher": "Edit|MultiEdit|Write", "hooks": [{"type": "command", "command": "but claude post-tool"}]}],
"Stop": [{"matcher": "", "hooks": [{"type": "command", "command": "but claude stop"}]}]
}
}
```
**3. MCP Server**
```bash
but mcp # Start MCP server for agent integration
```
</ai_integration>
<rules>
ALWAYS:
- Use `but` for all work within virtual branches
- Use `git` only for integrating completed work into main
- Return to `gitbutler/workspace` after git operations: `git checkout gitbutler/workspace`
- Snapshot before risky operations: `but snapshot --message "..."`
- Assign files immediately after creating: `but rub <id> <branch>`
- Check file IDs with `but status` before using `but rub`
NEVER:
- Use `git commit` on virtual branches — breaks GitButler state
- Use `git add` — GitButler manages index
- Use `git checkout` on virtual branches — no checkout needed
- Push `gitbutler/integration` to remote — it's local-only
- Mix Graphite and GitButler in same repo — incompatible models
- Pipe `but status` directly — causes panic; capture output first:
```bash
status_output=$(but status)
echo "$status_output" | head -5
```
</rules>
<troubleshooting>
## Quick Troubleshooting
| Symptom | Cause | Solution |
|---------|-------|----------|
| Branch not showing in `but log` | Not tracked | `but track --parent <parent>` |
| Files not committing | Not assigned | `but rub <file-id> <branch>` |
| Conflicts in workspace | All branches applied | Resolve in files or reassign hunks |
| Mixed git/but broke state | Used git commands | `but base update` or reinit |
| Broken pipe panic | Output consumed partially | Capture output to variable first |
| Filename with dash fails | Interpreted as range | Use file ID from `but status` |
| Lost work | Need recovery | Use `but oplog` and `but undo` |
## Recovery Pattern
```bash
# View recent operations
but oplog
# Undo last operation
but undo
# Or restore to specific snapshot
but restore <snapshot-id>
# If workspace corrupted
but base update
# Last resort: but init
```
**See [references/reference.md](references/reference.md#troubleshooting-guide) for comprehensive troubleshooting**
</troubleshooting>
<comparison>
## GitButler vs Graphite
| Aspect | Graphite | GitButler |
|--------|----------|-----------|
| Model | Linear stacks of physical branches | Virtual branches, optional stacking |
| Branch switching | Required (`gt up`/`gt down`) | Never needed (all applied) |
| PR submission | ✓ `gt submit --stack` | ✗ CLI only (use `gh` or GUI) |
| Multi-agent | Serial (checkout required) | Parallel (virtual branches) |
| Post-hoc organization | Difficult | `but rub` trivial |
| CLI completeness | Full automation | Partial (missing PR/push) |
**Choose GitButler for**: Exploratory work, multi-agent, post-hoc organization
**Choose Graphite for**: Production automation, PR submission, terminal-first
</comparison>
<references>
- [references/reference.md](references/reference.md) — complete CLI reference and troubleshooting
- [references/examples.md](references/examples.md) — real-world workflow patterns
- [multi-agent skill](../multi-agent/SKILL.md) — multi-agent coordination
- [stack-workflows skill](../stack-workflows/SKILL.md) — stacked branches
- [complete-branch skill](../complete-branch/SKILL.md) — merging to main
- [GitButler Docs](https://docs.gitbutler.com/) — official documentation
</references>
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### ../multi-agent/SKILL.md
```markdown
---
name: multi-agent
version: 2.0.0
description: This skill should be used when coordinating multiple agents, parallel development, agent handoffs, or when multi-agent, concurrent agents, or parallel agents are mentioned.
---
# GitButler Multi-Agent Coordination
Multiple agents → virtual branches → parallel execution → zero coordination overhead.
<when_to_use>
- Multiple agents working on different features simultaneously
- Sequential agent handoffs (Agent A → Agent B)
- Commit ownership transfer between agents
- Parallel execution with early conflict detection
- Post-hoc reorganization of multi-agent work
NOT for: single-agent workflows (use standard GitButler), projects needing PR automation (Graphite better)
</when_to_use>
<core_advantage>
**Traditional Git Problem:**
- Agents must work in separate worktrees (directory coordination)
- Constant branch switching (context loss, file churn)
- Late conflict detection (only at merge time)
**GitButler Solution:**
- Multiple branches stay applied simultaneously
- Single shared workspace, zero checkout operations
- Immediate conflict detection (shared working tree)
- Each agent manipulates their own lane
</core_advantage>
<patterns>
## Pattern 1: Parallel Feature Development
```bash
# Agent 1
but branch new agent-1-auth
echo "auth code" > auth.ts
but rub auth.ts agent-1-auth
but commit agent-1-auth -m "feat: add authentication"
# Agent 2 (simultaneously, same workspace!)
but branch new agent-2-api
echo "api code" > api.ts
but rub api.ts agent-2-api
but commit agent-2-api -m "feat: add API endpoints"
# Result: Two independent features, zero conflicts
```
## Pattern 2: Sequential Handoff
```bash
# Agent A: Initial implementation
but branch new initial-impl
# ... code ...
but commit initial-impl -m "feat: initial implementation"
# Agent B: Takes ownership and refines
but rub <agent-a-commit> refinement-branch
# ... improve code ...
but commit refinement-branch -m "refactor: optimize implementation"
```
## Pattern 3: Cross-Agent Commit Transfer
```bash
# Instant ownership transfer
but rub <commit-sha> agent-b-branch # Agent A → Agent B
but rub <commit-sha> agent-a-branch # Agent B → Agent A
```
</patterns>
<naming>
## Branch Naming Convention
```
<agent-name>-<task-type>-<brief-description>
Examples:
- claude-feat-user-auth
- droid-fix-api-timeout
- codex-refactor-database-layer
```
Makes ownership immediately visible in `but status` and `but log`.
</naming>
<ai_integration>
## AI Integration Methods
**1. Agents Tab (Claude Code)**
- GUI-based launcher tied to branches
- Each virtual branch = independent session
- Automatic commit management per session
- Parallel agent execution with branch isolation
**2. Lifecycle Hooks**
```json
{
"hooks": {
"PreToolUse": [{"matcher": "Edit|MultiEdit|Write", "hooks": [{"type": "command", "command": "but claude pre-tool"}]}],
"PostToolUse": [{"matcher": "Edit|MultiEdit|Write", "hooks": [{"type": "command", "command": "but claude post-tool"}]}],
"Stop": [{"matcher": "", "hooks": [{"type": "command", "command": "but claude stop"}]}]
}
}
```
**3. MCP Server**
```bash
but mcp # Enables programmatic agent integration
```
**Key Instruction for Agents:**
> "Never use the git commit command after a task is finished"
Let GitButler manage commits via hooks or MCP.
</ai_integration>
<coordination>
## Coordination Protocols
**Status Broadcasting:**
```bash
# File-based coordination
but status > /tmp/agent-$(whoami)-status.txt
# Or use Linear/GitHub comments
# "[AGENT-A] Completed auth module, committed to claude-auth-feature"
```
**Snapshot Cadence:**
```bash
# Before risky operations
but snapshot --message "Before merging conflicting branches"
# If it breaks
but undo
```
**Concurrent Safety:**
1. Snapshot before risky operations
2. Broadcast status regularly to other agents
3. Respect 🔒 locks — files assigned to other branches
4. Use `but --json` for programmatic state inspection
</coordination>
<rub_power>
## The `but rub` Power Tool
Single command handles four critical multi-agent operations:
| Operation | Example | Use Case |
|-----------|---------|----------|
| **Assign** | `but rub m6 claude-branch` | Organize files to branches post-hoc |
| **Move** | `but rub abc1234 other-branch` | Transfer work between agents |
| **Squash** | `but rub newer older` | Clean up history |
| **Amend** | `but rub file commit` | Fix existing commits |
</rub_power>
<comparison>
## vs Other Workflows
| Aspect | Graphite | Git Worktrees | GitButler |
|--------|----------|---------------|-----------|
| Multi-agent concurrency | Serial | N directories | Parallel ✓ |
| Post-hoc organization | Difficult | Difficult | `but rub` ✓ |
| PR Submission | `gt submit` ✓ | Manual | GUI only |
| Physical layout | 1 directory | N × repo | 1 directory ✓ |
| Context switching | `gt checkout` | `cd` | None ✓ |
| Conflict detection | Late (merge) | Late (merge) | Early ✓ |
| Disk usage | 1 × repo | N × repo | 1 × repo ✓ |
</comparison>
<rules>
ALWAYS:
- Use unique branch names per agent: `<agent>-<type>-<desc>`
- Assign files immediately after creating: `but rub <id> <branch>`
- Snapshot before coordinated operations
- Broadcast status to other agents when completing work
- Check for 🔒 locked files before modifying
NEVER:
- Use `git commit` — breaks GitButler state
- Let files sit in "Unassigned Changes" — assign immediately
- Modify files locked to other branches
- Mix git and but commands during active agent sessions
</rules>
<troubleshooting>
## Common Issues
| Symptom | Cause | Solution |
|---------|-------|----------|
| Agent commit "orphaned" | Used `git commit` | Find with `git reflog`, recover |
| Files in wrong branch | Forgot assignment | `but rub <id> <correct-branch>` |
| Conflicting edits | Overlapping files | Reassign hunks to different branches |
| Lost agent work | Branch deleted | `but undo` or restore from oplog |
## Recovery
```bash
# Find orphaned commits
git reflog
# Recover agent work
but oplog
but undo
# Extract from snapshot
git show <snapshot>:index/path/to/file.txt
```
</troubleshooting>
<limitations>
## Current Limitations
- **No PR submission CLI** — use `gh pr create` after organizing
- **Overlapping file edits** — adjacent lines can only go to one branch
- **No stack navigation CLI** — no `gt up`/`gt down` equivalent
**Recommendation:** Use for exploratory multi-agent work. For production automation requiring PR submission, consider Graphite until CLI matures.
</limitations>
<references>
- [version-control skill](../version-control/SKILL.md) — core GitButler workflows
- [stack-workflows skill](../stack-workflows/SKILL.md) — stacked branches
- [GitButler AI Docs](https://docs.gitbutler.com/features/ai-integration/) — official AI integration
- [Agents Tab Blog](https://blog.gitbutler.com/agents-tab) — Claude Code integration details
</references>
```
### ../complete-branch/SKILL.md
```markdown
---
name: complete-branch
version: 2.0.0
description: This skill should be used when completing branches, merging to main, creating PRs with GitButler, or when `--complete-branch` flag is mentioned.
---
# Complete GitButler Virtual Branch
Virtual branch ready → snapshot → merge to main → cleanup → return.
<when_to_use>
- Virtual branch work is complete and ready to ship
- Tests pass and code is reviewed (if required)
- Ready to merge changes into main branch
- Need to clean up completed branches
NOT for: ongoing work, branches needing more development, stacks (complete bottom-to-top)
</when_to_use>
<prerequisites>
**Before completing any branch:**
- [ ] GitButler initialized (`but --version` succeeds)
- [ ] Virtual branch exists with committed changes
- [ ] Main branch tracked as base
- [ ] No uncommitted changes (or changes assigned to branches)
- [ ] Tests passing
</prerequisites>
<quick_start>
## Quick Start (7 Steps)
```bash
# 1. Verify branch state
but status
but log
# 2. Create safety snapshot
but snapshot --message "Before integrating feature-auth"
# 3. Switch to main
git checkout main
# 4. Update main from remote
git pull origin main
# 5. Merge virtual branch
git merge --no-ff refs/gitbutler/feature-auth -m "feat: add user authentication"
# 6. Push to remote
git push origin main
# 7. Clean up and return
but branch rm feature-auth
git checkout gitbutler/workspace
```
</quick_start>
<pre_flight>
## Pre-Integration Checklist
```bash
# All work committed?
but status # Your branch should show committed changes
# Tests passing?
bun test # or npm test, cargo test, etc.
# Branch up to date with main?
but base update
# No uncommitted changes?
but status # Should show no unassigned files
git status # Should be clean or show only .gitbutler/ changes
# Safety snapshot created?
but snapshot --message "Before integrating feature-auth"
```
</pre_flight>
<workflows>
## Integration Workflows
### A. Direct Merge to Main
```bash
# 1. Verify branch state
but status
but log
# 2. Create snapshot
but snapshot --message "Before integrating feature-auth"
# 3. Switch to main
git checkout main
# 4. Update main
git pull origin main
# 5. Merge with --no-ff (preserves history)
git merge --no-ff refs/gitbutler/feature-auth -m "feat: add user authentication"
# 6. Push
git push origin main
# 7. Clean up
but branch rm feature-auth
git checkout gitbutler/workspace
```
### B. Pull Request Workflow
```bash
# 1. Push branch to remote
git push origin refs/gitbutler/feature-auth:refs/heads/feature-auth
# 2. Create PR
gh pr create --base main --head feature-auth \
--title "feat: add user authentication" \
--body "Description..."
# 3. Wait for review and approval
# 4. Merge PR (via GitHub UI or CLI)
gh pr merge feature-auth --squash
# 5. Update main and clean up
git checkout main
git pull origin main
but branch rm feature-auth
git checkout gitbutler/workspace
```
### C. Stacked Branches (Bottom-Up)
```bash
# Must merge in order: base → dependent → final
# 1. Merge base branch first
git checkout main && git pull
git merge --no-ff refs/gitbutler/feature-base -m "feat: base feature"
git push origin main
but branch rm feature-base
git checkout gitbutler/workspace
# 2. Update remaining branches
but base update
# 3. Merge next level
git checkout main && git pull
git merge --no-ff refs/gitbutler/feature-api -m "feat: API feature"
git push origin main
but branch rm feature-api
git checkout gitbutler/workspace
# 4. Repeat for remaining stack levels
```
</workflows>
<recovery>
## Error Recovery
### Merge Conflicts
```bash
# View conflicted files
git status
# Resolve conflicts manually
# Stage resolved files
git add src/auth.ts
# Complete merge
git commit
# Verify and push
git push origin main
# Clean up
but branch rm feature-auth
git checkout gitbutler/workspace
```
### Push Rejected (Main Moved Ahead)
```bash
git pull origin main
# Resolve any conflicts if main diverged
git push origin main
```
### Undo Integration (Not Pushed Yet)
```bash
git reset --hard HEAD~1
git checkout gitbutler/workspace
```
### Undo Integration (Already Pushed)
```bash
git revert -m 1 HEAD
git push origin main
```
</recovery>
<cleanup>
## Post-Integration Cleanup
```bash
# Delete integrated virtual branch
but branch rm feature-auth
# Clean up remote branch (if created for PR)
git push origin --delete feature-auth
# Verify workspace is clean
but status # Should show remaining active branches only
but log # Branch should be gone
```
</cleanup>
<rules>
ALWAYS:
- Create snapshot before integration: `but snapshot --message "..."`
- Use `--no-ff` flag to preserve branch history
- Return to workspace after git operations: `git checkout gitbutler/workspace`
- Run tests before integrating
- Complete stacked branches bottom-to-top
NEVER:
- Merge without snapshot backup
- Skip updating main first (`git pull`)
- Forget to return to `gitbutler/workspace`
- Merge middle of stack before base
- Force push to main without explicit confirmation
</rules>
<troubleshooting>
## Common Issues
| Symptom | Cause | Solution |
|---------|-------|----------|
| Merge conflicts | Diverged from main | Resolve conflicts, stage, commit |
| Push rejected | Main moved ahead | `git pull`, resolve, push |
| Branch not found | Wrong ref path | Use `refs/gitbutler/<name>` |
| Can't return to workspace | Integration branch issue | `git checkout gitbutler/workspace` |
## Emergency Recovery
```bash
# If integration went wrong
but oplog
but undo # Restores pre-integration state
# If stuck after git operations
git checkout gitbutler/workspace
```
</troubleshooting>
<best_practices>
## Best Practices
**Keep branches small:**
- Small branches = easier merges
- Aim for single responsibility per branch
**Update base regularly:**
```bash
but base update
```
**Test before integrating:**
- Always run full test suite before merging
**Meaningful merge commits:**
```bash
# Good: Describes what and why
git merge --no-ff feature-auth -m "feat: add JWT-based user authentication"
# Bad: Generic message
git merge --no-ff feature-auth -m "Merge branch"
```
</best_practices>
<references>
- [version-control skill](../version-control/SKILL.md) — core GitButler workflows
- [stack-workflows skill](../stack-workflows/SKILL.md) — stacked branches
- [REFERENCE.md](../version-control/REFERENCE.md) — CLI reference and troubleshooting
</references>
```
### references/reference.md
```markdown
# GitButler Reference
Complete CLI reference, JSON schemas, troubleshooting, and recovery patterns.
---
## Command Reference
### Global Options
```bash
but [OPTIONS] <COMMAND>
Global Options (must come BEFORE subcommand):
-C, --current-dir <PATH> Run from specified directory
-j, --json JSON output format
-h, --help Show help
```
**Critical**: Global flags come first:
```bash
✓ but --json status
✗ but status --json # Error: unexpected argument
```
### Inspection Commands
| Command | Description |
|---------|-------------|
| `but status` | View uncommitted changes and file assignments |
| `but status -f, --files` | Show modified files in each commit |
| `but status -r` | Display code review status |
| `but log` | View commits on active branches |
| `but oplog` | View operations history (snapshots) |
| `but .` or `but /path` | Open GitButler GUI for repository |
**Status Output Example:**
```
╭┄00 [Unassigned Changes]
│ m6 A test-file.md
│ p9 M existing-file.ts
├╯
╭┄g4 [feature-branch]
│ 🔒 i3 M locked-file.ts
● abc1234 feat: initial commit
├╯
● 0c60c71 (common base) [origin/main]
```
**File Status Codes:**
- `A` — Added
- `M` — Modified
- `D` — Deleted
- `🔒` — Locked (belongs to this branch's commits)
**IDs:**
- `00`, `g4` — Branch IDs
- `m6`, `p9`, `i3` — File/hunk IDs (use with `but rub`)
### Branch Management
| Command | Description |
|---------|-------------|
| `but branch new <name>` | Create virtual branch (based on trunk) |
| `but branch new <name> --anchor <parent>` | Create stacked branch |
| `but branch new <name> -a <parent>` | Short form for stacked branch |
| `but branch delete <name>` | Soft delete (requires confirmation) |
| `but branch delete <name> --force` | Force delete |
| `but branch list` | List all branches |
| `but branch list --local` | Only local branches |
| `but branch rm <name>` | Remove virtual branch |
### Committing
| Command | Description |
|---------|-------------|
| `but commit -m "message"` | Commit to inferred branch |
| `but commit <branch> -m "message"` | Commit to specific branch |
| `but commit <branch> -o -m "msg"` | Only commit assigned files (`-o` flag) |
| `but commit` | Opens `$EDITOR` for message |
**Note:** Unlike git, GitButler commits all changes by default. Use `-o/--only` to commit only assigned files.
### File and Commit Manipulation
#### `but rub` (Swiss Army Knife)
```bash
but rub <source> <target>
```
| Source | Target | Operation | Description |
|--------|--------|-----------|-------------|
| File ID | Branch ID | **Assign** | Move file to branch |
| File ID | Commit SHA | **Amend** | Add file changes to commit |
| Commit SHA | Branch ID | **Move** | Relocate commit to branch |
| Commit SHA | Commit SHA | **Squash** | Combine newer into older |
#### Other Editing Commands
| Command | Description |
|---------|-------------|
| `but new <target>` | Insert blank commit (before commit ID or at top of branch) |
| `but describe` | Edit commit message or rename branch |
| `but absorb` | Amend uncommitted changes (v0.17.6+) |
| `but mark "pattern" <branch>` | Auto-assign files matching pattern |
| `but unmark` | Remove mark rules |
### Forge Integration (GitHub)
| Command | Description |
|---------|-------------|
| `but forge auth` | Authenticate with GitHub (OAuth flow) |
| `but forge list-users` | List authenticated accounts |
| `but forge forget <username>` | Remove authenticated account |
| `but push` | Push changes to remote |
| `but publish` | Publish review requests for branches |
| `but publish -b <branch>` | Publish specific branch |
| `but publish -f, --with-force` | Allow force push (default: true) |
| `but publish -r, --run-hooks` | Execute pre-push hooks (default: true) |
### Base Branch Operations
| Command | Description |
|---------|-------------|
| `but base check` | Fetch remotes and check mergeability |
| `but base update` | Update workspace with latest from base |
### Operations History (Undo/Restore)
| Command | Description |
|---------|-------------|
| `but oplog` | View operation history |
| `but undo` | Undo last operation |
| `but restore <snapshot-id>` | Restore to specific snapshot |
| `but snapshot --message "msg"` | Create manual snapshot |
### AI Integration Commands
**Claude Code Hooks:**
| Command | Purpose |
|---------|---------|
| `but claude pre-tool` | Run before code generation/editing |
| `but claude post-tool` | Run after editing completes |
| `but claude stop` | Run when agent session ends |
**Cursor Hooks:**
| Command | Purpose |
|---------|---------|
| `but cursor after-edit` | Triggered when Cursor edits files |
| `but cursor stop` | Triggered when task completes |
**MCP Server:**
| Command | Purpose |
|---------|---------|
| `but mcp` | Start MCP server for agent integration |
---
## JSON Output Schemas
### `but --json status`
Key fields:
- `path` — Filename as ASCII array (requires decoding)
- `assignments` — Hunk-level file assignments
- `stackId` — Which stack this belongs to (null if unassigned)
**Limitations:**
- File IDs (`m6`, `g4`) not exposed in JSON
- Paths are ASCII arrays, not strings
- Parse text output for IDs
### `but --json log`
Key fields:
- `tip` — Current HEAD of branch (commit SHA)
- `baseCommit` — Where branch diverges from parent
- `pushStatus` — `completelyUnpushed` | `unpushedCommits` | `fullyPushed`
- `state.type` — `LocalOnly` | `LocalAndRemote`
- `parentIds` — Parent commits (useful for finding stacks)
**Useful jq patterns:**
```bash
# Get all branch names
but --json log | jq '.[0].branchDetails[] | .name'
# Check push status
but --json log | jq '.[0].branchDetails[] | {name, pushStatus}'
# Find unpushed branches
but --json log | jq '.[0].branchDetails[] | select(.pushStatus != "fullyPushed") | .name'
```
---
## GitButler vs Graphite
| Aspect | Graphite | GitButler |
|--------|----------|-----------|
| **Model** | Linear stacks of physical branches | Virtual branches with optional stacking |
| **Workflow** | Plan → Branch → Code → Commit → Stack | Code → Organize → Assign → Commit |
| **Branch Switching** | Required (`gt up`/`gt down`) | Never needed (all applied) |
| **Branch Creation** | `gt create -am "msg"` | `but branch new name [--anchor parent]` |
| **Committing** | `gt modify -cam "msg"` | `but commit -m "msg"` |
| **Stack Navigation** | ✓ `gt up`/`gt down` | ✗ No CLI equivalent |
| **PR Submission** | ✓ `gt submit --stack` | ✗ No CLI (GUI or `gh` CLI) |
| **JSON Output** | Limited | ✓ Comprehensive via `--json` |
| **Multi-Feature Work** | Switch branches | All in one workspace |
| **CLI Completeness** | ✓ Full automation | ⚠️ Partial (missing PR/push) |
**Choose Graphite when:**
- Need end-to-end CLI automation
- PR submission required in scripts
- Terminal-first workflow
- Stack navigation commands needed
**Choose GitButler when:**
- Multiple unrelated features simultaneously
- Multi-agent concurrent development
- Exploratory coding (organize after)
- Post-hoc commit reorganization
- Visual organization preferred
**Don't use both in same repo** — incompatible models.
---
## Troubleshooting Guide
### Quick Reference
| Symptom | Cause | Solution |
|---------|-------|----------|
| Broken pipe panic | Output piped directly | Capture to variable first |
| Filename with dash fails | Interpreted as range | Use file ID from `but status` |
| Branch not in `but log` | Not tracked | `but track --parent <parent>` |
| Files not committing | Not assigned | `but rub <file-id> <branch>` |
| Mixed git/but broke state | Used git commands | `but base update` or `but init` |
| Workspace stuck loading | Backend timeout | Check oplog, restore snapshot |
| "Workspace commit not found" | HEAD changed externally | `git checkout gitbutler/workspace` |
### Common Issues
#### Broken Pipe Panic
**Problem:** `but status` panics when output consumed partially.
```bash
✗ but status | head -5 # Panic!
✓ status_output=$(but status)
echo "$status_output" | head -5
```
#### Filename Parsing Issues
**Problem:** Dashes in filenames interpreted as range syntax.
```bash
✗ but rub file-with-dashes.md branch # Fails
✓ but rub m6 branch # Use file ID from but status
```
#### Integration Branch Conflicts
**Problem:** Mixed `git` and `but` commands corrupted state.
**Solutions:**
1. `but base update` to resync
2. If severely broken: `but init` to reinitialize
#### Files Not Committing
**Causes:**
1. Files not assigned to branch
2. Missing `-o` flag (only commit assigned files)
```bash
# Check assignments
but status
# Assign files
but rub <file-id> <branch>
# Commit with -o flag
but commit <branch> -o -m "message"
```
#### Workspace Stuck Loading
**Symptoms:**
- Loading spinner indefinitely
- Can see trunk/remote branches but not workspace
**Recovery:**
1. Wait 60 seconds for timeout
2. Check logs: `~/Library/Logs/com.gitbutler.app/GitButler.log` (macOS)
3. Use Operations History to restore previous snapshot
4. Last resort: Remove and re-add project
#### "GitButler workspace commit not found"
**Cause:** `gitbutler/workspace` branch modified or deleted outside GitButler.
**Recovery:**
```bash
# Return to integration branch
git checkout gitbutler/integration
# If that fails, check oplog
cat .git/gitbutler/operations-log.toml
git log <head_sha>
# Remove and re-add project to GitButler
```
### Recovery Scenarios
#### Lost Work (Accidentally Deleted Branch)
```bash
# Check oplog for deletion
but oplog
# Undo deletion (if last operation)
but undo
# Or restore to snapshot before deletion
but restore <snapshot-id>
```
#### Corrupted Workspace State
```bash
# Step 1: Snapshot current state
but snapshot --message "Before recovery"
# Step 2: Update base
but base update
# Step 3: Last resort - reinitialize
but init
```
#### Recovering from Mixed Git/But Commands
**If you committed with `git commit`:**
```bash
# Work is still in working directory
# Find orphaned commit
git reflog
# Create branch from it
git branch recovered <commit-sha>
# Return to GitButler
git checkout gitbutler/integration
```
**If you checked out another branch:**
```bash
# Return to GitButler
git checkout gitbutler/integration
# GitButler will resume operation
```
#### Virtual Branches Disappeared
Virtual branches are Git refs — they're still there:
```bash
# List all virtual branch refs
git for-each-ref refs/gitbutler/
# Create regular branch from virtual branch
git branch recovered-feature refs/gitbutler/Feature-A
# Or push directly to remote
git push origin refs/gitbutler/Feature-A:refs/heads/feature-a
```
#### Extract Data from Corrupted Project
```bash
# Backup everything
cp -r .git .git-backup
# Extract all virtual branch refs
git for-each-ref refs/gitbutler/ > gitbutler-refs.txt
# Create regular branch from each
while read sha type ref; do
name=$(basename "$ref")
git branch "recovered-$name" "$sha"
done < gitbutler-refs.txt
# Extract latest oplog snapshot
LATEST=$(cat .git/gitbutler/operations-log.toml | grep head_sha | awk '{print $3}' | tr -d '"')
git archive $LATEST index/ | tar -x -C recovered-uncommitted/
```
### Operations Log (Oplog) Deep Dive
**Location:** `.git/gitbutler/operations-log.toml`
**Snapshot contents:**
```
<snapshot-commit>
├── virtual_branches.toml # Branch metadata
├── virtual_branches/ # Branch content trees
├── index/ # Working directory state
├── target_tree/ # Base branch (e.g., main)
└── conflicts/ # Merge conflict info
```
**Operation types:**
- `CreateCommit` — Made a commit
- `CreateBranch` — Created branch
- `UpdateWorkspaceBase` — Updated base branch
- `RestoreFromSnapshot` — Reverted to snapshot
- `FileChanges` — Uncommitted changes detected
- `DeleteBranch` — Deleted branch
- `SquashCommit` — Squashed commits
**Manual inspection:**
```bash
# Find oplog head
OPLOG_HEAD=$(cat .git/gitbutler/operations-log.toml | grep head_sha | awk '{print $3}' | tr -d '"')
# View snapshot history
git log $OPLOG_HEAD --oneline
# Show virtual branches config from snapshot
git show <snapshot-sha>:virtual_branches.toml
# Extract file from snapshot
git show <snapshot-sha>:index/path/to/file.txt
```
### Prevention Best Practices
**Golden Rules:**
1. **NEVER remove project to fix errors** — may delete actual source files
2. **Commit frequently** — committed work is safer than WIP
3. **Push virtual branches to remote** — backup your work
4. **Don't mix GitButler and stock Git commands** — choose one workflow
**Before risky operations:**
```bash
but snapshot --message "Before major reorganization"
```
**Before GitButler updates:**
1. Commit everything
2. Push all branches to remote
3. Verify Operations History accessible
---
## Version History
**v0.18.2** (Nov 2024) — UX improvements, bug fixes
**v0.18.1** (Nov 2024) — Codegen agent CLI-aware
**v0.18.0** (Nov 2024) — `but .` command, enhanced status output
**v0.17.6** — `but absorb` command
**v0.17.0** — Shell completion, push capability, review status display
```
### references/examples.md
```markdown
# GitButler Examples
Real-world patterns and workflows for virtual branches, multi-agent collaboration, and post-hoc organization.
---
## Basic Workflows
### First Virtual Branch
```bash
# Initialize (one time)
cd /path/to/repo
but init
# Check state
but status
# ● 0c60c71 (common base) [origin/main]
# Create branch
but branch new feature-user-auth
# Make changes
echo "export function authenticate()" > src/auth.ts
echo "test('authenticates user')" > src/auth.test.ts
# Check status for file IDs
but status
# ╭┄00 [Unassigned Changes]
# │ m6 A src/auth.ts
# │ p9 A src/auth.test.ts
# Assign and commit
but rub m6 feature-user-auth
but rub p9 feature-user-auth
but commit feature-user-auth -m "feat: add user authentication"
```
### Context Switching (No Checkout!)
```bash
# Working on feature when bug reported
but branch new feature-dashboard
echo "Dashboard code" > dashboard.ts
but rub <id> feature-dashboard
# Bug reported - switch context immediately (no checkout!)
but branch new bugfix-login-timeout
echo "Fix timeout" > login.ts
but rub <id> bugfix-login-timeout
# Both exist in same workspace
but status # Shows both branches
# Commit bugfix first (urgent)
but commit bugfix-login-timeout -m "fix: resolve login timeout"
# Continue feature work
but commit feature-dashboard -m "feat: add dashboard"
```
---
## Reorganizing Work
### Moving Commits Between Branches
```bash
# Oops, committed to wrong branch!
but log
# Shows def5678 "feat: add new feature" on bugfix-branch
# Create correct branch
but branch new feature-new-capability
# Move the commit
but rub def5678 feature-new-capability
# Commit moved!
but log
```
### Squashing Commits
```bash
# Too many small commits
but log
# c3d4e5f, c2d3e4f, c1d2e3f on feature-branch
# Squash (newer into older)
but rub c3d4e5f c2d3e4f
```
### Post-Hoc File Assignment
```bash
# Made changes without branches
echo "Auth code" > auth.ts
echo "API code" > api.ts
echo "Docs" > README.md
but status
# Shows all files in Unassigned Changes
# Create branches and organize
but branch new feature-auth
but branch new feature-api
but branch new docs-update
# Assign to respective branches
but rub m6 feature-auth
but rub p9 feature-api
but rub i3 docs-update
# Commit each
but commit feature-auth -m "feat: add authentication"
but commit feature-api -m "feat: add API endpoints"
but commit docs-update -m "docs: update readme"
```
---
## Multi-Agent Patterns
### Parallel Feature Development
```bash
# Agent 1 (Claude)
but branch new claude-feature-auth
echo "Auth implementation" > src/auth.ts
but rub <id> claude-feature-auth
but commit claude-feature-auth -m "feat: add authentication"
# Agent 2 (Droid) - simultaneously, same workspace!
but branch new droid-feature-api
echo "API implementation" > src/api.ts
but rub <id> droid-feature-api
but commit droid-feature-api -m "feat: add API endpoints"
# Zero conflicts, zero coordination overhead
```
### Sequential Handoffs
```bash
# Agent A: Initial implementation
but branch new feature-user-management
echo "Initial user code" > user.ts
but rub <id> feature-user-management
but commit feature-user-management -m "feat: initial user management"
# Agent A hands off to Agent B
but branch new feature-user-management-tests --anchor feature-user-management
# Agent B: Adds tests
echo "Tests for user management" > user.test.ts
but rub <id> feature-user-management-tests
but commit feature-user-management-tests -m "test: add user management tests"
```
### Cross-Agent Commit Transfer
```bash
# Agent A finishes work
but branch new agent-a-feature
but commit agent-a-feature -m "feat: implementation complete"
# Agent B creates their branch
but branch new agent-b-continuation
# Transfer commit from A to B
but rub abc1234 agent-b-continuation
# Agent B continues
echo "More work" >> feature.ts
but commit agent-b-continuation -m "feat: continue implementation"
```
---
## Stack Management
### Creating a Linear Stack
```bash
# Base refactoring
but branch new refactor-database
echo "Refactor database layer" > db-refactor.ts
but rub <id> refactor-database
but commit refactor-database -m "refactor: restructure database"
# Build on refactoring
but branch new feature-new-model --anchor refactor-database
echo "New data model" > model.ts
but rub <id> feature-new-model
but commit feature-new-model -m "feat: add new data model"
# Add tests on top
but branch new test-new-model --anchor feature-new-model
echo "Model tests" > model.test.ts
but rub <id> test-new-model
but commit test-new-model -m "test: comprehensive model tests"
# Visualize stack
but log
```
### Submit Stack as PRs
```bash
git push origin refactor-database
gh pr create --title "refactor: database layer" --base main
git push origin feature-new-model
gh pr create --title "feat: new data model" --base refactor-database
git push origin test-new-model
gh pr create --title "test: model tests" --base feature-new-model
```
---
## Emergency Recovery
### Recover Deleted Branch
```bash
# Oops, deleted wrong branch
but branch delete important-feature --force
# Check oplog
but oplog
# Undo deletion
but undo
# Verify recovery
but log # Branch recovered!
```
### Recover from Bad Reorganization
```bash
# Snapshot before risky operations
but snapshot --message "Before reorganizing commits"
# Attempt reorganization
but rub <commit1> <branch1>
but rub <commit2> <branch2>
# Result is a mess - restore to snapshot
snapshot_id=$(but oplog | grep "Before reorganizing" | awk '{print $1}')
but restore $snapshot_id
# Back to pre-reorganization state!
```
### Recover from Mixed Git/But Commands
```bash
# Made changes on virtual branch
but branch new my-feature
echo "changes" > file.ts
# Accidentally used git
git add file.ts
git commit -m "oops" # WRONG!
# Recovery
but base update
# If still broken, reinitialize
but snapshot --message "Before recovery"
but init
```
---
## Tips and Patterns
### Branch Naming
```bash
# Agent-based naming
but branch new claude-feat-user-auth
but branch new droid-fix-api-timeout
# Task-based naming
but branch new feature-authentication
but branch new bugfix-timeout
```
### Snapshot Cadence
```bash
but snapshot --message "Before major reorganization"
but snapshot --message "Before multi-agent coordination"
but snapshot --message "Before complex stack changes"
```
### File Assignment Discipline
```bash
# Good: Assign immediately
echo "code" > file1.ts
but rub <id> my-branch # Right away
```
### JSON Output
```bash
# Get all branch names
but --json log | jq '.[0].branchDetails[] | .name'
# Check push status
but --json log | jq '.[0].branchDetails[] | {name, pushStatus}'
# Find unpushed branches
but --json log | jq '.[0].branchDetails[] | select(.pushStatus != "fullyPushed") | .name'
```
```
### ../stack-workflows/SKILL.md
```markdown
---
name: stack-workflows
version: 2.0.0
description: This skill should be used when creating stacked branches, dependent features, reviewable PR breakdown, or when stack, stacked branches, or `--anchor` are mentioned.
---
# GitButler Stack Workflows
Dependent branches → anchor-based stacking → reviewable chunks.
<when_to_use>
- Sequential dependencies (e.g., refactor → API → frontend)
- Large features broken into reviewable chunks
- Granular code review (approve/merge early phases independently)
- Post-hoc stack organization after exploratory coding
NOT for: independent parallel features (use virtual branches), projects using Graphite stacking
</when_to_use>
<stack_vs_virtual>
## Stacked vs Virtual Branches
| Type | Use Case | Dependencies |
|------|----------|--------------|
| **Virtual** | Independent, unrelated work | None — parallel |
| **Stacked** | Sequential dependencies | Each builds on parent |
Stacked branches = virtual branches split into dependent sequence.
Default: Virtual branches are stacks of one.
</stack_vs_virtual>
<create>
## Creating Stacks
```bash
# Base branch (no anchor)
but branch new base-feature
# Stacked branch (--anchor specifies parent)
but branch new child-feature --anchor base-feature
# Third level
but branch new grandchild-feature --anchor child-feature
```
**Result:** `base-feature` ← `child-feature` ← `grandchild-feature`
**Short form:** `-a` instead of `--anchor`
```bash
but branch new child -a parent
```
</create>
<patterns>
## Stack Patterns
### Feature Dependency Stack
```bash
# Auth foundation
but branch new auth-core
but commit auth-core -m "feat: add authentication core"
# OAuth layer depends on auth core
but branch new auth-oauth --anchor auth-core
but commit auth-oauth -m "feat: add OAuth integration"
# Social login depends on OAuth
but branch new auth-social --anchor auth-oauth
but commit auth-social -m "feat: add social login"
```
### Refactoring Stack
```bash
# Extract utilities
but branch new refactor-extract-utils
but commit refactor-extract-utils -m "refactor: extract common utilities"
# Update consumers
but branch new refactor-use-utils --anchor refactor-extract-utils
but commit refactor-use-utils -m "refactor: use extracted utilities"
# Clean up
but branch new refactor-cleanup --anchor refactor-use-utils
but commit refactor-cleanup -m "refactor: remove deprecated code"
```
### Deep Stack (5 levels)
```bash
but branch new db-schema
but branch new data-access --anchor db-schema
but branch new business-logic --anchor data-access
but branch new api-endpoints --anchor business-logic
but branch new frontend-integration --anchor api-endpoints
```
</patterns>
<post_hoc>
## Post-Hoc Stack Organization
**Problem:** Created branches independently, now want to stack them.
**Solution:** Recreate with correct anchors:
```bash
# Current: three independent branches
# feature-a, feature-b, feature-c
# Stack feature-b on feature-a
but branch new feature-b-stacked --anchor feature-a
commit_sha=$(but log | grep "feature-b:" | head -1 | awk '{print $1}')
but rub $commit_sha feature-b-stacked
but branch delete feature-b --force
# Stack feature-c on feature-b-stacked
but branch new feature-c-stacked --anchor feature-b-stacked
commit_sha=$(but log | grep "feature-c:" | head -1 | awk '{print $1}')
but rub $commit_sha feature-c-stacked
but branch delete feature-c --force
```
</post_hoc>
<pr_workflow>
## PR Preparation for Stacks
**GitButler CLI lacks native PR submission.** Use GitHub CLI:
```bash
# Push branches
git push -u origin base-feature
git push -u origin dependent-feature
# Create PRs with correct base branches
gh pr create --base main --head base-feature \
--title "feat: base feature" \
--body "First in stack"
gh pr create --base base-feature --head dependent-feature \
--title "feat: dependent feature" \
--body "Depends on base-feature PR"
```
**GitHub Settings:**
- Enable automatic branch deletion after merge
- Use **Merge** strategy (recommended) — no force pushes needed
- Merge bottom-to-top (sequential order)
</pr_workflow>
<reorganize>
## Stack Reorganization
### Squashing Within Stack
```bash
newer_commit=$(but log | grep "newer" | awk '{print $1}')
older_commit=$(but log | grep "older" | awk '{print $1}')
but rub $newer_commit $older_commit
```
### Moving Commits Between Stack Levels
```bash
commit_sha=$(but log | grep "specific commit" | awk '{print $1}')
but rub $commit_sha correct-branch
```
### Splitting a Branch
```bash
# Original has multiple features
but branch new second-feature --anchor original-branch
commit_sha=$(but log | grep "second feature commit" | awk '{print $1}')
but rub $commit_sha second-feature
```
</reorganize>
<navigation>
## Stack Navigation
**Note:** Virtual branches don't need checkout — all branches active simultaneously.
```bash
# View full stack structure
but log
# Work on any branch directly (no checkout needed)
but commit base-feature -m "update base"
but commit dependent-feature -m "update dependent"
# JSON for programmatic analysis
but --json log | jq '.[] | .branchDetails[] | {name, baseCommit}'
```
</navigation>
<rules>
ALWAYS:
- Create stacks with `--anchor` from the start
- Merge stacks bottom-to-top (base first, dependents after)
- Snapshot before reorganizing: `but snapshot --message "Before stack reorganization"`
- Keep each level small (100-250 LOC) for reviewability
- Delete empty branches after reorganization
NEVER:
- Skip stack levels when merging
- Stack independent, unrelated features (use virtual branches)
- Create deep stacks (5+ levels) without good reason
- Forget anchor when creating dependent branches
</rules>
<troubleshooting>
## Common Issues
| Symptom | Cause | Solution |
|---------|-------|----------|
| Stack not showing in `but log` | Missing `--anchor` | Recreate with correct anchor |
| Commits in wrong stack level | Wrong branch targeted | `but rub <sha> correct-branch` |
| Can't merge middle of stack | Wrong order | Merge bottom-to-top only |
## Recovery
```bash
# Recreate branch with correct anchor
but branch new child-stacked --anchor parent
commit_sha=$(but log | grep "child:" | head -1 | awk '{print $1}')
but rub $commit_sha child-stacked
but branch delete child --force
```
</troubleshooting>
<best_practices>
## Best Practices
### Planning
- Start simple: 2-3 levels max initially
- Single responsibility per level
- Only stack when there's a real dependency
### Maintenance
- Run `but log` regularly to verify structure
- Commit to correct branches immediately
- Clean up empty branches
### Communication
- Clear commit messages explaining why stack level exists
- Descriptive names indicating stack relationship
- Share `but status` when coordinating
</best_practices>
<references>
- [version-control skill](../version-control/SKILL.md) — core GitButler workflows
- [complete-branch skill](../complete-branch/SKILL.md) — merging to main
- [multi-agent skill](../multi-agent/SKILL.md) — multi-agent coordination
- [GitButler Stacks Docs](https://docs.gitbutler.com/features/branch-management/stacked-branches)
</references>
```