GitButler Code
Sign inCreate an account

Docs

How GitButler Code works: your space, organizations, pull requests, and the grit commands that go with them.

Getting started

GitButler Code hosts Git repositories over HTTPS. You work with them through Grit, a Git engine and command line written in Rust, or with any Git client.

Install Grit, then sign in. grit login opens GitButler Code in your browser to approve the sign-in, and stores an access token with your credential helper.

curl -fsSL https://grit-scm.com/install | sh
grit login

GitButler Code is Grit's default host, so commands that take <owner>/<project> without a URL use https://code.gitbutler.com.

To use plain Git, make a token on the Tokens page. Git sends it as the password for fetches and pushes. There is no SSH.

Your space

Your handle is your working space. Projects at /<you>/<project> are private to you, like the copy on your laptop. Nobody else sees them, including the members of your organizations.

Push any branch you like. A push to a name you don't have yet creates the project:

grit push <you>/<project> <branch>

Your branches stay private until you share one or propose one in a pull request.

Organizations

Organizations are domains. Claim one on the Organizations page and prove it with a DNS TXT record:

Name   _gitbutler-code.<domain>
Type   TXT
Value  gitbutler-code-verification=<token>

Whoever proves a domain becomes its first owner. No domain yet? <you>.gitbutler.dev is already yours and needs no proof.

Owners add and remove members. Members open projects and integrate pull requests.

Owners can also let people join by email: with Anyone with a verified @<domain> email address joins as a member checked on the organization's page, everyone who has verified an address at the domain becomes a member, now and whenever they verify one later. Add and verify addresses under Settings → Emails: GitButler Code mails a link to each address, and a verified address belongs to one account.

Canonical projects and your copy

An organization's projects live at /<domain>/<project>. Each is public, so anyone can clone it, or members only. Nobody pushes to them, owners included. Their main branch moves only when a pull request is integrated.

Your copy of a canonical project lives in your space, at /<you>/<project>. Make one with Copy into on the project's page, or let grit clone or grit sync make it for you. Your copy follows upstream: upstream's branches, usually just main, are read-only in it and brought to upstream's tip whenever your copy is used. Put your work on branches of other names.

grit clone https://code.gitbutler.com/<domain>/<project>

Syncing with grit sync

grit sync keeps a clone of a GitButler Code project and your space in step, in both directions. It:

  • fetches the project and your space,
  • fast-forwards your main to upstream's main,
  • brings down the branches in your space that you don't have, and fast-forwards the ones that are behind,
  • pushes every active branch to your space. A branch is active unless it is main, a branch your copy follows from upstream, or already merged into main.
grit sync

A branch that has moved on both sides is reported and left alone, never overwritten. Bring the two together with grit pull --rebase on that branch, then sync again.

When you clone an organization's project with grit clone, Grit points pushes at your space (it sets remote.origin.pushurl), so grit push and git push go to your copy while fetches still come from the organization. grit sync sets this up too, in a clone made some other way.

Sharing a branch

Share a branch of your space with other people to work on it together. Each person gets it in their own space and has their own head of it: only you move yours, so nobody's push can overwrite anyone else's work, and nothing ever needs --force. Every head anyone pushes is kept in the share's log.

grit share add <branch> <user>...
grit share add <branch> --link

Each person you name is emailed. Their copy of the project, with the branch in it, appears in their space the first time they fetch from it: grit clone https://code.gitbutler.com/<them>/<project>, or grit sync in a clone they have. A share link works for anyone signed in: they open it and press Accept, or run grit share accept <link>. Links work once and for 7 days unless you say otherwise (--uses, --expires).

The other members' heads arrive beside yours as origin/shared/<branch>/<user>. grit status shows them under your branch, grit sync fast-forwards you to a head that builds on yours, and when someone's head has diverged from yours it says what each of you did to each commit. When the two go together without losing anything, grit sync and grit push combine them for you; when someone has to decide, they list the options and exit with status 3, and grit share resolve <branch> <option> applies one. grit share list shows everyone's head. Anyone on the branch can add people; the person who shared it can remove them (but can't be removed), and anyone can leave with grit share leave. A member's copy holds only the shared branches and the base they build on, never the sharer's other branches.

To share only part of a project, add --path <path> (or --no-history for the whole tree): grit share add <branch> <user> --path src/rpc. They get a project of their own with a snapshot of main and the branch's own commits, cut down to those paths, and none of the project's history. They work with grit or plain git as usual; what they push is put back onto the real history, so everyone else gets full commits. A push that would undo changes outside their paths stays in their space and the push says why (outside_paths). When main moves, their next fetch brings a new snapshot of it.

