Dec 11th, 2020 - written by Kimserey with .
When using a branching mechanism like GitFlow or Mainline, we usually create pull request from short lived branches to long lived branches (e.g. develop
, master
). The pull request diff page gives a view of the changes that were brought by the branch. By default all repositories managers like GitHub, GitLab or Bitbucket uses “triple dot” diff to show diff. In this post we’ll look at the difference between “triple dot” and “double dot” diff.
In order to check the diff between two branches in the follownig scenario:
1
2
3
C (3fd5643, my-branch)
/
B---D (5b07cc7, master)
with git we can use
1
git diff master my-branch
which is the same as
1
git diff master..my-branch
This will give us the difference between master
and my-branch
, which is the difference between D
and C
.
It will show:
In this particular example, because my-branch is branched from master, by definition there is no extra commit on master that C does not contain hence a simple git diff master
is guaranteed to show only the changes brought by my-branch.
In other words, for branches, diff double dot gives us the difference between the tip of two branches.
We saw how double dot allows us to see the diff between two latest of two branches.
Now with our previous example, let’s say someone merge a change:
1
2
3
C (3fd5643, my-branch)
/
B---D---E (5b07cc7, master)
If we make a pull request, we only want to show our changes based of the merge-base (also known as most common ancestor). We don’t want E
to show up in our pull request diff - because first it wasn’t brought by us, and second it’s already on master
, there is no value in adding it for review “again”.
In order to do this, repository managers like GitHub or GitLab use the “triple dot”:
1
git diff master...my-branch
The triple dot compares my-branch
with the most common ancestor between master
and my-branch
. This allows to only show changes brought up by my-branch
. To find the base, we can use merge-base
:
1
git merge-base master my-branch
This would give us the most recent common ancestor between master
and my-branch
which we could then use with a double dot diff to find just the changes added by my-branch
:
1
git diff `git merge-base master my-branch` my-branch
This essentially results in the same as a triple dot diff.
A common scenario where pull-requests diffs can be confusing is when we branch from master
, then branch out of the first branch and employ a squash
merge strategy to merge pull-requestes. This scenario can happen when we want to break a pull-requests in smaller bits to gradually get them reviewed and don’t want to keep intermediate commits:
1
2
3
4
5
D (feature-2 / open PR against master #2)
/
C (feature-1 / open PR against master #1)
/
B (master)
In this scenario, we branch out of master
B
where we add a commit C
in feature-1
and submit a pull-request #1 to merge back to master
. At the same time, we branch out from feature-1
at C
and create a commit D
on feature-2
where we open a second pull-request against master
#2.
At this point, PR #1 contains the changes from C
and PR #2 contains the changes from C
and D
in their diffs.
Now if PR #1 is merged,
1
2
git checkout master
git merge --squash feature-1
we’ll get the following graph:
1
2
3
4
5
D (feature-2: open PR against master #2)
/
C (feature-1)
/
B---E (master: squashed commit of feature-1)
master
now has E
which contains the merge of C
, the result of the squash merge.
But if we check the pull-request #2 of feature-2
, because it uses triple-dot diff, the pull-request will still show the diff of C
plus D
even though C
has been merged to master
(via E
). We can see the same diff if we do:
1
git diff master...feature-2
This is because even thought C
is merged, the merge-base hasn’t changed due to the squash behaviour. To fix this we can either merge master
into feature-2
:
1
2
git checkout feature-2
git merge master
which will bring the graph to:
1
2
3
4
5
D---F feature-2 (Merge branch 'master' into feature-2)
/ /
C / (featuer-1)
/ /
B---E (master: squashed commit of feature-1)
Hence moving the common ancestor to E
and therefore the difference would only be what is in F
versus E
which would be only D
, as the changes in D
.
Or the other way would be to rebase feature-2
onto master
:
1
git rebase --onto master feature-1 feature-2
which will result in the following graph:
1
2
3
4
5
C (featuer-1)
/
B---E (master: squashed commit of feature-1)
\
D' (feature-2: rebased from feature-1 onto master)
And that concludes today’s post, I hope you learnt the difference between triple dot and double dot diff in git and how repository managers display the diffs in pull-request!