git-queue
Manage PR queues: ordered series of dependent branches and their numbered pull requests.
⚠️ Alpha software — run at your own risk. git-queue is young and moving fast. It rewrites branches and force-pushes (with lease) as part of normal operation. Expect rough edges, keep backups of work you can't afford to lose, and read command output before trusting it.
A PR queue is an ordered series of branches where branch N is built on
branch N-1. The queue has a well-defined FIFO merge order: the branch at the
front merges into the base first, then the next, and so on. git-queue tracks
that order, keeps the whole queue rebased on its base, and opens one PR per
branch — each targeting the branch before it, titled [k/n], and cross-linked
with a shared queue map.
Why "queue"? Partly because
git-stackwas taken — but mostly because the standard nomenclature is wrong. So-called "stacked" PRs don't pop last-in-first-out; they merge first-in-first-out, which is a queue. If you already think in stacked PRs, just mentally replace "queue" with "stack".
◉ ui #12 [OPEN] ← current
◯ service #11 [OPEN]
◯ api #10 [OPEN]
┴
main (trunk)
Install
cargo install puts the git-queue binary on your PATH, so git queue …
works immediately (git's standard subcommand mechanism). git queue setup then
walks through the optional extras interactively (see below).
Prefer a prebuilt binary? Each GitHub release ships archives and a shell installer (built by dist) for Linux and macOS (x86-64 and arm64):
|
(Windows: the PowerShell installer, git-queue-installer.ps1, via irm … | iex.)
Or just download an archive, extract it, and put git-queue on your PATH.
git queue setup
It asks about each, per step:
- git hooks (per repo) — a plain
git commit/--amendon a queue branch auto-requeues its descendants and stamps a Stable-Commit-Id. Without them, usegit queue commit/git queue amendexplicitly. - merge-order gate (per repo) — submit/sync post a red/green commit status per PR.
- man page — installs
git-queue.1to a directory on yourMANPATH, soman git-queueandgit queue --helpwork (git routes--helpto the man page;git queue helpshows the inline CLI help). - shell completion — for bash, zsh or fish (detected from
$SHELL). Or install it by hand:git queue completions <shell>prints the script to stdout. git qalias — addsalias.q = queueto your global git config, so the shortergit q …works.- agent integrations — the Claude Code skill and/or an AGENTS.md section when those tools are detected.
--yes accepts the two repo-local steps (hooks, gate) non-interactively; the
user-scoped steps (man page, completion, alias, skill) are interactive only.
Requirements
- git —
git queue commit/syncusegit replay(git ≥ 2.44) andgit queue amend/rewordusegit history(git ≥ 2.55). Other commands work on older git; the ones needing a feature will tell you if it's missing. gh— the authenticated GitHub CLI, forsubmit/yank(PR management). Not needed for local queue operations.
The man page is generated from the CLI definition itself (
clap_mangen), so it never drifts. Regenerate manually withgit-queue man --dir <man1-dir>.
Workflow
# ... trunk moves on, a teammate pushes to a branch, or you amend an earlier PR ...
Named queues
Every queue has a name — asked for interactively when a new queue starts
(create/track/edit; --queue <name> scripts it, and non-interactive
runs fall back to the branch name). The name appears in each PR's header
("📚 eql-v3 PR · 2 of 3"), namespaces the queue's branches (queue/<name>/<branch>),
namespaces branches — create, like edit, takes short
names and creates queue/<name>/<short> in namespaced queues (and every
command that accepts a branch resolves short names back) — and keys the
queue's own description:
PR bodies are fully generated from this metadata: the queue map, then
"About this queue", then "About this branch" — regenerated on every
submit/sync, so edits belong in describe/describe-branch (a hand-written
body on an adopted PR is imported as the branch description rather than lost).
Queues on any base branch
A queue doesn't have to start on trunk. Run create from any branch — a
release branch, a long-lived bugfix branch — and that branch becomes the
queue's base: the front PR targets it, sync keeps the queue rebased on
it, and status shows it. Or name the base explicitly with --base:
&&
Commands
| Command | Description |
|---|---|
git queue create <name> [--base <b>] [--queue <q>] |
Create the next branch of the queue (short names become queue/<qname>/<name> in namespaced queues, like edit). |
git queue edit [--queue <n>] |
Open the whole queue in an editor: each branch is a [name] section over its commits. Commits keep their order — move/rename/add/remove the headers to reassign them. Refs move; no commit is rewritten. Removed branches are deleted. (alias: split) |
git queue track [--parent <b>] [--stamp-ids|--no-stamp-ids] [--edit] |
Adopt the current branch into a queue (parent defaults to trunk). Offers to stamp Stable-Commit-Ids onto the adopted commits — asks first, since that rewrites their hashes. --edit then opens the queue editor to divide the commits into multiple queued branches. |
git queue untrack |
Forget the current branch's queue metadata. |
git queue describe [-m <text>] |
Describe the current queue; shows under "About this queue" in every PR of the queue (opens $EDITOR without -m). |
git queue describe-branch [-m <text>] |
Describe the current branch; shows under "About this branch" in its PR. |
git queue name [<name>] |
Show or set the current queue's name. |
git queue ls (list) |
List every queue, most recently touched first. |
git queue status |
Show the queue tree: PR numbers/states, a margin marker on the current branch, and any conflicting files. Colourised on a terminal. |
git queue log |
The status tree with each branch's commits indented beneath it, newest first, each prefixed by its abbreviated Stable-Commit-Id. |
git queue up / down (next/prev) |
Check out the child / parent branch. |
git queue checkout <commit> |
Detach HEAD on a queue commit (SHA or Stable-Commit-Id) to edit it in place: git commit inserts a new commit after it, git commit --amend revises it (id preserved) — the rest of the queue rebases on top via the hooks or git queue requeue. |
git queue commit [-m <msg>] |
Make a new commit on the current branch, then requeue all descendants onto the new tip. |
git queue amend |
Fold staged changes into the current commit and update every descendant. |
git queue reword [<commit>] |
Rewrite a commit message and update descendants (defaults to HEAD). |
git queue move <c>[..<c>] --new-parent <c> |
Move a commit (or an inclusive range) elsewhere in the queue — within one PR or across PRs. Commits can be named by revision or Stable-Commit-Id (unique prefix ok). Everything after the removal and insertion points is requeued; conflicts persist as markers. |
git queue requeue (restack) |
Requeue the current branch's descendants onto its tip. |
git queue sync [--no-push] |
Pull remote commits, drop branches whose PRs have merged (reparenting their children), requeue onto the latest base, push back with --force-with-lease, and reconcile the PRs of every published queue (open missing ones, revive closed ones, fix bases/titles/queue maps). |
git queue submit [--draft] (push) |
Push the current queue line and open/update its numbered PRs (revives a child PR GitHub closed when its base was deleted). |
git queue yank |
Close every open (non-merged) PR in the current queue. |
git queue setup [--yes] [--undo] |
Interactive, per-step setup: git hooks, merge-order gate, the Claude Code skill, and an AGENTS.md section for other agents. --undo reverses everything. |
git queue doctor |
Report whether merge-order signalling is enabled (read-only). |
Signalling merge order
Enable the gate via git queue setup to warn reviewers off merging PRs out of order. It
sets a local flag (queue.gate = status) — no GitHub setup, workflow, ruleset,
or admin rights required. With it on, git queue submit posts a
git-queue/merge-order commit status on every open PR in the queue: green ✓
("Ready — front of the queue") on the PR that merges next, red ✗ ("Do not merge
— merge PR #N first") on every PR behind it, with the status's Details link
pointing at the PR that must land first. As PRs land, git queue sync +
git queue submit promote the PR now at the front to green.
git queue doctor reports whether the gate is on. The gate is advisory: the
red ✗ appears in the PR's checks list, but the merge button still works. PRs
stay normal, reviewable, non-draft PRs.
Why a commit status, not something that disables the merge button? Every hard block GitHub offers lives on the base branch's rules — and with base-chaining a non-front PR targets an intermediate queue branch, so a ruleset that gates the merge also rejects git-queue's pushes to that branch (and draft PRs, the one PR-level block, read socially as "not ready for review"). A commit status is the strongest signal that leaves PRs normal and pushes unblocked.
Adopting existing PRs
A queue can form around work that already has a PR — open a PR by hand, later
git queue track the branch and create more on top. sync (and submit)
reconcile the whole line: missing PRs are opened, and an existing PR is
adopted rather than overwritten — its hand-written title is kept (just
numbered [k/n]) and its body is preserved below the queue map. A queue with
no PRs at all is never auto-published; run git queue submit for that.
Change identity: the Stable-Commit-Id trailer
Commit SHAs are useless identifiers in a rewrite-heavy workflow — every amend,
move and requeue mints new ones. git-queue therefore gives each change a
stable identity: a Stable-Commit-Id: trailer in the commit message (the same idea as
Gerrit's Change-Id), minted once and carried by git itself through every
rebase, cherry-pick, replay and amend.
git queue commitstamps one automatically, andgit queue setupadds acommit-msghook so plaingit commiton a queue branch does too. Commits off the queue are never touched.git queue trackoffers to stamp the branch's existing commits (with a confirmation — stamping rewrites those commits, so their hashes change and an already-pushed branch will be force-pushed on the next sync/submit). Use--stamp-ids/--no-stamp-idsto decide non-interactively.syncuses id correspondence to tell teammate work apart from stale copies of your own rewritten commits: only genuinely new commits are pulled, so a local amend or move can never conflict with its own pre-rewrite self on the remote. Id-less commits fall back to patch-equivalence.syncalso drops branches whose every id already appears on trunk — which detects squash-merges, where SHAs and patch-ids are destroyed but GitHub's default squash message preserves the constituent trailers.
Anywhere a command takes a commit — move (including ranges and
--new-parent) and reword — you can pass a Stable-Commit-Id instead of a
revision: the full id or any unique prefix, exactly as git queue log shows
them.
Ids are optional and incremental: old commits without them keep working via the previous heuristics, and new commits pick them up as they're made.
Landing a queue
PRs merge in FIFO order — the oldest PR (#1, at the front of the queue)
merges first, then the next, and so on. After a PR lands, run git queue sync —
it detects the merged PR, reparents the branches behind it onto the base, and
rebases them; then git queue submit retargets their PR bases and refreshes the
queue list. Each PR body shows live approval/merge-state emojis (✅/♻️/⏳ while open,
🟣/⚫ once merged/closed) as of the last submit.
Deleting the merged branch is handled too: if a merge deletes the branch (via
--delete-branch or the repo's auto-delete setting), sync reparents its
orphaned children onto the queue's base, and — because GitHub closes a PR whose base branch
was deleted — submit revives that child PR (reopening it, or opening a fresh
one retargeted to trunk).
Why GitHub must never see a "collapsed" queue
GitHub permanently marks a PR merged the moment a push makes its head tip
reachable from its base branch — no button required. In a healthy queue that
never happens (each branch extends its parent; parents never contain their
children), but a collapsed state — a parent force-set to a child's tip, or a
branch whose commits were all moved away — would trip it, mislabelling a
mid-queue PR as merged while the front of the queue is still open. sync and
submit therefore refuse to push any branch whose tip contains an open child
PR's head, and tell you how to untangle it instead.
Editing a branch in the middle of a queue
When you change a branch that has descendants, git-queue propagates the change through the queue automatically — forks included — using two engines depending on what you're doing:
| You want to… | Command | Engine | On conflict |
|---|---|---|---|
| Add new work | git queue commit |
git replay --contained (whole subtree, one atomic ref update) |
conflict markers are persisted into the committed files so it always finishes, and the affected branches are flagged with a loud warning |
| Amend a commit | git queue amend |
git history fixup (atomic, worktree-free) |
aborts cleanly, nothing changes, loud warning — git history cannot leave markers |
| Reword a message | git queue reword |
git history reword |
aborts cleanly |
| Move a commit / range | git queue move <c>[..<c>] --new-parent <c> |
git rebase -i --update-refs with a scripted todo (whole line rewritten, branch refs ride along) |
conflict markers are persisted and the affected branches flagged |
Branches left holding persisted conflict markers are shown with ⚠ conflict markers in git queue status. Search for <<<<<<<, resolve, and commit.
Prefer plain git commit? Run git queue setup and a post-commit /
post-rewrite hook will call git queue requeue for you (the hooks are guarded
against recursion and no-op off a queue).
Note:
git queue --helpandgit queue <cmd> --helpare intercepted by git to open a man page (man git-queue). Rungit queue setup(and accept the man page step) so that page exists; otherwise usegit queue helporgit-queue --helpfor inline help.
How it works
State lives in the repository's own git config (nothing outside git):
| Key | Meaning |
|---|---|
queue.trunk |
Trunk override. Normally unset: git-queue uses the remote's default branch (origin/HEAD), falling back to main/master. |
queue.remote |
Remote to push/fetch (default origin). |
branch.<n>.queueParent |
Parent branch of <n>. |
branch.<n>.queueParentSha |
Parent tip when <n> was last based — the rebase anchor used by sync. |
branch.<n>.queuePr |
Cached PR number. |
branch.<n>.queueDescription |
PR body text set by git queue describe. |
queue.gate |
status once git queue setup enables merge-order signalling. |
(Conflict-marker state is not stored — status detects <<<<<<< in each
branch's tip live, so it can never report a stale warning.)
Branches form a forest under trunk via parent pointers; a queue line is the
linear chain from its base to a leaf. submit pushes front-first (so each PR's
base exists), points each PR at the branch below it, and writes the [k/n]
titles. Every PR body gets a formatted, linked queue list prepended to the
top, followed by that branch's describe text under a divider:
Part of a queue. The PRs merge in FIFO order — the numbered order below, #1 first.
1. 2.3.
<your `git queue describe` text>
The list is bounded by hidden markers, so re-running submit re-renders it in
place (idempotent) without disturbing your description.
See DESIGN.md for the full design.
Claude skill
skills/using-git-queue/SKILL.md is a Claude
Code skill (CipherPowers format) that teaches Claude to drive git queue
confidently — the mental model, which command to reach for, conflict handling,
and worked examples. Copy it into your plugin's skills/ directory to enable it.
Development
Releasing (maintainers)
Releases are automated with release-plz off the
release branch, with dist building
the release artifacts (.github/workflows/release-plz.yml, release.yml,
release-plz.toml):
- Promote work onto
release(e.g. mergemain→release). release-plz opens/updates a "chore: release" PR that bumps the version inCargo.tomland regeneratesCHANGELOG.mdfrom the Conventional Commits history. - Merge that release PR — release-plz publishes to
crates.io and pushes a
v<version>tag. That tag triggers dist, which buildsgit-queuefor Linux and macOS (x86-64 and arm64) and creates the GitHub Release with the archives, checksums, andcurl | sh/irm | iexinstallers, using the CHANGELOG section as the notes. release-plz itself does not create the release (git_release_enable = false), so the two don't collide.
Publishing uses crates.io Trusted Publishing (OIDC) — no CARGO_REGISTRY_TOKEN
secret. The crate's trusted publisher on crates.io must be configured for this
repo and the release-plz.yml workflow. Optionally set RELEASE_PLZ_TOKEN (a
fine-grained PAT or GitHub App token) so the release PR triggers CI; it falls
back to the default GITHUB_TOKEN. Because versions are derived from commit
messages, land your commits as feat: / fix: / feat!: etc.
The dist config lives in Cargo.toml ([workspace.metadata.dist]) and
.github/workflows/release.yml; regenerate the workflow after changing the
config with dist generate.