Part 2 · Lesson 10 of 12 · about 15 minutes
Two diffs, two questions
Two dots ask how two snapshots differ. Three dots ask what a branch changed.
git diff main..add-loader and git diff main...add-loader will cover before you run them, and check your prediction against Git.A recipe that never got its reason
Since Lesson 2 this course has told you to review a branch with three dots: git diff main...HEAD. It never said what the third dot is for, or what goes wrong with two. That was fine while you wrote the code yourself. When an agent wrote it, the diff is how you judge the work, so you need to know exactly what the diff is comparing.
Every diff has two ends. The two commands differ only in where they put the left end. To say where, you need one new idea.
The merge base
Walk backwards through the history of main and of your branch. The most recent commit they both contain is their merge base: the point where the branch split off. Git's definition is "the best common ancestor(s) between two commits to use in a three-way merge."1 GitHub's docs describe it as "The most recent common commit of both branches (merge base)."3
One command prints it:
$ git merge-base main add-loader
394e8c73a0127ff8373d51eafb6b2ce45582bf06
The output is "a commit which is reachable from both" names by following parents.1 It is a full 40-character hash, and it is the same whichever name you put first.
add-loader. Two dots cover those and, in reverse, the dashed commit that landed on main after the split.Two diffs
| Two-dot diff | Three-dot diff | |
|---|---|---|
| Command | git diff main..add-loader | git diff main...add-loader |
| Question | How do these two snapshots differ right now? | What did this branch change since it split? |
| Left end | The tip of main | The merge base |
| Right end | The tip of add-loader | The tip of add-loader |
| Same as | git diff main add-loader2 | git diff $(git merge-base main add-loader) add-loader2 |
Work that landed on main after the split | Shows up, reversed | Doesn't show up |
The Git reference says the two-dot form "is synonymous to the earlier form (without the ..) for viewing the changes between two arbitrary <commit>." The three-dot form is "to view the changes on the branch containing and up to the second <commit>, starting at a common ancestor of both."2
Why "reversed"?
A diff is a recipe for turning the left end into the right end. Suppose someone adds a line to README.md on main after your branch split. The tip of main has that line. The tip of your branch doesn't, because the branch started before the line existed. To turn the left end into the right end, the recipe has to take the line out. So the two-dot diff prints it with a minus sign.
A red minus line in a two-dot diff doesn't mean the agent deleted anything. It can mean main gained that line after the branch split. If you review an agent's branch with two dots, you will blame it for undoing other people's work.
The three-dot diff avoids this. GitHub's docs put it this way: "Because the three-dot comparison uses the merge base, it focuses on 'what a pull request introduces.'"3 That's why "Pull requests on GitHub show a three-dot diff."3 The Files changed tab you've used since Lesson 4 is this lesson's right-hand column.
When the two are the same
If main hasn't moved since the branch split, the merge base is the tip of main. Both diffs then have the same two ends and print the same text. The distinction only matters once main moves. Lesson 11 is about that moment.
git log uses the dots differently. In git log main..HEAD from Lesson 6, two dots select a range of commits. In git diff the dots pick two endpoints, and the docs warn that there they "do not mean a range."2
Try it: predict, then check
The simulator has two new commands, git merge-base and git diff. It has no files, so its diff prints no diff text. It tells you which commits' changes the diff would cover and marks them on the graph: a square for the merge base, a shaded disc for changes the diff includes, and a dashed ring for changes it includes in reverse.
main has two commits. add-loader split from the second one and has two commits of its own. Before you press Enter on tasks 2, 3, 5 and 6, decide which commits will be marked. Then run the command and see whether you were right.
After task 5 the dashed ring sits on main's new commit: the two-dot diff has spread onto main. After task 6 the marks are back where they were in task 2. The branch didn't change, so the three-dot diff didn't either.
Do it for real: actual diff text
Now the same thing in penguins-lab, with real files. Tick each step as you go.
-
$ git switch main $ git pull $ git switch -c add-loader -
import pandas as pd def load_penguins(path: str) -> pd.DataFrame: """Read the penguins CSV into a DataFrame.""" return pd.read_csv(path)$ git add load.py $ git commit -m "Add load_penguins" -
$ git merge-base main add-loader 394e8c73a0127ff8373d51eafb6b2ce45582bf06 $ git log --oneline -1 main 394e8c7 … $ git diff --stat main...add-loader load.py | 5 +++++ 1 file changed, 5 insertions(+) $ git diff --stat main..add-loader load.py | 5 +++++ 1 file changed, 5 insertions(+)Your hashes will differ from these, but your two hashes will start with the same seven characters.
-
Run the tests with `python -m pytest`.$ git switch main $ git switch -c readme-testing # ...edit README.md... $ git commit -am "Say how to run the tests"Land it with the Lesson 4 loop:
git push -u origin readme-testing,gh pr create --fill,gh pr checks --watch,gh pr merge --squash --delete-branch. Thengit switch mainandgit pull. -
$ git merge-base main add-loader 394e8c73a0127ff8373d51eafb6b2ce45582bf06 $ git log --oneline -1 main c9e448a Say how to run the tests (#12) -
$ git diff --stat main...add-loader load.py | 5 +++++ 1 file changed, 5 insertions(+) $ git diff --stat main..add-loader README.md | 2 -- load.py | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) -
$ git diff main..add-loader -- README.md --- a/README.md +++ b/README.md @@ -1,3 +1 @@ # Palmer Penguins Data Lab - -Run the tests with `python -m pytest`.Nobody deleted that line.
add-loadernever had it. The two-dot diff shows the README change frommain, reversed.
Leave add-loader unmerged. Lesson 11 picks it up from here.
Recall the commands
Type each command from memory.
Print the merge base of main and add-loader.
Show only what add-loader changed since it split from main.
Show how the tip of main and the tip of add-loader differ right now.
Check yourself
Which commit is the merge base of main and add-loader?
- Their most recent shared commit
- The newest commit on main
- The first commit ever made
- The newest commit on add-loader
The merge base is the best common ancestor: the latest commit that both histories contain. git merge-base main add-loader prints it.
git diff main..add-loader shows a README line being removed. The agent never touched the README. What is the likely cause?
- main gained that line later
- The agent secretly deleted it
- Git lost the file's history
- The diff ran on GitHub
Two dots compare the two tips. A line that landed on main after the split is missing from the branch's snapshot, so it shows as a removal.
When do the two-dot and three-dot diffs print exactly the same text?
- main hasn't moved since the split
- The branch has only one commit
- Both branches were pushed to GitHub
- The branch touches one file only
If main hasn't moved, the merge base is the tip of main, so both diffs have the same two ends.
Which diff does a pull request's Files changed tab show?
- Three-dot, from the merge base
- Two-dot, between the two tips
- Only the latest commit's changes
- Everything since the first commit
"Pull requests on GitHub show a three-dot diff": the merge base against the latest version of the head branch.
git diff main..HEAD and git diff main...HEAD each cover." Check its answer with git merge-base.
Sources
- Git reference, git-merge-base (description and discussion). Output also verified locally with Git 2.52.
- Git reference, git-diff: the two-dot form, the three-dot form, and the note that diff compares endpoints, not ranges.
- GitHub Docs, Three-dot and two-dot Git diff comparisons and About three-dot comparison on GitHub.