# GitButler Documentation ## Table of Contents - [butler-flow](#butler-flow.mdx) - [cli-overview](#cli-overview.mdx) - [gitbutler-tui](#gitbutler-tui.mdx) - [guide](#guide.mdx) - [index](#index.mdx) - [overview](#overview.mdx) - [releases](#releases.mdx) - [why-gitbutler](#why-gitbutler.mdx) - [workspace-branch](#workspace-branch.mdx) - [custom-csp](#custom-csp.mdx) - [fetch-push](#fetch-push.mdx) - [fixing-conflicts-outside-gitbutler](#fixing-conflicts-outside-gitbutler.mdx) - [recovering-stuff](#recovering-stuff.mdx) - [gerrit-mode](#gerrit-mode.mdx) - [timeline](#timeline.mdx) - [github-integration](#github-integration.mdx) - [gitlab-integration](#gitlab-integration.mdx) - [ai-assistance](#ai-assistance.mdx) - [branch-lanes](#branch-lanes.mdx) - [commits](#commits.mdx) - [merging](#merging.mdx) - [moving-branches](#moving-branches.mdx) - [pushing-and-fetching](#pushing-and-fetching.mdx) - [rules](#rules.mdx) - [signing-commits](#signing-commits.mdx) - [stacked-branches](#stacked-branches.mdx) - [upstream-integration](#upstream-integration.mdx) - [virtual-branches](#virtual-branches.mdx) - [debugging](#debugging.mdx) - [contact-us](#contact-us.mdx) - [open-source](#open-source.mdx) - [supporters](#supporters.mdx) - [but-absorb](#but-absorb.mdx) - [but-agent](#but-agent.mdx) - [but-alias](#but-alias.mdx) - [but-amend](#but-amend.mdx) - [but-apply](#but-apply.mdx) - [but-branch](#but-branch.mdx) - [but-clean](#but-clean.mdx) - [but-commit](#but-commit.mdx) - [but-completions](#but-completions.mdx) - [but-config](#but-config.mdx) - [but-diff](#but-diff.mdx) - [but-discard](#but-discard.mdx) - [but-gui](#but-gui.mdx) - [but-land](#but-land.mdx) - [but-move](#but-move.mdx) - [but-oplog](#but-oplog.mdx) - [but-pick](#but-pick.mdx) - [but-pr](#but-pr.mdx) - [but-pull](#but-pull.mdx) - [but-push](#but-push.mdx) - [but-redo](#but-redo.mdx) - [but-resolve](#but-resolve.mdx) - [but-reword](#but-reword.mdx) - [but-rub](#but-rub.mdx) - [but-setup](#but-setup.mdx) - [but-show](#but-show.mdx) - [but-skill](#but-skill.mdx) - [but-squash](#but-squash.mdx) - [but-stage](#but-stage.mdx) - [but-status](#but-status.mdx) - [but-teardown](#but-teardown.mdx) - [but-tui](#but-tui.mdx) - [but-unapply](#but-unapply.mdx) - [but-uncommit](#but-uncommit.mdx) - [but-undo](#but-undo.mdx) - [but-update](#but-update.mdx) - [commands-overview](#commands-overview.mdx) - [installation](#installation.mdx) - [ai-stuff](#ai-stuff.mdx) - [branching-and-commiting](#branching-and-commiting.mdx) - [conclusion](#conclusion.mdx) - [configuration](#configuration.mdx) - [conflict-resolution](#conflict-resolution.mdx) - [editing-commits](#editing-commits.mdx) - [forges](#forges.mdx) - [initializing-a-repository](#initializing-a-repository.mdx) - [inspecting](#inspecting.mdx) - [operations-log](#operations-log.mdx) - [rubbing](#rubbing.mdx) - [scripting](#scripting.mdx) - [tutorial-overview](#tutorial-overview.mdx) - [updating-the-base](#updating-the-base.mdx) - [getting-started](#getting-started.mdx) - [parallel-agents](#parallel-agents.mdx) - [review-agent-work](#review-agent-work.mdx) - [tuning-agent-behavior](#tuning-agent-behavior.mdx) - [useful-requests](#useful-requests.mdx) # cli-overview.mdx In addition to the Desktop graphical client, GitButler ships with a command line interface that allows you to do all of the same powerful things that the GUI does, but from the terminal or via scripts or agents. https://www.youtube.com/watch?v=Jg8L3SbgZ3o This includes tooling for: - [managing stacked and parallel branches](cli-guides/cli-tutorial/branching-and-commiting) - [inspecting and diffing](cli-guides/cli-tutorial/inspecting) - [an operations log and restoration of older states](cli-guides/cli-tutorial/operations-log) - [powerful commit editing](cli-guides/cli-tutorial/rubbing) - [upstream management](cli-guides/cli-tutorial/updating-the-base) - [conflict resolution](cli-guides/cli-tutorial/conflict-resolution) - [working with forges](cli-guides/cli-tutorial/forges) - [aliases and configuration](cli-guides/cli-tutorial/configuration) - [an interactive terminal UI](gitbutler-tui) All with a simple command line tool. ## Quick Reference For a quick reference of the most commonly used commands, check out our [**GitButler CLI Cheat Sheet**](/cli/cheat) - available as a web page or downloadable PDF. ## Getting Started Feel free to get started with our [Tutorial](cli-guides/installation) or just dig into the available commands. # butler-flow.mdx Butler Flow is a lightweight, branch-based workflow enabled by GitButler's virtual branch functionality. For most modern software development teams that want to set up a culture of shipping, who push to production every day, who are constantly testing and deploying, one of the most popular and effective development workflows is [GitHub Flow](https://docs.github.com/en/get-started/quickstart/github-flow). These are wise words, enterprising men quote 'em. Don't act surprised, you guys, [cuz I wrote 'em](https://scottchacon.com/2011/08/31/github-flow). However, a decade later, and with a new and more powerful branching toolset, we're able to amend that basic workflow to be simpler, faster, more flexible, and less error prone. Let's take a quick minute to explore what the Butler Flow is for software development teams using GitButler and how this can make all of our lives easier. ## Overview In a nutshell, the basic development cycle is very simple. - All work is based off a "target branch", which is a representation of released production code. - All work immediately exists in a branch. - Work can be shared with teammates for review as early as possible. - All branches that are close to merging can be applied locally for integration testing. - Branches can be reviewed independently and merged after final review. - Integrated branches are automatically removed from developer context. ### The Target Branch In stock, vanilla Git tooling, there is nothing specified as the production branch, no special "trunk". It is only by convention that this is enforced. In GitButler, branches will not work without the specification of a special "target branch". Everything exists in relation to this special branch, everything that differs from it must be accounted for by being owned by some other branch, until those changes are integrated. ### Parallel Branches Once you choose a target branch, everything that is in your working directory and not ignored by the Git Ignore specification must be owned by a virtual branch. If you don't have one active, GitButler will automatically create one for you. All subsequent changes to your working directory, either by applying other branches or directly modifying files, must be owned by a virtual branch. Branches are meant to be small, independent and rapidly integrated. However, longer lived branches can be continuously re-integrated, keeping them clean and mergeable for long periods of time if needed, while still being shareable and reviewable with your team. Parallel branches can be started and ended entirely independently of each other. Developers can work on longer branches, while starting, reviewing, finishing, merging, and deleting small ones without ever changing branch context. ### Collaboration All your team's work, whether created and managed by GitButler or not, exists on the central server as normal Git branches. These will automatically be pulled down and kept up to date by GitButler and can be converted into parallel branches and applied to your working directory in addition to your branches. This allows you to integrate work from the rest of your team continuously and early, while still keeping non-dependent changes separated and independently reviewable and mergable. It allows you to review code without needing to entirely switch contexts, and to do so early and often in an executable environment. Merge conflicts are known almost as soon as they occur and can be communicated about and collaborated on far before an upstream merge. ### Maintenance Parallel branches can remain applied locally until they are merged into your upstream target branch. Once integrated by any fashion (squash merge, rebase, merge), the virtual branch is automatically disposed of, keeping cruft and overhead low. # gitbutler-tui.mdx Use `but tui` when you want a live, keyboard-first GitButler workspace in your terminal. It opens an interactive view of your workspace state: unassigned changes, active branches, commits, files, and the common base. ```sh but tui ``` Run it from a GitButler-managed repository. If the repository has not been set up yet, run [`but setup`](/commands/but-setup) first. ## Pick the right terminal workflow - Use `but tui` for the full workspace view: branches, commits, assigned changes, unassigned changes, diffs, and local history operations. - Use [`but diff --tui`](/commands/but-diff) for focused diff review without the full workspace editing surface. - Use [`but stage`](/commands/but-stage) when you want to assign selected files or hunks to a branch. - Use [command mode](#use-command-mode) inside `but tui` when you need a `but` command without leaving the TUI. - Use plain CLI commands when you need repeatable output for scripts, agents, or logs. ## What you can do In the full workspace TUI, you can: - Inspect unassigned changes, branch lanes, commits, and file-level changes. - Open a details pane for the selected branch, commit, file, or hunk. - Create commits and empty commits. - Create branches. - Move commits or branches. - Rub changes into a branch or commit. - Mark multiple commits and act on them together. - Reword commits or branch names. - Discard local work after a confirmation prompt. - Undo and redo GitButler operations. For the complete shortcut list, press `?` inside the TUI. The help popup is generated from the current keybindings, so it is the source of truth. ## Move around Most navigation uses Vim-style keys, with arrow keys where they make sense. - `j` / `k` move down and up. - `Shift+j` / `Shift+k` jump to the next or previous section. - `g` jumps to unassigned changes. - `Shift+g` jumps to the common merge base. - `t` opens the branch picker. - `Esc` backs out of the current mode. - `q` quits. The hotbar at the bottom shows the most useful keys for the current mode. ## Inspect changes Select a branch, commit, file, or hunk, then press `d` to open details. The details pane shows commit metadata and diffs for the selected row. - `d` toggles the split details pane. - `Shift+d` toggles full-screen details. - `f` shows files in the selected commit. - `Shift+f` shows files across all commits. - `l` focuses the details pane. - `h` returns focus to the status list. - `+` and `-` resize the split details pane. - In details mode, `j` / `k` move between hunks. - In details mode, `Shift+j` / `Shift+k` scroll. - In details mode, `r` starts rubbing from the current hunk. - In details mode, `Shift+c` copies the current hunk. ## Change local history - Press `c` to create a commit from the selected unassigned or assigned changes. Press `i` in commit mode to write the message inline, `e` to leave it empty, or `b` to commit to a new branch. - Press `n` to insert an empty commit. - Press `b` to create a branch. - Press `m` to move the selected commit or branch, then choose the destination and press `Enter`. - Press `r` to rub the selected source into another target. In rub mode, `Shift+t` keeps the target message and `Shift+s` keeps the source message. - Press `Space` to mark commits, then use `r` to rub marked commits together. - Press `Enter` on a commit or branch to reword it inline. Press `Shift+m` to reword a commit in your configured editor. - Press `x` to discard the selected local work. Destructive actions ask for confirmation. - Press `u` to undo the last operation and `Shift+u` to redo it. GitButler records these operations in the operations log, so you can also inspect and recover with [`but oplog`](/commands/but-oplog), [`but undo`](/commands/but-undo), and [`but redo`](/commands/but-redo). ## Use command mode Use command mode when you want the TUI to stay open but need one command. - `:` runs a `but` command. For example, type `branch list` rather than `but branch list`. - `!` runs an external command. It does not run through your shell, so shell syntax like pipes, redirects, built-ins, and `&&` needs `sh -c '...'`. - `Ctrl+r` reloads the workspace state. After the command finishes, press `Enter` to return to the TUI. Successful commands reload the workspace state. Failed commands return with an error. ## Use the diff TUI [`but diff --tui`](/commands/but-diff) opens a smaller TUI focused only on diffs. It has a file list on the left and a diff pane on the right. ```sh but diff --tui but diff g0 --tui but diff my-branch --tui ``` - `j` / `k` move through files in the file list. - `h` / `l` / `Tab` switch panes. - In the diff pane, `j` / `k` scroll one line. - `Space` or `PageDown` scrolls down a page. - `PageUp` scrolls up a page. - `q` quits. You can make `but diff` use the TUI by default: ```sh but config ui set tui true ``` Use `--no-tui` for a one-off plain diff: ```sh but diff --no-tui ``` ## Stage hunks interactively Run [`but stage`](/commands/but-stage) without file arguments to open the hunk picker. This is the most precise way to assign only part of a file to a branch. ```sh but stage but stage --branch my-branch ``` If you have one active branch, GitButler uses it. If you have several active branches, GitButler asks which branch to stage to first. If you have no branches, GitButler creates one with a generated name. The stage picker starts with all hunks selected. When you confirm, selected hunks are assigned to the target branch and deselected hunks are explicitly left unassigned. - `j` / `k` move through files or hunks, depending on the active pane. - `h` / `l` / `Tab` switch panes. - `Space` toggles the selected file or hunk. - `a` selects all hunks. - `n` deselects all hunks. - `PageDown` / `PageUp` scroll the diff pane. - `s` or `Enter` stages the selected hunks. - `q` or `Esc` cancels. After staging, commit only the assigned changes with: ```sh but commit --only -m "Update billing copy" my-branch ``` Without `--only`, `but commit` also includes eligible unassigned changes for that branch. # index.mdx ## Overview GitButler is a new Source Code Management system designed to manage your branches, record and backup your work, be your Git client, help with your code and much more. Our focus is everything after writing code in your editor and before sharing it on GitHub. We're focused on your working directory and how we can help with everything you do there. We ship a [desktop GUI client](/overview) and [a CLI](/cli-overview) that work together. ### Desktop Client ### Command Line Interface Here you will find documentation on the product and the way that we're running our beta and building our product with your help. ## Getting Started Check out our [Getting Started](/guide) guide to get started with GitButler, or check out our helpful video overview: https://www.youtube.com/watch?v=DhJtNNhCNLM ## Why We're Doing This Read about it over [Why GitButler](/why-gitbutler) Section. ## What We Do The GitButler client is a powerful Git client. You can manage your branches, work on multiple things at once, push and fetch from your Git server, easily rebase and modify commits and more. We have a unique approach to merge conflicts that helps split up any conflicting work. It also keeps a timeline so that you can easily undo any operation. - [Parallel Branches](/features/branch-management/virtual-branches) - [First Class Conflicts](/features/branch-management/merging) - [Project History](/features/timeline) # overview.mdx Use GitButler with coding agents when you want messy local changes to become reviewable branches and commits without moving every task into a separate worktree. GitButler keeps one working directory while organizing changes into separate branches and commits. Agents can do that through the [`but` CLI](/cli-overview), and you can inspect the same state in the [Desktop Client](/overview). For when to use GitButler instead of separate worktrees, see [Parallel agents](/ai-agents/parallel-agents#how-this-differs-from-worktrees). You decide how far the agent goes. You can tell it to stop after local commits, or you can allow it to push and open pull requests. GitButler gives the agent version-control commands; your instructions decide when it stops. We ran coding agents through the same version-control tasks with Git, Jujutsu, and GitButler and published the results at [vcbench.dev](https://vcbench.dev). With GitButler, agents used about 80% fewer commands and finished around 60% faster than with plain Git, at similar reliability. It is our own benchmark, so the tasks, deterministic grader, and full results are public for you to check. ## Quick start Run this from the repository where your agent will work: ```sh but agent setup ``` The wizard can install the GitButler skill, write GitButler version-control instructions for your agent, and prepare the repository with `but setup` when GitButler needs workspace mode. It shows the files and exact instructions before writing anything. For the full setup path, see [Getting started](/ai-agents/getting-started). To choose or adjust the policies by hand, see [Tuning agent behavior](/ai-agents/tuning-agent-behavior). ## What agents can do with GitButler - **Parallel branches in one workspace.** An agent can create separate branches for independent work while staying in the same working directory. That gives you separate review branches without creating a new worktree, directory, and setup for each task. - **Selected files and hunks.** The agent can assign selected files or hunks to a branch, then commit that branch. You can inspect the split before the work is pushed. - **Stacked branches for dependent work.** If one change depends on another, the agent can put the dependent branch on top of the branch it needs. - **History edits without an interactive rebase.** The agent can move a change from one commit to another, reorder commits, reword commits, absorb fixes, or split a commit with `but` commands. - **Review and recovery before publishing.** You can inspect the same branches and commits in the CLI or Desktop Client, then use [`but oplog`](/commands/but-oplog) to restore an earlier local GitButler state if the branch layout needs to be rolled back. ## History edits as direct commands History edits are where agent workflows often get brittle. Moving one file's changes from one commit to another with Git can involve patch restore/reset steps, fixup commits, autosquash, or an interactive rebase with one or more `edit` stops. With GitButler, that kind of edit becomes a direct operation: move this file's changes into that commit. The operation can still rewrite commits and can still conflict. The difference is control flow: the agent gets targeted tools for the edit instead of driving a multi-step rebase session. For more detail, see the [`but rub` command reference](/commands/but-rub) and the [CLI guide to moving changes between commits](/cli-guides/cli-tutorial/rubbing#moving-files-between-commits). GitButler does not currently focus on a direct "push to main" workflow. Today, it is best used to get from agent changes to clean local branches, commits, and PRs or MRs quickly. # why-gitbutler.mdx The GitButler manifesto, as it were. Everyone loves a good manifesto. So, why is there a need for a new Git client in the world? Don't we have enough? Isn't the command line just fine? Having cofounded GitHub, trained dozens of corporate teams on distributed version control tools and literally written the book on Git, we have spent a lot of time and energy over the last decade thinking about the source code management tools that software developers use every day. GitHub has changed the way that millions of developers across the world collaborate and work with their source code, but as sophisticated and user friendly as that tool is in our daily coding lives, using GitHub or GitLab or Bitbucket still requires all of those developers to work with a command line tool that is confusing, difficult to use, error prone and not originally designed or built for the workflows and processes that most developers today use. That tool is Git. Sure, some small minority will use a GUI of some sort, but even those tools are mostly wrappers around the core concepts of Git itself, never reimagining what source code management could be or if Git itself is actually good at helping them with the tasks they face on a daily basis. I've never personally used one because they do little that Git itself doesn't and honestly it's generally easier to just do those tasks on the command line, where it's quick and efficient and I don't have to take my hands off the keyboard. But what if we broke down everything that you try to accomplish with Git, with source code management tools in general, reduce them down to first principles and imagine a tool that does all of those things better? Are you using Git because it's the best way you can imagine accomplishing those tasks, or are you using it because it's what is there, it's what works with GitHub, it's the only real option? The reality is that source code management tools have changed very little on a fundamental level in the last 40 years. If you look at the tools and commands and interface that RCS had in the 80s, or Subversion had in the 90s, is it really massively different than how you use Git today on a daily basis? Yes, Git has easy branching, acceptable merging, a nice network transport method to move your code around, but you're still making manual checkins, you're still trying to remember obscure arguments, you're still losing work when things get complicated. GitButler is rethinking everything between when you write code in your editor of choice and when you push that code to GitHub for review. Why are you making 'wip' commits when your SCM should be recording everything for you? Why are everyone's commit messages close to useless? Why is `git blame` the best way to get context on the code your team has written? Why can't you seamlessly transition work between computers? We are creating not only a new kind of Git client, but an entirely new way of thinking about managing the code that you work on. A tool that helps you at every step of the software development process. A code concierge, hard at work for you to ensure that you'll never lose a moment of work again. That you'll have all the context and support you'll need around every line of code you work on. Managing your source code can be different, smarter, leaps ahead of the 40 year old concepts that we're using today. Our goal is to make sure that nobody ever has to read Scott's book again. That you don't have to learn how to manage your source code management tool. # guide.mdx Here is a quick overview of how to get started with GitButler. In this guide, we will: - Get setup and start working with a local repository - Do work and commit on several branches simultaneously - Push your work to a remote repository and get it integrated - Reorder and edit your commits - Undo anything ## Importing a Local Repository After [downloading](https://gitbutler.com/downloads) and installing GitButler, you will be greeted with the welcome screen. From here you can Let's start by importing an existing Git project on your machine. If you click "Add local project" and then choose the directory of the existing Git initialized project, then we will start the ## Choosing a Target Branch The first step is to choose your "target" branch. GitButler focuses on a trunk based development model, so first you need to tell us what branch means "production" to you. Normally this is something like `origin/master` or `origin/main`. If you would like to use our AI features like commit message generation, you can also sign up for a GitButler account and log in. This is optional, but we'll do it here so we can show off these features. You can also connect GitButler to your GitHub account so we can automatically open Pull Requests for you (this is also optional). ## Set Up Git Authentication In order to push to a remote repository, you will need to set up your Git authentication. We provide three ways to do this, but generally it's easiest to use the existing Git executable. You can hit the "Test credentials" button to make sure everything is working properly. ## Ready to Go Ok, now we're all setup and you should see our main working screen. If you had a branch with some work in progress, you should see it imported already. Otherwise, you will see a blank workspace with no branches applied. ## Do Some Work Now we can start working. GitButler will watch your working directory and immediately show any changes that you make. In the above case, we can see that there are some changes to the `README.md` file. Let's create a new branch and commit our changes to it. You can click the "Create Branch" button in the workspace, or the "Commit to new branch" button below the Unassigned changes. You can click on any file to see what the diff is. Now that you have a branch, you can simply hit the "Start a Commit" button on the branch you want to commit to, then either write your commit message, or hit "Generate Message" to let AI look at the diffs and write a message for you. Once you've done that, you should see something like this: ## Split secondary work into a new branch Now let's say we decide to add a new feature and don't want it to depend on our README change. Instead of stashing the changes and doing that, or committing unrelated work into the branch I'm on, I can simply create a new parallel branch, drag my work over there and commit there. Now both branches are applied, but I can commit to them independently and push them to a remote repository at different times. ## Integrate Your Work Since the README updating branch is ready to be merged, I can push that branch up, open a Pull Request on it and wait for it to be integrated, while I continue to work on and commit to my other feature branch. Now we have a series of commits on our feature branch, which we've continued to work on while we're waiting for our README update to be integrated. GitButler sees that our target branch has new work and shows us the "Update" button, which we can click to rebase our feature branch on top of the new work and also remove the README branch automatically, since we can see that it has been integrated upstream. ## Reorder and Edit Your Commits GitButler makes it very easy to edit, reorder, squash and split commits. If you hover over any commit, you will see a drag handle on the top right. You can drag the commit to reorder it, or drag it on top of another commit to squash them together. To split a commit, you can insert an "empty" commit in between the two commits you want to split, then drag the changes from the commit you want to split into the empty commit. Now you can drag the changes from the commit you want to split into the new empty commit. Finally, you can edit the commit message of the previously empty commit to give it a meaningful message, and now you have split the original commit into two separate commits. You can also uncommit any commit without dependencies, or even individual files in a commit. All of this makes it very easy to build up and maintain a nice, clean history that is easy to understand and work with. ## Unapply a stack If you decide you don't want to work on a branch anymore, you can simply unapply it. This will remove all the changes from that branch from your working directory, but keep the branch around in case you want to reapply it later. ## Undo Anything If at any time, you do something you didn't mean to do, you can always undo it. You can undo a commit, a commit edit, a squash, an upstream update, a branch application, anything. To do this, click the "Operations History" button in the sidebar, find the action you want to rewind to and hit the "Revert" button. # releases.mdx GitButler is released on a regular basis in two separate tracks. Their version numbers are incremented independently. 1. **Release** - stable releases 2. **Nightly** - development releases built at minimum 1x per day via GitHub Actions. You can find the download links and changelogs for the latest releases on our [GitHub Releases](https://github.com/gitbutlerapp/gitbutler/releases). ## Platforms We bundle and ship GitButler for Mac OS, Windows, and Linux. ### Windows | Arch | Format | In-app updater | | --- | --- | --- | | `x86_64` | `msi` | | ### Mac OS | Arch | Format | In-app updater | | --- | --- | --- | | `x86_64` | `dmg` | | | `arm64` | `dmg` | | ### Linux | Arch | Format | In-app updater | | --- | --- | --- | | `x86_64` | `deb` | | | `x86_64` | `rpm` | | | `x86_64` | `AppImage` | | > Support for the Linux releases are complicated a bit through a core dependency of our framework, `libwebkit2gtk`, which is used to provide the web view on Linux. Tauri v1 required `libwebkit2gtk-4.0` which is not available in Ubuntu 24.04 or Debian 13 and newer. > > We've recently upgraded to Tauri v2 (as of Nightly `0.5.845` and Release `0.13.9`), and it now requires `libwebkit2gtk-4.1`. This version of the package is not available in the repositories for Ubuntu 20.04 and older as well as Debian 11 and older. > > For more information, check out the [pinned issue](https://github.com/tauri-apps/tauri/issues/9662) in the Tauri repository. # workspace-branch.mdx If you run some normal Git commands (like `git log`) while in GitButler mode, you'll see a few special branches that GitButler maintains behind the scenes. The one that most people get confused by is the `gitbutler/workspace` commit. There are a few different reasons that we need it, so let's take a quick look. If you run a normal `git log` on a GitButler managed repository, you will see something like this: ```git commit de56d20e282f7641d48d288b510141996c3c3cfc (HEAD -> gitbutler/workspace) Author: GitButler Date: Wed Sep 9 09:06:03 2020 +0800 GitButler Workspace Commit This is is a merge commit of the parallel branches in your workspace. For GitButler to manage multiple parallel branches, we maintain this commit automatically so other tooling works properly. If you switch to another branch, GitButler will need to be reinitialized. Here are the branches that are currently applied: - update-homepage (refs/gitbutler/update-homepage) branch head: a32f33273948837078e5f5a4e1677ab6274a4629 For more information about what we're doing here, check out our docs: https://docs.gitbutler.com/workspace-branch commit a32f33273948837078e5f5a4e1677ab6274a4629 (update-homepage) Author: Scott Chacon Date: Mon Jan 26 07:33:31 2026 +0500 hero update - new branding ``` That first commit is a merge commit that we rebuild as you modify branches in GitButler. The reason that it exists is mainly because if you have more than one branch applied in your workspace, when other tools run `git status`, it will look strange, since Git has no concept of having several branches applied at once. ## Status, Diff and Log To keep Git command output for things that look at the index and HEAD (such as status or diff) somewhat sane, we modify your index to look like the union of all the committed states of all your applied parallel branches. This makes git diff and git status behave more or less like you would expect. For instance, if you have two files on Branch A and two files on Branch B, then git status will simply list four files as modified. If you run git log, the first commit should be our custom commit message and the tree of that commit is the union of all the committed work on all your applied parallel branches, as though they were all merged together into one (something stock Git can understand). ## Committing, Branching, Checking Out However, if you try to use something that writes to HEAD, like git commit or git checkout, then you might have some headaches. For this reason, we install custom Git hooks for `pre-commit` and `post-checkout` that will protect this from happening. If you try to commit when in GitButler managed mode, the `pre-commit` hook should disallow it and tell you how to fix it. ```git ❯ git commit -am 'commit on the workspace branch' GITBUTLER_ERROR: Cannot commit directly to gitbutler/workspace branch. GitButler manages commits on this branch. Please use GitButler to commit your changes: - Use the GitButler app to create commits - Or run 'but commit' from the command line If you want to exit GitButler mode and use normal git: - Run 'but teardown' to switch to a regular branch - Or directly checkout another branch: git checkout If you no longer have the GitButler CLI installed, you can simply remove this hook and checkout another branch: rm ".git/hooks/pre-commit" ``` If you want to get out of this mode, you can follow any of those instructions. The easiest is running `but teardown`, but simply switching directly to a normal Git branch will also do the trick. # custom-csp.mdx By default GitButler uses a strict Content Security Policy (CSP) to protect against various attacks, such as cross-site scripting (XSS) and data injection attacks. This policy restricts the sources from which content can be loaded, as well as the hosts the application can connect to, ensuring that only trusted sources are allowed. However, there are some cases where you may need to customize the CSP to allow certain features or integrations. Some examples include: - Self-hosted GitHub Enterprise instances - Self-hosted GitLab instances - Self-hosted Ollama instances In those cases you are likely to observe an error message that looks something like this: ``` Refused to connect to https://./api/v4/projects/9/merge_requests because it does not appear in the connect-src directive of the Content Security Policy. ``` You can resolve this issue by adding your host to the CSP. ## Adding a Custom CSP You can add a custom CSP by editing the GitButler configuration file, found at the following location: ```bash ~/Library/Application\ Support/gitbutler/settings.json ``` ```bash C:\Users\[username]\AppData\Roaming\gitbutler\settings.json ``` ```bash ~/.config/gitbutler/settings.json ``` The file is in JSONC format and follows the [following schema](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/but-settings/assets/defaults.jsonc) In order to add your custom CSP entry, you want to add an `extraCsp` entry to the JSON file. The `extraCsp` entry is an object that contains a `hosts` array, which is where you can add your custom hosts. For example: ```json "extraCsp": { "hosts": ["https://subdomain.example.com", "http://another-subdomain.example.com"] } ``` Note that if `extraCsp` is the only entry in the JSON file, you may want to enclose it in a top-level object, like this: ```json { "extraCsp": { "hosts": ["https://subdomain.example.com", "http://another-subdomain.example.com"] } } ``` The changes will take effect the next time you start GitButler. # fetch-push.mdx If you are having trouble pushing or fetching from a remote, this is likely related to git authentication. Here are a few configuration options you can try out, found in the project settings. ## Configuring the auto-fetch frequency GitButler will periodically fetch from your configured remotes in order to display new branches etc. By default, this happens every every 15 minutes. You can configure the interval or completely disable this behavior by editing the application's `settings.json` file: ```bash ~/Library/Application\ Support/gitbutler/settings.json ``` ```bash C:\Users\[username]\AppData\Roaming\gitbutler\settings.json ``` ```bash ~/.config/gitbutler/settings.json ``` The file is in JSONC format and follows the [following schema](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/but-settings/assets/defaults.jsonc) ```json "fetch": { "autoFetchIntervalMinutes": 15 } ``` A negative value (e.g. -1) disables auto fetching. Note that if `fetch` is the only entry in the JSON file, you may want to enclose it in a top-level object. ## Available authentication methods GitButler can be configured to use several different git authentication methods. You can switch between them in your project settings. You can try multiple different options and see if any of them are appropriate for your setup. Note that if you are on Windows, the only applicable method is the "Git executable", therefore the application will now show this as a configuration option. ### Use a Git executable (default) The default way to push and fetch is for GitButler to use an existing system Git executable. This should use whatever authentication mechanism that Git uses for the remote that you're trying to push to or fetch from. ### Use an existing SSH key If already have an SSH key set up (eg. `~/.ssh/id_rsa`), you can instruct GitButler to use it. In case the key is password protected, you can also provide the password to it (which will be stored locally). ### Use locally generated SSH key This option generates a new SSH key which will be stored locally in the application [data dir](https://docs.gitbutler.com/development/debugging#data-files). For this to work you will need to add the new public key to your Git remote provider. ### Use a git credential helper If your system is set up with a credential helper, GitButler can use that. For more info on git credential helpers, see this [article](https://git-scm.com/doc/credential-helpers). ### FIDO security keys (YubiKey, etc.) If you're using a FIDO key, check out this issue to see how people have set it up with the Git executable method: [#2661](https://github.com/gitbutlerapp/gitbutler/issues/2661) ### Keys managed by 1Password Keys stored in 1Password should properly use it as an SSH agent for authentication and signing commits if you use the Git executable option. (Previously tracked in [#2779](https://github.com/gitbutlerapp/gitbutler/issues/2779)) ### Host certificate checks There is an option to ignore host certificate checks when authenticating with ssh. This may be a helpful option to enable in some cases. ## Other known issues ### Git remote servers with a non-standard SSH port In some cases, the git remote may be setup on a port number other than 22. If the port is set in your `~/.ssh/config` file, GitButler will not be able to recognize that - tracked in GitHub issue [#2700](https://github.com/gitbutlerapp/gitbutler/issues/2700). As a workaround you may set your remote in the [SSH format](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols) (eg. `ssh://git@example.com:3022/foo/bar.git`) Updating parallel branches when the respective remote has new commits If you have added a remote branch to your active workspace in GitButler, or pushed a virtual branch to the remote, and new commits are added to the remote branch, there is currently no way to sync those new commits into the existing virtual branch in GitButler. This is being tracked in the GitHub issue [#2649](https://github.com/gitbutlerapp/gitbutler/issues/2649). The current workaround is to undo any local commits and then stash your local changes manually using [git stash](https://git-scm.com/docs/git-stash) and then delete the virtual branch that has upstream changes. Then you can update the trunk by clicking the update button next to the word "Trunk" in the sidebar on the left to make sure all new upstream changes are synced, then select the remote branch that has the new changes and click the "Apply +" button above the list of commits for the branch. Once the updated branch is applied to your working directory, you can manually `git stash pop` your stashed changes and then resolve any merge conflicts. ### OAuth app access restrictions on your GitHub organization If you're submitting code and PRs to a repository under an organization on GitHub, you may receive an error that, despite having correct authorization credentials, your organization has enabled OAuth app access restrictions. These restrictions are an organization-level security feature designed to prevent unauthorized third-party applications from accessing organization resources. To solve this, go to Applications. Select the "Authorized OAuth Apps" tab, and look for "GitButler Client". If you don't find "GitButler Client", it's possible you haven't yet set GitButler up for personal use. If so, try creating a test commit on a test branch for a personal repository using GitButler, which you can delete after, then check the same tab as before. If you see "GitButler Client", click on it and, under "Organization Access", across from the organization you wish to enable GitButler for, click either the "Request" or "Grant" button, depending on whether you are a contributor or owner, respectively. If you're a contributor clicking "Request", note that you'll need to wait for an organization owner to approve your access request before you can proceed. Note for organization owners: To streamline this process for your team members, you can pre-approve GitButler for all organization members. This eliminates the need for individual access requests and approvals. This can be managed through your organization's OAuth app access settings. ### Help on Discord If none of the available options helps, feel free to hop on our [Discord](https://discord.gg/MmFkmaJ42D) and we will be happy to help you out. # fixing-conflicts-outside-gitbutler.mdx If you have ended up with conflicted commits and GitButler is completely unresponsive, they can be recovered using plain git commands in the following manner: ## Consider hopping on a call with one of us. The resolution steps make use of some advanced git functions, if you are not comfortable with any of the steps - we are more than happy to walk you through any recovery processes. Join our Discord and let us know about your situation. One of us will help you work through your problem either through text or via a call. ## Backup! First, make a copy of your entire repo. We don't want to lose any data if we make a mistake in recovery. ## Make a new branch Conflicts often come up as part of a cherry pick so we want to re-perform the rebase manually - resolving any conflicted commits as we go. I want the commits to sit on top of origin/master, so I'll run the following commands to make a new and empty branch to re-build the branch on top of: ``` git switch -c reconstruction git reset --hard origin/master ``` ## Looking at the commits We can now get an idea of what operations we need to perform to reconstruct the branch. By running: ``` git log --graph --oneline ``` We can see all the commits that are in our branch. For my branch, it looks as follows: ``` > git log --oneline --graph reimplement-insert-blank-commit * b1b1bf07d (reimplement-insert-blank-commit) Improvements to rebase engine for better composability * c8f5b92a0 Rename reword_commit to commit_reword * e1fc3b9f5 Reimplement insert blank commit ``` We want to work from the bottom of this list to the top. To get a better idea of the state of a given commit, we can run: ``` git cat-file -p ``` We can identify if the commit is conflicted by the presence of a header that looks as follows: ``` gitbutler-conflicted ``` ## Reconstructing the branch Depending on the state of a commit and it's parent, there are some different operations we want to perform to re-perform the rebase. ### If a commit is conflicted If a commit is conflicted, we want to first look at the tree of the conflicted commit. We can do that with the following command: ``` git cat-file -p ^{tree} ``` For the first commit in my list, that looks like: ``` > git cat-file -p e1fc3b9f5^{tree} 040000 tree 24e291fb0867efec629b933c00aaeaff39365efd .auto-resolution 040000 tree ffde17e2a4d4c045869b300b4ec9027851581e33 .conflict-base-0 100644 blob dca5869dd76a1eeadeba9387ec7f94b318085c7e .conflict-files 040000 tree 3b23a61344b84fa3f7b93b1ca058d24846a31f57 .conflict-side-0 040000 tree b5a91de1f2ce0a248472d03c1701a20289e4d657 .conflict-side-1 100644 blob 2af04b7f1384300b742f6112005cddc5a87be022 README.txt ``` Here we see the conflicted representation of a commit in GitButler. There are four entries that are relevant here: - `.auto-resolution` - This contains a resolution attempt that GitButler made when cherry-picking the commit. - `.conflict-base-0` - This contains the tree of the commit that was cherry-picked to produce the conflicted commit. - `.conflict-side-0` - This contains the tree of the commit that we tried to cherry-pick onto. - `.conflict-side-1` - This contains the tree of the origional commit before it was cherry-picked. To re-perform the cherry-pick that GitButler was trying to do. We do that by first making a commit that holds the `.conflict-base-0` tree which can be done by running: ``` git commit-tree -p HEAD -m "base" ``` For me, that looks like: ``` > git commit-tree ffde17e2a4d4c045869b300b4ec9027851581e33 -p HEAD -m "base" 0100ea63fe63a2894567de42371f8d6cf79e4a85 ``` This has given us an OID in return. This is the object ID of the commit we just created. We then want to create a commit that contains the `.conflict-side-1` tree, and has that new "base" commit as it's parent. We can do that by running: ``` git commit-tree -p -m "Desired commit message" ``` For me this looks like: ``` git commit-tree b5a91de1f2ce0a248472d03c1701a20289e4d657 -p 0100ea63fe63a2894567de42371f8d6cf79e4a85 -m "Reimplement insert blank commit" 35d518d2ea68635631593faff34b11e3b1904014 ``` Using that returned commit OID, we can then bring that commit on top of our branch with: ``` git cherry-pick ``` For me, that looked like: ``` git cherry-pick 35d518d2ea68635631593faff34b11e3b1904014 ``` Git may prompt you to solve some conflicts here which you can resolve in the standard manner. ### If a commit is **not conflicted**, but has a **conflicted parent**. If the commit is not conflicted, but the commit before it in your log WAS conflicted, then we similarly need to create a commit to cherry-pick on our own. First, you will want to take a look at that parent's commit tree with: ``` git cat-file -p ``` We want to make a base commit that uses the `.auto-resolution` tree. We can do that with: ``` git commit-tree -p HEAD -m "Desired commit message" ``` We then want to make a commit that has the tree of the non-conflicted commit, with the parent as the base commit we just made. We can first find the tree of the non-conflicted commit by running: ``` git cat-file -p ``` and copying the entry after `tree`. We then want to make our commit to cherry pick with: ``` git commit-tree -p -m "desired commit message" ``` We can then cherry-pick that commit with `git cherry-pick` onto our branch, following the standard conflict flow if applicable. ### If the commit is **not conflicted** and its parent is **not conflicted** If this is the case, we can run the standard `git cherry-pick` command to bring that commit into our reconstruction branch, following the standard conflict flow if applicable. ## Pushing your reconstructed branch Once you have finished bringing all of your commits into your reconstruction branch, you can then push it to your remote via `git push`. # recovering-stuff.mdx How to dig around our internal data to find (nearly) anything GitButler saves data in a few different ways. As we're still in beta, sometimes things might break and it may look like you've lost work, but you almost certainly haven't. We're pretty good about saving stuff a lot. Here's how to recover almost anything you had in your working directory or parallel branches. ## GitButler References If everything crashes or the UI isn't working at all, you may be surprised to know that even though your parallel branches don't show up in a normal git branch output, we do actually constantly write them out as Git references (just not in refs/heads). ```bash title="Terminal" ❯ git for-each-ref | grep gitbutler e63b3bac82835dc17083a785d25db8b4b46744b9 commit refs/gitbutler/add-can-create-method-to-notebook 98ef3cd6eea14ee4159a600e448271c0d777efe2 commit refs/gitbutler/add-conditional-blocks-for-image-and-video c7e27b9f99f25160a4d5f07d5972c217bdd44319 commit refs/gitbutler/add-database-schema-conversion-script 4afdfed6c14b57491a9d295c31613fd79b92f63a commit refs/gitbutler/add-gems-for-test-group ``` These references are just like git branches - they point to a commit that has the latest version of your branch. You can create other git branches off of them, you can push them to GitHub, etc. You will have one for each virtual branch (applied or unapplied) that you've created (that you haven't deleted). If you've committed everything on a virtual branch, the reference will just point to the latest commit. If you have work in progress on the branch, it will point to a WIP commit that includes those changes. So for example, if I have the following two parallel branches, one fully committed and one with work pending: I can view the git branches like this: ```bash title="Terminal" ❯ git show gitbutler/Convert-tables-to-utf8mb4 commit 841e4db701ca41206c03f1f4fe345f7e27d05eab Author: Scott Chacon Date: Fri Feb 23 10:30:17 2024 +0100 my latest commit ❯ git show gitbutler/Add-database-schema-conversion-script commit d95e7f4da1611ea6bb8a80da06e66ca923fbff55 Author: GitButler Date: Fri Feb 23 10:30:18 2024 +0100 GitButler WIP Commit This is a WIP commit for the virtual branch 'Add database schema conversion script' This commit is used to store the state of the virtual branch while you are working on it. It is not meant to be used for anything else. ``` See how the `Add-database-schema-conversion-script` reference points to a "WIP commit"? The tree of that commit has all those changed files in it as though we had committed them. If you don't want to search through all your refs with `for-each-refs`, you can also just run a normal `git log` command and we'll show you what references we've written and which modified files are in each one: ```bash title="Terminal" ❯ git log commit 2d8afe0ea811b5f24b9a6f84f6d024bb323a2db5 (HEAD -> gitbutler/workspace) Author: GitButler Date: Fri Feb 23 10:30:18 2024 +0100 GitButler Integration Commit This is an integration commit for the parallel branches that GitButler is tracking. Due to GitButler managing multiple parallel branches, you cannot switch back and forth between git branches and parallel branches easily. If you switch to another branch, GitButler will need to be reinitialized. If you commit on this branch, GitButler will throw it away. Here are the branches that are currently applied: - Add database schema conversion script (refs/gitbutler/Add-database-schema-conversion-script) - butler/Gemfile - butler/README.md - butler/db/schema.rb - butler/db/migrate/20240209144600_change_mysql_charset.rb - .pscale.yml - Convert tables to utf8mb4 (refs/gitbutler/Convert-tables-to-utf8mb4) branch head: 841e4db701ca41206c03f1f4fe345f7e27d05eab - butler/create_column_conversions.rb Your previous branch was: refs/heads/sc-branch-comments The sha for that commit was: 5e16e99667db9d26f78110df807853a896120ff3 For more information about what we're doing here, check out our docs: https://docs.gitbutler.com/features/branch-management/integration-branch ``` You can see the two `gitbutler` refs under the "Here are the branches that are currently applied" section. Again, these are real git refs, just not under `refs/heads` so that we don't pollute your `git branch` output. But if GitButler crashes at some point, you can still push them to GitHub or whatever you want. Here is an example pushing my virtual branch to a GitHub branch called `convert-tables`: ```bash title="Terminal" ❯ git push origin refs/gitbutler/Convert-tables-to-utf8mb4:refs/heads/convert-tables Enumerating objects: 6, done. Counting objects: 100% (6/6), done. Delta compression using up to 10 threads Compressing objects: 100% (4/4), done. Writing objects: 100% (4/4), 474 bytes | 474.00 KiB/s, done. Total 4 (delta 2), reused 1 (delta 0), pack-reused 0 remote: Resolving deltas: 100% (2/2), completed with 2 local objects. remote: remote: Create a pull request for 'convert-tables' on GitHub by visiting: remote: https://github.com/gitbutlerapp/web/pull/new/convert-tables remote: To github.com:gitbutlerapp/web.git * [new branch] refs/gitbutler/Convert-tables-to-utf8mb4 -> convert-tables ``` ## GitButler Operations Log Ok, let's say that your work was not in one of those refs for some reason. Maybe you hit some weird bug and it completely changed everything in a way where now you're sitting on the couch in the dark with a glass of whisky, slowly mumbling the word "GitButler..." and plotting your revenge. Most of the time, we'll have whatever you're looking for in our operations log. The easiest way to access this is to use the built in Project History UI: [Project History](/features/timeline) However, let's dig into how this works, just in case you want to check it out yourself. Every time that GitButler does some possibly data-changing operation, we store a snapshot of your project state in our operations log before the operation happens so you can undo it if you want to. This is stored as a Git commit history that is parallel to your projects (ie, no common parents). You can inspect this by looking at the `.git/gitbutler/operations-log.toml` file. ```bash title="Terminal" ❯ tree .git/gitbutler .git/gitbutler ├── operations-log.toml └── virtual_branches.toml 1 directory, 2 files ❯ cat .git/gitbutler/operations-log.toml head_sha = "16e47cb1d091ca9dd44327fef2f5305b09403a95" [modified_at] secs_since_epoch = 1717663406 nanos_since_epoch = 560458000 ``` If we look at this commit, we can see the history of all of the project history snapshots that GitButler is keeping: ```bash title="Terminal" ❯ git log 16e47cb1d091ca9dd44327fef2f5305b09403a9 -2 commit 16e47cb1d091ca9dd44327fef2f5305b09403a95 Author: GitButler Date: Thu Jun 6 10:43:26 2024 +0200 CreateBranch Version: 1 Operation: CreateBranch name: Virtual branch commit 2c95aa06d76b3230f1a51d9f89a211770d93ae51 Author: GitButler Date: Thu Jun 6 10:28:30 2024 +0200 UpdateWorkspaceBase Version: 1 Operation: UpdateWorkspaceBase ``` You can see that before creating a branch or updating our workspace with upstream work, we're recording the state of our project so we have an undo point. So what data are we keeping here in addition to this trailer information? Let's look at the tree of one of these commits: ```bash title="Terminal" ❯ git cat-file -p 16e47cb1d091ca9dd44327fef2f5305b09403a95^{tree} 040000 tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 conflicts 040000 tree b283f22f5abf4ed9c612c1b5b3b9a98ec25474b0 index 040000 tree b283f22f5abf4ed9c612c1b5b3b9a98ec25474b0 target_tree 100644 blob d839dca7e14f5833ad737b4adbf337bd20489927 virtual_branches.toml 040000 tree a0821552c0e7d5defe369d577af5e3a87b442469 virtual_branches ``` The parallel branches toml file has the interesting metadata: ```bash title="Terminal" ❯ git cat-file -p 16e47cb1d091ca9dd44^{tree}:virtual_branches.toml [default_target] branchName = "master" remoteName = "origin" remoteUrl = "git@github.com:gitbutlerapp/gitbutler.git" sha = "e00e54af7f903ef84600079a45490a7f07e4702e" pushRemoteName = "origin" [branch_targets] [branches.09ef54c4-1081-4a52-8182-a5ec725016b6] id = "09ef54c4-1081-4a52-8182-a5ec725016b6" name = "commit signing settings" notes = "" applied = false upstream = "refs/remotes/origin/commit-signing-settings" upstream_head = "b60a66452dfecef74103346af6a3291ad677d246" created_timestamp_ms = "1717489406268" updated_timestamp_ms = "1717489406268" tree = "b28e7eefdd7b6f36456516b696146a2ea7638ca4" head = "b60a66452dfecef74103346af6a3291ad677d246" ownership = "" order = 4 selected_for_changes = 1717489406268 ``` The virtual_branches tree has the actual contents of those computed branches in case we need to recreate them: This allows you to get contents of any file in any of your virtual branch states as well. # timeline.mdx Undo nearly any of your actions or go back in time to an earlier state. ## How it works Before GitButler does any major action, it records the state of everything (your virtual branch state, your uncommitted work, conflict state, etc) and stores it in your Git object database as snapshots. You can hit the 'revert' button on any of the entries and it will restore the state of all of these things to what they looked like when they were recorded, letting you go back in time. ## Restoring State If you hover over any of the entries, you will see a button named "Revert" that will restore the state of things to right before you did that action. So if you revert one that says "Create Commit", it will put you where you were right before you made that commit. ## Recovering Content Occasionally, GitButler will also take snapshots of files that were changed recently, even if they weren't committed. If this, or any other action, sees changes in files, you can see which ones and view the change by clicking on the file name. # gerrit-mode.mdx Not _everyone_ uses GitHub or GitLab to review code and collaborate. If you use the [Gerrit](https://www.gerritcodereview.com/) code review tool, GitButler has a mode for you! In fact, GitButler is the best Gerrit client there is. ## What is Gerrit If you've never heard of Gerrit, it's used by large teams like the Android or Chrome projects to manage huge numbers of changes and users across large numbers of interdependent repositories. Here is an example of incoming changesets on the [Android project](https://android-review.googlesource.com/q/status:open+-is:wip,50): ## How is Gerrit different than Pull/Merge Requests? Good question. With GitHub or GitLab, when you send a pull/merge request, the review process is branch based. If you add more commits on top of your branch, the changes are squashed into one big unified diff for review. Most teams tend to avoid rebasing anything that was already shared. Gerrit is a commit based review system. Every review is based on exactly one commit. It's very common to edit shared commits and submit new versions of them to address feedback. This model works _very well_ with GitButler's easy [commit editing](/features/branch-management/commits) features. With any other Git client, interactive rebasing and amending tends to be quite painful and error prone, making it fairly difficult to work with Gerrit's model. With GitButler, it's ideal. Just drag and drop changes and update your changesets easily. ## How to turn on Gerrit Mode If you have a Gerrit remote, GitButler will automatically enable Gerrit Mode when the project is being added to GitButler. You can also enable it manually. To manually turn on Gerrit Mode in GitButler, you just have to set a Git config option called `gitbutler.gerritMode` in the project you want to act in a Gerrit compatible fashion: ``` ❯ cd my_project ❯ git config gitbutler.gerritMode 1 ``` ## What is Gerrit Mode Now GitButler will change its behavior in the following ways: - When you commit, we will automatically inject a `Change-Id` trailer into the commit in the format that Gerrit expects. You do not need to [setup a `commit-msg` hook](https://gerrit-review.googlesource.com/Documentation/cmd-hook-commit-msg.html) like you do with other Git clients. - When you push, it will not push to a matching branch name on the remote. Instead it will push to `refs/for/main` (or whatever the name of the target branch is set to be). - After a push, we record the change url and show you the link and number for each commit automatically. We can also set some extra push options when we push, including: - [Topics](https://gerrit-review.googlesource.com/Documentation/cross-repository-changes.html) - [Hashtags](https://gerrit-review.googlesource.com/Documentation/intro-user.html#hashtags) - [WIP status](https://gerrit-review.googlesource.com/Documentation/intro-user.html#wip) # github-integration.mdx GitButler integrates seamlessly with GitHub, allowing you to manage the pull requests associated with your branches directly from the GitButler client. You can setup multiple GitHub accounts and pick which account to use for specific projects. This makes it easy to manage work across different organizations or personal repositories. ## Setting up GitHub Integration To set up GitHub integration, you need to connect your GitButler client to your GitHub account. This is done through the GitButler settings. Open the GitButler settings by clicking on the profile button in the sidebar, then navigate to the **Integrations** tab, then click on the **Add account** button for GitHub. This will offer you to create an account out of the following options: - **Device Flow**: This option will generate a code and ask you to open a browser window to authorize GitButler to access your GitHub account. This is the recommended option for most users. - **Personal Access Token**: This option allows you to manually enter a personal access token from GitHub. This is useful for users who prefer not to use OAuth or need specific scopes for their token. - **GitHub Enterprise**: This option allows you to connect to a GitHub Enterprise instance by providing the base URL for the API and a personal access token. We'll select the **Device Flow** option for this example. A code will be generated for you. Copy it to your clipboard. Next, click on the **Open GitHub activation page** button. This will open a browser window to the GitHub authorization page. Once you are on the GitHub authorization page, paste the code into the input field and it should let you know that you are authorized. Return to the GitButler client and click on the **Check the status** button. If everything went well, your account should be connected and you should see your GitHub username in the Integrations tab. You can always forget an account by clicking on the **Forget** button next to the account name. Alternatively, you can also connect your GitHub account using a Personal Access Token by selecting the **Personal Access Token** option when adding an account. The permissions required for GitButler to manage pull requests are read access to *Metadata* and read and write access to *Pull Requests* The required permissions might change in the future as new features are added. In the case of GitHub Enterprise, select the **GitHub Enterprise** option when adding an account. This will ask you for the base URL of your GitHub Enterprise instance's API as well as a Personal Access Token with the same permissions as above. For example, if your GitHub Enterprise Server's hostname is **github.a-super-serious-company.com**, then set the base URL to **github.a-super-serious-company.com/api/v3**. See [GitHub's Octokit base URL documentation](https://github.com/octokit/octokit.js?tab=readme-ov-file#constructor-options) for more information. ## Using GitButler to Manage Pull Requests Once you have set up GitHub integration, you can open your pull requests directly from the GitButler branch. When you create a new branch or commit changes, GitButler will automatically check if there are any associated pull requests on GitHub. You can view these pull requests in the "Pull Requests" tab in the sidebar of the Branches page. # gitlab-integration.mdx Create your GitLab Merge requests without leaving GitButler. ## Setting up the GitLab Integration In order to set up the GitLab integration, you will need two things. Firstly, a GitLab Personal Token and the Project ID for your project. ### Creating a GitLab Personal Token To create a GitLab Personal Token, navigate to your GitLab preferences, and then click on the "Access tokens" tab. You will then see an "Add new token" button. Pressing this button will provide you with a form (as captured below) where you can specify the token name, expiration date, and scopes. We currently require the `api` scope. In this example, I've chosen to set the expiration date for the token to a year from now. On completion, you will be showen the following screen which contains your GitLab Personal token. ### Finding your GitLab Project ID Navigate to the main page of the repository you want to configure the integration for. On the top left next to the "Fork" button, you will see three dots. Click on those three dots, and click on "Copy project ID". This will copy the GitLab Project ID to your clipboard. ### Configuring GitButler Inside GitButler, navigate to the project settings by clicking on the small cog icon in the bottom left. Scroll down to the bottom of the "Project" tab where you will see a form for entering your GitLab details. The provided GitLab Personal Token will be stored securly in your operating system's keychain. ### Custom GitLab Instances You may also provide a different Instance URL if you are using a self-hosted GitLab instance. Note that if you use a custom GitLab instance, you will likely need to configure a custom CSP (Content Security Policy) to allow GitButler to connect to it. You can find more information on how to do that in the [Custom Content Security Policy (CSP)](/troubleshooting/custom-csp) section of the documentation. ## Usage You will now have a "Submit for Review" button on each branch which you can use to create a Merge Request. Once the Merge Request is created you will be able to see the status of it from within the client. Please note that we do not yet integrate with CI status. # ai-assistance.mdx ## Getting Started ### Global AI Setup (One-time) 1. Navigate to **Global settings** → **AI Options** 2. Choose your AI provider: - **GitButler API** (default): Uses OpenAI through GitButler's servers - no API key needed - **Your own key**: Bring your own OpenAI, Claude, Ollama, or LM Studio credentials 3. If using your own key, enter your API credentials ### Per-Project Setup 1. Open **Project settings** → **AI options** 2. Enable **"Enable branch and commit message generation"** 3. Optionally enable **"Enable experimental AI features"** for advanced functionality ## Features ### Branch Name Generation Automatically creates descriptive, kebab-case branch names based on your code changes. **Usage:** - Right-click on a branch header and select **Generate branch name** - GitButler analyzes commit messages in the branch and suggests an appropriate name - Generated names use kebab-case format and avoid conflicts with existing branches ### Commit Message Generation Creates professional commit messages following best practices. **Features:** - Semantic prefixes (`feat:`, `fix:`, `refactor:`) - 50-character title limit, 72-character body wrap - Explains what changed and why - Real-time streaming as AI generates the message - Based on actual code diffs, not just file names **Usage:** 1. Make changes to your files (staging is automatic in GitButler) 2. Click the **Generate message** button in the commit message editor 3. AI streams the generated message in real-time 4. Review and edit before committing **Example format:** ``` feat: add user authentication system Implements JWT-based authentication with login and registration endpoints. Includes password hashing and session management to secure user accounts. ``` ### Pull Request Descriptions Generates comprehensive PR descriptions when creating pull requests. **Usage:** 1. Create a pull request from your branch 2. In the PR creation dialog, click **Generate PR description** 3. AI analyzes all commits in your branch and generates a description 4. Review and edit the generated content before creating the PR **Generated content includes:** - High-level summary based on commit messages - Structured description following PR templates (if configured) - Context derived from the changes in your branch ## Advanced Features ### Custom Prompts - **Global Prompts**: Create custom prompts in **Global settings** → **AI Options** - **Project-Specific Prompts**: Assign specific prompts per project in **Project settings** → **AI options** - **Commit & Branch Prompts**: Separate customization for commit messages and branch names ## Configuration ### Global Settings (Global settings → AI Options) - **AI Provider**: Choose between OpenAI, Anthropic, Ollama, or LM Studio - **Key Options**: Use GitButler API or bring your own credentials for each provider - **Model Selection**: Choose specific models per provider (GPT-4o, Claude Sonnet, etc.) - **Amount of provided context**: Set how many characters of git diff to send to AI ### Project Settings (Project settings → AI options) - **Enable branch and commit message generation**: Master toggle for AI features in this project - **Enable experimental AI features**: Access to advanced AI functionality (requires GitButler API) - **Custom prompts**: Assign specific prompts from global settings to this project for commits and branches ## Troubleshooting **AI features not working?** 1. **Check Global Settings**: Navigate to **Global settings** → **AI Options** and verify: - AI provider is configured (OpenAI, Anthropic, etc.) - Key option is selected (GitButler API or your own key) - If using your own key, ensure it's entered correctly - Model is selected for your chosen provider 2. **Check Project Settings**: Open **Project settings** → **AI options** and ensure: - **"Enable branch and commit message generation"** is turned ON - This setting must be enabled for each project individually 3. **Verify API Access**: Ensure sufficient API quota and valid credentials **AI buttons not appearing?** - The project-level toggle in **Project settings** → **AI options** controls button visibility - Without this enabled, Generate buttons won't appear in the UI **Need better suggestions?** - Customize prompt templates in **Global settings** → **AI Options** - Make meaningful code changes with clear patterns - Use descriptive variable names and comments in your code - Review [troubleshooting guide](https://docs.gitbutler.com/troubleshooting/custom-csp) for advanced configurations # branch-lanes.mdx All of your branches - remote, local, and virtual / applied or not - are managed in the Branch Tab. This is where you can see all of your branches, apply them to your workspace, and manage your parallel branches. You can access the Branches tab by clicking on the "Branches" icon in the sidebar. The interface looks something like this: ## Branch List The first pane on the left shows you the parallel branches and stacks that you have as well as the other branches that you have available (legacy git branches, remote branches and PRs). All of these branches can be converted into parallel branches by clicking them and then clicking the "Apply to workspace" button on the top of the branch view (middle pane). Local branches can also be fully deleted here. ### Current Workspace Target The "Current workspace target" is the view of the target branch that you've set. It will show you essentially a `git log` of `origin/master` or whatever you set as your target branch, and it will show you if there are any commits upstream that you have not integrated locally yet. We will automatically check for new upstream changes every few minutes, but you can also click the update button to check immediately. # merging.mdx By default, GitButler rebases the work on your parallel branches when you update your target branch (upstream) work. Often this works just fine and the commits are simply rebased. Occasionally, you will have conflicts with upstream work. In this case, GitButler will not do what Git normally does, which is to stop at each conflicted commit and make you fix it before moving on. Instead, it will apply the changes that it can and store the commit as a "conflicted" commit and continue the rebasing process. When you go to update from upstream, GitButler will show you all the branches that it will rebase and will let you know if any of them will have conflicts: In this case, when you perform the rebase, that branch will then contain "conflicted" commits. They will be marked in the UI as conflicted and you can click on them to get a "resolve conflict" button to start the resolution process. When you click that, GitButler will remove the other parallel branches and other work from your working directory and check out just this commit with its conflict markers. It will show you a special "edit mode" screen, where you are directly editing this commit. If you want to cancel this conflict resolution, you can just hit 'Cancel' and it will go back to your normal state. If you have fixed all the issues, you can click "Save and Exit" and it will commit the conflict resolution and if needed, rebase any further commits on that branch on top of your new work. # moving-branches.mdx ## Overview GitButler allows you to move branches between stacks through a simple drag-and-drop interface. This is useful when you want to reorganize your work, change branch dependencies, or consolidate related branches into a single stack. ## Moving a Branch to Another Stack You can move a branch from one stack to another by dragging it to a specific position in the target stack. ### How to Move a Branch 1. **Drag the branch header** - Click and hold on the branch header (the section displaying the branch name) 2. **Drop on target location** - Drag it to the position where you want to insert it in another stack When you hover over a valid drop target during the drag operation, you'll see visual indicators showing where the branch will be placed. ### Drop Zones There are two types of drop zones when moving branches: - **Between branches** - Drop the branch between two existing branches in a stack to insert it at that position - **Top of stack** - Drop at the top of a stack to make the moved branch the first branch The drop zones appear as horizontal lines with visual feedback when you hover over them during a drag operation. ## Creating a New Stack (Tearing Off) You can also separate a branch from its current stack and create a new independent stack for it. This is called "tearing off" a branch. ### How to Tear Off a Branch 1. **Drag the branch header** - Click and hold on the branch header you want to separate 2. **Drop outside the stacks area** - Drag it to the designated dropzone outside of the existing stacks This creates a new standalone stack containing just that branch. ## Important Considerations ### Branch Requirements Not all branches can be moved. A branch must meet these conditions to be movable: - **No conflicts** - The branch must not have any merge conflicts - **Has commits** - The branch must contain at least one commit - **Different target stack** - You can only move branches to different stacks (not within the same stack) ### What Happens During a Move When you move a branch: 1. **Branch is extracted** - The branch and its commits are extracted from the source stack 2. **Rebasing occurs** - The branch is rebased onto the new base in the target stack 3. **References updated** - Git references are updated to reflect the new structure 4. **Stack cleanup** - If the moved branch was the last one in its stack, that stack is deleted ### Pull Request Updates If you're using GitHub integration and the branch has an associated pull request: - **PR descriptions are automatically updated** - The PR base and description are updated to reflect the new stack structure - **Stack PRs are synchronized** - All PRs in both the source and target stacks have their descriptions updated to maintain proper stacking information ## Related Features - [Stacked Branches](/features/branch-management/stacked-branches) - Learn about working with stacked branches - [Branch Lanes](/features/branch-management/branch-lanes) - Understand the branch lane interface - [Parallel Branches](/features/branch-management/virtual-branches) - Core concepts of GitButler's virtual branch system # commits.mdx GitButler gives you a lot of tooling for creating, modifying, squashing, splitting and undoing commits. GitButler has lots of ways to craft the exact commits that you want to end up with. With other Git clients, you tend to have to run somewhat complicated `git rebase -i` type commands to change commit messages, split a commit up or absorb new changes into an existing commit. With GitButler, most of these are simply drag-and-drop operations. Here are some of the cool things you can do very easily with GitButler. ## Creating Commits Once you have changes on a virtual branch and want to commit them, you can hit the "Start a Commit" button in any lane, which gives you an editor to write a summary and optional description for your commit message. If you want AI to use your diff to generate a commit message, you can hit the "Generate message" button. ## AI Commit Message Settings If you want to use AI for generating your commit messages or branch names from time to time, there are quite a few options in your user preferences. You can choose from [OpenAI](https://platform.openai.com/), [Anthropic](https://www.anthropic.com/), [Ollama](https://www.ollama.com/) or [LM Studio](https://lmstudio.ai/) as your engine. For both OpenAI and Anthropic, you can either use your own API key to directly send your request to their servers, or you can proxy via our server (which you need to be logged in for). If you use your own key for OpenAI or Anthropic, you can choose which model you would like us to use. If you don't want to send your diff to another server, you can also use Ollama or LM Studio, which are a local LLM servers. With Ollama, you can run nearly any open source large language model ([Llama 3](https://www.ollama.com/library/llama3), [Phi 3](https://www.ollama.com/library/phi3), [Mistral](https://www.ollama.com/library/mistral), [Gemma](https://www.ollama.com/library/gemma), etc) entirely locally. Note that if you choose to configure a self-hosted Ollama server, you will likely need to add a custom CSP (Content Security Policy) to allow GitButler to connect to it. You can find more information on how to do that in the [Custom Content Security Policy (CSP)](/troubleshooting/custom-csp) section of the documentation. With all of these models, you can also customize the prompt if you want something more specific. In the "Custom AI prompts" section, you can add new prompts and select which one you want to use per project. This is useful for following certain formats or generating messages in other languages, etc. Custom prompts can contain three variables which we will replace with the appropriate values. Those include: - `%{emoji_style}` - Instructs the LLM whether or not to make use of [GitMoji](https://gitmoji.dev) in the title prefix, based on your settings. - `%{brief_style}` - Instructs the LLM to not exceed 1 sentence when generating the commit message. - `%{diff}` - The contents of the diff. - `%{branch_name}` - The name of the current branch. Available in "Commit Message" custom prompt only. ## Absorbing New Work If you have a commit and get some feedback on it or find an issue and wish to amend it, you can very easily absorb changes into existing commits. Simply drag the file into the commit you want to absorb that change into and drop it there. This will both rewrite that commit to include the new changes and also rebase every commit upstream from it automatically. ## Undoing Commits You can easily undo any commit in your stack by expanding the commit and hitting the 'Undo' button. This will rebase all the commits above it and leave whatever work was in that commit as new uncommitted changes. "Undo"ing a commit does not throw it away, it simply makes that work not in a commit anymore. It will not discard the changes. ## Undoing One File in a Commit If you want to undo a single file in a commit, you can expand the commit and click on the file you want to undo. Then hit "Uncommit". This will remove that file from the commit and leave it as uncommitted changes. ## Squashing Commits Squashing two commits into a single combined commit is also very simple. Just drag one commit on top of another one. ## Splitting Commits Splitting commits is slightly more complex. GitButler allows you to create an "empty" commit anywhere and then drag changes into it. Here is an example of creating an empty commit between two other commits, dragging changes from both of them into it and then absorbing new work into it as well. You can also notice that I easily edit the commit message by just hitting the "edit message" button. ## Moving Commits You can also arbitrarily change the order of your commits by dragging and dropping them, which rebases everything to change the order. ## Edit Mode The other way that you can modify a commit is to go into "Edit Mode". When you click on a commit, there is a button that says "Edit commit". If you click this, GitButler will check out that commit by itself into your working directory (automatically stashing everything else temporarily). The screen will go into "Edit mode", indicating that you're in a special state where you're focusing on this one commit. Then you can change whatever you want and when you click "Save and exit", it will amend the commit you were editing and rebase anything on top of it. This is useful for things like getting feedback on a series and being able to go into the appropriate commit, make the changes and continue, as opposed to squashing work. # pushing-and-fetching.mdx GitButler can authenticate with an upstream Git server in several different ways. You can just tell us to use the system Git executable, which you can setup however you want. You can use our built in SSH protocol with your own SSH key (this does not require you to have Git installed), or you can use the default [Git credentials helper](https://git-scm.com/doc/credential-helpers). You can set your preference (and test if it works) in your project's "Git authentication" section: Once that's done, GitButler will be able to automatically fetch upstream work and push new branches to your upstream server. ## Land branches without pull requests If your project does not review changes through pull requests, you can switch a project to the "push to main" workflow. During repository onboarding, check "Push to main / Skip pull requests mode". For an existing project, open the project settings, go to the Git section, and turn on "Land branches directly". With this on, the "Create PR" button on the bottom branch of a stack becomes a "Land" button. Landing integrates the branch straight into the target branch — fast-forwarding when possible, otherwise with a merge commit — and pushes the result. It works without a forge integration. Landing bypasses code review, CI checks, and branch protection, and a branch protected against direct pushes will reject it. The CLI equivalent is [`but land`](/commands/but-land). # rules.mdx Rules are a powerful automation feature in GitButler that automatically assign file changes to specific branches based on conditions you define. Instead of manually dragging changes between lanes, you can set up rules that automatically route changes where they belong. ## Overview When you're working on multiple branches simultaneously, you often know in advance which types of changes should go to which branch. For example, documentation updates might always go to a `docs` branch, while UI changes go to a `feature/ui-redesign` branch. Rules eliminate the manual work of assigning changes by automatically evaluating your uncommitted changes and routing them to the appropriate branch based on filters you define. ## How Rules Work Rules are evaluated whenever files change in your working directory (the `fileSytemChange` trigger). Each rule consists of: 1. **Filters**: Conditions that determine which changes the rule applies to 2. **Action**: Assigns matching changes to a specific branch Multiple rules can exist, and they are evaluated in order. Within a single rule, multiple filters are combined with AND logic - all conditions must match for the rule to apply. ## Creating a Rule To create a rule: 1. Open the **Rules** drawer at the bottom of the GitButler interface 2. Click the **+** button to add a new rule 3. Select the target branch where matching changes should be assigned: - Specify a branch by name - **Leftmost lane**: The leftmost branch in your workspace - **Rightmost lane**: The rightmost branch in your workspace 4. (Optional) Add filters to specify which changes should match 5. Click **Save rule** If you don't add any filters, the rule will match all changes.

