跳到正文
DEV Community· petgem·· 3 小時前AI 評分48

破壞 Lullwood 發布流程的 Squash Merge

The Squash Merge That Broke Our Lullwood Release Train

AI 導讀

Lullwood 的自動化流程把兩個修復 PR 以 squash merge 合併,令 main 與 release/next 失去共同歷史。問題直到下一次版本切割時才以檔案衝突顯現。修正措施是按分支名稱強制建立真正的 merge commit,並在版本切割前確認提交恰有兩個父提交。

正文

Lullwood, our free browser horror game built in three.js and Next.js, deploys from main, and for a few days in August main quietly stopped sharing history with the branch that feeds it. Nobody told it to. This is the git mechanics of how a squash merge did that, and why we found the real cause only on the second try.

How the train runs

Every feature ships the same way. A branch merges into release/next, and periodically release/next merges into main, which is what actually deploys. That second merge, the version cut, only works if the two branches share history.

The automation that merges pull requests was set to squash-merge anything on a branch named lul-* once it carried a ship marker. For an ordinary feature branch that is the right default, because one clean commit reads better than ten.

It is the wrong default for one kind of pull request: a repair PR whose entire purpose is a real merge commit, recording that main had been merged into release/next. A squash discards the second parent of a merge. Two of those repair PRs got squashed on the way in, and the ancestry edge they existed to create was thrown away in the same motion that appeared to land them.

Why a squash does this

A merge commit has two parents, and git decides what to merge next by walking those ancestry edges back to a common base. A squash merge writes one new commit with one parent. The file contents match, but the history no longer records that the branches were joined, so the next cut can see the same lines changed on both sides with no join between them, and it conflicts.

Nothing broke immediately. The first symptom anyone saw was the next version cut, which opened with a conflict on a file both branches had touched. The problem had already happened days earlier.

Where it broke: the first fix hit the wrong thing

The obvious patch was to detect a release-train repair PR and merge it for real instead of squashing it, by checking the pull request title for a fixed prefix. It shipped, the repair was redone, and it got squashed anyway.

Two things were true at once.

  • The title check could not work in general. Pull request titles are generated from the head commit's subject line, and no convention is enforced anywhere. A differently worded commit gave a differently worded title and skipped the check entirely.
  • The real cause was elsewhere. A separate piece of automation armed GitHub's own native auto-merge in squash mode the moment the pull request opened, completely independent of the merge bot the first fix had patched.

The first fix corrected a mechanism that never actually fired.

What actually held

The working fix stopped inferring intent from titles or content and keyed off the one thing fully under the author's control: the branch name. Every place in the pipeline that can merge a pull request now checks for the release-train branch pattern and forces a real merge commit for it. That covers the bot and both of GitHub's own auto-merge arm points.

On top of that, the version-cut workflow asserts that the commit it is about to tag has exactly two parents, and refuses loudly if it does not. A squashed cut now fails immediately instead of surfacing as a conflict on the cut after next. One way to read the parent count by hand:

git rev-list --parents -n 1 <commit>

That prints the commit followed by its parents, so a real merge commit shows two hashes after the first.

The fix itself shipped without the marker that lets a pull request merge itself on green checks. It touches merge rules and release cuts, the one category of change that still gets a blocking review before it lands, even under a review policy built to let most things merge unreviewed. Some categories of mistake are still worth slowing down for.

What to take from it

If a rule exists to protect a merge commit, key it on something the author controls and write down every path that can merge. We patched one of three. And turn the invariant into a check that fails the moment it is violated, so the damage cannot hide for days.

We're one human founder plus a fleet of Claude agents building Lullwood in the open.

The squash that broke the release train
Play Lullwood free in the browser

來源:DEV Community · dev.to