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.

You will recognise a stale branch, update it with 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 branchIts merge base with mainTwo dots versus three dots
main hasn't moved since the splitThe tip of mainIdentical
main has moved: the branch is staleAn older commitDifferent. Two dots also show main's new work, reversed
The branch has been updatedThe tip of main againIdentical 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

Stale: main moved after the split main add-loader merge base Updated: main merged into the branch main add-loader merge base
The square is the merge base. Updating adds a merge commit to the branch. From then on, everything on 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:

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:

  1. Both branches split from the same commit on main. Neither is stale.
  2. You squash-merge the first pull request. main gains one new commit.
  3. 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.

  1. $ git switch add-loader
    $ git push -u origin add-loader
    $ gh pr create --fill
    $ gh pr view --web

    On Files changed you see only load.py. That is the three-dot diff, and it looks exactly as it would if main had 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.

  2. $ git fetch
    $ git log --oneline HEAD..origin/main
    c9e448a Say how to run the tests (#12)

    One commit on main that the branch lacks. It is stale.

  3. $ git merge origin/main
    Merge made by the 'ort' strategy.
     README.md | 2 ++
     1 file changed, 2 insertions(+)
  4. $ 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 log prints nothing. The README lines are gone from the two-dot diff, because the branch now has them.

  5. $ python -m pytest -q
    $ git push
    $ gh pr checks --watch
  6. $ 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?

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?

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?

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?

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 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.

Read Keeping your pull request in sync with the base branch in GitHub Docs (2 minutes), then the "Merging often" paragraph of Branches.
Ask Claude: "Fetch, then tell me whether this branch is behind origin/main. If it is, merge origin/main in, rerun pytest, and summarise what changed on main." or "List my open pull requests and say which ones are behind main."

Sources

  1. GitHub Docs, Branches: About three-dot comparison on GitHub and Merging often.
  2. GitHub Docs, Keeping your pull request in sync with the base branch.
  3. GitHub Docs, Managing suggestions to update pull request branches.
  4. GitHub Docs, About protected branches — Require status checks before merging (strict and loose).
  5. GitHub Docs, Events that trigger workflows — pull_request (default activity types; the merge branch).
  6. Git reference, gitrevisions — the two-dot range notation.
  7. 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.
  8. Git reference, git-merge (--edit, --no-edit).