diff --git a/src/content/docs/stacks.mdx b/src/content/docs/stacks.mdx index d39fd06f97..c8610d4ca7 100644 --- a/src/content/docs/stacks.mdx +++ b/src/content/docs/stacks.mdx @@ -61,7 +61,7 @@ reviewers see the logical progression of your work. - One local branch (no juggling N branches for N PRs) - Automatic PR chaining with dependency tracking - Smart updates that only touch PRs that changed -- Standard Git, no new commands to learn +- Everyday Git for daily work: the commit and rebase commands you already use - Ships with skills for Claude Code, Cursor, and any agent supporting [skills.sh](https://skills.sh) - Complements [Merge Queue](/merge-queue) for safe, fast landing diff --git a/src/content/docs/stacks/compare.mdx b/src/content/docs/stacks/compare.mdx index de2bf33c72..421aa5cc37 100644 --- a/src/content/docs/stacks/compare.mdx +++ b/src/content/docs/stacks/compare.mdx @@ -12,6 +12,10 @@ These pages give you an honest look at how Mergify Stacks compares so you can pick what fits your team. + + Stacking by hand with `git rebase` and `gh pr create`. The baseline every + other tool is measured against. + GitHub's native stacking CLI. Multi-branch model with GitHub UI integration. diff --git a/src/content/docs/stacks/compare/gh-stack.mdx b/src/content/docs/stacks/compare/gh-stack.mdx index bb153b18ae..990d4f2422 100644 --- a/src/content/docs/stacks/compare/gh-stack.mdx +++ b/src/content/docs/stacks/compare/gh-stack.mdx @@ -73,9 +73,9 @@ between them with `gh stack up` and `gh stack down`. | Push changes | `mergify stack push` | `gh stack push` | | Create PRs | Included in `mergify stack push` | Separate `gh stack submit` command | | View the stack | `mergify stack list` | `gh stack view` | -| Edit mid-stack | `mergify stack edit` (interactive rebase) | Checkout the branch, edit, rebase | +| Edit mid-stack | `mergify stack edit ` (targeted pause) | Checkout the branch, edit, rebase | | Reorder changes | `mergify stack reorder` or `mergify stack move` | Manual rebase + `gh stack rebase` | -| Squash changes | `mergify stack edit` (squash/fixup) | Manual | +| Squash changes | `mergify stack squash` or `mergify stack fixup` | Manual | | Cascading rebase | Automatic on push | `gh stack rebase --upstack` / `--downstack` | | Sync after merge | `mergify stack sync` | `gh stack sync` | | Open PR in browser | `mergify stack open` (interactive picker) | Links from `gh stack view` | @@ -83,7 +83,7 @@ between them with `gh stack up` and `gh stack down`. | Draft PRs | `mergify stack push --draft` | `gh stack submit --draft` | | Merge Queue | [Native Mergify integration](/merge-queue) | GitHub's built-in merge queue | | Merge a stack | Bottom-up via Merge Queue | `gh stack merge` (not yet implemented) | -| Stack visualization | Stack comment on each PR | Native GitHub UI stack map | +| Stack visualization | Stack comment + browser-extension panel | Native GitHub UI stack map | | Branch naming | Auto-generated ([configurable](/stacks/concepts#branch-mapping)) | Developer-chosen or auto-numbered | | Navigate between PRs | N/A (single branch) | `gh stack up` / `down` / `top` / `bottom` | | Unstack | Not needed (standard Git branches) | `gh stack unstack` | @@ -121,8 +121,10 @@ yet implemented. ## Where gh-stack Is Stronger **Native GitHub UI.** gh-stack gets a stack map rendered directly in the PR -interface. Mergify posts a stack comment on each PR, which works well but is -a comment rather than a native UI element. +interface. Mergify posts a stack comment on each PR and, with the +[browser extension](/merge-queue/browser-extensions) installed, renders the +stack and its revision history as an in-PR panel. Both work well, but neither +is a built-in GitHub UI element. ## Other Considerations diff --git a/src/content/docs/stacks/compare/graphite.mdx b/src/content/docs/stacks/compare/graphite.mdx index 33ea19234d..9866aabb06 100644 --- a/src/content/docs/stacks/compare/graphite.mdx +++ b/src/content/docs/stacks/compare/graphite.mdx @@ -78,11 +78,11 @@ between them with `gt up`, `gt down`, `gt top`, and `gt bottom`. | Create a stack | `mergify stack new` + commits | `gt create [name]` per branch | | Push changes | `mergify stack push` (rebase + push + PRs) | `gt submit` (force-push + PRs) | | View the stack | `mergify stack list` | `gt log` | -| Edit mid-stack | `mergify stack edit` (interactive rebase) | `gt modify` (auto-restacks) | +| Edit mid-stack | `mergify stack edit ` (targeted pause) | `gt modify` (auto-restacks) | | Absorb fixups | Not supported | `gt absorb` (auto-routes staged hunks) | | Split a commit | Not supported | `gt split` (by commit, hunk, or file) | -| Reorder changes | `mergify stack edit` (rebase -i) | `gt reorder` (interactive) | -| Squash / fold | `mergify stack edit` (squash/fixup) | `gt squash` / `gt fold` | +| Reorder changes | `mergify stack reorder` or `mergify stack move` | `gt reorder` (interactive) | +| Squash / fold | `mergify stack squash` or `mergify stack fixup` | `gt squash` / `gt fold` | | Undo | Git reflog | `gt undo` (reverts recent `gt` operations) | | Sync after merge | `mergify stack sync` | `gt sync` | | Navigate between PRs | N/A (single branch) | `gt up` / `down` / `top` / `bottom` | @@ -215,13 +215,13 @@ This installs the CLI and adds a `commit-msg` hook that generates | Graphite | Mergify Stacks | |---|---| | `gt create` (create a branch) | `git commit` (create a commit) | -| `gt modify` | `git commit --amend` or `mergify stack edit` | +| `gt modify` | `git commit --amend` or `mergify stack edit ` | | `gt submit` | `mergify stack push` | | `gt up` / `down` / `top` / `bottom` | Not needed (single branch) | | `gt restack` | Automatic on `mergify stack push` | | `gt sync` | `mergify stack sync` | | `gt log` | `mergify stack list` | -| `gt absorb` | `git commit --fixup` + `mergify stack edit` | +| `gt absorb` | No direct equivalent (amend each commit by hand) | ### Workflow before and after diff --git a/src/content/docs/stacks/compare/plain-git.mdx b/src/content/docs/stacks/compare/plain-git.mdx new file mode 100644 index 0000000000..4c9ff7b0d6 --- /dev/null +++ b/src/content/docs/stacks/compare/plain-git.mdx @@ -0,0 +1,155 @@ +--- +title: Mergify Stacks vs Plain Git +description: An honest comparison of Mergify Stacks and stacking by hand with git rebase and gh pr create. +--- + +import { Image } from 'astro:assets'; +import StacksLocalModel from '~/components/StacksLocalModel.astro'; +import ComparisonLogo from '~/components/ComparisonLogo.astro'; +import StackComment from '../../../images/stack-comment.png'; + + + +You don't need a tool to stack pull requests. Git already lets you split work +into a chain of commits, and `gh pr create` opens a PR for each one. Plenty of +teams run a small shell wrapper around exactly that. This page is an honest +look at what you give up, and what you get back, by letting Stacks manage the +chain instead of doing it by hand. + +## Stacking by Hand + +The manual recipe is straightforward, at least at first. You create a branch +per change, rebase each branch onto the one below it, push them all, and open a +PR for each with the right base: + +```bash +git checkout -b feat/model main +# work, commit +git checkout -b feat/endpoint feat/model +# work, commit +git checkout -b feat/tests feat/endpoint +# work, commit + +git push -u origin feat/model feat/endpoint feat/tests +gh pr create --base main --head feat/model +gh pr create --base feat/model --head feat/endpoint +gh pr create --base feat/endpoint --head feat/tests +``` + +It works. The cost shows up later, every time the stack changes. Amend the +bottom commit and you re-push three branches by hand. Reorder two changes and +you rebase each dependent branch in turn. When the bottom PR merges, you +re-target the next PR to `main` yourself and clean up the merged branch. Each +of these is a few commands, and each is easy to get subtly wrong. + +## One Branch Instead of a Tree + +Stacks keeps you on a single branch of ordinary commits. There are no per-PR +branches to create, name, or rebase: + + + +A [Change-Id](/stacks/concepts#change-id) trailer (added by a `commit-msg` +hook) ties each commit to its PR, so the mapping survives amends, rebases, and +reorders. `mergify stack push` rebases on the target branch, pushes a remote +branch per commit, opens or updates one PR each, and chains them in dependency +order, in one command. + +## Feature Comparison + +| Task | Plain Git + `gh` | Mergify Stacks | +|---|---|---| +| Model | One branch per PR, rebased in a tree | One branch, many commits | +| Map commit to PR | You track which branch is which PR | Change-Id trailer, automatic | +| Open PRs | `gh pr create` per branch, set each base | Included in `mergify stack push` | +| Amend mid-stack | Rebase every dependent branch, re-push each | `mergify stack edit `, then push | +| Reorder | Manual `git rebase -i` across branches | `mergify stack reorder` / `mergify stack move` | +| Squash / drop | `git rebase -i`, then re-push affected branches | `mergify stack squash` / `fixup` / `drop` | +| Push only what changed | You decide which branches to push | Smart updates: only changed PRs are touched | +| After a merge | Re-target next PR, delete merged branch | `mergify stack sync` detects and drops merged commits | +| Re-targeting on merge | Manual base change per PR | Automatic when the bottom PR lands | +| Stack overview on the PR | None (or hand-written) | Stack comment + revision history on every PR | +| Merge Queue | Per-PR, no stack awareness | [Stack-aware queueing](/merge-queue/stacks) | + +## Where Plain Git Is Enough + +Stacks earns its keep on changes that span several commits and get reworked +during review. If that's not your situation, the manual path is fine, and +honestly simpler: + +- **Your changes are already small.** A one-commit fix is one PR. There's + nothing to stack, so `gh pr create` is all you need. + +- **You rarely reorder or amend mid-stack.** The manual cost is in reworking a + chain. If you push once and merge, you never pay it. + +- **You only stack occasionally.** For a one-off pair of dependent PRs, three + `gh` commands beat installing a tool. + +Stacks doesn't change this calculus by force. Even with the CLI installed, a +single small commit is still a single small PR. + +## Where Mergify Is Stronger + +**The chain maintains itself.** Most of the work is keeping a stack correct +through review, not creating it. Smart updates push only the PRs whose commits +changed, re-targeting happens automatically as PRs merge, and `mergify stack +sync` drops already-merged commits and rebases the rest. By hand, each of those +is a manual step you can forget. + +**Identity survives history rewrites.** Because the Change-Id lives in the +commit message, amending or reordering a commit keeps it mapped to the same PR. +A hand-rolled script keyed on branch names loses that mapping the moment you +rebase, which is why homegrown wrappers tend to recreate PRs or orphan +branches. + +**Reviewers get context for free.** Every PR carries a stack comment and a +revision-history timeline showing where it sits in the chain and what changed +between pushes. A custom script would have to generate and maintain that +itself. + +Stack comment showing a PR's position in the chain + +**Merge Queue integration.** Stacks are queued as a unit: +[`@mergifyio queue`](/commands/queue) on the top PR enqueues the whole chain +bottom-up, and a failure cascades cleanly to the rest. See +[Stacked PRs in the Merge Queue](/merge-queue/stacks). + +## Coming From a Custom Script + +If you already wrap `git rebase` and `gh pr create`, you don't have to throw +anything away. Stacks runs on the same Git primitives, so you can adopt it +incrementally: + +```bash +uv tool install mergify-cli +mergify stack setup +``` + +Setup installs the `commit-msg` hook that adds Change-Ids and a `pre-push` hook +that nudges you toward `mergify stack push`. From there: + +- **Existing commits without Change-Ids** get one when you reword them: run + `git rebase -i `, mark each commit `reword`, and the hook fills in the + trailer on save. + +- **Re-runs are safe.** `mergify stack push` compares each commit's Change-Id + and SHA against GitHub and only touches what changed, so running it twice + does no extra work. + +- **There's an off-ramp.** On GitHub, Stacks creates ordinary branches and PRs, + plus the stack and revision-history comments it posts on each PR. None of it + is a proprietary format. The only local artifact is the `Change-Id:` trailer + in your commit messages. Stop using the CLI and your history is still clean, + standard Git; the trailer is just an inert line of text. + +## Getting Started + +```bash +uv tool install mergify-cli +mergify stack setup +mergify stack push +``` + +See the [setup guide](/stacks/setup) for the full walkthrough, or +[Creating Stacks](/stacks/creating) to push your first stack. diff --git a/src/content/docs/stacks/reviewing.mdx b/src/content/docs/stacks/reviewing.mdx index 7b28c7aec1..1f247c1aa3 100644 --- a/src/content/docs/stacks/reviewing.mdx +++ b/src/content/docs/stacks/reviewing.mdx @@ -5,6 +5,7 @@ description: A reviewer's guide to navigating and reviewing stacked pull request import { Image } from 'astro:assets'; import StackComment from '../../images/stack-comment.png'; +import RevisionHistory from '../../images/stacks-revision-history.png'; Stacked PRs look and feel like regular pull requests, just smaller. You don't need to install anything or learn new tools to review them. @@ -55,6 +56,25 @@ Review comments work exactly like any other PR: When the author addresses your feedback, they'll rebase and push. The PR updates in place, and your review comments stay attached to the relevant lines. +## Revision History + +Each push keeps a revision-history comment on the PR, so you can see how the +commit evolved between reviews instead of guessing what changed since you last +looked. The timeline marks every revision, from the initial push through later +rebases and content edits, and links to the underlying commit SHAs: + +Mergify Stacks panel with the stack and its revision history timeline on a PR + +When the author amends a commit, they can attach a short note explaining why. +That reason shows up against the matching revision, so you don't have to diff +old and new SHAs to understand what moved. + +:::tip + The [Mergify browser extension](/merge-queue/browser-extensions) renders the + stack and its revision history directly in the GitHub pull request, as shown + above. +::: + ## Merging Stacked PRs Stacked PRs merge bottom-up. The first PR in the stack (targeting `main`) diff --git a/src/content/docs/stacks/setup.mdx b/src/content/docs/stacks/setup.mdx index faaeba6fdf..b92120a7bb 100644 --- a/src/content/docs/stacks/setup.mdx +++ b/src/content/docs/stacks/setup.mdx @@ -134,6 +134,7 @@ These optional Git config settings let you customize Stacks behavior: | `mergify-cli.stack-branch-prefix` | `stack/{username}` | Prefix for remote branch names | | `mergify-cli.stack-create-as-draft` | `false` | Create PRs as drafts by default | | `mergify-cli.stack-keep-pr-title-body` | `false` | Don't overwrite PR title/body on updates | +| `mergify-cli.stack-revision-history` | `true` | Keep a revision-history comment on each PR | Example: diff --git a/src/content/docs/stacks/updating.mdx b/src/content/docs/stacks/updating.mdx index 6d263af64c..a2dd76e855 100644 --- a/src/content/docs/stacks/updating.mdx +++ b/src/content/docs/stacks/updating.mdx @@ -9,8 +9,14 @@ After pushing a stack, you'll need to make changes: responding to review feedback, adding commits, rearranging order, or squashing. This page covers every common scenario. -In all cases, the workflow is the same: **make your changes locally, then run -`mergify stack push`** to sync with GitHub. +In all cases, the workflow is the same: **edit your stack locally, then run +`mergify stack push`** to sync with GitHub. Stacks ships dedicated commands for +every common history rewrite, so you don't have to drive `git rebase -i` by +hand. + +Each editing command accepts a commit as either a short SHA or a +[Change-Id](/stacks/concepts#change-id) prefix. Most of them also support +`--dry-run` (`-n`) to print the plan without touching your branch. ## Amend the Latest Commit @@ -31,31 +37,15 @@ Only the PR corresponding to the amended commit is updated. ## Edit a Commit Mid-Stack -To modify a commit that isn't at the top of your branch, use interactive rebase. -Stacks provides a shortcut: +To modify a commit that isn't at the top of your branch, point +`mergify stack edit` at it. Pass a short SHA or Change-Id prefix and the rebase +pauses on that commit: ```bash -mergify stack edit -``` - -This opens `git rebase -i` from the fork point of your stack. You'll see your -commits listed: - -```text -pick a1b2c3d feat: add user data model -pick d4e5f6a feat: add user registration endpoint -pick g7h8i9b test: add user registration tests -``` - -Change `pick` to `edit` for the commit you want to modify: - -```text -edit a1b2c3d feat: add user data model -pick d4e5f6a feat: add user registration endpoint -pick g7h8i9b test: add user registration tests +mergify stack edit a1b2c3d ``` -Save and close. Git pauses at that commit so you can make changes: +Git stops at the target so you can make changes: ```bash # Make your changes @@ -70,25 +60,42 @@ Then push: mergify stack push ``` +Only the amended commit's PR (and any later PRs whose content shifts) is +updated. + +## Reword a Commit Message + +To change a commit's message without touching its contents: + +```bash +mergify stack reword d4e5f6a -m "feat: add user registration API endpoint" +``` + +The Change-Id is preserved, so the PR keeps its identity. Because Stacks +derives the PR title and body from the commit message, the corresponding PR's +title and body update on the next push. + ## Reorder Commits -Open interactive rebase and change the line order: +List every commit in the order you want, by SHA or Change-Id prefix: ```bash -mergify stack edit +mergify stack reorder d4e5f6a a1b2c3d g7h8i9b ``` -```text -pick d4e5f6a feat: add user registration endpoint -pick a1b2c3d feat: add user data model -pick g7h8i9b test: add user registration tests +To move a single commit without listing the whole stack, use `move` with +`first`, `last`, `before`, or `after`: + +```bash +mergify stack move g7h8i9b first +mergify stack move a1b2c3d after d4e5f6a ``` -Save and close. The PRs re-chain automatically when you push. +The PRs re-chain automatically when you push. :::caution Reordering can cause conflicts if later commits depend on earlier ones. Git - will pause and ask you to resolve conflicts during the rebase. + pauses and asks you to resolve conflicts during the rebase. ::: ## Add a Commit @@ -105,42 +112,79 @@ correct position in the chain. ## Remove a Commit -Open interactive rebase and change `pick` to `drop` (or delete the line): +Drop one or more commits by SHA or Change-Id prefix: + +```bash +mergify stack drop d4e5f6a +``` + +On push, the orphan remote branch is deleted and the remaining PRs re-chain. + +## Squash or Fixup Commits + +To fold a commit into its parent and **drop** its message, use `fixup`: + +```bash +mergify stack fixup g7h8i9b +``` + +To combine commits into a target while **keeping** the target's message, use +`squash`. Sources go before the `into` token, the target after it: ```bash -mergify stack edit +mergify stack squash d4e5f6a into a1b2c3d ``` -```text -pick a1b2c3d feat: add user data model -drop d4e5f6a feat: add user registration endpoint -pick g7h8i9b test: add user registration tests +Pass `-m` to rewrite the resulting message instead of keeping the target's: + +```bash +mergify stack squash d4e5f6a into a1b2c3d -m "feat: add user model and endpoint" ``` -On push, the orphan remote branch is deleted and the remaining PRs re-chain. +The surviving commit keeps its Change-Id, so the corresponding PR is updated. +The absorbed commit's remote branch is cleaned up automatically. -## Squash Commits +## Preview Before You Rewrite -To combine two commits into one, use `squash` or `fixup` in interactive rebase: +The history-rewriting commands (`reword`, `reorder`, `move`, `drop`, `fixup`, +`squash`) accept `--dry-run` (`-n`) to print the resulting plan without changing +your branch: ```bash -mergify stack edit +mergify stack reorder d4e5f6a a1b2c3d g7h8i9b --dry-run ``` -```text -pick a1b2c3d feat: add user data model -squash d4e5f6a feat: add user registration endpoint -pick g7h8i9b test: add user registration tests +## Record Why You Amended + +When you amend a commit that already has a PR, reviewers see a new revision but +not the reason behind it. Attach a note before pushing to explain the change: + +```bash +git commit --amend +mergify stack note -m "address review: rename foo() to bar()" +mergify stack push ``` -The surviving commit keeps its Change-Id, so the corresponding PR is updated. The -absorbed commit's remote branch is cleaned up automatically. +The note travels with the stack and appears in the PR's revision history, so +reviewers understand what changed between revisions without diffing SHAs. See +[Reviewing Stacks](/stacks/reviewing#revision-history) for the reviewer's view. + +By default, `mergify stack push` keeps a revision-history comment on each PR up +to date. Pass `--no-revision-history` (or set +`mergify-cli.stack-revision-history` to `false`) to turn it off. ## After a PR Merges -When the bottom PR in your stack merges into `main`, the next PR's base -automatically updates to `main`. To keep your local branch in sync, just push -again: +When the bottom PR in your stack merges into `main`, bring your local branch +back in sync: + +```bash +mergify stack sync +``` + +`sync` fetches the latest `main`, detects which commits already merged, drops +them from your branch, and rebases the rest. Then push to update the remaining +PRs: ```bash mergify stack push @@ -169,5 +213,5 @@ After PR #1 merges, commit A lands on `main`. The remaining PRs re-chain: ]} /> -Stacks rebases on `main` automatically, detects that the merged commit is already -in `main`, and skips it. The remaining PRs update as needed. +`sync` rebases on `main`, detects that the merged commit is already there, and +skips it. The remaining PRs update as needed on the next push. diff --git a/src/content/images/stack-comment.png b/src/content/images/stack-comment.png index 8e2c99474d..9a2ddae9f5 100644 Binary files a/src/content/images/stack-comment.png and b/src/content/images/stack-comment.png differ diff --git a/src/content/images/stacks-revision-history.png b/src/content/images/stacks-revision-history.png new file mode 100644 index 0000000000..e2d17baac1 Binary files /dev/null and b/src/content/images/stacks-revision-history.png differ diff --git a/src/content/navItems.tsx b/src/content/navItems.tsx index 83f9771caa..c4f85c539c 100644 --- a/src/content/navItems.tsx +++ b/src/content/navItems.tsx @@ -223,6 +223,7 @@ const navItems: NavItem[] = [ icon: 'lucide:scale', children: [ { title: 'Overview', path: '/stacks/compare', icon: 'lucide:lightbulb' }, + { title: 'vs Plain Git', path: '/stacks/compare/plain-git', icon: 'simple-icons:git' }, { title: 'vs gh-stack', path: '/stacks/compare/gh-stack', icon: 'simple-icons:github' }, { title: 'vs Graphite',