When the branch is done, anyone on it who can integrate runs grit integrate <branch> (or integrates its pull request). It merges their head, so everyone else's changes have to be in it first: if someone's head has changes theirs doesn't, GitButler Code refuses and names them. Take them with grit sync, or leave that person out with grit integrate <branch> --without <user>; their changes stay in their space. Then the share is over: the branch is deleted from everyone's copy and everyone is told. Pushing that branch again with commits that were integrated makes a branch nobody shares, and grit says so and offers grit share resolve <branch> rebase (keep only what's new), rename <name> or reshare.

Choose what reaches your inbox under Settings → Notifications: by default every change to a shared branch is emailed, one thread per branch.

Pull requests and grit integrate

Propose a branch of your copy for the canonical project from its page, or go straight to:

https://code.gitbutler.com/<you>/<project>/pulls/new/<branch>

Opening a pull request copies the branch into the canonical repository as refs/pull/<n>/head, so everyone who can read the project sees those commits and nothing else of yours. Pushing to the branch again updates the pull request.

A member integrates it with a merge commit (Merge branch '<you>/<branch>' into main), or GitButler Code refuses and names the conflicting paths.

grit integrate

In a clone, grit integrate <branch> pushes the branch to your space and merges it into its target:

  • In a copy of an organization's project, it merges into the organization's main, through a pull request that is integrated at once. This needs you to be a member. If you aren't, it tells you so and gives you the link to propose the branch instead.
  • In a project of your own, it merges into that project's main.
grit integrate <branch> [--without <user>]

For a shared branch, it waits for everyone's changes unless you leave someone out with --without, and ends the share once merged.

In a snapshot checkout, grit integrate checks the branch in and merges it into the checkout's target branch, then refreshes the checkout.

Snapshot checkouts and slices

For part of a large project, check out a snapshot instead of cloning the whole history. grit checkout downloads one snapshot of main, cut down to the paths you need, and builds a normal Git repository around it. Commit as usual, then grit checkin grafts your commits back onto the real history.

grit checkout https://code.gitbutler.com/<owner>/<project> [slice]
grit checkin

A slice names a part of a project people work on. Slices live in .grit/slices.toml on main, versioned with the code, so every checkout sees the same ones:

[slices.lib]
description = "The library everything builds on"
paths = ["grit-lib"]

grit slice lists a project's slices and adds new ones. Slices can take folders from anywhere in the project, and they can overlap.

History API

Ask the server about history instead of cloning it. GitButler Code serves gj's history RPC for every project:

POST https://code.gitbutler.com/.grit/rpc/<owner>/<project>/<method>

The methods are info (refs), graph (commit graph rows), commits, objects and prefetch (commit bodies, trees and blobs), history (the commits that changed a path), and has and update (compare-and-swap pushes). The server holds the whole history, so answers are exact even from a one-commit checkout.

Requests use the same tokens and the same access rules as Git: send the token as a bearer token or the basic-auth password. Nobody updates an organization's projects this way either.

Large files

A project can keep large files out of its history log. It is off by default. A project's owner (for a canonical project, the organization's owners) turns it on with a size limit under the project page's Settings tab. Once it is on, the project's Code menu shows how to clone it. It can't be turned off again, and projects with it on can't be copied or proposed from yet.

Pushes are unchanged. To clone, make a partial clone that fetches large files from the project's large-file store as they are checked out. The Large files page shows these commands with the project's own key and limit:

grit clone --blobs auto \
    --config remote.lop.url=https://code.gitbutler.com/.grit/lop/<key> \
    --config remote.lop.promisor=true \
    --config promisor.acceptFromServer=knownUrl \
    https://code.gitbutler.com/<owner>/<project>

git clone --filter=blob:limit=<limit> \
    -c remote.lop.url=https://code.gitbutler.com/.grit/lop/<key> \
    -c remote.lop.promisor=true \
    -c promisor.acceptFromServer=knownUrl \
    https://code.gitbutler.com/<owner>/<project>          # Git 2.49 or newer

A clone without the filter, or Git before 2.49, fails on files only the store holds.

Agents

Hand your agent the prompt from the home page. It installs Grit, reads the docs written for agents, and learns where its work goes: its own branches in your space, proposed back when they're ready.

To set an agent up by hand, point it at grit-scm.com/llms.txt and the agent guide, and save the Grit skill where it will find it:

mkdir -p .agents/skills/grit && grit skill > .agents/skills/grit/SKILL.md

Agents should pass --markdown to every grit command, read the result from stdout, and check the exit code. Use --json instead when a script needs to parse the output. If an agent isn't signed in, it should ask you to run grit login.

Git compatibility

GitButler Code serves Git's smart HTTP protocol, versions 0, 1 and 2, through Grit, so any Git client works: clone, fetch and push as usual. It is HTTPS only; there is no SSH. Use a token from the Tokens page as the password, or let grit login store one for you.

git clone https://code.gitbutler.com/<owner>/<project>

For everything else Grit does, see the Grit docs.