0bf849e15 days ago36 lines · 3.3 KB| 1 | # 0027 — Squash and rebase carry the head along |
| 2 |
| 3 | **Status:** adopted |
| 4 |
| 5 | ## Decision |
| 6 |
| 7 | `POST /pulls/:number/merge` takes `method`: `merge` (the default), `squash` or `rebase`. All three build their result in the base cache and land it through `commitPush`, exactly as [0016](0016-a-pull-request-spans-two-logs.md) describes for the merge commit. |
| 8 |
| 9 | - **merge** is unchanged: `merge-tree`, then a commit with the base tip and the head tip as parents. |
| 10 | - **squash** uses the same `merge-tree` result, committed with the base tip as its only parent. The subject defaults to `<title> (#<number>)` and the body to the head's commit messages; the request's detail carries both as `squash`, and the merger may rewrite either before merging. The author is the request's author and the committer is whoever pressed the button. |
| 11 | - **rebase** replays every non-merge commit in `mergeBase..head` onto the base tip, one `merge-tree --merge-base=<commit>^` per commit. Each copy keeps its author, author date and message; the committer is whoever pressed the button. Merge commits are dropped, the way `git rebase` linearizes a branch. |
| 12 |
| 13 | The pack for every method includes the head tip as well as the new base tip, excluding only the base tip. |
| 14 |
| 15 | ## Why the head rides along |
| 16 |
| 17 | A merged request reads its commits and diff from the base cache alone, as `mergeCommit^1..headSha` ([0016, addendum](0016-a-pull-request-spans-two-logs.md)). A merge commit makes the head reachable, so its objects were already in the pack. A squash or a rebase leaves the head unreachable from the base, and for a fork its commits exist in no object the base log holds. |
| 18 |
| 19 | Adding the head to the pack keeps the addendum true for every method, with no new column and no second code path for merged requests: |
| 20 |
| 21 | - `mergeBase(mergeCommit^1, headSha)` is still the commit the branches diverged from, since both the squashed commit's parent and the last rebased commit's parent descend from the base tip. |
| 22 | - The commits tab lists the head's original commits, and review comments pinned to `headSha` stay on the diff they were written against. |
| 23 |
| 24 | The objects are unreachable from any ref, which is safe because nothing prunes a cache: [0012](0012-forward-only-materialization.md) replays every entry with `index-pack`, which keeps everything in the pack. |
| 25 |
| 26 | ## Alternatives rejected |
| 27 |
| 28 | - **`git replay`** does the rebase in one call, but it is marked experimental, its output for a raw sha range is empty unless a ref is involved, and writing a scratch ref into a shared cache is what [0016](0016-a-pull-request-spans-two-logs.md) already rejects. |
| 29 | - **Storing the method and the pre-merge base tip** on the row would let a merged request read `baseSha..mergeCommitSha` instead. It loses the original commits for a squash and every review comment's line for both, and needs a migration for a value the pack already answers. |
| 30 |
| 31 | ## Consequences |
| 32 |
| 33 | - `mergeable` still reports whether the two tips merge. A rebase can conflict where the merge does not, one commit at a time; that surfaces as a 409 naming the paths of the first commit that failed, and nothing is written but loose objects. |
| 34 | - Rebased commits lose their signatures, the same as any `git rebase`. |
| 35 | - A head made only of merge commits rebases to nothing, and is refused as having nothing to merge. |
| 36 | - The method is the merger's choice on each request, not a repository setting: every writer may use all three. |