git-queue 0.1.3

Manage queues of dependent branches and their numbered pull requests
Documentation

git-queue

Manage PR queues: ordered series of dependent branches and their numbered pull requests.

Website: https://freshtonic.github.io/git-queue/

⚠️ 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-stack was 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 git-queue     # from crates.io — installs the `git-queue` binary
git queue --version
git queue setup             # optional: man page, completion, alias, hooks, gate

Or from a clone (cargo install --path .):

git clone git@github.com:freshtonic/git-queue.git
cd git-queue
cargo install --path .

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 install scripts (built by dist) for macOS, Linux, and Windows. On macOS/Linux:

curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/freshtonic/git-queue/releases/latest/download/git-queue-installer.sh | sh

On macOS or Linux you can also use Homebrew:

brew install freshtonic/git-queue/git-queue

On Windows, use the PowerShell installer:

powershell -ExecutionPolicy ByPass -c "irm https://github.com/freshtonic/git-queue/releases/latest/download/git-queue-installer.ps1 | iex"

Or just download an archive for your platform (macOS/Linux .tar.xz, Windows .zip), extract it, and put git-queue on your PATH.

git queue setup

git queue setup             # interactive
git queue setup --undo      # reverse everything it did

It asks about each, per step:

  • git hooks (per repo) — a plain git commit/--amend on a queue branch auto-requeues its descendants and stamps a Stable-Commit-Id. Without them, use git queue commit / git queue amend explicitly.
  • merge-order gate (per repo) — submit/sync post a red/green commit status per PR.
  • man page — installs git-queue.1 to a directory on your MANPATH, so man git-queue and git queue --help work (git routes --help to the man page; git queue help shows 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 q alias — adds alias.q = queue to your global git config, so the shorter git 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

  • gitgit queue commit/sync use git replay (git ≥ 2.44) and git queue amend/reword use git 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, for submit/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 with git-queue man --dir <man1-dir>.

Workflow

git checkout main
git queue create api           # new branch on trunk; make commits
git queue create service       # next in the queue, after `api`; make commits
git queue create ui            # next in the queue, after `service`; make commits

git queue status               # view the queue and PR states
git queue prev / next          # walk toward the front/back (aliases: down/up)

git queue submit               # push all branches + open/update numbered PRs
# ... trunk moves on, a teammate pushes to a branch, or you amend an earlier PR ...
git queue sync                 # pull remote commits, requeue onto the base, push (with lease)
git queue submit               # refresh the PRs

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:

git queue name                 # show the current queue's name
git queue name eql-v3          # (re)name it
git queue describe             # describe the QUEUE  -> "About this queue" in every PR
git queue describe-branch      # describe the BRANCH -> "About this branch" in its PR
git queue ls                   # all queues, most recently touched first

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:

git checkout release-1.2 && git queue create fix-a   # base inferred
git queue create fix-a --base release-1.2            # base named explicitly

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 commit stamps one automatically, and git queue setup adds a commit-msg hook so plain git commit on a queue branch does too. Commits off the queue are never touched.
  • git queue track offers 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-ids to decide non-interactively.
  • sync uses 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.
  • sync also 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 --help and git queue <cmd> --help are intercepted by git to open a man page (man git-queue). Run git queue setup (and accept the man page step) so that page exists; otherwise use git queue help or git-queue --help for 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:

### 📚 Queued PR · 2 of 3

Part of a queue. The PRs merge in FIFO order — the numbered order below, #1 first.

1. 🟣 [#10 `api`]…/pull/10`main`
2. ♻️🟢 **[#11 `service`]…/pull/11`api`**  👈 **this PR**
3. ⏳🟢 [#12 `ui`]…/pull/12`service`

---

<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

cargo test      # unit tests + integration tests against throwaway repos
cargo build

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):

  1. Promote work onto release (e.g. merge mainrelease). release-plz opens/updates a "chore: release" PR that bumps the version in Cargo.toml and regenerates CHANGELOG.md from the Conventional Commits history.
  2. Merge that release PR — release-plz publishes to crates.io and pushes a v<version> tag. That tag triggers dist, which builds git-queue for Linux and macOS (x86-64 and arm64) and creates the GitHub Release with the archives, checksums, and curl | sh / irm | iex installers, 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.