Creating a rule that assigns changes to the leftmost lane

## Filter Types Rules support filters that match file paths or changed line content. ### Path Matches Regex Matches file paths using a regular expression pattern. **Example use cases:** - Match all TypeScript files: `.*\.ts$` - Match files in a specific directory: `^src/components/.*` - Match documentation files: `.*\.(md|mdx)$` ### Content Matches Regex Matches the content of changed lines using a regular expression pattern. This filter only looks at added lines (lines that start with `+` in the diff). **Example use cases:** - Match changes containing TODOs: `TODO` - Match changes with specific function calls: `console\.log` - Match changes with certain patterns: `@deprecated`

Creating a rule that assigns changes containing "fix" to a specific branch

## Managing Rules ### Editing Rules To edit an existing rule: 1. Double-click the rule or click the ellipsis menu (...) and select "Edit rule" 2. Modify the branch assignment or filters 3. Click **Save rule** ### Deleting Rules To delete a rule: 1. Click the ellipsis menu (...) on the rule 2. Select "Delete rule" 3. Confirm the deletion ## Understanding Rule Evaluation ### Order Matters Rules are evaluated in the order they appear in the Rules drawer (most recent first). The first matching rule determines where a change is assigned. ### AND Logic Within Rules When a rule has multiple filters, **all filters must match** for the rule to apply. For example, a rule with both "Path Matches Regex: `.*\.ts$`" and "Content Matches Regex: `TODO`" will only match TypeScript files that contain the text "TODO" in their changes. ### OR Logic Across Rules If you want to match changes that meet any of several conditions (OR logic), create separate rules for each condition. ### Interaction with Hunk Dependencies Rules respect hunk dependencies (locks). If a change depends on a commit in a specific branch, it cannot be automatically reassigned by rules, even if it matches a rule's filters. ## Best Practices 1. **Start simple**: Begin with one or two basic path-matching rules before adding complex filters 2. **Order your rules**: Place more specific rules before general catch-all rules 3. **Test your regex**: Make sure your regular expressions match what you intend - it's easy to be too broad or too narrow 4. **Use catch-all rules carefully**: A rule with no filters will match everything, which can interfere with other rules 5. **Consider your workflow**: Rules work best when you have predictable patterns in how your work is organized 6. **Leverage leftmost/rightmost**: Using position-based targeting lets you reorganize lanes without updating rules ## Limitations - Rules can only assign changes to branches that exist in your workspace (applied branches) - Rules currently only support the `assign` action for filesystem changes ## Related Features - [Parallel Branches](/features/branch-management/virtual-branches): Understanding the branch system that rules work with - [Branch Lanes](/features/branch-management/branch-lanes): How lanes are organized and how rules interact with lane positioning # signing-commits.mdx GitHub and GitLab provide a mechanism to verify signed commits using an uploaded public SSH or GPG key. GitButler can be configured automatically sign all your commits. Git provides a mechanism to sign your commits with a GPG key or SSH key. This enables other developers to make sure that you were actually the person who committed it, rather than someone else just setting their email to yours and committing it as if they were you. To make this work, a signature is added to the commit header and then that signature is checked against public key stored somewhere, generally for most people the most useful way to verify these signatures is through GitHub or GitLab. This is what a verified commit looks like on both systems: This means that the server has a public key that you used to sign the commits that is associated to your account and has verified that this user actually signed this commit. In order for this to work, you need to: 1. Tell GitButler to sign your commits 2. Upload your key as a "signing key" to GitHub or GitLab (or elsewhere) ## Telling GitButler to Sign For GitButler to sign commits, you need to setup Git to sign commits, as we do roughly the same thing that Git itself tries to do, and we read and respect most of the same Git config settings. The main difference is that instead of only the `commit.gpgSign` as the flag that tells Git to automatically sign commits, we look for `gitbutler.signCommits` first. Thus, if Git would sign, GitButler will attempt to sign your commits with the normal Git settings as well. But if something goes wrong, `gitbutler.signCommits` will be set to `false` in the repository-local settings to prevent commits from failing generally. Set `gitbutler.signCommits` to `true` in your user-level `~/.gitconfig`, to prevent GitButler from automatically disabling commit signing for that repository. We look to see if we have a signing key in `user.signingkey`. If we have a key, we look for 'ssh' in `gpg.format`, otherwise we use GPG. We will respect `gpg.ssh.program` for ssh if there is a different binary path, and `gpg.program` for GPG. We also identify literal SSH keys in the `user.signingkey` field. The only major thing we don't support yet is `gpg.ssh.defaultKeyCommand` for other ways to get a key other than the `user.signingkey` field. We also don't support the X.509 smime stuff. Here is an example, if you have a public key here `.ssh/id_ed25519.pub` then you can setup signing with something like this: ```bash title="Terminal" $ git config --global user.signingkey "/Users/schacon/.ssh/id_ed25519.pub" $ git config --global gpg.format ssh $ git config --global gitbutler.signCommits true ``` You can also set this up in your project settings, perhaps a little more easily: The nice thing here is that you can also test the settings easily by hitting the "Test Signing" button. There are lots of other ways to set up GPG or SSH commit signing: - 1Password is a very easy way to [SSH sign commits](https://blog.1password.com/git-commit-signing/). - GitHub has a [good guide](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key) on how to setup GPG or SSH keysigning. - Here is a nice simple gist for [GPG signing on Windows](https://gist.github.com/BoGnY/f9b1be6393234537c3e247f33e74094a). Earlier versions of GitButler would only sign with its generated SSH key. Although we've removed that functionality, you can easily set it back up by pointing the signingKey at the generated SSH Key. The key is located in the following locations: ```bash /Users/[username]/Library/Application Support/com.gitbutler.app/keys/ed25519.pub ``` ```bash C:\Users\[username]\AppData\Roaming\com.gitbutler.app\keys\ed25519.pub ``` ```bash ~/.local/share/gitbutler-tauri/keys/ed25519.pub ``` ## Upload Your Signing Key For GitHub or GitLab to verify your signatures, you need to say that the SSH or GPG public key we are using is a valid signing key for your user. ### Adding to GitHub You can click on the "Add key to GitHub" link in the settings page right about the signing toggle, or you can go here (https://github.com/settings/ssh/new) to paste that public key in. Now your signed commits should show up as "Verified". ### Adding to GitLab For GitLab you need to go to "SSH Keys" in your profile: https://gitlab.com/-/profile/keys and click the "Add new key" button. Now paste in the public SSH key you copied from GitButler, name it and make sure the "Usage Type" is either "Signing" or "Authentication and Signing". # stacked-branches.mdx Create a stack of dependent branches to be reviewed and merged in order. ## Overview GitButler allows you to create an ordered stack of branches where each branch depends on (and is based on) the previous one. The application also supports creating the appropriate stacked Pull Requests (when used with a GitHub remote). This is useful when you have multiple changesets that depend on each other but it is desirable to have them reviewed and merged separately (and in sequence). > All of the Pull Request stack orchestration is done locally in the client, which means that your repo content is not shared with a cloud service. ## Use cases Using stacked branches (Pull Requests) can be helpful for shipping smaller changes more frequently. ### Breaking up a larger change into smaller ones Consider a scenario where you are implementing a medium/large feature in your software project. In the course of implementation you end up performing the following sub-tasks: 1. Refactor a part of the codebase to accommodate the new feature 2. Implement an API endpoint supporting the feature 3. Implement the frontend part of the feature consuming the API While the feature is considered complete only when all of the subtasks are implemented, reviewed and merged, in many cases it is considered beneficial to ship each stage of the feature on its own, potentially behind a feature flag. Not only the risk of merge conflicts with colleagues is reduced, but also eventual bugs are easier to track down / revert / fix as compared to a single large change. ### More granular (easier) review process On GitHub at least, code reviews are performed on per-branch basis. While it is possible to view individual commits in a Pull Request, it is not possible to approve and merge a subset of commits from the PR. Utilizing stacked pull requests, means that the sub-tasks of a larger change are in their own PRs. This way it is possible to approve and merge the initial part of a stack (e.g. a refactor) while still iterating on the remaining sub-tasks. ## Comparison to Parallel Branches Stacking and Parallel Branches are similar in that they allow you to separate code changes / commits into different branches. In both cases, the changes are available in your working directory. The main difference is that Parallel Branches are **independent** from one another, while stacked branches **depend** on the ones that come before it. Because of this, the two features are not mutually exclusive but rather complementary. For example a bugfix change that is unrelated to a feature can be put in a separate virtual branch. On the other hand, a change that depends on a previous change can be put in a stacked branch above the one it depends on. In fact GitButler implements stacked branches as Parallel Branches that are split into multiple dependent branches. ## Workflow By default, parallel branches in the app are simply stacks of one. With version `0.14.0` or newer you can create a new dependent branch within a lane by clicking the `+` button above the branch name. > The workflow below assumes a GitHub remote. If you are using a different forge, you can still use this functionality but will need to manually create/update the Pull/Merge Requests 1. Creating a new dependent branch forms a stack within the lane. 2. New commits land in the top branch of the stack. 3. Pushing is done for the stack as a whole. Note: The Pull Requests will be created in a way where each branch points to its parent - see [Automatic branch deletion](#automatic-branch-deletion) 4. Pull requests must be created one at a time starting from the bottom of the stack. 5. The PRs will contain a footer with stack information, and as you add more PRs it will keep all up to date. 6. You can drag changes into commits to amend them (e.g. incorporating review feedback) as well as move and squash commits. 7. If a change in your stack is independent (e.g. an unrelated bugfix) it can be moved to a different virtual branch (or stack). This works for both uncommitted changes and existing commits that you may want to relocate. 8. Review/merge your PRs starting from the bottom up. After a PR/branch from your stack has been merged, it is reflected in the Stack and you should force push to reflect the changes on the remote as well. 9. When all branches of a stack have been merged, the stack is complete. ## GitHub configuration for stacked PRs _TLDR:_ 1. Enable automatic branch deletion [automatic branch deletion](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-the-automatic-deletion-of-branches) on GitHub. 2. If possible, consider using the the "Merge" strategy when merging PRs. #### Automatic branch deletion When reviewing a PR in a stack, it is important to be able to view only the changes in the branch that is being reviewed. Of course, in pure Git terms, a stacked branch will contain all the changes from the branches below it. In order to show only the expected Files changed and Commits for PRs in a stack, each PR is created to target the branch below it in the stack. This is true for all but the bottom branch in the stack, which targets the default branch of the repository as usual. > Every branch in the stack contains the commits from the branches below it. This of course does not mean that a Pull Request should be merged into its parent. When the bottom branch is merged on GitHub, **if** the PR branch is deleted, GitHub will automatically update any PRs that used to target it to target the default branch instead. If the newly merged branch from the bottom of the stack is not deleted, the next PR in line will still target it and there is a risk of accidentally merging it into the now out of date branch. For this reason it is _highly recommended_ to [enable on GitHub](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-the-automatic-deletion-of-branches) the automatic deletion of branches after merging. _NB:_ If you merge the first PR but the branch is not deleted and then merge the second PR, the app can still recover from this, see [Troubleshooting](#troubleshooting). #### Merge strategy The app will support any merge strategy you wish to use - "Merge", "Rebase" or "Squash". However, due to the nature of merge, the GitButler will be able to create a slightly better experience if the "Merge" strategy is used. The reason for this is with merge commits you will be able to merge all the branches in the stack from GitHub without having to force push in the app. ## Troubleshooting Firstly, if you run into any issue with the app (stacking or not), you can always get in touch either on [Discord](https://discord.com/invite/MmFkmaJ42D) or via the in-app feedback icon (we will get back via email). With that said, here are some workarounds for common issues. #### Accidentally merged a stack branch into an already merged branch before it If you merged the bottom Pull Request into main but the branch was _not_ deleted, then the target of the next Pull Request would not be automatically updated. Under these conditions merging that next Pull Request, means it would be merged into the original, now out of date, bottom PR. A mitigation for this is to rename the branch, push and re-create the Pull Request. #### Accidentally merged a branch into a branch before it (not integrated into main/master yet) Merging of branches in a stack should be done from the bottom up. With the GitHub interface, it is possible to incorrectly merge a Pull Request which is in the middle of the stack. In this case it will merged in the parent branch. In order to recover from this situation you can simply force push the branches and then re-create the PR that was incorrectly merged. # virtual-branches.mdx Parllel branches are a powerful feature of GitButler that allow you to work on multiple branches at the same time, committing to them independently and simultaneously. This is a key part of the GitButler experience, allowing you to manage your work in a flexible and efficient way that is not possible with traditional Git tooling. ## Overview With normal Git branching, you can only work on one branch at a time. There is one `HEAD` reference and one index. With parallel branches, you can have multiple branches applied to your working directory at the same time. Each branch is represented as a vertical lane, and you can drag changes between these lanes to commit them independently. Each lane also has its own staging area, so you can stage changes for each branch before deciding to commit them. ## How it works Let's say that you make changes to two different files and `git status` would list two modified files. In GitButler, you can "assign" the change in each file to a different "virtual" branch, then when you commit, it will create a commit that only contains the changes in that file for that branch. One of the nice things with this approach is that since you're starting from changes in a single working directory, you can be sure that all branches that you create from it will merge cleanly, as you're essentially starting from the merge product and extracting branches of work from it. # upstream-integration.mdx Sometimes you work on a branch and someone else pushes to the same upstream branch. Often you won't know this until you try to push and Git tells you something like this: In this scenario, GitButler gives you some nice tooling to help you know when this happens as early as possible and help you deal with it easily. If someone else has pushed to a branch that you're working on, you will see the upstream commits without having to integrate them. You can look at the commits without having to merge them into your branch or rebase your work on top of them. When you decide that you do want to integrate the changes, you have two options - rebase or interactively integrate. ## Rebase the changes If you select "Rebase upstream changes", it will do the equivalent of a `git pull --rebase` which rebases the commits you have locally on top of the ones that the other person has pushed, so you end up with a state like this: Now you can push your commit back upstream without a force push. Easy peasy. ## Interactively integrate the changes However, let's say that you want to do something more complex. Maybe the other implemented the same thing that you did and you want to drop one of them or one of yours, or squash commits together or reorder them. In any of these cases, you can choose the "Interactive integration" option and you get something that looks like this: Here you can reorder commits however you want, you can choose to skip some of them, you can squash some of them down, etc. Just make the commits look however you prefer and then hit the "Integrate changes" button and push your final result back to the server. # debugging.mdx If you are having technical issues with the GitButler client, here are a few things you can do to help us help you. Or help yourself. If you get stuck or need help with anything, hit us up over on Discord, here's [GitButler Discord Server Link](https://discord.gg/MmFkmaJ42D). The first things to try is checking out the frontend related logs in the console by opening the developer tools in GitButler via the "View" -> "Developer Tools" menu option. Next, if you launch GitButler from the command line, you can view the backend logs directly in your terminal. ## Logs Often the most helpful thing is to look at the logs. GitButler is a Tauri app, so the logs are in your OS's [app log directory](https://docs.rs/tauri/1.8.1/tauri/api/path/fn.app_log_dir.html). This should be: ```bash ~/Library/Logs/com.gitbutler.app/ ``` ```bash C:\Users\[username]\AppData\Local\com.gitbutler.app\logs ``` ```bash ~/.config/gitbutler/logs/ [OR] ~/.local/share/gitbutler-tauri/logs/ ``` In this directory, there should be rolling daily logs: ```bash title="Terminal" ❯ cd ~/Library/Logs/com.gitbutler.app ❯ tree -L 1 ├── GitButler.log ├── GitButler.log.2023-09-02 ├── GitButler.log.2023-09-03 ├── GitButler.log.2023-09-04 ├── GitButler.log.2023-09-05 ├── GitButler.log.2023-09-06 ├── GitButler.log.2023-09-07 ├── GitButler.log.2023-09-08 ├── GitButler.log.2023-10-10 ├── GitButler.log.2024-01-30 └── tokio-console ❯ tail GitButler.log.2024-01-30 2024-01-30T13:02:56.319843Z INFO get_public_key: gitbutler-app/src/keys/commands.rs:20: new 2024-01-30T13:02:56.320000Z INFO git_get_global_config: gitbutler-app/src/commands.rs:116: new key="gitbutler.utmostDiscretion" 2024-01-30T13:02:56.320117Z INFO git_get_global_config: gitbutler-app/src/commands.rs:116: new key="gitbutler.signCommits" 2024-01-30T13:02:56.320194Z INFO get_public_key: gitbutler-app/src/keys/commands.rs:20: close time.busy=317µs time.idle=47.0µs 2024-01-30T13:02:56.320224Z INFO git_get_global_config: gitbutler-app/src/commands.rs:116: close time.busy=204µs time.idle=25.3µs key="gitbutler.utmostDiscretion" 2024-01-30T13:02:56.320276Z INFO git_get_global_config: gitbutler-app/src/commands.rs:116: close time.busy=133µs time.idle=35.8µs key="gitbutler.signCommits" 2024-01-30T13:02:56.343467Z INFO menu_item_set_enabled: gitbutler-app/src/menu.rs:11: new menu_item_id="project/settings" enabled=false 2024-01-30T13:02:56.343524Z INFO menu_item_set_enabled: gitbutler-app/src/menu.rs:11: close time.busy=35.7µs time.idle=28.8µs menu_item_id="project/settings" enabled=false ``` ## Data Files GitButler also keeps its own data about each of your projects. The virtual branch metadata, your user config stuff, a log of changes in each file, etc. If you want to inspect what GitButler is doing or debug or reset everything, you can go to our data directory. ```bash ~/Library/Application Support/com.gitbutler.app/ ``` ```bash C:\Users\[username]\AppData\Roaming\com.gitbutler.app ``` ```bash ~/.local/share/gitbutler-tauri/ ``` In this folder there are a bunch of interesting things. ```bash title="Terminal" ❯ cd ~/Library/Application\ Support/com.gitbutler.app ❯ tree . ├── keys │ ├── ed25519 │ └── ed25519.pub ├── projects.json └── settings.json 4 directories, 4 files ``` The `projects.json` file will have a list of your projects metadata: ```bash title="Terminal" ❯ cat projects.json [ { "id": "71218b1b-ee2e-4e0f-8393-54f467cd665b", "title": "gitbutler-blog", "description": null, "path": "/Users/scottchacon/projects/gitbutler-blog", "preferred_key": "generated", "ok_with_force_push": true, "api": null, "gitbutler_data_last_fetch": null, "gitbutler_code_push_state": null, "project_data_last_fetch": { "fetched": { "timestamp": { "secs_since_epoch": 1706619724, "nanos_since_epoch": 202467000 } } } } ] ``` The `settings.json` are some top level preferences you've set. ```bash title="Terminal" ❯ cat settings.json { "appAnalyticsConfirmed": true, "appNonAnonMetricsEnabled": true } ``` Finally, the `keys` directory holds the SSH key that we generate for you in case you don't want to go through creating your own. It's only used if you want to use it to sign commits or use it for authentication. ### Per-project repository data Most repository-specific GitButler state lives next to the repository itself, inside the `.git` directory. By default, GitButler stores that data in `.git/gitbutler` for release builds, nightly builds and developer builds. You can override that location with a Git config change, here for the stable build: ```bash title="Terminal" ❯ git config --local gitbutler.storagePath gitbutler-alt ``` You can also set it globally if you want all repositories opened by GitButler on that machine to use the same base configuration: Setting it to a shared directory is useful if the project locations themselves are on a filesystem that doesn't support Sqlite very well, like a network drive. ```bash title="Terminal" ❯ git config --global gitbutler.storagePath /path/to/gitbutler-projects ``` Setting it to a relative path would force a channel, like Nightly, to reuse the data of stable builds. ```bash title="Terminal" ❯ git config --global gitbutler.nightly.storagePath gitbutler ``` The Git config key depends on the app channel: - Release builds use `gitbutler.storagePath` - Nightly builds use `gitbutler.nightly.storagePath` - Developer builds use `gitbutler.dev.storagePath` There are a couple of constraints on the configured path: - Relative paths are resolved relative to the repository's `.git` directory - If the resolved path stays inside `.git`, it must be under a top-level directory whose name starts with `gitbutler`, case-insensitive - You cannot point it at `.git` itself - If the resolved path ends up outside `.git`, GitButler appends a project-path specific identifier so multiple repositories can share the same base directory safely For example, these are valid: ```bash title="Terminal" ❯ git config --local gitbutler.storagePath gitbutler-alt ❯ git config --local gitbutler.storagePath ../../gitbutler-projects ``` If you use a path outside `.git`, GitButler treats it as a base directory. For example, `../../gitbutler-projects` becomes something like `../../gitbutler-projects/` after resolution. ## Linux ### `glibc` Errors The Linux installation is currently being built in a GitHub Action with Ubuntu 24.04. This means support is limited to those installations using the same or newer version of `glibc`. Unfortunately we cannot build using earlier versions of Ubuntu due to another incompatibility with `libwebkit2gtk-4.1` and Tauri at the moment. If you're using an older distribution, you may be interested in trying our Flatpak package available on Flathub. ### `Failed to create EGL image from DMABuf` If you start GitButler from the command line and see a bunch of these or similar `EGL` / `DMABuf` related messages printed to the console and are only getting a white screen to render, you can try launching GitButler with the following environment variables: - `WEBKIT_DISABLE_DMABUF_RENDERER=1` - `WEBKIT_DISABLE_COMPOSITING_MODE=1` This issue most likely stems from an incompatibility between your version of OpenGL (`mesa`) and `libwebkit2gtk-4.1`. # open-source.mdx GitButler is a [Fair Source software project](https://blog.gitbutler.com/gitbutler-is-now-fair-source/), built on a vast foundation of open source software. We're committed to giving back to the community and supporting the projects that we rely on. As part of our commitment to open source, we are an early member of the [Open Source Pledge](https://osspledge.com/). We have pledged to give back to the open source community by donating $2000 per year per full time software developer that we employ to the OSS projects and maintainers that we depend on. You can read more about our reasoning to join the Open Source Pledge in our announcement blog post and 2024 report: [GitButler Joins the Open Source Pledge](https://blog.gitbutler.com/open-source-pledge-2024). # contact-us.mdx There are a few ways to get in touch with us for feedback, bug reports, feature requests, etc. } href="mailto:hello@gitbutler.com" title="Email" description="The simplest way to get in touch with us is to email us" /> } href="https://discord.com/invite/MmFkmaJ42D" title="Discord" description="We are also available to chat on our Discord server" /> # supporters.mdx Thinking about paying for Beta software? Sounds odd, right? No worries, the main stuff in GitButler stays the same whether you pay or not. But hey, we're all about building a cool gang here. We want to know who really digs our butler. And those early supporters? They're like VIPs to us. ## Perks for Early Supporters - Access to our Early Bird Discord room, for life - Invitations to exclusive Berlin parties, when it's warm here - Care packages of schwag, sent your way - Pricing locked in, no matter how we decide to charge later - First look at any new features as we go - Whatever else we can think of over time Your support helps us grow and make GitButler even better. Join us on this adventure! ## How to Support Us You need to have a GitButler account to support us. If you don't have one, sign up first. } href="https://gitbutler.com/profile" title="GitButler" description="Support GitButler with a monthly contribution" /> Thanks, from the GitButler Crew! # but-absorb.mdx The semantic for finding "the appropriate commit" is as follows: - If a change has a dependency to a particular commit, it will be amended into that particular commit - If a change is staged to a particular lane (branch), it will be amended into a commit there - If there are no commits in this branch, a new commit is created - Changes are amended into the topmost commit of the leftmost (first) lane (branch) Optionally an identifier to an Uncommitted File or a Branch (stack) may be provided. - If an Uncommitted File id is provided, absorb will be performed for just that file - If a Branch (stack) id is provided, absorb will be performed for all changes staged to that stack - If no source is provided, absorb is performed for all uncommitted changes If `--dry-run` is specified, no changes will be made; instead, the absorption plan (what changes would be absorbed by which commits) will be shown. **Usage:** `but absorb [SOURCE] [OPTIONS]` ## Arguments * `` — If the Source is an uncommitted change - the change will be absorbed. If the Source is a stack - anything staged to the stack will be absorbed accordingly. If not provided, everything that is uncommitted will be absorbed ## Options * `--dry-run` — Show the absorption plan without making any changes # but-agent.mdx Runs a guided setup wizard for installing GitButler agent skills and writing workflow steering instructions into supported agent instruction files. ## Examples Start the interactive setup wizard (`but agent setup` is equivalent): ```text but agent ``` Print the default generated steering text: ```text but agent setup --print ``` **Usage:** `but agent ` ## Subcommands ### `but agent setup` Configure GitButler skills and workflow instructions for coding agents. Starts an interactive wizard that generates GitButler workflow steering, installs selected agent skills, and optionally writes the generated steering into agent instruction files. ## Examples Start the setup wizard: ```text but agent setup ``` Print the default steering text without modifying files: ```text but agent setup --print ``` **Usage:** `but agent setup [OPTIONS]` **Options:** * `--print` — Print the default generated steering text without prompting or modifying files # but-alias.mdx Aliases allow you to create shortcuts for commonly used commands. They are stored in git config under the `but.alias.*` namespace. ## Examples List all configured aliases: ```text but alias ``` Create a new alias: ```text but alias add st status but alias add stv "status --verbose" ``` Remove an alias: ```text but alias remove st ``` **Usage:** `but alias ` ## Subcommands ### `but alias list` List all configured aliases (default) **Usage:** `but alias list` ### `but alias add` Add a new alias Creates a new alias that expands to the given command. Examples but alias add st status but alias add stv "status --verbose" but alias add co "commit --only" **Usage:** `but alias add [OPTIONS]` **Arguments:** * `` — The name of the alias to create (required) * `` — The command and arguments that the alias should expand to If the value contains spaces or special characters, quote it: "status --verbose" (required) **Options:** * `-g`, `--global` — Store the alias globally (in ~/.gitconfig) instead of locally ### `but alias remove` Remove an existing alias Examples but alias remove st but alias remove co --global **Usage:** `but alias remove [OPTIONS]` **Arguments:** * `` — The name of the alias to remove (required) **Options:** * `-g`, `--global` — Remove from global config (in ~/.gitconfig) instead of local # but-amend.mdx Use `but amend --changes [,...]`. **Usage:** `but amend [OPTIONS]` ## Arguments * `` — Commit ID to amend into. In the legacy two-positional form, this can be the source ID. (required) ## Options * `-p`, `--changes` `` — Uncommitted file or hunk CLI IDs to amend into the commit. Can be specified multiple times or as comma-separated values. # but-apply.mdx If you want to apply an unapplied branch to your workspace so you can work on it, you can run `but apply `. This will apply the changes in that branch into your working directory as a parallel applied branch. ## Examples Apply by branch name: ```text but apply my-feature-branch ``` **Usage:** `but apply ` ## Arguments * `` — Name of the branch to apply (required) # but-clean.mdx A branch is considered empty if it has no local commits, no assigned changes, and (by default) no upstream-only commits. The entire operation is recorded as a single oplog entry, so it can be undone with `but undo`. ## Examples Remove all empty branches: ```text but clean ``` Preview which branches would be removed: ```text but clean --dry-run ``` Pull latest changes first, then clean: ```text but clean --pull ``` Also remove branches that only have upstream commits: ```text but clean --include-upstream ``` **Usage:** `but clean [OPTIONS]` ## Options * `--dry-run` — Preview which branches would be removed without actually deleting them * `--pull` — Pull latest changes from the remote before cleaning * `--include-upstream` — Also remove branches that have upstream-only commits but no local commits or changes # but-branch.mdx This includes creating, deleting, listing, and showing details about branches. By default without a subcommand, it will list the branches. To apply or unapply branches, use `but apply` and `but unapply`. **Usage:** `but branch ` ## Subcommands ### `but branch new` Creates a new branch in the workspace If no branch name is provided, a new parallel branch with a generated name will be created. You can also specify an anchor point using the `--anchor` option, which can be either a commit ID or an existing branch name to create the new branch from. This allows you to create stacked branches. **Usage:** `but branch new [BRANCH_NAME] [OPTIONS]` **Arguments:** * `` — Name of the new branch **Options:** * `-a`, `--anchor` `` — Anchor point - either a commit ID or branch name to create the new branch from ### `but branch delete` Deletes a branch from the workspace This will remove the branch and all its commits from the workspace. If the branch has unpushed commits, you will be prompted for confirmation unless the `--force` flag is used. **Usage:** `but branch delete ` **Arguments:** * `` — Name of the branch to delete (required) ### `but branch list` List the branches in the repository By default, shows the active branch and the 20 most recently updated branches. You can use the `--all` flag to show all branches, `--local` to show only local branches, or `--remote` to show only remote branches. You can also filter branch names by specifying a substring, such as `but branch list feature` to show only branches with "feature" in the name. If you want to check for review status, you can add `--review` to fetch and display pull request or merge request information for each branch. This will make the command slower as it needs to query the forge. By default, the command checks if each branch merges cleanly into the *upstream base target branch* (not your workspace). You can disable this check with `--no-check` to make the command faster. By default it also calculates the number of commits each branch is ahead of the base branch. You can disable this with `--no-ahead` to make the command faster. **Usage:** `but branch list [FILTER] [OPTIONS]` **Arguments:** * `` — Filter branches by name (case-insensitive substring match) **Options:** * `-l`, `--local` — Show only local branches * `-r`, `--remote` — Show only remote branches * `-a`, `--all` — Show all branches (not just active + 20 most recent) * `--no-ahead` — Don't calculate and show number of commits ahead of base (faster) * `--review` — Fetch and display review information (PRs, MRs, etc.) * `--no-check` — Don't check if each branch merges cleanly into upstream * `--empty` — Include branches with no commits on them (hidden by default) ### `but branch show` Show commits ahead of base for a specific branch This shows the list of commits that are on the specified branch but not yet integrated into the base target branch. You can also choose to fetch and display review information, show files modified in each commit with line counts, generate an AI summary of the branch changes, and check if the branch merges cleanly into upstream. **Usage:** `but branch show [OPTIONS]` **Arguments:** * `` — CLI ID or name of the branch to show (required) **Options:** * `-r`, `--review` — Fetch and display review information * `-f`, `--files` — Show files modified in each commit with line counts * `--ai` — Generate AI summary of the branch changes * `--check` — Check if the branch merges cleanly into upstream and identify conflicting commits ### `but branch update` Update your local branch with the content of its remote counterpart. This allows you to resolve the divergence between your local branch and its tracked remote in different ways. **Usage:** `but branch update [OPTIONS]` **Arguments:** * `` — Name of the local branch to integrate (required) **Options:** * `-s`, `--strategy` `` — Strategy to use for the integration. If no strategy is specified, we default to pull-rebase (default: `pull-rebase`) * `--dry-run` — Preview the resulting branch state without persisting changes * `-v`, `--verbose` — Show additional dry-run details like the current divergence * `-i`, `--interactive` — Open the generated integration script in an editor # but-commit.mdx The `but commit` command allows you to create a new commit on a specified branch (stack) with the current uncommitted changes. If there is only one branch applied, it will commit to that branch by default. If there are multiple branches applied, you must specify which branch to commit to, or if in interactive mode, you will be prompted to select one. By default, all uncommitted changes and all changes already staged to that branch will be included in the commit. If you only want to commit the changes that are already staged to that branch, you can use the `--only` flag. It will not commit changes staged to other branches. Use `but commit empty --before ` or `but commit empty --after ` to insert a blank commit. This is useful for creating a placeholder commit that you can amend changes into later using `but rub` or `but absorb`. **Usage:** `but commit [BRANCH] [OPTIONS]` ## Subcommands ### `but commit empty` Insert a blank commit before or after the specified commit. This is useful for creating a placeholder commit that you can then amend changes into later using `but rub` or `but absorb`. You can provide a message with `-m` or modify it later using `but reword`. This allows for a more Jujutsu style workflow where you create commits first and then fill them in as you work. Create an empty commit and then rub or absorb changes into it whenever you prefer. ## Examples Insert at the top of the first branch (no arguments): ```text but commit empty ``` Insert before a commit: ```text but commit empty ab ``` Explicitly insert before a commit: ```text but commit empty --before ab ``` Insert after a commit (at the top of the stack if target is a branch): ```text but commit empty --after ab ``` **Usage:** `but commit empty [TARGET] [OPTIONS]` **Arguments:** * `` — The target commit or branch to insert relative to. If a target is provided without --before or --after, defaults to --before behavior. If no arguments are provided at all, inserts at the top of the first branch. **Options:** * `--before` `` — Insert the blank commit before this commit or branch * `--after` `` — Insert the blank commit after this commit or branch * `-m`, `--message` `` — Commit message for the inserted blank commit ## Options * `-m`, `--message` `` — Commit message * `--message-file` `` — Read commit message from file * `-c`, `--create` — Whether to create a new branch for this commit. If the branch name given matches an existing branch, that branch will be used instead. If no branch name is given, a new branch with a generated name will be created * `--before` `` — Insert the commit before this commit or branch * `--after` `` — Insert the commit after this commit or branch * `-o`, `--only` — Only commit staged files, not unstaged files * `-a`, `--all` — No-op compatibility flag for git commit -a * `-n`, `--no-hooks` — Bypass pre-commit hooks * `-i`, `--ai` `` — Generate commit message using AI with optional user summary. Use --ai by itself or --ai="your instructions" (equals sign required for value) * `-p`, `--changes` `` — Uncommitted file or hunk CLI IDs to include in the commit. Can be specified multiple times or as comma-separated values. If not specified, all uncommitted changes (or changes staged to the target branch) are committed * `--diff` — Always show diff inside the editor. By default the diff will be shown unless it's large. The diff will always be shown if --diff is passed, regardless of the size of the diff. (default: `false`) * `--no-diff` — Never show the diff inside the editor (default: `false`) # but-config.mdx Without a subcommand, displays an overview of important settings including user information, target branch, forge configuration, and AI setup. ## Examples View configuration overview: ```text but config ``` View/set user configuration: ```text but config user but config user set name "John Doe" but config user set email john@example.com ``` View/set forge configuration: ```text but config forge ``` View/set target branch: ```text but config target ``` View/set metrics: ```text but config metrics ``` **Usage:** `but config ` ## Subcommands ### `but config user` View and configure user information (name, email, editor). Without arguments, displays current user.name, user.email, and core.editor. Use subcommands to set or unset configuration values. Examples View user configuration: but config user Set user name (locally): but config user set name "John Doe" Set user email globally: but config user set --global email john@example.com Unset a local value: but config user unset name **Usage:** `but config user` ### `but config forge` View and manage forge configuration. Shows configured forge accounts (GitHub, GitLab, etc.) and authentication status. Use subcommands to authenticate or forget accounts. Examples View configured forge accounts: but config forge Authenticate with a forge: but config forge auth List authenticated accounts: but config forge list-users Forget an account: but config forge forget username **Usage:** `but config forge` ### `but config target` View or set the target branch. Without arguments, displays the current target branch. With a branch name, sets the target branch. Examples View current target: but config target Set target branch: but config target origin/main **Usage:** `but config target [BRANCH]` **Arguments:** * `` — New target branch to set (e.g., "origin/main") ### `but config metrics` View or set metrics collection. GitButler uses metrics to help us know what is useful and improve it. Privacy policy: https://gitbutler.com/privacy Without arguments, displays the current setting. Examples View metrics configuration: but config metrics Enable metrics: but config metrics enable Disable metrics: but config metrics disable **Usage:** `but config metrics [STATUS]` **Arguments:** * `` — Whether metrics are enabled ### `but config ui` View and configure UI preferences. Without arguments, displays current UI settings. Use subcommands to set or unset configuration values. Examples View UI configuration: but config ui Enable TUI mode for diff by default: but config ui set tui true Disable TUI mode: but config ui set tui false **Usage:** `but config ui` ### `but config ai` View and configure AI provider settings. Without subcommands, this starts an interactive setup flow. Use provider subcommands for non-interactive configuration. Examples Interactive setup: but config ai View current AI configuration: but config ai show Configure OpenAI non-interactively: but config ai openai --key-option bring-your-own --api-key-env OPENAI_API_KEY --model gpt-5.4-nano Configure Ollama locally: but config ai --local ollama --endpoint localhost:11434 --model llama3.1 **Usage:** `but config ai [OPTIONS]` **Options:** * `--local` — Configure local repository git config instead of global user config * `--global` — Configure global user git config # but-diff.mdx Without any arguments, it shows the diff of all uncommitted changes. Optionally, a CLI ID argument can be provided, which chan show the diff specific to - an uncommitted file - a branch - an entire stack - a commit - a file change within a commit **Usage:** `but diff [TARGET] [OPTIONS]` ## Arguments * `` — The CLI ID of the entity to show the diff for ## Options * `--tui` — Open an interactive TUI diff viewer * `--no-tui` — Disable the interactive TUI diff viewer (overrides but.ui.tui config) # but-discard.mdx This command permanently discards changes to files, restoring them to their state in the HEAD commit. Use this to undo unwanted modifications. The ID parameter should be a file ID as shown in `but status`. You can discard a whole file or specific hunks within a file. ## Examples Discard all changes to a file: ```text but discard a1 ``` **Usage:** `but discard ` ## Arguments * `` — The ID of the file or hunk to discard (as shown in but status) (required) # but-gui.mdx Running `but gui` will launch the GitButler graphical user interface in the current directory's GitButler project. This provides a visual way to manage branches, commits, and uncommitted changes, complementing the command-line interface. You can also just run `but .` as a shorthand to open the GUI. **Usage:** `but gui [PATH] [OPTIONS]` ## Arguments * `` — Path to the directory to open as a GitButler project. Defaults to the current directory ## Options * `-n`, `--new-window` — Open the project in a new application window (default: `false`) # but-completions.mdx ## Examples ```bash # bash, put in .bashrc or .bash_profile depending on system setup eval "$(but completions bash)" # zsh, put in .zshrc eval "$(but completions zsh)" # fish, put in config.fish but completions fish | source ``` **Usage:** `but completions [SHELL]` ## Arguments * `` — The shell to generate completions for, or the one extracted from the SHELL environment variable # but-move.mdx Commit moves: - By default, commits are moved to be before (below) the target. - Use `--after` to move the commit after (above) the target instead. - Use comma-separated commit IDs to move multiple commits together. - When moving to a branch, the commit is placed at the top of that branch's stack. Branch moves: - Move one branch on top of another to stack them. - Move a branch to `zz` to tear it off (unstack it). ## Examples Move a commit before another commit: ```text but move abc123 def456 ``` Move multiple commits before another commit: ```text but move abc123,789abc def456 ``` Move a commit after another commit: ```text but move abc123 def456 --after ``` Move multiple commits after another commit: ```text but move abc123,789abc def456 --after ``` Move a commit to a different branch (places at top): ```text but move abc123 my-feature-branch ``` Move multiple commits to a different branch (places at top): ```text but move abc123,789abc my-feature-branch ``` Stack one branch on top of another: ```text but move feature/frontend feature/backend ``` Tear off (unstack) a branch: ```text but move feature/frontend zz ``` **Usage:** `but move [OPTIONS]` ## Arguments * `` — Commit/branch identifier to move. Use comma-separated commit IDs for multi-commit moves (required) * `` — Target commit/branch identifier, or zz to unstack a branch (required) ## Options * `-a`, `--after` — Move the commit after (above) the target instead of before (below). Only valid for commit-to-commit moves # but-land.mdx Lands the branch onto the configured target (for example `origin/master`) without going through a pull request — the "just push to the target" workflow. By default the target is fast-forwarded to the branch tip when possible (no merge commit); otherwise a merge commit is created. For a local (`gb-local`) target the refs are moved locally; otherwise the result is pushed to the remote. After landing, the remaining applied branches are reconciled onto the moved target, just like `but pull`. Requires an active GitButler workspace. Updating the target is direct and not easily reversible, so a confirmation is required (use `--yes` to skip it in scripts). When NOT to use this: if your project lands changes through pull requests / code review, use `but push` and open a PR (`but pr new`) instead — `but land` deliberately bypasses that process. On a real remote, a branch protected against direct pushes will reject the land. ## Examples Land a branch by its CLI ID: ```text but land bu ``` Land a branch by name, forcing a merge commit: ```text but land my-feature-branch --no-ff ``` **Usage:** `but land [OPTIONS]` ## Arguments * `` — Branch ID or name to land onto the target branch (required) ## Options * `--yes` — Skip the confirmation prompt * `--no-ff` — Always create a merge commit, even when the branch can be fast-forwarded # but-pick.mdx This command allows you to pick individual commits from unapplied branches and apply them to your current workspace branches. The source can be: - A commit SHA (full or short) - A CLI ID (e.g., "c5" from `but status`) - An unapplied branch name (shows interactive commit selection) If no target branch is specified: - In interactive mode: prompts you to select a target branch - If only one branch exists: automatically uses that branch - In non-interactive mode: fails with an error ## Examples Pick a specific commit into a branch: ```text but pick abc1234 my-feature ``` Pick using a CLI ID: ```text but pick c5 my-feature ``` Interactively select commits from an unapplied branch: ```text but pick feature-branch ``` **Usage:** `but pick [TARGET_BRANCH]` ## Arguments * `` — The commit SHA, CLI ID, or unapplied branch name to cherry-pick from (required) * `` — The target virtual branch to apply the commit(s) to # but-oplog.mdx Displays a list of past operations performed in the repository, including their timestamps and descriptions. This allows you to restore to any previous point in the history of the project. All state is preserved in operations, including uncommitted changes. You can use `but oplog restore ` to restore to a specific state. By default, shows the last 20 oplog entries (same as `but oplog list`). **Usage:** `but oplog ` ## Subcommands ### `but oplog list` List operation history. Displays a list of past operations performed in the repository, including their timestamps and descriptions. This allows you to restore to any previous point in the history of the project. All state is preserved in operations, including uncommitted changes. You can use `but oplog restore ` to restore to a specific state. **Usage:** `but oplog list [OPTIONS]` **Options:** * `--since` `` — Start from this oplog SHA instead of the head * `-s`, `--snapshot` — Show only on-demand snapshot entries ### `but oplog snapshot` Create an on-demand snapshot with optional message. This allows you to create a named snapshot of the current state, which can be helpful to always be able to return to a known good state. You can provide an optional message to describe the snapshot. **Usage:** `but oplog snapshot [OPTIONS]` **Options:** * `-m`, `--message` `` — Message to include with the snapshot ### `but oplog restore` Restore to a specific oplog snapshot. This command allows you to revert the repository to a previous state captured in an oplog snapshot. You need to provide the SHA of the oplog entry you want to restore to, which you can find by running `but oplog` or `but oplog list`. **Usage:** `but oplog restore ` **Arguments:** * `` — Oplog SHA to restore to (required) # but-push.mdx `but push` will update the remote with the latest commits from the applied branch(es). Without a branch ID: - Interactive mode: Lists all branches with unpushed commits and prompts for selection - Non-interactive mode: Automatically pushes all branches with unpushed commits With a branch ID: - `but push bu` - push the branch with CLI ID "bu" - `but push feature-branch` - push the branch named "feature-branch" **Usage:** `but push [BRANCH_ID] [OPTIONS]` ## Arguments * `` — Branch name or CLI ID to push. If not specified, will list all branches and prompt for selection in interactive mode ## Options * `-f`, `--with-force` — Force push even if it's not fast-forward (default: `true`) * `-s`, `--skip-force-push-protection` — Skip force push protection checks * `--no-hooks` — Bypass pre-push hooks * `-d`, `--dry-run` — Show what would be pushed without actually pushing # but-redo.mdx **Usage:** `but redo` # but-pull.mdx This fetches the latest changes from the remote and rebases all applied branches on top of the updated target branch. You should run this regularly to keep your branches up to date with the latest changes from the main development line. You can run `but pull --check` first to see if your branches can be cleanly merged into the target branch before running the update. **Usage:** `but pull [OPTIONS]` ## Options * `-c`, `--check` — Only check the status without updating (equivalent to the old but base check) # but-reword.mdx You can easily change the commit message of any of your commits by running `but reword ` and providing a new message in the editor. This will recreate the commit with the new message and then rebase any dependent commits on top of it. You can also use `but reword ` to rename the branch. **Usage:** `but reword [OPTIONS]` ## Arguments * `` — Commit ID to edit the message for, or branch ID to rename (required) ## Options * `-m`, `--message` `` — The new commit message or branch name. If not provided, opens an editor * `-f`, `--fix-formatting` — Format the existing commit message to 72-char line wrapping without opening an editor * `--diff` — Always show diff inside the editor. By default the diff will be shown unless it's large. The diff will always be shown if --diff is passed, regardless of the size of the diff. (default: `false`) * `--no-diff` — Never show the diff inside the editor (default: `false`) # but-rub.mdx The `rub` command is a simple verb that helps you do a number of editing operations by doing combinations of two things. For example, you can "rub" a file onto a branch to stage that file to the branch. You can also "rub" a commit onto another commit to squash them together. You can rub a commit onto a branch to move that commit. You can rub a file from one commit to another. ## Operations Matrix Each cell shows what happens when you rub SOURCE → TARGET: ```text SOURCE ↓ / TARGET → │ zz (uncommitted) │ Commit │ Branch │ Stack ─────────────────────┼─────────────────┼────────────┼─────────────┼──────────── File/Hunk │ Unstage │ Amend │ Stage │ Stage Commit │ Undo │ Squash │ Move │ - Branch (all changes) │ Unstage all │ Amend all │ Reassign │ Reassign Stack (all changes) │ Unstage all │ - │ Reassign │ Reassign Uncommitted (zz) │ - │ Amend all │ Stage all │ Stage all File-in-Commit │ Uncommit │ Move │ Uncommit to │ - ``` Legend: - `zz` is a special target meaning "uncommitted" (no branch) - `-` means the operation is not supported - "all changes" / "all" refers to all uncommitted changes from that source ## Examples Squashing two commits into one (combining the commit messages): ```text but rub 3868155 abe3f53f ``` Amending a commit with the contents of a modified file: ```text but rub README.md abe3f53f ``` Moving a commit from one branch to another: ```text but rub 3868155 feature-branch ``` **Usage:** `but rub ` ## Arguments * `` — The source entity to combine (required) * `` — The target entity to combine with the source (required) # but-resolve.mdx When a commit is in a conflicted state (marked with conflicts during rebase), use this command to enter resolution mode, resolve the conflicts, and finalize. ## Workflow 1. Enter resolution mode: `but resolve ` 2. Resolve conflicts in your editor (remove conflict markers) 3. Check remaining conflicts: `but resolve status` 4. Finalize resolution: `but resolve finish` Or cancel: `but resolve cancel` Alternatively, resolve with AI in one step: `but resolve --ai`, or `but resolve --ai` to resolve all conflicted commits, oldest first. When in resolution mode, `but status` will also show that you're resolving conflicts. **Usage:** `but resolve [COMMIT] [OPTIONS]` ## Subcommands ### `but resolve status` Show the status of conflict resolution, listing remaining conflicted files **Usage:** `but resolve status` ### `but resolve finish` Finalize conflict resolution and return to workspace mode. This commits the resolved changes, rebases any commits on top of the resolved commit, and returns to the normal workspace. **Usage:** `but resolve finish` ### `but resolve cancel` Cancel conflict resolution and return to workspace mode. This discards all changes made during resolution and restores the workspace to its pre-resolution state. **Usage:** `but resolve cancel [OPTIONS]` **Options:** * `-f`, `--force` — Forcibly remove any changes made ## Options * `--ai` — Resolve the conflicts with the configured AI model and apply the result. With a commit ID this resolves only that commit; without one it resolves all conflicted commits in the workspace, oldest first. Undo the result with but undo. # but-setup.mdx This command will: - Add the repository to the global GitButler project registry - Switch to the gitbutler/workspace branch (if not already on it) - Set up a default target branch (the remote's HEAD) - Add a gb-local remote if no push remote exists If you have an existing Git repository and want to start using GitButler with it, you can run this command to set up the necessary configuration and data structures. ## Examples Initialize a new git repository and set up GitButler: ```text but setup --init ``` **Usage:** `but setup [OPTIONS]` ## Options * `--init` — Initialize a new git repository with an empty commit if one doesn't exist. This is useful when running in non-interactive environments (like CI/CD) where you want to ensure a git repository exists before setting up GitButler. # but-show.mdx When given a commit ID, displays the full commit message, author information, committer information (if different from author), and the list of files modified. When given a branch name, displays the branch name and a list of all commits on that branch. Use --verbose to show full commit messages and files changed. ## Examples Show commit details by short commit ID: ```text but show a1b2c3d ``` Show commit details by CLI ID: ```text but show c5 ``` Show branch commits by branch name: ```text but show my-feature-branch ``` Show branch with full commit details: ```text but show my-feature-branch --verbose ``` **Usage:** `but show [OPTIONS]` ## Arguments * `` — The commit ID (short or full SHA), branch name, or CLI ID to show details for (required) ## Options * `-v`, `--verbose` — Show full commit messages and files changed for each commit # but-skill.mdx Skills provide enhanced AI capabilities for working with GitButler through Claude Code, Codex, and other AI assistants. Use `but skill install` to install the GitButler skill files. By default, it prompts for scope (repository or global home directory) and then format. When run outside a git repository, local scope is unavailable and the default install location is global (home directory). You can still install to a custom location with `--path` using an absolute or `~` path. ## Examples Install interactively (prompts for scope and format): ```text but skill install ``` Install the skill globally: ```text but skill install --global ``` **Usage:** `but skill ` ## Subcommands ### `but skill install` Install the GitButler CLI skill files for Coding agents By default, the command prompts you to choose installation scope first (current repository or global home directory), then prompts you to select a skill folder format (Agent Skills / .agents, Claude Code, OpenCode, Codex, GitHub Copilot, Cursor, Windsurf) unless you specify a custom path with --path. When run outside a git repository, local scope is unavailable and the default install location is global (home directory). You can still install to a custom location with `--path` using an absolute or `~` path. Use --global to install the skill in a global location instead of the current repository. In non-interactive mode, specify --path or --detect. ## Examples Install interactively (prompts for scope and format): ```text but skill install ``` Install globally (prompts for format): ```text but skill install --global ``` Install to a custom path: ```text but skill install --path .agents/skills/gitbutler ``` Auto-detect installation location (update existing installation): ```text but skill install --detect ``` **Usage:** `but skill install [OPTIONS]` **Options:** * `-g`, `--global` — Install the skill globally instead of in the current repository * `-p`, `--path` `` — Custom path where to install the skill (relative to repository root or absolute). Outside a repository, relative paths require --global * `-d`, `--detect` — Automatically detect where to install by finding existing installation ### `but skill check` Check if installed GitButler skills are up to date with the CLI version Scans for installed skill files and compares their version with the current CLI version. By default, checks both local (repository) and global installations. ## Examples Check all installed skills: ```text but skill check ``` Check and automatically update outdated skills: ```text but skill check --update ``` Check only global installations: ```text but skill check --global ``` **Usage:** `but skill check [OPTIONS]` **Options:** * `-g`, `--global` — Only check global installations (in home directory) * `-l`, `--local` — Only check local installations (in current repository) * `-u`, `--update` — Automatically update any outdated skills found # but-squash.mdx Can be invoked in three ways: 1. Using commit identifiers: `but squash ` or `but squash ...` - Squashes all commits except the last into the last commit 2. Using a commit range: `but squash ..` - Squashes all commits in the range into the last commit in the range 3. Using a branch name: `but squash ` - Squashes all commits in the branch into the bottom-most commit **Usage:** `but squash [COMMITS] [OPTIONS]` ## Arguments * `` — Commit identifiers, a range (commit1..commit2), or a branch name ## Options * `-d`, `--drop-message` — Drop source commit messages and keep only the target commit's message * `-m`, `--message` `` — Provide a new commit message for the resulting commit * `-i`, `--ai` `` — Generate commit message using AI with optional user summary or instructions. Use --ai by itself or --ai="your instructions" (equals sign required for value) # but-stage.mdx Without arguments, opens an interactive TUI for selecting files and hunks to stage. With arguments, stages the specified file or hunk to the given branch. Usage: ```text but stage # interactive TUI selector but stage --branch # interactive, specific branch but stage # direct staging ``` For the interactive hunk picker workflow, see [https://docs.gitbutler.com/gitbutler-tui#stage-hunks-interactively](https://docs.gitbutler.com/gitbutler-tui#stage-hunks-interactively) **Usage:** `but stage [FILE_OR_HUNK] [BRANCH_POS] [OPTIONS]` ## Arguments * `` — File or hunk ID to stage * `` — Branch to stage to (positional) ## Options * `-b`, `--branch` `` — Branch to stage to (for interactive mode) # but-status.mdx This shows unstaged files, files staged to stacks, all applied branches (stacked or parallel), commits on each of those branches, upstream commits that are unintegrated, commit status (pushed or local), and base branch information. ## Examples Normal usage: ```text but status ``` Shorthand with listing files modified ```text but status -f ``` **Usage:** `but status [OPTIONS]` ## Options * `-f` — Determines whether the committed files should be shown as well (default: `false`) * `-v`, `--verbose` — Show verbose output with commit author and timestamp (default: `false`) * `-r`, `--refresh-prs` — Forces a sync of pull requests from the forge before showing status (default: `false`) * `-u`, `--upstream` — Show detailed list of upstream commits that haven't been integrated yet (default: `false`) * `--no-hint` — Disable hints about available commands at the end of output (default: `false`) # but-teardown.mdx This command: - Creates an oplog snapshot of the current state - Finds the first active branch and checks it out - Alternatively, use `--checkout-to ` to override this default - Cherry-picks any dangling commits from gitbutler/workspace - Provides instructions on how to return to GitButler mode This is useful when you want to temporarily or permanently leave GitButler management and work with standard Git commands. ## Examples Exit GitButler mode: ```text but teardown ``` ```text but teardown --checkout-to my-feature-branch ``` **Usage:** `but teardown [OPTIONS]` ## Options * `-c`, `--checkout-to` `` — Explicit override for which local branch to checkout to # but-unapply.mdx If you want to unapply an applied branch from your workspace (effectively stashing it) so you can work on other branches, you can run `but unapply `. This will remove the changes in that branch from your working directory and you can re-apply it later when needed. You will then see the branch as unapplied in `but branch list`. The identifier can be: - A CLI ID pointing to a stack or branch (e.g., "bu" from `but status`) - A branch name If a branch name (or an identifier pointing to a branch) is provided, the entire stack containing that branch will be unapplied. ## Examples Unapply by branch name: ```text but unapply my-feature-branch ``` Unapply by CLI ID: ```text but unapply bu ``` **Usage:** `but unapply ` ## Arguments * `` — CLI ID or name of the branch/stack to unapply (required) # but-uncommit.mdx Use `--discard` to remove the selected committed changes entirely instead. Wrapper for `but rub zz`. **Usage:** `but uncommit [OPTIONS]` ## Arguments * `` — Commit ID or file-in-commit ID to uncommit (required) ## Options * `-d`, `--discard` — Discard the selected committed changes instead of moving them to uncommitted * `--diff` — Show the resulting uncommitted diff after uncommitting # but-pr.mdx If you are authenticated with a forge using but config forge auth, you can use the but pr or but mr commands to create pull requests (or merge requests) on the remote repository for your branches. Running but pr without a subcommand defaults to but pr new, which will prompt you to select a branch to create a PR for. **Usage:** `but pr [OPTIONS]` ## Subcommands ### `but pr new` Create a new review for a branch. If no branch is specified, you will be prompted to select one. If there is only one branch without a review, you will be asked to confirm **Usage:** `but pr new [BRANCH] [OPTIONS]` **Arguments:** * `` — The branch to create a review for **Options:** * `-m`, `--message` `` — review title and description. The first line is the title, the rest is the description * `-F`, `--file` `` — Read review title and description from file. The first line is the title, the rest is the description * `-f`, `--with-force` — Force push even if it's not fast-forward (defaults to true) (default: `true`) * `-s`, `--skip-force-push-protection` — Skip force push protection checks * `--no-hooks` — Bypass pre-push hooks * `-t`, `--default` — Use the default content for the review title and description, skipping any prompts. If the branch contains only a single commit, the commit message will be used (default: `false`) * `-d`, `--draft` — Whether to create reviews as a draft (default: `false`) ### `but pr auto-merge` Enable or disable the automatic merging of a review or reviews. If no reviews are specified, you will be prompted to select one or multiple of the review associated with branches in your workspace **Usage:** `but pr auto-merge [SELECTOR] [OPTIONS]` **Arguments:** * `` — The target of this operation. This can be one or multiple (comma-separated): - Branch names, - Branch IDs, - Stack IDs (in which case, all the reviews associated with the stacked branches are selected), - Associated review IDs (i.e. PR numeric IDs or MR numeric IDs, without the symbol). **Options:** * `-d`, `--off` — Whether to disable the automatic merging of the review(s) (default: `false`) ### `but pr set-draft` Set an existing review (or set of reviews) as draft. If no reviews are specified, you will be prompted to select one or multiple of the review associated with branches in your workspace **Usage:** `but pr set-draft [SELECTOR]` **Arguments:** * `` — The target of this operation. This can be one or multiple (comma-separated): - Branch names, - Branch IDs, - Stack IDs (in which case, all the reviews associated with the stacked branches are selected), - Associated review IDs (i.e. PR numeric IDs or MR numeric IDs, without the symbol). ### `but pr set-ready` Set an existing review (or set of reviews) as ready-to-review. If no reviews are specified, you will be prompted to select one or multiple of the review associated with branches in your workspace **Usage:** `but pr set-ready [SELECTOR]` **Arguments:** * `` — The target of this operation. This can be one or multiple (comma-separated): - Branch names, - Branch IDs, - Stack IDs (in which case, all the reviews associated with the stacked branches are selected), - Associated review IDs (i.e. PR numeric IDs or MR numeric IDs, without the symbol). ### `but pr template` Configure the template to use for review descriptions. This will list all available templates found in the repository and allow you to select one **Usage:** `but pr template [TEMPLATE_PATH]` **Arguments:** * `` — Path to the review template file within the repository ## Options * `-d`, `--draft` — Whether to create reviews as a draft (default: `false`) # but-undo.mdx **Usage:** `but undo` # commands-overview.mdx ## Command reference ### Basics - [setup](./but-setup): Set up a Git repository to be managed by GitButler - [teardown](./but-teardown): Exit GitButler mode and return to normal Git ### Inspection - [status](./but-status): Show the project workspace state - [diff](./but-diff): Show a diff for the workspace or a CLI ID - [show](./but-show): Show information about a commit or branch ### Branching and committing - [commit](./but-commit): Commit changes to a stack - [stage](./but-stage): Stage changes to a branch - [branch](./but-branch): Commands for managing branches - [merge](./but-merge): Local branch merging - [discard](./but-discard): Discard uncommitted changes - [resolve](./but-resolve): Resolve conflicts in a commit - [unapply](./but-unapply): Unapply a branch from the workspace - [apply](./but-apply): Apply a branch to the workspace - [clean](./but-clean): Remove empty branches from the workspace - [pick](./but-pick): Cherry-pick a commit from an unapplied branch ### Rules - [mark](./but-mark): Create or remove a rule for auto-assigning or auto-committing - [unmark](./but-unmark): Remove all marks from the workspace ### Server interactions - [push](./but-push): Push changes in a branch to remote - [pull](./but-pull): Pull upstream changes and update your branches - [pr](./but-pr): Create and manage reviews on GitHub, GitLab, and other forges ### Editing commits - [rub](./but-rub): Combine two entities to amend, squash, move, or unassign work - [absorb](./but-absorb): Amend changes into the commits where they belong - [reword](./but-reword): Edit a commit message or branch name - [uncommit](./but-uncommit): Move commit changes back to unassigned work - [amend](./but-amend): Amend a file change into a commit - [squash](./but-squash): Squash commits together - [move](./but-move): Move a commit or branch to a different location ### Operations log - [oplog](./but-oplog): View and manage operation history - [undo](./but-undo): Undo the last operation - [redo](./but-redo): Redo the last undo ### Helper commands - [gui](./but-gui): Open the GitButler GUI for the current project - [tui](./but-tui): Open the interactive terminal UI - [update](./but-update): Manage GitButler CLI and app updates - [alias](./but-alias): Manage command aliases - [config](./but-config): View and manage GitButler configuration - [agent](./but-agent): Set up GitButler for AI coding agents - [skill](./but-skill): Manage AI agent skills for GitButler # but-update.mdx Check for new versions, install updates, or suppress update notifications. **Usage:** `but update ` ## Subcommands ### `but update check` Check if a new version of the GitButler CLI is available **Usage:** `but update check` ### `but update suppress` Suppress update notifications temporarily Hide update notifications for the specified number of days (1-30). Useful when you want to stay on a specific version temporarily. **Usage:** `but update suppress [DAYS]` **Arguments:** * `` — Number of days to suppress (1-30, default: 1) ### `but update install` Install or update the GitButler desktop application. By default, auto-detects your current channel (release/nightly) and installs the latest version for that channel. macOS: Installs the full GitButler desktop application. The CLI (but) is included with the app and will also be updated. Linux: Installs and updates only the CLI itself. Note: For other platforms and install forms, see https://gitbutler.com/downloads **Usage:** `but update install [TARGET]` **Arguments:** * `` — What to install: "nightly", "release", or a version like "0.18.7" Examples: but update install Auto-detect channel and install latest but update install nightly Install latest nightly build but update install release Install latest stable release but update install 0.18.7 Install specific version # but-tui.mdx The GitButler TUI provides a visual experience similar to the GitButler GUI - right in your terminal. For the full workflow and key bindings, see [https://docs.gitbutler.com/gitbutler-tui](https://docs.gitbutler.com/gitbutler-tui) **Usage:** `but tui` # installation.mdx How to install and setup the GitButler CLI. ## Installing the `but` CLI Ok, first thing is first, let's get our `but` CLI installed. Currently there are two ways to do this. ### Via the Desktop Client If you have the desktop client installed, you can go into your global settings and click on the "Install CLI" button in the "general" section. ### Curl install You can install the CLI by running the following command in your terminal: ```bash curl -fsSL https://gitbutler.com/install.sh | sh ``` ## Setup If you go into any existing Git repository and run `but setup`, it will make some neccesary changes to your setup in order for GitButler to manage your data. If you run almost any `but` command in an existing Git repository in an interactive terminal, it will ask you if you want to set it up and then run the command you were trying to run. So basically just run `but` anywhere to get started. At any time after this, you can run `but teardown` to undo the GitButler changes and go back to being a boring old Git project. It will not remove GitButler metadata, so feel free to go back and forth if you need to. # branching-and-commiting.mdx Now that your project is setup and GitButler is installed and configured, you can start branching and committing. ## The Simple Flow Let’s begin with a simple workflow, one that should be familiar to Git users. We will: - Do some work - Create a new branch - Commit to that branch ### Status Let’s begin by seeing what the status of the working directory is by running `but status`. This will tell you a little more than `git status`, it will list: 1. All files in your working directory that differ from your base branch (`origin/main`) the last time you updated it that aren’t assigned to a branch 2. A list of the active branches that you have and 1. All assigned file changes in each branch 2. All commits in each branch So it's sort of like a combination of `git status` and a shortlog of what is on your branches that is not on `origin/master`. It looks something like this: {/* restore [f0f437258043] */} {/* run but rub wu gemfile-fixes */} {/* run but rub te feature-bookmarks */} ```cli [branching-and-commiting-but-status-1, 506px] but status ``` Here we can see three applied branches: `gemfile-fixes` stacked on `feature-bookmarks` and independent `sc-branch-26`. There are also three unassigned files. You can also simply run `but` to get the status. Check out [`but alias`](/commands/but-alias) to set the default of `but` to something else. ### Create a Branch Let’s look at a very simple case first. Let’s say we’ve just modified some files and don’t have a branch yet. Our status might look like this: {/* restore [04d62f15beb4] */} ```cli [branching-and-commiting-but-status-2, 308px] but status ``` Now let’s say that we want to put those unassigned file changes into a commit on a new branch called `user-bookmarks`. To do this, you can use the `but branch new ` command. {/* run git branch -D user-bookmarks */} ```cli [branching-and-commiting-but-branch-1, 88px] but branch new user-bookmarks ``` Now if you run `but status` you can see your new empty branch: ```cli [branching-and-commiting-but-status-3, 374px] but status ``` ### Commit to a Branch Now we can commit our unassigned changes to that branch. You can simply assign your changes to the branch first to commit later (we'll cover that later in [Rubbing](./rubbing)), but for now let's keep it simple and just commit them directly using the `but commit` command. ```cli [branching-and-commiting-but-commit-1, 88px] but commit -m 'all the user bookmarks' ``` If you don’t specify the `-m` commit message, GitButler will try to open an editor with a tempfile where you can write a longer commit message. It will use the `$EDITOR` environment variable if it’s set, or the `core.editor` Git or GitButler config setting, or it will prompt you for a command to run if you’re in an interactive terminal. Now our status looks like this, with all unassigned files in a new commit on our new branch: ```cli [branching-and-commiting-but-status-4, 286px] but status ``` You also don't _need_ to create a branch to commit if you have none currently applied. If you run `but commit` and there are no active branches, GitButler will simply create a temporarily named one that you can later rename if you want. ## Stacked and Parallel Branches Ok, that’s the simple case, pretty straightforward. However, GitButler can also do some pretty cool things that Git either cannot do or struggles with, namely: - Having multiple active branches that you can work on in parallel. - Managing stacked branches. That is, both multiple independent and dependent active branches. Even at the same time if you want. ### Parallel Branches Parallel branches is very simple, you can create multiple simultaneously active branches that you can assign and commit changes to in your workspace. To create a parallel branch, you simply create a new branch the same way we did before. Let’s say that we want to create a `liked-tweets` branch alongside our existing `user-bookmarks`. We simply run the same `but branch new` command again: {/* run git branch -D liked-tweets */} {/* run echo 'test' > app/controllers/likes_controller.rb */} {/* run echo 'test' > app/models/like.rb */} ```cli [branching-and-commiting-but-branch-2, 88px] but branch new liked-tweets ``` Now if we run `but status` we can see our previous branch and our new empty branch. ```cli [branching-and-commiting-but-status-5, 374px] but status ``` We can see our previous branch and the commit we made, our new empty branch and a couple of modified files. Now we can commit the unassigned changes to that branch with `but commit -m "liked tweets changes" liked-tweets` ```cli [branching-and-commiting-but-commit-2, 88px] but commit -m "liked tweets changes" liked-tweets ``` And now we have one commit in each lane. ```cli [branching-and-commiting-but-status-6, 374px] but status ``` Here we specified the entire branch name as the commit target (as there is more than one), but you can also use the two character short code that is next to each one. If you don’t specify a branch identifier and you have more than one active branch, then GitButler will prompt you for which branch you wish to commit the unassigned changes to. We can also see which files were modified in each commit with the `--files` or `-f` option to `but status`: ```cli [branching-and-commiting-but-status-7, 550px] but status -f ``` ### Stacked Branches The other way you can create new branches is to make them stacked, that is, one depends on another one and has to be merged in that order. To create a new stacked branch in GitButler, you can run `but branch new` with a target branch ID. If we go back in time and instead stack our `liked-tweets` branch, we can make it dependent on the `user-bookmarks` branch by providing it as a stacking "anchor" with `-a` option: {/* run git branch -D liked-tweets-stacked */} {/* restore [e32713a1f41c] */} ```cli [branching-and-commiting-but-branch-3, 88px] but branch new -a user-bookmarks liked-tweets-stacked ``` ```cli [branching-and-commiting-but-status-8, 374px] but status ``` Now we can commit to our stacked branch. ```cli [branching-and-commiting-but-commit-3, 88px] but commit -m "liked tweets changes" liked-tweets-stacked ``` ```cli [branching-and-commiting-but-status-9, 352px] but status ``` Now if you push to a forge, GitButler will set up the reviews (Pull Request or Merge Request) as a stacked request, where `user-bookmarks` has to be merged either before or with `liked-tweets` but they can be reviewed independently. ## Assigning and Committing Changes The other way to commit to a branch is to explicitly assign changes to it. This is somewhat like running `git add` in Git, where you’re staging some changes for a future commit. However, unlike Git where you have to do this or override it with `-a` or something, the default in GitButler is to commit all changes by default and only leave out unassigned changes with the flag `-o` or `--only`. ### Staging Changes So, how do we stage changes to a specific branch and then only commit those changes? Let’s look at an example `but status` with six modified files and two empty, parallel branches and assign and commit one file to each branch as a separate commit. {/* restore [d5c7317b0fd4] */} ```cli [branching-and-commiting-but-status-10, 440px] but status ``` We will assign each file to a different branch and then see the result. We assign file changes to branches using the `but stage` command, which assigns changes to branches, much like `git add`, but you can do this for multiple branches. You can either stage the file identifier that you see next to each file, or all or part of the file path. For example, in this case to identify the `app/models/bookmark.rb` file, you can do either: - `g0` - `app/models/bookmark.rb` So lets stage the bookmark changes to the bookmarks branch: ```cli [branching-and-commiting-but-stage-1, 154px] but stage h0,i0,k0 user-bookmarks ``` Now we can run `status` and see that these are staged. ```cli [branching-and-commiting-but-st-1, 484px] but st ``` Now let's rub the user changes into the `user-changes` branch: ```cli [branching-and-commiting-but-stage-2, 110px] but stage h0 user-changes ``` Now we have some file changes assigned to each branch and still some unassigned changes: ```cli [branching-and-commiting-but-status-11, 528px] but status ``` Now, if we want to create a commit in the `user-bookmarks` branch, we can either run `but commit bo` which will create a commit with the files assigned as well as both files that are unassigned, but _not_ the file assigned to the `user-changes` lane. Or, we can make a commit with _only_ the assigned files in `user-bookmarks` by using the `-o` option to `but commit`. ```cli [branching-and-commiting-but-commit-4, 88px] but commit -o -m "liked tweets view" bo ``` Now if we look at our status we can see a commit on our branch instead of the assigned changes: ```cli [branching-and-commiting-but-st-2, 440px] but st ``` Now let's commit all the rest of the changes (assigned and unassigned) to our other branch: ```cli [branching-and-commiting-but-commit-5, 88px] but commit -m 'bookmarks stuff' ch ``` ```cli [branching-and-commiting-but-status-12, 374px] but status ``` ### Committing Specific Files or Hunks Instead of staging files first and then committing with `-o`, you can also directly specify which files or hunks to include in a commit using the `-p` or `--changes` option. This lets you commit only specific changes without having to assign them to a branch first. For example, if you have multiple unassigned files and only want to commit some of them: ```bash but commit -p h0,i0 -m "only these two files" user-bookmarks ``` You can specify files in several ways: - **By CLI ID**: Use the short identifier shown in `but status` (e.g., `h0`, `i0`) - **Space-separated**: `--changes h0 i0 k0` - **Comma-separated**: `-p h0,i0,k0` - **By path**: `-p app/models/bookmark.rb` This also works with hunk IDs. When a file has multiple hunks (shown in `but status -f` or `but diff`), you can commit individual hunks rather than the entire file. This is useful when you have changes in the same file that belong to different logical commits. If you don't specify `-p`, all uncommitted changes (or changes staged to the target branch) are committed. Use `-p` when you need fine-grained control over what goes into a commit. ### Assigning Ranges If you happen to have a large number of changes, you can also use ranges or lists for rubbing assignment. So for example, if we go back to this status: {/* restore [6fdd8fb1d547] */} {/* run but rub l0 zz */} ```cli [branching-and-commiting-but-status-13, 484px] but status ``` Then you can assign the everything in `app/` to a branch with: ```cli [branching-and-commiting-but-stage-3, 154px] but stage h0-j0 user-bookmarks ``` ```cli [branching-and-commiting-but-status-14, 528px] but status ``` # ai-stuff.mdx Use `--ai` when you want the CLI to generate text for a single command. Use `but agent setup` when you want a coding agent to use GitButler's version-control workflow. ## `--ai` options Supported commands can take `--ai` to generate text from your current changes. ### `but commit --ai` Use `but commit --ai` to generate a commit message from the changes you are committing. This commits the selected work with the generated message. You can edit the message afterward with `but reword `. ### `but squash --ai` Use `but squash --ai` to generate a combined commit message when squashing commits. More commands will gain `--ai` support over time. ## `but agent setup` Run the setup wizard from the repository where your coding agent will work: ```bash but agent setup ``` The wizard can install the GitButler skill, save workflow preferences in supported agent instruction files, and run `but setup` for the repository when GitButler needs workspace mode. Use `but skill install` when you only want to install or update the skill files without writing workflow instructions. To update an existing install without going through the prompts, run: ```bash but skill install --detect ``` We keep these templates up to date, so update the skill after updating GitButler. ## Agent workflow docs For the full workflow: - Start with [AI agents overview](/ai-agents/overview). - Install and configure the skill with [Getting started with AI agents](/ai-agents/getting-started). - Use [Useful requests](/ai-agents/useful-requests) for prompt examples. - Use [Tuning agent behavior](/ai-agents/tuning-agent-behavior) for standing instructions. # conclusion.mdx Ok, that's a short guide to GitButler's command line interface. Join us in [Discord](https://discord.com/invite/MmFkmaJ42D) if you have any other questions or suggestions for how we can improve the tool. Thanks! # configuration.mdx We've already covered `but config` a bit in dealing with forges and target branches. You can also use it for some basic user configuration, such as setting your name and email address for commits and your default editor. ```cli [configuration-but-config-1, 286px] but config user ``` ## Aliases GitButler also has a built in aliasing system in case you want to provide some shorthands. ```cli [configuration-but-alias-1, 286px] but alias ``` To add a new alias, you can run `but alias add `, which you can also provide a `-g` or `--global` if you want it to be a global alias. This will put this data in your local or global Git config file. You will notice that there is a `default` alias, which is what runs when you just run `but` with no arguments. If you overwrite the `default` alias, you can set up something other than `status` to run by default. # conflict-resolution.mdx In this world nothing can be said to be certain, except death, taxes and merge conflicts. There are several different ways that you can run into merge conflicts when using Git (and thus, GitButler, or any other branching version control system). Perhaps there are changes that have been merged upstream that modified the same files as you did in your branch. Or maybe you uncommitted something that commits above it depended on. ## First Class Conflicts in GitButler First, it's important to understand how GitButler deals with conflicts. While Git generally has to check out conflicts in your working directory and make you resolve them before you can commit, GitButler can partially apply a conflicting change and store the commit marked as "conflicted". This means that: - Rebases _always_ succeed, just sometimes it results with commits in a conflicted state. - You can deal with conflicts in any order and at any time. So, let's take a look at what this looks like and how we can deal with conflicted commits when they arise. {/* restore [e53a4a85d83d] */} {/* run git push -f origin 96ccca9:main */} ```cli [conflict-resolution-but-status-1, 396px] but status ``` Let's say that this is our status and we've decided to pull in from upstream. The changes that have been merged in by someone else upstream conflict with ours. When we run `but pull`, it will result in conflicts in our branch (but it will succeed). ```cli [conflict-resolution-but-pull-1, 286px] but pull ``` Ok, the `pull` tells us that we have conflicts and it also gives us a cheat sheet for what to do to resolve them, which is essentially "run `but resolve`". So first let's see what our conflicted branch looks like with `but status`. ```cli [conflict-resolution-but-status-2, 330px] but status ``` Notice how we have _two_ commits that are conflicted, but one that is not. You could have any number of commits marked as conflicted in a branch, and you'll need to resolve each of them one by one. If we were to dig into the details here, a few things have actually happened. First of all, we have applied the upstream changes, so if we were to look at the files that conflict, we will see the upstream version rather than what we had done. Second, the commits that are not in a conflicted state are still applied - those changes are still in your working directory. In fact, even the conflicted commit's changes will be applied in the areas where they don't conflict. You could potentially have several conflicted commits in your branch. When you resolve one, everything above it is rebased and may introduce new conflicts or may resolve other conflicts, depending on the resolution. However, for now, let's look at a simple resolution flow. All you really need is one command: `but resolve`. If you run `but resolve`, it will look through all your applied branches for any conflicted commits. If it finds any, it will list them out and ask you which you want to start with and default to the lowest one on the first branch. ```git ❯ but resolve Found conflicted commits: Branch: update-homepage ● 42165fe branding change: readme ● 404b604 hero update - new branding Would you like to start resolving these conflicts? Enter commit ID to resolve [default: 42165fe]: ``` If you hit enter, it will check out the conflict markers in that commit into your working directory. ```git ❯ but resolve You are currently in conflict resolution mode. - resolve all conflicts - finalize with but resolve finish - OR cancel with but resolve cancel Conflicted files remaining: ✗ README.md Checking out conflicted commit 42165fe ``` So now you're in a special mode called "Edit Mode" in GitButler, where we've directly checked out a commit to work on. If you run any other commands, we'll warn you that you're currently in this mode. For the conflicts, we put in zdiff3 style headers, so you can see your side, their side and also the ancestor. For example, if we look at the conflicted README.md file ```git ❯ head README.md <<<<<< ours # The Why Experience |||||| ancestor # Twitter Clone ====== # X Clone >>>>>> theirs ``` So you can see that we started with "Twitter Clone" and upstream changed it to "X Clone" and locally I changed the same line to "The Why Experience". Now I can resolve these three versions into a single line. If I do that and then again run `but status`, you can see that GitButler notices that the conflicts in the README file has been resolved. ```git ❯ but st Initiated a background sync... You are currently in conflict resolution mode. - resolve all conflicts - finalize with but resolve finish - OR cancel with but resolve cancel No conflicted files remaining! Files resolved: ✓ README.md ``` If you had other conflicted files, it would give you a list of what was still unresolved so you could work your way through the list. However, now that we've resolved everything, we can either run `but resolve finish` or just `but resolve` and it will move us to the next step. Technically you can just keep running `but resolve` and it will figure out what the next thing to do is. ```git ❯ but resolve You are currently in conflict resolution mode. - resolve all conflicts - finalize with but resolve finish - OR cancel with but resolve cancel No conflicted files remaining! Files resolved: ✓ README.md All conflicts have been resolved! Finalize the resolution now? [Y/n]: y Initiated a background sync... ✓ Conflict resolution finalized successfully! The commit has been updated with your resolved changes. ⚠ Warning: New conflicts were introduced during the rebase: ● 4f671a1 hero update - new branding Run but status to see all conflicted commits, or but resolve to resolve them. ``` # editing-commits.mdx While you can rub changes in and out of commits, you can also edit the commit message of any commit in your workspace quite easily. ## Editing Commit Messages You can edit commit messages with the `but describe` command. So if we have this status: {/* restore [d69fffa7c6eb] */} ```cli [editing-commits-but-status-1, 330px] but status ``` Then you can edit the message of any commit by running `but reword `, which will open up your editor of choice with the existing commit message and when you exit the editor, replace that message in the commit and rebase everything above it. The editor would look something like this: ``` add user changes # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # Changes in this commit: # modified: app/models/user.rb # modified: config/routes.rb # ~ ~ ~ ~ ``` Pretty simple. ## Changing Branch Names Just like changing commit messages, you can also use `but reword` to change the name of a branch. So, in the above example, if we wanted to change the branch name from `user-bookmarks` to `feature-awesome-thing`, we can do this: ```cli [editing-commits-but-reword-1, 88px] but reword -m feature-awesome-thing us ``` Et voila. ```cli [editing-commits-but-status-2, 330px] but status ``` {/* TODO: Edit Mode */} # forges.mdx We've touched on how to update your local branches with `but pull`, but what about when you want to take your awesome new branches of work and put them on a server to get them integrated or collaborate with other people? The two main commands to get work out are `but push` and `but pr`. Let's start with `but push` as it's a little simpler. ## Pushing The very simple example would be to simply run `but push `. ``` ❯ but push update-homepage ✓ Push completed successfully update-homepage -> origin/update-homepage ((new branch) -> 1d31833) ``` You can also run `but push` by itself. If there is only one applied branch, it will push that one. If there are several applied branches, you can choose which to push or select 'all' to push all of them. You can also supply `-d` or `--dry-run` to see what _would_ be pushed up. ## Pull Requests That's the simple way to push branches to your default remote and update already pushed ones. The other common thing to do is to open Pull Requests on GitHub. GitButler's CLI has a built in command for opening and updating PRs, called `but pr`. Much like `but push`, if there is only one branch, it will open a PR for that, otherwise it will ask you which branch to open one for. Or you can be explicit with something like `but pr `. ``` ❯ but pr Do you want to open a new PR on branch 'sc-switch-wording-to-x'? [Y/n]: y → Pushing sc-switch-wording-to-x... ✓ Pushed to origin → Creating PR for sc-switch-wording-to-x → main... ✓ Created PR #69 Title: Switch Wording to X Branch: sc-switch-wording-to-x URL: https://github.com/schacon/why/pull/69 ``` Once it's opened, you'll get a URL you can view the pull request on. ## Landing Without a Pull Request Not every project reviews changes through pull requests. If you're working solo, or your team pushes straight to the target branch, `but land` skips the PR step entirely and lands a branch directly onto the target (for example `origin/main`). ``` ❯ but land update-homepage This lands update-homepage directly onto origin/main without a pull request — skipping any code review, CI checks, or branch protections your team may rely on. Land update-homepage directly onto origin/main? [y/N]: y Landed update-homepage onto origin/main. ``` The target is fast-forwarded when possible, otherwise a merge commit is created, and the result is pushed to the remote. Afterwards, your remaining applied branches are rebased onto the moved target, just like `but pull`. Because this updates the target branch directly and is not easily reversible, `but land` asks for confirmation first. A branch protected against direct pushes will reject the land — in that case, use `but pr` instead. See [`but land`](/commands/but-land) for all options. ## Forge Authentication In order to open a PR on GitHub, you'll need to authenticate to that forge. You can see which authentications you have by running `but config`: ```cli [forges-but-config-1, 572px] but config ``` If you have not authenticated to a forge yet, you can run `but config forge auth`, which will ask you which type of authentication you would like to do: ``` ❯ but config forge auth ? Select an authentication method: > Device flow (OAuth) Personal Access Token (PAT) GitHub Enterprise ``` Selecting one of the options will allow you to provide an authentication token for GitButler to use for opening and modifying PRs. Currently, GitButler only authenticates to GitHub servers, but support for Merge Requests on GitLab and other forges is coming. # initializing-a-repository.mdx If you run any `but` command in a repository that has never been seen by GitButler before, it will automatically ask you if you want to setup the repository for GitButler. It will guess most things needed, but everything can be changed later if it got anything wrong. The most important thing is to figure out the target branch - the main branch that you'll want to merge things into and you consider 'production' or 'golden'. Normally this is something like `origin/main`, but GitButler should be pretty good at guessing. You can also run `but setup` manually to set everything up explicitly: ```ansi but-setup-5faf7f36 but setup ``` As the command says, it does a few things to prepare your repository for being managed by GitButler. Unlike a tool like [Jujutsu](https://docs.jj-vcs.dev/latest/git-compatibility/) or [Sapling](https://sapling-scm.com/docs/git/git_support_modes/), GitButler mainly operates on normal Git repositories, so nearly all Git commands will work with anything produced or managed by GitButler. You can think of it more like a new porcelain than a different system. However, one thing we need to do in order to enable having parallel applied branches is create a "megamerge" commit that automatically merges in the heads of all your applied branches, so that other tools `git status` will correctly show you what you expect. This means that we do two things: - We create a new branch called `gitbutler/workspace` pointing to a constantly rewritten and ephemeral merge commit and check this branch out so HEAD is pointing to it (again, for `git status` reasons in tools like VSCode or whatever) - We add custom `pre-commit` and `post-checkout` hooks (moving and continuing to call any existing hooks) to try to prevent you from accidentally committing on top of our managed mega-merge commit. The mega-merge workspace commit will soon only be needed once you actually have more than one branch applied, so at some point we won't automatically do it on setup, but we're working on it. Both of these things can be quickly and easily undone by running `but teardown`, doing any Git committing stuff you need to do and then re-running `but setup` to go back to GitButler tooling. You can also simply checkout a git branch with `git checkout ` and the `post-checkout` hook we installed should clean up after itself. # inspecting.mdx When you're working with projects, sometimes you'll need to inspect things to see what the differences are or summarize work. The first thing to remember is that GitButler is basically an advanced Git client, which means that you can use any Git inspection command without problems when you're using GitButler. This includes things like `git show`, `git diff`, `git log`, `git blame`, `git bisect`, etc. So we have not tried to recreate the functionality of these, but instead focused on some of the common needs in a modern workflow that these tools may not do well. Let's look at a simple scenario. {/* restore [bd526ee8c76c] */} {/* run but stage h0 us */} {/* run git push -f origin 32a2175758f7f649ed7a030a17fd21213a5e400f:refs/heads/main */} ```cli [inspecting-but-status-1, 396px] but status --files ``` Here we have an unstaged file (`Gemfile`), a file staged to the `user-bookmarks` branch (`README.md`), and two commits on our branch. ## Diffing things For most of this, you could use `git diff`. For example, to see everything that is uncommitted, you can just run `git diff`. ```cli [inspecting-git-1, 836px] git diff HEAD ``` However, I don't find that a super readable format, even if it's useful in applying with the Unix `patch` command. Since most people tend not to be emailing patches around, we tried to optimize for a much more human readable format: ```cli [inspecting-but-diff-1, 814px] but diff ``` You can see that this is the same information, but a bit more easily understandable. You can also focus the diff output to any of the short codes in that status output. For example, to just see what is staged to `user-bookmarks` you can run `but diff l0`. To only see the changes committed to the bookmarks controller file in the "create bookmarks" commit you can run `but diff n0`, to only see what modifications have not been staged you can run `but diff zz`, and so on. ## Listing Branches When GitButler creates and modifies branches, it is manipulating real Git branches, so you can see them and inspect them with normal Git commands as well. While you can use the `git branch` command to see all your branches, the `but branch` command is a bit nicer. Here is what `git branch` might output (this example repo has over 100 branches, so let's just truncate it): ```cli [inspecting-git-2, 286px] git branch | head -10 ``` This output is actually _better_ than the default Git output for this, because I have a config setting of `branch.sort -comitterdate`, so at least it's showing me the branches by last commit rather than the default of alphabetically. The `but branch` command, however, is built specifically to help you identify the branches you're looking for and give you some useful information about them. Let's give it a try: ```cli [inspecting-but-branch-1, 638px] but branch ``` You can immediately notice that this is a very different type of listing. We're not just showing the names of the branches, but also some very useful information about all of the branches that are available to us. First, we show any applied branches - these are the branches that are currently applied into your workspace. Next, all the _unapplied_ branches - that is, the other branches that you don't currently have active in your working directory. For each branch, you'll see how many commits "ahead" it is, that is, how many commits are on that branch that are not on the target branch (eg `origin/main`). In other words, if this branch were merged to production, what would come in with it? There is also a "✓" or "✗" that indicates if this branch is cleanly mergeable with your target branch. It also shows the last author of a commit on that branch and orders everything by how long ago the last commit was. The point of this listing is to help you easily see what work you have available, not merged into your target, that you might want to work on. ## Filtering your Branches Running `but branch` defaults to running `but branch list`, which has a bunch of other options (filtering to only local or remote branches, not calculating mergability for speed, etc). The most useful option might be the filtering, for example, you can type a partial match string and it will filter the output: ```cli [inspecting-but-branch-2, 220px] but branch list book ``` ## Looking at a Branches If you want to see what is on a branch, you can inspect a specific branch by running `but branch show `. This will show the commits on this branch ahead of your target. Essentially, it runs the equivalent of `git log origin/main..` with some more introspection. ```cli [inspecting-git-3, 308px] git log origin/main..feature-awesome-thing ``` Now let's look at `but branch show` ```cli [inspecting-but-branch-3, 264px] but branch show feature-awesome-thing ``` Pretty much a short log of the branch difference from our target branch. However, there are a bunch of options if you want to dig in further. The `-r` option will show you PR information if one is opened on this branch. The `-f` option will show you the files modified in each commit. The real fun one is adding `--ai`, which will take a look at the changes and summarize what the changes actually do. Let's run all of them at the same time: ```cli [inspecting-but-branch-4, 682px] but branch show sc-branch-28 --ai -r -f ``` ## Showing a Commit If you want to look at a specific commit in any of these circumstances, you can use the `but show` command with the commit hash. ```cli [inspecting-but-show-1, 264px] but show a42580b96ed7b432 ``` ## Deleting Branches As long as we're talking about branches, let's show how to get rid of them. If you've merged one, it will by default not be shown anymore with `but branch` anyhow, but if you have one with some work on it that you want to abandon, you can also easily delete it with `but branch delete` (or `-d`). # operations-log.mdx GitButler maintains a detailed log of all operations, making it easy to track what happened and undo changes when needed. ## Viewing the Operations Log See all recent GitButler operations: ```cli [operations-log-but-oplog-1, 550px] but oplog ``` ## Undoing the last operation Undo the last operation: ```cli [operations-log-but-undo-1, 132px] but undo ``` ## Restoring to a previous point You can restore to any point in the operations history by running the `but oplog restore` command with the SHA from `but oplog`. ```cli [operations-log-but-oplog-2, 154px] but oplog restore 6fdd8fb1d547 ``` Restorations create a new oplog entry before running, so you can always easily undo it in the same manner. It can be a bit confusing as to what state it restores to. It will restore to what your project looked like _before_ the operation was run. So for example, if there is a `CreateCommit` operation and you restore to that SHA, it will put your state back to the moment before the commit happened. ## Creating Snapshots You can also manually create snapshots of moments that you want to be able to revert to at any point, without some other operation needing to automatically save it. ```cli [operations-log-but-oplog-3, 154px] but oplog snapshot ``` Now you can copy that SHA and restore to that exact point at any time in the future. # rubbing.mdx As we saw in the [Branching and Committing](branching-and-commiting) section, the `but stage` command can be used to assign changes to branch lanes. However, we can also do this with a command called `but rub`. Not only can it stage changes though, it can be used to do _so much_ more. Rubbing is essentially combining two things. Since there are lots of _things_ in the tool, combining them together can do lots of different operations. Most of them should be fairly intuitive once you understand the concept. Let’s take a look at what is possible with this very straightforward command. ## Unassigning Changes We already showed how you can use `rub` to assign a file change or set of changes to a branch for later committing (rubbing a file and a branch), but what if you want to undo that? Move assignments to a different lane or revert them to being unassigned for later? As you may have noticed in the `but status` output, there is a special identifier `zz` which is always the “unassigned” ID. If you rub anything to `zz` then it will move it to unassigned. So given this status: {/* restore [f0f437258043] */} {/* run but rub g0,h0 gemfile-fixes */} ```cli [rubbing-but-status-1, 528px] but status ``` We can re-unassign the `README.new.md` file with `but rub h0 zz`. Or, we can re-assign that file to the `sc-branch-26` parallel branch with `but rub h0 sc-branch-26`. ## Amending Commits However, branch assignment is not all we can do with rubbing. We can also use it to move things to and from commits. A common example would be to amend a commit with new work. Let’s say that we sent commits out for review and got feedback and instead of creating new commits to address the review, we wanted to actually fix up our commits to be better. This is somewhat complicated to do in Git (something something [fixup commit, autosquash](https://blog.gitbutler.com/git-autosquash), etc). However, with `rub` it’s incredibly simple. Just rub the new changes into the target commit rather than a branch. Let’s say that we have a branch with some commits in it, we’ve made changes to two files and want to amend two different commits with the new changes. ```cli [rubbing-but-status-2, 528px] but status ``` If we want to update the first commit (`da42d06`) with the `README-es.md` changes and the last commit (`fdbd753`) with the `app/views/bookmarks/index.html.erb` changes, we can run the following two `rub` commands: ```cli [rubbing-but-rub-1, 88px] but rub h0 da42d06 ``` ```cli [rubbing-but-rub-2, 88px] but rub app/views/bookmarks/index.html.erb fdbd753 ``` ```cli [rubbing-but-status-3, 770px] but status --files ``` Notice that the SHAs have changed for those commits. It has rewritten the commits to have the same messages but incorporated the changes you rubbed into those patches. Also notice that you can use either the commit SHA or file path instead of the short ID if you prefer more typing. If you wanted to rub all the unassigned changes into a specific commit, you could also do that by rubbing the unstaged section to a commit, for example `but rub zz f55a30e` which would take all unstaged changes (if there were any) and amend commit `f55a30e` with them. ## Squashing Commits File changes are not the only thing that you can rub. You can also rub commits into things. To squash two commits together, you simply rub them together. Let’s look at a simple example of a branch with two commits on it. We'll squash them into one: {/* restore [eb63f26e885a] */} ```cli [rubbing-but-status-4, 330px] but status ``` We can absorb the top commit into the bottom one by running `but rub `: ```cli [rubbing-but-rub-3, 88px] but rub 0f 08 ``` Now we can see that we only have one commit in our branch: ```cli [rubbing-but-status-5, 308px] but status ``` You probably want to [edit the commit message](editing-commits) after this too, since it will simply combine the two commit messages. ## Uncommitting Let’s say that we want to just _undo_ a commit - that is, pretend that we had not made that commit and instead put the changes back to unassigned status. In this case we would use the special `00` ID that we talked about earlier, just like unassigning changes, we can unassign commits. So, if we’re back to this status: {/* restore [eb63f26e885a] */} ```cli [rubbing-but-status-6, 330px] but status ``` And we want to un-commit the first commit (`0fa2965`) as though we had never made it, you can rub to `zz`: ```cli [rubbing-but-rub-4, 88px] but rub 0f zz ``` Now if we look at our status again, we will see that commit removed and those files back in the unassigned status: ```cli [rubbing-but-status-7, 418px] but status ``` ## Moving Commits We can also use rubbing to move a commit from one branch to another branch if we have multiple active branches and committed to the wrong one, or otherwise decide that we want to split up independent work. Let’s say that we have two commits on one branch and created a second parallel branch to move one of the commits to so it's not dependent. {/* restore [f71c049739a9] */} ```cli [rubbing-but-status-8, 396px] but status ``` We can move the “second commit” commit to the `move-second-commit` branch with `but rub`: ```cli [rubbing-but-rub-5, 88px] but rub 0f mo ``` Now we can see that the commit has been moved to the `move-second-commit` branch, breaking up the series into two independent branches with one commit each. ```cli [rubbing-but-status-9, 396px] but status ``` Notice that the only SHA that changed was the one that moved, since nothing else needed to be rebased. Rubbing a commit to another branch always adds it to the top of that branch. As you might imagine, you can also simultaneously move and squash by rubbing a commit in one branch on a commit in another branch too. ## Moving Files between Commits You can also move specific file changes from one commit to another. {/* restore [6ccc38e33e0e] */} To do that, you need identifiers for the files and hunks in an existing commit, which you can get via a `but status -f`, or `but status --files` that tells status to also list commit file IDs. ```cli [rubbing-but-status-10, 638px] but status -f ``` So now we can move the changes from one commit to another by rubbing pretty easily. Let’s take the `app/controllers/bookmarks_controller.rb` change and move it down to the "second commit" commit on the other branch: ```cli [rubbing-but-rub-6, 88px] but rub 08:3 2e ``` Now the change is in the "second commit" on the other branch: ```cli [rubbing-but-status-11, 638px] but status -f ``` Also notice that the SHAs of both commits were changed, as they both needed to have content modified. ## Splitting Commits Ok, so now we can be pretty specifc about moving changes around to all these different states. The last thing we’ll cover here is splitting commits, which requires a new command that creates a new empty commit called `but commit empty`. The general strategy here is that to split a commit, you would make a new empty commit above or below it, then rub changes from the one commit into the empty commit until it's how you want it to look, then you're done. Let’s say we have a branch with a single commit on it and want to split it into two commits. {/* restore [64df45a76e99] */} ```cli [rubbing-but-status-12, 308px] but status ``` Now we want to split the "add bookmark model and associations" into two separate commits. The way we do this is to insert a blank commit in between `06` and `f5` and then rub changes into it (then probably edit the commit message). We can insert a blank commit by running `but commit empty --after 6a` which inserts a blank commit above the specified commit. ```cli [rubbing-but-commit-1, 88px] but commit empty --after 6a ``` Now we have a blank commit: ```cli [rubbing-but-status-13, 462px] but status -f ``` Now we can use the previous method of moving file changes from other commits into it, then edit the commit message with `but reword 54` (for more on the `reword` command, see [Editing Commits](editing-commits), coming up next). # scripting.mdx ### JSON Errwhere All of the commands are designed to be very user friendly when we have an interactive terminal, but you can also very easily script everything by passing the `--json` or `-j` option to anything. For example, here is using `but show` on a commit. ```cli [scripting-but-show-1, 308px] but show 2672465 ``` Now with the `--json` option: ```cli [scripting-but-show-2, 682px] but show --json 2672465 | jq ``` You can do this with anything - committing, status, diffing, etc. Just throw a `-j` in there and you get parseable data (or, data your agent can very easily work with). # updating-the-base.mdx The target branch is the foundation that your feature branches build upon. Keeping it updated and managing it properly is crucial for a smooth workflow. ## Understanding the Target Branch The target branch is typically your main production branch that acts as the basis for all your local branches. In practice, this is generally not actually a local branch, it's usually the branch on whatever server you're using to collaborate and merge changes into, so generally it's something like `origin/main` or `origin/master`. When GitButler is first initialized in a project, you are asked to choose a branch to target, as everything in your working directory that doesn't exactly match the tip of this branch is technically a fork of what is considered production. Whatever that target branch looks like when you choose it is set as your 'base'. You can always check your target branch setting with `but config`: ```cli [updating-the-base-but-config-1, 572px] but config ``` Or get more information with `but config target`: ```cli [updating-the-base-but-config-2, 308px] but config target ``` When you start working, everything that is different from that base goes into a branch based off of it. ## Understanding Upstream When you first set your target branch (ie, `origin/main`), we record the state of the branch at that time. However, if someone else merges work into that branch while you're working, the target branch moves forward, but the work you're doing is still based off of where it was. We call this 'upstream' work, and the commit that the target _was_ pointing at and your branches were based off of is your "base". The problem is that now the stuff we're working on is out of date. It may conflict with what is upstream, it may need the work that is upstream, etc. So how do we get our branch up to date? ## Viewing Upstream When you run `but status`, we will by default show you a summary of upstream work if there is any. You can see a more detailed list of what is upstream by `but status --upstream` (or `-u`). {/* restore [cc9d20b8099c] */} {/* run git push -f origin 32a2175758f7f649ed7a030a17fd21213a5e400f:refs/heads/main */} Let's take a look at what this looks like. Let's say that our project is at this state: ```cli [updating-the-base-but-status-1, 396px] but status -u ``` We can see that there are two commits upstream (ie, merged into `origin/main` since we started our branch). Technically, there could be more reachable commits, but we only show the first parents, so merges of long branches show up as just the merge commits, to simplify things a bit. Now let's say that we would like to pull in the upstream work and rebase our branches on top of the new upstream to update them. We can check what all would happen with `but pull --check`. ```cli [updating-the-base-but-pull-1, 308px] but pull --check ``` This will fetch the very latest work, then check that upstream work against your currently applied branches to see if anything has been integrated (and thus we can remove), anything conflicts with upstream work, or a merge/rebase should work cleanly. In this example, we can see that our `user-bookmarks` branch would be cleanly rebased if we did a pull, and that there are two things that have been merged since we started our branches. ## Updating the Base When you feel like you want to get your active branches up to date, you can run `but pull`. This will fetch the very latest work, then rebase your active branches on top of the new target commit to be your new base. Let's run it in our example. ```cli [updating-the-base-but-pull-2, 396px] but pull ``` ```cli [updating-the-base-but-status-2, 308px] but status ``` OK, now we can see that our integrated branch was removed, our `gemfile-fixes` branch was successfully rebased and our `sc-branch-26` work is marked as conflicted. We'll see how to deal with that state in a minute. # tutorial-overview.mdx Using the GitButler CLI is meant to make a specific common workflow very simple, which is roughly: - Create a branch - Do work on that branch - Commit to that branch - Optionally, create another branch if you find unrelated work you need to do - Work on and commit to that branch - Submit a branch for review - Create a stacked branch if needed to continue on dependent work - Update your base if work has been integrated to remove merged work - Rinse and repeat Additionally, GitButler is very good at editing commits (amending fixup work, squashing, rewording messages, etc), it keeps a simple log of what you've done in case you need to go back in time, it makes collaborating on a branch with others easy, it has great GitHub/Lab integration and more. Let's walk through some of the things it can do and what a typical day using the GitButler CLI might look like. # getting-started.mdx This page is for coding agents that can read local instruction files and run commands in your repository. The GitButler skill does not give the agent new permissions. It tells the agent how to use `but` instead of driving Git through checkout, stash, add, commit, and rebase commands. ## Install the `but` CLI If you already have GitButler Desktop installed, you can install the CLI from the Desktop Client settings. For terminal-only setup, run: ```sh curl -fsSL https://gitbutler.com/install.sh | sh ``` See [Installation and setup](/cli-guides/installation) for the full CLI install options. ## Run the agent setup wizard From the repository where the agent will work, run: ```sh but agent setup ``` The wizard asks which agents you use, where the setup applies, and which workflow preferences you want. It can: - install the GitButler skill for Codex, Claude Code, Cursor, GitHub Copilot, Windsurf / Devin, OpenCode, or Agent Skills; - save workflow instructions globally, in this repository, or both; - run `but setup` for this repository when GitButler needs workspace mode. Before it writes anything, the wizard shows the skill install paths, instruction files, any repository setup step, and the exact generated text. `but agent` with no subcommand starts the same wizard. For command details, see [`but agent`](/commands/but-agent). The wizard installs the skill for you. To update an installed skill later without rerunning the wizard, use [`but skill`](/commands/but-skill). ## Set up the repository GitButler currently needs the repository in workspace mode for its multi-branch model: multiple GitButler branches in one working directory. The setup wizard runs this for you when needed. You can also run it yourself: ```sh but setup ``` For the full list of setup changes, see [`but setup`](/commands/but-setup). GitButler is working toward a plain Git mode that switches into a workspace only when multiple concurrent branches are needed. Follow [gitbutlerapp/gitbutler#11866](https://github.com/gitbutlerapp/gitbutler/issues/11866) for that work. ## Add optional agent instructions The wizard can write common workflow preferences for you: folding small fixes into the right commits, splitting mixed work, stacking dependent branches, updating from the target branch, opening draft PRs, landing approved work directly onto the target instead of opening pull requests (single-repository setups only), using a publish phrase, naming branches, following commit-message conventions, and creating checkpoint commits. Use [Tuning agent behavior](/ai-agents/tuning-agent-behavior) when you want to read the policies before choosing them, adjust them after the wizard runs, or copy individual snippets by hand. These instructions steer agent behavior; they are not access controls. Use your usual repository permissions and branch protection for hard limits. For prompt examples that ask for specific branch and commit outcomes, see [Useful requests](/ai-agents/useful-requests). # parallel-agents.mdx Parallel agents do not require separate worktrees or pre-created branches. Once agents use GitButler for version-control writes, you can start another coding session in the same repository and prompt it like any other task: ```text Work on checkout validation. ``` When either session is ready, ask it to commit: ```text Commit your changes. ``` The agent uses GitButler to commit the changes for its task to its GitButler branch. The branch routing is the agent's job; your prompt can stay small. If two sessions touch the same file or generated output, have the agents call out the overlap before committing. You can also ask an agent to split independent work out of the current session. For example, if a feature session also finds a small bug fix, the agent can move the relevant changes or commits to a new branch and prepare a separate PR instead of stacking the fix on the feature. For more background on the branch model, see [Parallel branches](/features/branch-management/virtual-branches). The `Version control` instructions written by `but agent setup` (see [Getting started](/ai-agents/getting-started#add-optional-agent-instructions)) are useful if you want to steer commit behavior, but they are not a separate parallel-agent setup step. ## How this differs from worktrees Git has one checked-out branch per worktree. If you want two agents to work on two branches at the same time, the usual Git answer is multiple worktrees. GitButler gives you a different option: multiple active branches in one worktree, with each agent's commits organized onto the branch for its session. | | Multiple worktrees | GitButler parallel branches | | --- | --- | --- | | Workspace | One directory per agent | One shared working directory | | Branches | One checked-out branch per worktree | Multiple active branches in one worktree | | Isolation | Separate checkout | Shared filesystem and runtime state | | Setup cost | Usually more directories, dependency installs, build outputs, and dev servers | Reuse one install and dev server when tasks can share runtime state | | Version-control shape | Branches stay separate because work happens in separate directories | GitButler can commit the right subset of changes to each branch | | Best fit | Competing attempts, incompatible checkout states, isolated runtimes | Unrelated features or fixes that can share one workspace | Use multiple worktrees when agents need incompatible checkout states, separate runtime state, or competing attempts at the same task. Use GitButler parallel branches when the tasks are independent enough to share one workspace and you want less local overhead. ## Handle dependencies explicitly Parallel agents work best when sessions start independent. If one session starts depending on another, make that relationship explicit by stacking the branches: ```text The notification settings work now depends on checkout validation. Stack your branch on top of the checkout validation branch. ``` If an unrelated fix shows up inside a feature session, tell the agent to extract it instead: ```text The cache invalidation fix is independent. Move it to a separate GitButler branch and prepare a separate PR for it. ``` If the feature depends on the fix, put the fix on the lower branch and stack the feature above it instead. For stacked PR policy, see [Create stacked pull requests](/ai-agents/tuning-agent-behavior#create-stacked-pull-requests). ## Know what is shared Parallel GitButler branches are not runtime isolation. The agents share one filesystem, dependency install, generated files, and app state. That can surface overlap and broken builds earlier, but it can also hide accidental dependencies. Before shipping a branch independently, check whether it depends on another active branch. If two agents start editing the same files or generated output, decide whether to keep the work parallel, stack one branch on the other, or use separate worktrees. For more request examples, see [Useful requests](/ai-agents/useful-requests). # tuning-agent-behavior.mdx `but agent setup` can write many of these policies for you. Start with [Getting started](/ai-agents/getting-started) when you want the wizard path; use this page when you want to understand, copy, or adjust individual policies. Add these optional bullets under the same `## Version control` section as your baseline instructions. Use this page as a menu: copy only the policies you want for the repository and agent you are using. ## Amend local fixes into the right commits Use this when you want the agent to fold follow-up fixes into unpublished local commits when the new change clearly belongs with that commit's intent. With GitButler, the agent can move the relevant change into the commit where it belongs. ```md - For small cleanup or follow-up fixes, amend an unpublished local commit when the change clearly belongs with that commit's intent. - Do not create tiny fixup commits unless I ask. - Use GitButler to move the relevant changes into the commit where they belong. - Ask before rewriting pushed, reviewed, shared, or ambiguous history. ``` You do not need to tell the agent which command to use. The GitButler skill gives it the relevant operations. For background, see [`but absorb`](/commands/but-absorb) and [`but amend`](/commands/but-amend). ## Commit checkpoints after each completed turn Use this when you want local savepoints while the agent works. The checkpoints do not need to be the final review history. Before review, you can ask the agent to tidy unpublished local history. ```md - Commit after a working checkpoint, when the requested change is complete and relevant checks have passed or been reported. - Treat checkpoint commits as local savepoints, not final review history. - When I ask you to tidy the history, use GitButler to squash commits, reword commits, and move changes between commits where appropriate. - Only tidy unpublished local history unless I explicitly authorize changing pushed or shared history. ``` ## Create stacked pull requests Use this when you want dependent work reviewed as stacked pull requests. This is useful when one agent session depends on another session's branch, or when an agent is working on a branch that sits at the bottom of a stack. ```md - If this session depends on another in-flight branch, stack its branch on top of that dependency instead of mixing the changes. - If this session is working in a stack, put commits on the branch where they belong. - Ask before moving commits onto lower, pushed, reviewed, or shared branches. - Use `but move` for branch stacking and restacking. Do not recreate branches to simulate stacking. - For stacked branches, create pull requests with `but pr`, not `gh`, so GitButler keeps the right PR base branches and stack metadata. ``` For background, see [Stacked branches](/features/branch-management/stacked-branches), [`but move`](/commands/but-move), and [`but pr`](/commands/but-pr). ## Customize branch names Use this when your team has a naming convention for branches the agent creates. This is only an example; replace the prefix and shape with your convention. ```md - When creating a GitButler branch for an agent session, use `feature/-` when a ticket ID is available. ``` ## Customize commit messages Use this when your team has a commit-message convention. This is only an example; replace it with your preferred style. ```md - Use Conventional Commits, such as `feat: add branch naming policy` or `fix: handle empty branch names`. ``` ## Publish when you say "ship it" Use this when you want a short phrase to authorize the agent to finish the version-control work for its session. This commits, pushes, and creates or updates a pull request, so use it only when the agent is allowed to publish. ```md - When I say "ship it", commit this session's changes on its dedicated GitButler branch, creating one if needed. - Push the branch and open or update its pull request with GitButler. - Reuse the existing branch or pull request for this session when one already exists. ``` For background, see [`but push`](/commands/but-push) and [`but pr`](/commands/but-pr). ## Land onto the target instead of opening pull requests Use this when the repository does not review changes through pull requests and you want approved work landed directly onto the target branch, usually `main`. `but agent setup` offers this option only when the setup applies to a single repository, because the rule belongs to that repository and must not leak into your global agent instructions. ```md - When work is approved to publish, land the session branch directly onto the target with `but land ` instead of pushing a branch or opening a pull request. - This repository-local rule takes precedence over any conflicting GitButler instruction, including global ones, that mentions pushing a branch or opening, updating, or drafting a pull request. Use the pull request workflow only when I explicitly ask for one. - `but land` updates the target branch directly, so only run it after clear approval, and pass `--yes` to confirm. ``` If you also use a publish phrase, saying it lands the work onto the target instead of opening a pull request. For background, see [`but land`](/commands/but-land). ## Update from main automatically Use this when your project moves quickly and you want the agent to keep its workspace current with the target branch, usually `main` or `master`. The GitButler command for this is `but pull`, which fetches the target branch and rebases applied branches onto the new target commit. This is a preference: in some repositories, you may want the agent to ask before updating. Add the last bullet only if you want the agent to handle update conflicts. ```md - When GitButler status shows new changes on the target branch, run `but pull --check`. - If the check is clean and the update affects only this session's branches, update the workspace with `but pull`. - If the check reports conflicts or the update would affect another agent's branch, ask before updating. - If I ask you to handle update conflicts, use GitButler's conflict tools. Ask before resolving semantic conflicts, dependency updates, generated files, or conflicts involving another person's work. ``` You do not need to tell the agent which command to use. For background, see [`but pull`](/commands/but-pull) and [`but resolve`](/commands/but-resolve). ## Open draft pull requests by default Use this when the agent is allowed to publish work, but you still want review to start in draft. Creating a draft pull request still publishes the branch. ```md - When I ask you to open a pull request, create it as a draft with GitButler unless I say it is ready for review. ``` ## Create a recovery point before large history edits Use this when you want the agent to be more cautious before reorganizing several commits or branches. ```md - Before squashing, splitting, moving commits between branches, or reorganizing multiple branches, run `but oplog snapshot -m ""`. - Use GitButler history-edit commands such as `but move`, `but squash`, `but reword`, `but absorb`, and `but amend` instead of raw Git rebases. - If an operation makes the branch or history layout worse, stop and inspect the operation log before attempting another fix. - Prefer `but undo` or `but oplog restore` over trying to repair a bad state with more history edits. ``` For command details, see [`but oplog`](/commands/but-oplog) and [`but undo`](/commands/but-undo). ## Split unrelated hunks Use this when agents tend to commit whole files even when one file contains separate changes. ```md - If one file contains unrelated changes, split them by hunk instead of committing the whole file. - Keep tests with the behavior they verify. - Split generated output, docs-only edits, or mechanical cleanup into separate commits when each commit remains coherent on its own. - If the split is ambiguous, summarize the options before committing. ``` # review-agent-work.mdx When your coding agent uses GitButler, it can do the version-control work for you: create branches, commit changes, move work between commits, and reshape local history. The agent usually does this through the GitButler CLI. You may still want to preview what it created or manually adjust the branch and commit history before anything is pushed or turned into a PR. GitButler gives you three ways to inspect and adjust that state: the [TUI](/gitbutler-tui), the CLI, and the Desktop Client GUI. Use this page to pick the surface you want. The detailed workflows live in their own docs. ## GitButler TUI Use the TUI when you want a terminal view of the workspace without switching to the Desktop Client. It is useful for checking branch state, looking at what is on a branch or still unassigned, and making small manual adjustments to branch assignment, commit membership, or history shape. ```sh but tui ``` For the full workflow and keybindings, see the [TUI guide](/gitbutler-tui). ## GitButler CLI Use the CLI when you want exact output, scriptable commands, or the same view of the repository state that the agent uses. For command details, start with the [CLI overview](/cli-overview). ## GitButler Desktop Client Use the Desktop Client when you want a visual overview of branches, commits, assigned changes, unassigned changes, parallel work, and stacked work. It is also the visual surface for moving changes between branches and adjusting commit history. ```sh but gui ``` For details, see the [Desktop Overview](/overview), [Branch lanes](/features/branch-management/branch-lanes), and [Commits](/features/branch-management/commits). ## Operations history GitButler records version-control operations so you can inspect or undo local history edits. If a branch reorganization does not look right, inspect the operation history before making more changes. See [Timeline](/features/timeline), [`but oplog`](/commands/but-oplog), and [`but undo`](/commands/but-undo). # useful-requests.mdx Once the GitButler skill is installed and your baseline version-control instructions are in place, ask for the branch, commit, or pull request outcome you want. You do not need to know the `but` commands or CLI IDs; the agent uses those to build the structure you describe. Use these examples as one-off requests you can mix with normal coding prompts. They are intentionally short; add whatever constraints matter in your repo. For standing rules that always apply, see [Tuning agent behavior](/ai-agents/tuning-agent-behavior). ## Commit changes The agent commits the session's changes to its dedicated GitButler branch, not unrelated user or agent work. You can tell your agent: ```text Commit your changes. ``` Relevant command: [`but commit`](/commands/but-commit). ## Clean up history GitButler gives the agent direct tools for moving commits, squashing commits, rewording commits, and moving changes between commits. Describe the end result you want instead of writing out an interactive rebase plan. Keep history cleanup to unpublished local work unless you explicitly authorize rewriting pushed or shared branches. ```text Clean up the history. Squash WIP commits, split unrelated work, and reword messages based on intent. Show me the plan before changing history or pushing. ``` Relevant commands: [`but move`](/commands/but-move), [`but squash`](/commands/but-squash), [`but reword`](/commands/but-reword), and [`but rub`](/commands/but-rub). ## Split a large commit into smaller commits Use this when a commit is too large to review as one unit. Say how you want the work grouped; the agent can create the intermediate commits and move the right changes into them. This prompt is an example; replace the grouping rules with whatever matters for your project. ```text Split this into smaller commits by concern. Keep tests with the behavior they verify. ``` ## Put uncommitted fixes into existing commits Use this after review feedback, test fixes, or a small follow-up edit that belongs with an earlier local commit. ```text Amend your follow-up fixes into the appropriate local commits. ``` Relevant commands: [`but absorb`](/commands/but-absorb) and [`but amend`](/commands/but-amend). ## Take changes out of a commit Use this when something was committed by mistake, or when one commit contains a change that belongs somewhere else. ```text Take the debug logging out of the commit and leave it uncommitted. ``` ```text Move the docs changes out of the feature commit and into a separate docs commit. ``` Relevant commands: [`but uncommit`](/commands/but-uncommit) and [`but rub`](/commands/but-rub). ## Create stacked pull requests Stacked pull requests help when one change depends on another, but reviewers can still review the lower branch first. Creating draft PRs still pushes branches, so use this only when the agent is allowed to publish. ```text Make the API work the base branch and stack the UI work on top. Create draft PRs. ``` The agent can also stack or restack existing branches when the dependency structure changes. If branches have already been pushed or reviewed, ask the agent to show which PRs will change before restacking. If something in a stack turns out to be independent, ask the agent to move it out into a separate branch. For more background, see [Stacked branches](/features/branch-management/stacked-branches), [`but move`](/commands/but-move), and [`but pr`](/commands/but-pr). ## Work in parallel Use parallel branches when the work does not depend on another branch. GitButler lets multiple branches be active in the same workspace, so different agents can work on their own branches without creating and managing separate worktrees. This works best when the tasks do not depend on each other and are not editing the same files. Stack branches only when one branch depends on another. For the fuller multi-agent workflow, see [Parallel agents](/ai-agents/parallel-agents).