Part 2 · Lesson 11 of 12 · about 15 minutes
When main moves
A review describes a branch at one moment. main doesn't hold still.
git merge origin/main, and show that the two diffs from Lesson 10 agree again before you merge.Agree, drift, agree again
Lesson 10 ended with two diffs of the same branch that printed different things. Nothing about the branch had changed. main had moved. A branch goes through three states, and the two diffs tell you which one it is in:
| State of the branch | Its merge base with main | Two dots versus three dots |
|---|---|---|
main hasn't moved since the split | The tip of main | Identical |
main has moved: the branch is stale | An older commit | Different. Two dots also show main's new work, reversed |
| The branch has been updated | The tip of main again | Identical again |
GitHub's docs describe the drift: "When you use a two-dot comparison, the diff changes when the base branch is updated, even if you haven't made any changes to the topic branch."1 They describe the cure too: "When you merge the base branch, the diffs shown by two-dot and three-dot comparisons are the same."1
main is also in the branch, so the merge base is the tip of main.What a stale branch does to your review
A stale branch is one whose merge base with main is no longer the tip of main. Your three-dot diff is still an honest list of what the branch changed. What has gone stale is everything you concluded from it:
- You reviewed the change against an older
main. Merging puts the branch's changes on top of today'smain. Nobody has read that combination. - Your tests ran against an older
main.pyteston the branch tested the branch's files, and those files don't include what landed onmainsince. - The green check can be old too. A pull request workflow tests the merged result (Lesson 5), but "By default, a workflow only runs when a
pull_requestevent's activity type isopened,synchronize, orreopened."5 Someone else's merge intomainis none of those. The check tells you about themainthat existed the last time you pushed to the branch.
GitHub's advice is to update "to catch conflicts or test failures early."2 Its docs on status checks name the risk of skipping that step: "Status checks may fail after you merge your branch if there are incompatible changes with the base branch."4
Parallel agents make this routine
In Lesson 7 you ran two Claude Code sessions in two worktrees and opened two pull requests. Look at what the first merge does to the second branch:
- Both branches split from the same commit on
main. Neither is stale. - You squash-merge the first pull request.
maingains one new commit. - The second branch hasn't changed, but its merge base is now one commit behind the tip of
main. It is stale, and so is your review of it.
Every merged pull request moves main underneath every other open branch. With three agent branches open, each merge makes the other two stale. A squash merge behaves the same way as any other merge here: it puts a new commit on main that the other branches don't have.7 The rule for parallel work is short: after each merge, update the branches that are still open and check them again.
Spotting a stale branch
In your terminal
$ git fetch
$ git log --oneline HEAD..origin/main
c9e448a Say how to run the tests (#12)
HEAD..origin/main lists the commits that are on origin/main and not on your branch.6 Any output means the branch is stale. No output means it is up to date.
Fetch first, and compare with origin/main, not main. origin/main only moves when you fetch or pull (Lesson 4), and your local main only moves when you pull. If you skip the fetch, a stale branch looks up to date.
On GitHub
In the merge box near the bottom of a pull request, GitHub offers an Update branch button "when there are no merge conflicts and the branch is behind the base branch."2 The button appears in two cases: main is protected with "Require branches to be up to date before merging," or the repository setting Always suggest updating pull request branches is on (Settings → General → Pull Requests).3 If neither applies, a stale pull request shows no button, so turn the setting on in penguins-lab.
That protection setting is the strict form of the required status checks from Lesson 5. With it on, "The branch must be up to date with the base branch before merging."4 GitHub then refuses to merge a stale branch for you.
Updating the branch
You already know the command from Lessons 7 and 9. On the stale branch:
$ git fetch
$ git merge origin/main
Merge made by the 'ort' strategy.
README.md | 2 ++
1 file changed, 2 insertions(+)
Git opens your editor with a ready-made merge message. Save and close it, or add --no-edit to accept the message as it is.8 The merge commit joins main's new work to your branch. Now the branch contains everything on main, the merge base is the tip of main, and the two diffs agree. GitHub recommends doing this often: "merge the base branch (for example, main) into your topic branch frequently."1
If both sides changed the same lines you get a merge conflict, and you resolve it as you did in Lesson 3. After any update, run the tests again. They are now testing the combination that will land on main.
GitHub's Update branch button does the same merge on GitHub's side.2 If you use it, run git pull afterwards so your local branch has the merge commit too. The button's dropdown also offers a rebase. This course doesn't use rebase.
Do it for real: make the diffs agree again
You left add-loader stale at the end of Lesson 10. Tick each step as you go.
-
$ git switch add-loader $ git push -u origin add-loader $ gh pr create --fill $ gh pr view --webOn Files changed you see only
load.py. That is the three-dot diff, and it looks exactly as it would ifmainhad never moved. Nothing on that tab tells you the branch is stale. Then look in the merge box for Update branch. It is there only if one of the two settings above is on. -
$ git fetch $ git log --oneline HEAD..origin/main c9e448a Say how to run the tests (#12)One commit on
mainthat the branch lacks. It is stale. -
$ git merge origin/main Merge made by the 'ort' strategy. README.md | 2 ++ 1 file changed, 2 insertions(+) -
$ git log --oneline HEAD..origin/main $ git diff --stat origin/main...HEAD load.py | 5 +++++ 1 file changed, 5 insertions(+) $ git diff --stat origin/main..HEAD load.py | 5 +++++ 1 file changed, 5 insertions(+)The
git logprints nothing. The README lines are gone from the two-dot diff, because the branch now has them. -
$ python -m pytest -q $ git push $ gh pr checks --watch -
$ gh pr merge --squash --delete-branch $ git switch main $ git pull
Check yourself
You reviewed add-loader's three-dot diff yesterday. Since then another pull request merged into main. What is true now?
- Merging gives a result nobody reviewed
- The three-dot diff shows main's changes
- Git has deleted the branch's commits
- The pull request was closed automatically
The three-dot diff still lists only the branch's changes. What's new is the main they will land on. That combination hasn't been read or tested.
After git fetch, what does git log --oneline HEAD..origin/main print when your branch is up to date?
- Nothing at all
- Every branch commit
- main's latest commit
- A merge conflict
It lists commits on origin/main that your branch lacks. An up-to-date branch lacks none.
You run git merge origin/main on a stale branch. Where is its merge base with origin/main afterwards?
- At the tip of main
- At the branch's first commit
- At the repository's first commit
- Where it was before merging
After the merge the branch contains all of main, so the most recent shared commit is the tip of main. That's why the two diffs agree again.
Two agent branches are open. You squash-merge the first. What has happened to the second?
- It became stale; update and re-check
- It merged along with the first
- It lost the commits it had
- Nothing; its review is still current
The merge moved main. The second branch didn't change, but the main it will land on did.
Why run git fetch before you check whether a branch is stale?
- origin/main only moves when you fetch
- Fetch merges main into your branch
- Fetch pushes your branch to GitHub
- Fetch reruns the pull request checks
origin/main records where GitHub's main was the last time you talked to GitHub. Without a fetch you compare against old news.
Command drills
You have fetched. List, one line each, the commits on origin/main that your current branch doesn't have.
You're on a stale branch and you have fetched. Bring in the latest main from GitHub.
Summarise with --stat what your current branch changed since it split from origin/main.
Sources
- GitHub Docs, Branches: About three-dot comparison on GitHub and Merging often.
- GitHub Docs, Keeping your pull request in sync with the base branch.
- GitHub Docs, Managing suggestions to update pull request branches.
- GitHub Docs, About protected branches — Require status checks before merging (strict and loose).
- GitHub Docs, Events that trigger workflows — pull_request (default activity types; the merge branch).
- Git reference, gitrevisions — the two-dot range notation.
- GitHub Docs, About pull request merges — Squash and merge. The effect on a second open branch was verified locally with a squash merge and Git 2.52.
- Git reference, git-merge (
--edit,--no-edit).