ReadbackCourseReference · freeSign in
Reference6 sections · built in Lesson 07

LANDINGA BRANCH

The three things the green button can do, what each one costs, how to tell which one a repository uses, and the trap that manufactures conflicts out of nothing.

The three methods

GitHub calls them merge commit, squash and merge and rebase and merge (GitHub docs). Each maps onto plain git. All three produce byte-identical files — checked out, the three repositories are indistinguishable. Only the history differs.

Merge commit

git merge --no-ff feature. Every original commit survives with its own hash, and a new commit with two parents records that the two lines joined.

The only method that lets git log --first-parent main read as one line per landed branch.

Squash

git merge --squash feature, then one commit. One new commit on main, with no link to the branch at all.

The originals stay on the branch, unreferenced by main. See the trap below.

Rebase-merge

git rebase main on the branch, then a fast-forward. Every commit preserved, in order, linear — and every one of them a new object.

GitHub “always updates the committer information and creates new commit SHAs”, so the branch as reviewed no longer exists.

What each one costs

MethodKeeps / costs, and when it is right
Merge commitKeeps every commit and the record of the join. Costs a diamond-shaped history and every “fix a typo” commit in main forever. Right when several people work on one branch, or you need to know which change set something arrived in.
SquashKeeps a linear main where every commit is one reviewed change. Costs the individual commits, and springs the trap below. Right when an agent produces branches of eleven commits called “wip”, “fix”, “fix again” — which is most agent branches.
Rebase-mergeKeeps each commit as its own entry with no merge commits. Costs the truth: committer dates all become the merge moment, and every reviewed hash is dead. Right when commits are curated one-per-logical-change and you want fine-grained git bisect steps.

There is no correct answer, but there is a correct way to decide: pick the one whose failure mode you can live with, then make it the repository’s only enabled option so nobody chooses again. On GitHub that is Settings → General → Pull Requests. Leaving all three enabled means the choice is made by whoever clicks the button — which, increasingly, is not a person.

The squash trap

A squashed branch is dead the moment it lands

Squashing puts the branch’s content in main and leaves no link. So git branch --merged still calls the branch unmerged, and one more commit on it re-proposes every commit that was already squashed in — conflicting on lines that are already correct. Delete it and branch again from main.

The mechanism is reachability, not content. git branch --merged lists “branches whose tips are reachable from the specified commit” (git-branch), and --squash produces “the working tree and index state as if a real merge happened (except for the merge information)” (git-merge). That parenthesis is the whole story.

Walk it: the squash commit names the previous main commit, which names the one before. Nothing on that walk is the branch tip, and nothing anywhere names it. Its content is in main twice over; its commit is in no history at all.

After a squash-mergeWhat you see
git branch --merged mainThe branch is not listed, though every line of its work is in main.
git log --oneline main..featureStill reports the squashed commits as outstanding.
Adding one commit and re-openingThe new PR shows the old commits again, plus the new one.
Merging that second PRConflicts, in a repository where nothing is actually in conflict.

Telling which method a repository uses

  1. git log --graph --oneline --decorate -20 — diamonds mean merge commits; a flat line with (#123) suffixes means squash; a flat line with ordinary messages means rebase-merge.
  2. git branch --merged main — the sharper check. If branches whose work is plainly in main are still listed as unmerged, the repository squashes, and every one of those branches is a trap if anyone picks it back up.
  3. git log --format='%h %an %ad %cn %cd' --date=short — if every committer date on a run of commits is the same instant and the author dates are spread out, the repository rebase-merges.

The practical consequence for an agent: tell it the rule that follows. If the repository squashes, tidying a branch’s history before it lands is work spent on an artifact the merge deletes seconds later — but delete-and-rebranch afterwards is mandatory. If it merge-commits, leave the history alone. Rebasing to resolve conflicts against current main is a separate and legitimate reason under any method.

Taking a landing back

Landed byReverting it
SquashOrdinary: one commit in, one commit out. git revert <sha>.
Merge commitNeeds git revert -m 1 <sha> to name the mainline parent. Without -m git refuses outright — “usually you cannot revert a merge because you do not know which side of the merge should be considered the mainline” (git-revert).
Rebase-mergeOrdinary per commit, but there are several of them, and reverting them out of order will conflict.

The sting on the merge-commit side is worth knowing before you need it in a hurry: reverting a merge “declares that you will never want the tree changes brought in by the merge”, so re-merging that branch later brings in nothing. The fix is to revert the revert, which is as strange as it sounds and is what the docs recommend.

If a second person joins the repository

Everything above is about one branch landing. With several people, branches land while other branches are being tested — so a PR that was green against yesterday’s main can break main when it merges today. That is what a merge queue exists to prevent: it “helps increase velocity by automating pull request merges into a busy branch and ensuring the branch is never broken by incompatible changes” (GitHub docs).

What the queue doesDetail
Builds a merge groupChanges are “grouped into a merge_group with the latest version of the base_branch as well as changes from pull requests ahead of it in the queue” — so each PR is tested against the ones landing before it, not against main as it stands now.
Tests on temporary branchesIt “creates temporary branches with a special prefix to validate pull request changes”, e.g. main/pr-1, main/pr-2. Your CI runs against those.
Ejects failures“If there are failed required status checks or conflicts with the base branch, the pull request will be removed from the queue” — the rest of the queue continues without it.
Still needs a methodThe queue does not replace the choice above: you “select which method to use when merging queued pull requests: merge, rebase, or squash”.

For a solo repository this is machinery you do not need. It is worth recognising because it changes what an agent should expect: with a queue, clicking merge does not merge — it enqueues, the branch is tested again in a combination that has never existed before, and the PR can come back out of the queue without anyone touching it.

A4 or Letter
one ink, no chrome
Sources