
GitButler CLI
FreeStreamline your Git version control with concise commands.
Free · Opens the source repo
What GitButler CLI does
GitButler CLI is a command-line interface that simplifies version control tasks using Git. It replaces traditional Git commands with a more streamlined syntax, allowing developers to commit, push, branch, and manage their repositories efficiently. With commands like but diff and but status, users can quickly inspect changes, view commit orders, and manage branches without unnecessary verbosity. This skill is designed for developers who want to enhance their Git workflow by reducing the number of commands and steps typically required.
The GitButler CLI focuses on minimizing the need for ritual status checks and encourages users to adopt a more direct approach to version control. For instance, instead of running multiple commands to check status and then commit, users can perform these actions in a single line, creating new branches on-the-fly or committing specific changes with ease. This skill is particularly useful for those who frequently work with dirty files or need to manage complex branching scenarios.
In addition to its streamlined command structure, GitButler emphasizes the importance of using its commands exclusively for write operations. This ensures consistency in version control practices and prevents potential conflicts that can arise from mixing traditional Git commands with GitButler commands. By adhering to this approach, users can maintain a clean and efficient workflow.
Overall, GitButler CLI is an ideal tool for developers looking to optimize their Git usage. It provides a clear, concise set of commands that can significantly reduce the overhead associated with version control, making it easier to focus on coding rather than managing Git operations.
When to use it
Use GitButler when you want a more efficient way to handle version control tasks in your development workflow.
When not to use it
Avoid GitButler if you prefer traditional Git commands or need to use specific Git features not supported by this CLI.
What you can build with it
Quickly Inspect Changes
Use `but diff` to see changes in selected dirty files or hunks without cluttering the output.
Efficient Branch Management
Create and switch branches in one command with `but commit -b <branch> -m "<msg>"`.
Simplified Commit Process
Commit specific changes directly from the output of `but diff` to streamline your version control.
How to install GitButler CLI
View source1. Install with the skills CLI
npx skills add gitbutlerapp/gitbutler/skill --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by gitbutlerappGitButler CLI Skill
Use GitButler CLI (but) as the default version-control interface.
Start Here
Choose the narrowest first command by task; avoid ritual status checks:
# Selected dirty files/hunks:
but diff
# Commit order, branch/stack placement, conflict overview:
but status
# File/hunk IDs, per-commit files, amend/split details:
but status -fv
# Details for one known branch or commit:
but show <id>
Do not run plain but status and then but status -fv unless the compact output lacks file/hunk details needed for the task.
For "commit just/only/specific changes on a new branch", use the fast path:
but diff
but commit -b <branch> -m "<msg>" <id> <id>
but commit -b <branch> ... creates the branch when it does not exist and prints the created commit. Do not run a separate but branch new, status command, or verification diff unless the task needs workspace information that the result does not provide.
IDs
The first token on each but diff / but status line is that line's ID — pass it to commands as-is; never hardcode or invent IDs. IDs may be a single character when unambiguous; copy them exactly from command output.
- Changes and sources are positional, space-separated IDs (
but commit -b feat -m "msg" qs:5 uo). A hunk ID is written<file-id>:<hunk-id>(e.g.qs:5, copied frombut diff) — the part after the colon is the hunk's ID, not a line range (qs:16-40is invalid). Do not invent flags like--changes/--hunk/--ids, pass a line range, or comma-separate IDs —nk,pnis parsed as one ID and fails. but diffis the exception: it accepts at most one target. Barebut diffshows all uncommitted files; inspect committed files or other entities one target at a time — neverbut diff <id> <id>.- A committed file is
<commit-id>:<file-id>(e.g.uyr:n, shown under each commit).zzmeans the uncommitted area. - Commit IDs are stable change IDs that survive history edits (
amend,squash,move,uncommit,reword). Commits without a change ID (e.g. upstream-only) lead with a sha prefix instead, and#N-suffixed refs disambiguate duplicates — both go stale after history edits, and a stale sha can silently resolve to the wrong commit. The(sha …)on verbose commit lines is informational — do not pass it to commands. - File/hunk IDs copied from one diff read generally remain usable across chained commits; branch IDs are stable. If an ID stops resolving, re-read
but status/but diffand retry.
Chaining: mutation output is concise by default, so you may chain mutations with && off one inspection read. Add --status-after only when the next step needs workspace IDs or details that the mutation result does not provide. Chained but commit calls stack in the order written — the first is oldest, each later one goes on top. History edits may run in sequence when every commit ref involved is a change-ID ref; run them one at a time with --status-after when a ref is sha-based or #N-suffixed, or when the next command needs freshly issued IDs. Chaining but uncommit <id> && but diff is safe because bare diff needs no ID from the uncommit output.
Non-Negotiable Rules
- Use
butfor all write operations. Never rungit add,git commit,git push,git checkout,git merge,git rebase,git stash, orgit cherry-pick. If the user says agitwrite command, translate it tobutand run that. Exceptions:git add -- <path>to mark a conflicted uncommitted file resolved (see "Conflicts in uncommitted files"), and a worktree-local Git commit whenbut commitreports that linked worktrees are unsupported. Never runbut setupfrom a linked worktree. - Mutation commands print their result without appending workspace status. Add
--status-afteronly when the next step needs resulting workspace IDs or details; otherwise trust the mutation result and do not run a verification status/diff. - Branches marked
(merged upstream)have landed; runbut pullto remove them, or start new work on another branch.pushand mutations (commit,amend,squash,uncommit,reword,move) refuse landed branches and commits,absorbskips them with a notice, andcommitskips them when picking a default target. - In non-interactive CLI workflows, do not narrate progress between routine commands. Execute the needed
butcommands and give a concise final summary. - Prefer this skill and
references/reference.mdover exploratory help calls. Use<command> --helpwhen required syntax is missing or a command fails; use top-level help only when you genuinely need to discover an undocumented command.
Command Patterns
- Commit:
but commit -b <branch> -m "<msg>" <id> <id>—-b <branch>creates the branch if it does not exist - Commit everything uncommitted:
but commit -b <branch> -m "<msg>"(omit the IDs) - Several commits from one diff: chain
but commitcalls with&&(commits stack oldest-first) - Commit at a specific history position:
--above <commit-or-branch>or--below <commit-or-branch>instead of-b - Only one targeting flag (
-b/--above/--below) per command. Targeting is required when more than one stack is applied; without itbut commitfails with "Unclear where to commit. Found more than one stack". Several branches stacked together count as one stack — an untargeted commit then silently lands on the stack's top branch, so pass-bwhenever the branch matters. - Always pass
-m "<msg>"(or--no-message) tobut commit, and tobut squashwhenever its sources are commits or branches unless the target iszz— those compose a new message, and without a flag an editor opens and blocks. Squash sources that are uncommitted or committed files reuse the target's message and need no flag; squashing intozzrejects message flags outright. - Amend:
but amend -t <commit-or-branch> <file-or-hunk-id> <file-or-hunk-id>— a branch target resolves to its newest commit - Uncommit:
but uncommit <commit-id>(whole commit),but uncommit <branch>(all commits and remove the branch), orbut uncommit <commit-id>:<file-id>(one committed file); multiple committed-file sources in one call must come from one commit - Insert empty commit:
but commit --empty -b <branch> -m "<msg>" - Squash commits:
but squash <source-commit-id> [<source-commit-id>...] -t <target-commit-id> -m "<msg>" - Squash a whole branch into one commit:
but squash <branch> -m "<msg>"(no-t) - Uncommit and remove a branch:
but uncommit <branch> - Reorder commits:
but move <commit-id> --below <commit-id>(--abovefor the other direction; commit IDs, not branch names) - Reorder a block:
but move <commit-id> <commit-id> --below <following-commit-id>or--above <preceding-commit-id>(both anchors accept multiple space-separated sources) - Move commit to branch top:
but move <commit-id> -b <branch> - Stack branches:
but move <branch> --above <target-branch>(branch names or branch CLI IDs) - Tear off a branch:
but move <branch> --unstack - Discard:
but discard <id> [<id>...]— accepts branches, commits, committed files, uncommitted files/hunks, orzzfor all uncommitted changes - Push:
but push <top-branch>— pushes the selected branch and its ancestors; to update a stack, select its top branch once and never loop. Barebut pushpushes all unpushed work when run non-interactively — one push per stack (its topmost unpushed branch, ancestors included), so output has one entry per stack, not per branch. It exits non-zero if any stack failed; stacks that already pushed stay pushed, and rerunning after fixing the failure is safe (up-to-date stacks are skipped) - Pull (update workspace from the target):
but pull— the output reports the result;but pull --checkpreviews without updating when a preview is actually needed - Create PR:
but pr new <branch-id> [-m "Title..."] [-F pr_message.txt] [-t] [--draft]— auto-pushes first; do not runbut pushbefore it
Task Recipes
Update workspace from main
For "get latest from main", "update/sync this workspace", "rebase onto main", or "pull main":
but pull— one command; no preflight needed. Its output reports the resulting state, it refuses safely when uncommitted changes conflict, andbut undoreverts it.- If commits come back conflicted, resolve them oldest-first following the printed instructions:
but resolve <commit>, edit the files, thenbut resolve finish. Its result gives the current ID of the next conflict. Add--status-afterto the finish you expect to clear the last conflict only when the task needs the complete resulting workspace. When it says no conflicted commits remain, stop; do not run a verification status. Finishing a lower commit rebases the ones above it, so always work bottom-up.
but pull --check answers "would this conflict?" without updating. Do not use it as a routine
preflight; use it when the user asks for a preview, repository policy requires one, or other agents'
branches may move.
Rebasing applied branches onto the latest target IS but pull — never move, config target, unapply, or raw git pull/git rebase. The base shown in status is the last FETCHED state: when git log shows main (local or remote) ahead of it, that is exactly the update but pull fetches and applies — the target setting is not stale and repointing it is never the fix. Pull carries uncommitted changes along, and its output reports the resulting state. If it refuses because uncommitted changes conflict, park them: but commit -b <branch> -m "wip" <ids>, pull again, then but uncommit the parked commit (there is no stash; do not hand-revert files).
Commit selected files or hunks
but diff— shows file and hunk IDs for uncommitted changes. Do not run plainbut statusfirst.- Use file IDs when whole files belong in the commit; use hunk IDs (
<file-id>:<hunk-id>) when only part of a file belongs. Omit IDs you don't want committed. but commit -b <branch> -m "<msg>" <id1> <id2>— the branch is created if it does not exist, so no priorbut branch newis needed.- When the task requires knowing which changes remain, add
--status-after; otherwise the created-commit result is sufficient.
Edge case: if wanted and unwanted edits are in the same diff hunk, GitButler cannot split that hunk by ID. Only when the task requires keeping part of that hunk uncommitted, temporarily edit the working tree to isolate the wanted lines, commit those IDs, then restore the leftover lines so they remain uncommitted.
Amend into existing commit
but status -fv(orbut show <branch-id>) — locate file/hunk IDs and target commit IDs.but amend -t <commit-id> <id> <id>— one command per target commit. For several target commits, chain the amends with&&when every target is a change-ID ref; otherwise run them one at a time with--status-afterto get fresh refs.
Split an existing commit
Use this when an existing commit should be replaced by selected smaller commits.
but status -fvwhen you need the source commit, branch name, or placement anchor.but uncommit <source-commit-id> && but diffin one shell call exposes the commit's changes and prints the resulting file and hunk IDs.- Pick replacement contents from that dirty diff, not from the old committed diff.
- Determine the requested chronological order from the user's wording, then create the replacement commits oldest-first by chaining
but commitcalls. Do not reverse that order to matchbut status, which displays commits newest-first.-b <branch>puts each new commit at the TOP of that branch — if the split commit had commits above it, the replacements now sit above those preserved commits. - If commits from that branch must stay ABOVE the replacements, put the preserved block back on top instead of fighting anchors. Do not anchor with
--above <top>/--below <top>(sha/#Nanchors go stale as each insert rewrites history). Move the block together so its internal order stays intact: append&& but move <preserved-id> [<preserved-id>...] -b <branch>to the commit chain. Change-ID refs from step 1 stay valid; wait for fresh output first if any preserved ref is sha-based or#N-suffixed. - Leave unwanted changes uncommitted. Replacements created oldest-first appear newest-first in status — that is correct; do not reorder them. Add
--status-afterto the final mutation when you need to inspect the resulting order.
Reorder commits
but status displays commits newest/top first, while task specs often list history oldest to newest — translate before moving.
but statusonce to get commit IDs (use-fvonly if you also need file details).but move <source> --below <target-commit>places source immediately below target inbut status(older in oldest-to-newest history).--aboveplaces it immediately above (newer).but move <source> -b <branch>moves it to branch top/newest.- For an adjacent block, run ONE move, anchored either way:
but move <block-id> <block-id> --below <following-commit-id>orbut move <block-id> <block-id> --above <preceding-commit-id>. Pick an anchor outside the block; source order does not matter and the block keeps its internal order. Add--status-afterwhen you need to inspect the resulting order; do not move the anchor or block members again. - For other reorders, make the smallest set of moves.
Squash commits
but statusfor commit IDs and order.- Name the sources positionally and the result/target commit with
-t:but squash <source> [<source>...] -t <target> -m "<new message>". - To collapse an entire branch into one commit, pass just the branch and no
-t:but squash <branch> -m "<new message>". - Multiple independent groups may run in sequence off one status read (targets keep their change-ID refs); prefer newer/top groups first. Take fresh refs only when a ref is sha-based or
#N-suffixed. - Add
--status-afterto the final squash when you need to inspect the resulting history; do not re-verify with a separate status.
Stack existing branches
To make one existing branch depend on another: but move <child-branch> --above <parent-branch> (branch names or branch CLI IDs — commit reordering uses commit IDs). To unstack: but move <branch> --unstack.
DO NOT stack via uncommit + branch delete + branch new -a (git branch names persist after delete and it loses work), and do not use but undo to unstack.
Create or manage pull requests
but pr new <branch-id> pushes the selected branch and its ancestors, then creates the PR in one step — no prior but push. Provide -F pr_message.txt, -t, or -m with real newlines (zsh/bash: -m $'Title\n\nBody') so no editor opens. If forge auth is missing, run but config forge auth.
For stacked branches but pr is mandatory (it sets PR bases and stack metadata; gh pr create breaks that). To publish a whole stack: but pr new <top-branch-id> -t. Manage with but pr auto-merge|set-draft|set-ready <selector>. See references/reference.md for details.
Dependency conflict with another branch
Changes that build on another branch's commits cannot land on an independent branch. but commit and but amend fail atomically ("Cannot commit: N changes could not be applied"), naming the branch and commit each rejected change depends on — nothing is committed and no -b branch is created.
When there is a single dependency branch, the error's Hint gives the exact recovery command: but move <your-branch> --above <dependency-branch> to stack an existing branch on its dependency, or but branch new <name> --anchor <dependency-branch> when the target branch didn't exist yet. Run it, then retry the original command. When the error names dependencies without a Hint (several dependency branches, or the dependency is on the target branch itself), run but status -fv to see where the dependent commits live before choosing a placement.
If that recovery command fails, do NOT try uncommit, squash, or undo as a workaround — re-run but status -fv to confirm both branches exist and are applied, then retry with exact branch names.
Resolve conflicted commits (after pull, move, or reorder)
NEVER use git add, git commit, git checkout --theirs/--ours, or any git write command during resolution. Only but resolve commands plus direct file edits.
- Find conflicted commits: history-editing commands (
move,discard, …) warn about newly conflicted commits in their output, and thebut pullsummary lists them oldest-first; otherwisebut statusmarks them. but resolve <commit-id>— enters resolution mode and prints the conflict regions.- Edit the files to remove every conflict marker —
<<<<<<<,|||||||(the common-ancestor section),=======and>>>>>>>— and keep the correct content. Do NOT skip this; do NOT usebut amendon conflicted commits. but resolve finishreports leftover markers, surviving uncommitted changes, every remaining conflicted commit, and the exact currentbut resolve <id>command. Add--status-afterto the finish you expect to clear the last conflict only when the task needs the complete resulting workspace. When it says no conflicted commits remain, stop; do not run a verification status.- Repeat for remaining conflicted commits, oldest first — finishing a lower commit rebases the ones above it.
Conflicts in uncommitted files
but status marks uncommitted files with unresolved merge conflicts {conflicted}; they are excluded from committable changes and outside but resolve mode. Choose the desired contents or delete the file, then git add -- <path> to mark it resolved (the one permitted git add).
Git-to-But Map
| git | but |
|---|---|
git status | but status for branch/stack/commit overview; but status -fv for file/hunk details; but diff for selected dirty changes |
git add + git commit | but commit -b <branch> -m ... <ids> |
git checkout -b + commit | but commit -b <new-branch> -m ... <ids> |
git push | but push <branch-name> |
git rebase -i | but move, but squash, but reword |
git rebase --onto | but move <branch> --above <new-base> |
git checkout -- <file> / git restore | but discard <id> |
git cherry-pick | but pick |
gh pr create | but pr new <branch-id> -m "Title..." |
Notes
- Read-only git inspection (
git log,git blame,git show --stat) is allowed. - If
butprints anAGENT ACTION REQUIREDskill warning, run the suggested command once, then reload/use the GitButler skill. If it repeats, report it instead of retrying. - For command syntax and flags:
references/reference.md - For workspace model:
references/concepts.md - For workflow examples:
references/examples.md
Frequently asked questions about GitButler CLI
Similar skills
Release Candidate Preparation
Streamline your OpenAI Agents release process.
Gitmoji
Generate expressive commit messages with emojis.
GitHub Release
Automate your GitHub library release process effortlessly.
Commit Message Storyteller
Generate meaningful commit messages from your git diffs.
Author Contributions
Trace author contributions across branches in Git.
Implementation Kickoff
Streamline your code implementation process with ease.
