.gitbay/wiki/Stacked-MRs.org

v1.30.0
gitbay/.gitbay/wiki/Stacked-MRs.org rendered · source · history · blame · raw

131 lines · 5359 bytes

  1#+title: Stacked merge requests
  2
  3Two or more merge requests where each targets the source branch of the
  4one below it, down to =main=. Review and merge them one layer at a time;
  5the forge moves what is above onto =main= as each layer lands.
  6
  7* What a stack is
  8
  9#+begin_example
 10  main ─── A (feat-a)   !159  feat-a → main
 11             └── B (feat-b)   !160  feat-b → feat-a     stacked on !159
 12                   └── C (feat-c)   !161  feat-c → feat-b     stacked on !160
 13#+end_example
 14
 15The rule is one sentence: =B= is stacked on =A= when =B='s target branch
 16is =A='s source branch, both are open, and both are in the same
 17repository. Nothing is stored and no flag exists. The stack is a fact
 18about branches that the forge reads whenever it shows a merge request,
 19so it is never out of date and cannot be forgotten to be set.
 20
 21* Why
 22
 23- Keep working on a change that depends on one still in review, instead
 24  of waiting for it to merge or carrying both in one branch.
 25- Each merge request holds one reviewable change. The diff of =B= is
 26  =B='s commits only, not =A='s underneath it.
 27- Reviews and checks sit on the layer they were made on and stay there
 28  when the layer below merges.
 29
 30* The workflow is branches
 31
 32There is no stack command. Branch from the branch below, push, and open
 33the merge request against it:
 34
 35#+begin_src sh
 36git checkout -b feat-a main    && ...commit... && git push -u origin feat-a
 37gitbay mr create --source feat-a --target main   --title "A"
 38git checkout -b feat-b feat-a  && ...commit... && git push -u origin feat-b
 39gitbay mr create --source feat-b --target feat-a --title "B"
 40#+end_src
 41
 42The second =mr create= answers with a line the first did not:
 43
 44#+begin_example
 45created krz/gitbay!160 (feat-b -> feat-a)
 46stacked on !159 A
 47#+end_example
 48
 49=mr show= carries =stacked_on= (the merge request below) and =stacked=
 50(the ones above); =mr list= rows carry =stacked_on=; the merge request
 51page says "Stacked on !159" in the header and "Builds on this: !161"
 52below it.
 53
 54Changing a lower layer is a rebase you do yourself. Amend =feat-a=,
 55then =git rebase feat-a= on =feat-b= and each layer above, and
 56force-push them. A force-push stales the reviews on that layer when it
 57changes the layer's diff, the same as on any merge request; a rebase
 58that carries the same change keeps them. The server never rewrites your commits:
 59it holds no signing key, and a rebase it performed would land commits
 60nobody signed.
 61
 62* Merging
 63
 64Merge from the bottom. When =A= merges, every merge request stacked on
 65it is retargeted onto what =A= merged into, with a system comment:
 66
 67#+begin_example
 68retargeted from feat-a to main: !159 merged
 69#+end_example
 70
 71Reviews on the retargeted merge request are kept. After a fast-forward
 72or a merge commit, =A='s commits are on =main=, so =B='s diff against
 73=main= is the diff its reviewers approved; there is nothing to stale.
 74
 75That is also why a stack constrains the strategy. A squash or rebase
 76merge of =A= puts different commits on =main= than the ones =B= builds
 77on, and =B='s diff would carry =A='s changes a second time. So while
 78anything is stacked on a merge request, =--strategy squash= and
 79=--strategy rebase= are refused:
 80
 81#+begin_example
 82!159 is stacked on by !160; a squash merge rewrites the commits they
 83build on. Merge with --strategy ff or merge, or merge the stack into
 84feat-a first
 85#+end_example
 86
 87The second option is real: merging =B= into =feat-a= while =A= is open
 88is allowed and collapses =B= into =A=, whose head moves as on any push
 89to its branch. Closing =A= without merging leaves the stack alone; its
 90branch still exists and =B= still targets it.
 91
 92Merge gates apply per layer as on any merge request: required
 93approvals, resolved threads, green checks, and =require_signed_commits=,
 94which under a stack already forces fast-forward.
 95
 96* Where it works
 97
 98- CLI and bare SSH: everything above.
 99- Web: the header shows the stack both ways. Creating a merge request
100  against a branch that is another's source works from the form; the
101  stack appears once it exists.
102- iOS: renders the retarget comment; no stack view yet.
103- Same repository only. A fork's branch is not something another merge
104  request can target, so a stack cannot cross a fork.
105
106See [[Parity][Parity]] for the row.
107
108* A stack that merged
109
110The six merge requests that shipped v1.6.0 were the first stack merged
111on gitbay.org, one commit each, on 2026-09-02:
112
113| MR   | source          | target          |
114|------+-----------------+-----------------|
115| !159 | stack-1-reaper  | main            |
116| !160 | stack-2-runners | stack-1-reaper  |
117| !161 | stack-3-healthz | stack-2-runners |
118| !162 | stack-4-monitor | stack-3-healthz |
119| !163 | stack-5-lfs     | stack-4-monitor |
120| !164 | stack-6-verify  | stack-5-lfs     |
121
122Each was merged with =mr merge --strategy ff= in that order. After each
123one, the next reported =target_ref: main= and no =stacked_on=, with the
124comment naming the merge — =retargeted from stack-1-reaper to main:
125!159 merged= on !160, and so on up to !164. No =mr retarget= was typed.
126=main= ended with the six commits in order on top of the commit that
127had added stacking.
128
129One thing the run exposed: each fast-forward queued the commit's CI jobs
130again on =main=, although the same commit had just passed them on its
131branch. That is [[https://gitbay.org/krz/gitbay/issues/90][#90]].