crates/but/skill/SKILL.md
Use GitButler CLI (but) as the default version-control interface.
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.
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.
but commit -b feat -m "msg" qs:5 uo). A hunk ID is written <file-id>:<hunk-id> (e.g. qs:5, copied from but diff) — the part after the colon is the hunk's ID, not a line range (qs:16-40 is invalid). Do not invent flags like --changes / --hunk / --ids, pass a line range, or comma-separate IDs — nk,pn is parsed as one ID and fails.but diff is the exception: it accepts at most one target. Bare but diff shows all uncommitted files; inspect committed files or other entities one target at a time — never but diff <id> <id>.<commit-id>:<file-id> (e.g. uyr:n, shown under each commit). zz means the uncommitted area.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.but status/but diff and 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.
but for all write operations. Never run git add, git commit, git push, git checkout, git merge, git rebase, git stash, or git cherry-pick. If the user says a git write command, translate it to but and run that. Exceptions: git add -- <path> to mark a conflicted uncommitted file resolved (see "Conflicts in uncommitted files"), and a worktree-local Git commit when but commit reports that linked worktrees are unsupported. Never run but setup from a linked worktree.--status-after only when the next step needs resulting workspace IDs or details; otherwise trust the mutation result and do not run a verification status/diff.(merged upstream) have landed; run but pull to remove them, or start new work on another branch. push and mutations (commit, amend, squash, uncommit, reword, move) refuse landed branches and commits, absorb skips them with a notice, and commit skips them when picking a default target.but commands and give a concise final summary.references/reference.md over exploratory help calls. Use <command> --help when required syntax is missing or a command fails; use top-level help only when you genuinely need to discover an undocumented command.but commit -b <branch> -m "<msg>" <id> <id> — -b <branch> creates the branch if it does not existbut commit -b <branch> -m "<msg>" (omit the IDs)but commit calls with && (commits stack oldest-first)--above <commit-or-branch> or --below <commit-or-branch> instead of -b-b / --above / --below) per command. Targeting is required when more than one stack is applied; without it but commit fails 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 -b whenever the branch matters.-m "<msg>" (or --no-message) to but commit, and to but squash whenever its sources are commits or branches — 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 into zz rejects message flags outright.but amend -t <commit-or-branch> <file-or-hunk-id> <file-or-hunk-id> — a branch target resolves to its newest commitbut uncommit <commit-id> (whole commit) or but uncommit <commit-id>:<file-id> (one committed file); multiple committed-file sources in one call must come from one commitbut commit --empty -b <branch> -m "<msg>"but squash <source-commit-id> [<source-commit-id>...] -t <target-commit-id> -m "<msg>"but squash <branch> -m "<msg>" (no -t)but move <commit-id> --below <commit-id> (--above for the other direction; commit IDs, not branch names)but move <commit-id> <commit-id> --below <following-commit-id> or --above <preceding-commit-id> (both anchors accept multiple space-separated sources)but move <commit-id> -b <branch>but move <branch> --above <target-branch> (branch names or branch CLI IDs)but move <branch> --unstackbut discard <id> [<id>...] — accepts branches, commits, committed files, uncommitted files/hunks, or zz for all uncommitted changesbut push <branch-name> — always specify the branch; bare but push pushes ALL branches when run non-interactivelybut pull — the output reports the result; but pull --check previews without updating when a preview is actually neededbut pr new <branch-id> [-m "Title..."] [-F pr_message.txt] [-t] [--draft] — auto-pushes first; do not run but push before itFor "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, and but undo reverts it.but resolve <commit>, edit the files, then but resolve finish. Its result gives the current ID of the next conflict. Add --status-after to 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).
but diff — shows file and hunk IDs for uncommitted changes. Do not run plain but status first.<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 prior but branch new is needed.--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.
but status -fv (or but 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-after to get fresh refs.Use this when an existing commit should be replaced by selected smaller commits.
but status -fv when you need the source commit, branch name, or placement anchor.but uncommit <source-commit-id> && but diff in one shell call exposes the commit's changes and prints the resulting file and hunk IDs.but commit calls. Do not reverse that order to match but 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.--above <top>/--below <top> (sha/#N anchors 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.--status-after to the final mutation when you need to inspect the resulting order.but status displays commits newest/top first, while task specs often list history oldest to newest — translate before moving.
but status once to get commit IDs (use -fv only if you also need file details).but move <source> --below <target-commit> places source immediately below target in but status (older in oldest-to-newest history). --above places it immediately above (newer). but move <source> -b <branch> moves it to branch top/newest.but move <block-id> <block-id> --below <following-commit-id> or but 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-after when you need to inspect the resulting order; do not move the anchor or block members again.but status for commit IDs and order.-t: but squash <source> [<source>...] -t <target> -m "<new message>".-t: but squash <branch> -m "<new message>".#N-suffixed.--status-after to the final squash when you need to inspect the resulting history; do not re-verify with a separate status.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.
but pr new <branch-id> pushes the branch and 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.
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.
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.
move, discard, …) warn about newly conflicted commits in their output, and the but pull summary lists them oldest-first; otherwise but status marks them.but resolve <commit-id> — enters resolution mode and prints the conflict regions.<<<<<<<, ||||||| (the common-ancestor section), ======= and >>>>>>> — and keep the correct content. Do NOT skip this; do NOT use but amend on conflicted commits.but resolve finish reports leftover markers, surviving uncommitted changes, every remaining conflicted commit, and the exact current but resolve <id> command. Add --status-after to 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.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 | 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..." |
git log, git blame, git show --stat) is allowed.but prints an AGENT ACTION REQUIRED skill warning, run the suggested command once, then reload/use the GitButler skill. If it repeats, report it instead of retrying.references/reference.mdreferences/concepts.mdreferences/examples.md