Skip to content

Instantly share code, notes, and snippets.

@mtilson
Last active April 13, 2020 19:54
Show Gist options
  • Save mtilson/729757da4a1a7a76d5f71fe05d3b7127 to your computer and use it in GitHub Desktop.
Save mtilson/729757da4a1a7a76d5f71fe05d3b7127 to your computer and use it in GitHub Desktop.
what is introduced by feature branch OR what are the differences in triple-dot notations for 'git log' and 'git diff' [git]
Background
  • Let's say we have two users (schacon and jessica) both having write access to the same GitHub repo
[user@pro13 schacon (master)]$ git log --oneline --all 
* 6c4e96c (HEAD -> master, origin/master, origin/HEAD) Initial commit

[user@pro13 schacon (master)]$ git remote -v
origin	https://github.com/txxxx-xx/xxx.git (fetch)
origin	https://github.com/txxxx-xx/xxx.git (push)
[user@pro13 jessica (master)]$ git log --oneline --all 
* 6c4e96c (HEAD -> master, origin/master, origin/HEAD) Initial commit

[user@pro13 jessica (master)]$ git remote -v
origin	https://github.com/txxxx-xx/xxx.git (fetch)
origin	https://github.com/txxxx-xx/xxx.git (push)
  • jessica creates new branch, makes and commits changes, and pushes the branch to origin
[user@pro13 jessica (master)]$ git checkout -b jessica/featureC master
Switched to a new branch 'jessica/featureC'

[user@pro13 jessica (jessica/featureC)]$ vi featureC.txt
[user@pro13 jessica (jessica/featureC ✗)]$ git add featureC.txt
[user@pro13 jessica (jessica/featureC ✗)]$ git commit -m featureC-1
[jessica/featureC 9179f6b] featureC-1
 1 file changed, 1 insertion(+)
 create mode 100644 featureC.txt

[user@pro13 jessica (jessica/featureC)]$ vi featureC.txt
[user@pro13 jessica (jessica/featureC ✗)]$ git add featureC.txt
[user@pro13 jessica (jessica/featureC ✗)]$ git commit -m featureC-2
[jessica/featureC e7277bf] featureC-2
 1 file changed, 1 insertion(+)

[user@pro13 jessica (jessica/featureC)]$ git push origin jessica/featureC
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 4 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 544 bytes | 181.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a pull request for 'jessica/featureC' on GitHub by visiting:
remote:      https://github.com/txxxx-xx/xxx/pull/new/jessica/featureC
remote:
To https://github.com/txxxx-xx/xxx.git
 * [new branch]      jessica/featureC -> jessica/featureC

[user@pro13 jessica (jessica/featureC)]$ git co master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
  • schacon fetches origin, creates local branch based on the remote one pushed by jessica, and checks it out
[user@pro13 schacon (master)]$ git br -avv
* master                6c4e96c [origin/master] Initial commit
  remotes/origin/HEAD   -> origin/master
  remotes/origin/master 6c4e96c Initial commit

[user@pro13 schacon (master)]$ git fetch origin
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 0), reused 6 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), 524 bytes | 87.00 KiB/s, done.
From https://github.com/txxxx-xx/xxx
 * [new branch]      jessica/featureC -> origin/jessica/featureC
 
[user@pro13 schacon (master)]$ git br -avv
* master                          6c4e96c [origin/master] Initial commit
  remotes/origin/HEAD             -> origin/master
  remotes/origin/jessica/featureC e7277bf featureC-2
  remotes/origin/master           6c4e96c Initial commit

[user@pro13 schacon (master)]$ git checkout -b featureC origin/jessica/featureC
Branch 'featureC' set up to track remote branch 'jessica/featureC' from 'origin'.
Switched to a new branch 'featureC'

[user@pro13 schacon (featureC)]$ git br -avv
* featureC                        e7277bf [origin/jessica/featureC] featureC-2
  master                          6c4e96c [origin/master] Initial commit
  remotes/origin/HEAD             -> origin/master
  remotes/origin/jessica/featureC e7277bf featureC-2
  remotes/origin/master           6c4e96c Initial commit
Exercise
  • Let's consider different ways for schacon to see what will be introduced in its master branch if the featureC branch be merged into it
[user@pro13 schacon (featureC)]$ git log --oneline --all # full log
* e7277bf (HEAD -> featureC, origin/jessica/featureC) featureC-2
* 9179f6b featureC-1
* 6c4e96c (origin/master, origin/HEAD, master) Initial commit
  • See all the commits in the featureC branch (including ones the branch was build on) with <rev> range notation (see man gitrevisions)
[user@pro13 schacon (featureC)]$ git log --oneline featureC
e7277bf (HEAD -> featureC, origin/jessica/featureC) featureC-2
9179f6b featureC-1
6c4e96c (origin/master, origin/HEAD, master) Initial commit
  • Exclude commits in the master branch with --not <branch> flag
[user@pro13 schacon (featureC)]$ git log --oneline featureC --not master
e7277bf (HEAD -> featureC, origin/jessica/featureC) featureC-2
9179f6b featureC-1
  • Exclude commits in the master branch with <rev1>..<rev2> range notation (see man gitrevisions)
[user@pro13 schacon (featureC)]$ git log --oneline master..featureC
e7277bf (HEAD -> featureC, origin/jessica/featureC) featureC-2
9179f6b featureC-1
  • Show commits limited by triple-dot range notation (<rev1>...<rev2>), which here has the same result as the above <rev1>..<rev2> range. See man gitrevisions for the differences in meanings for triple-dot syntax applied as 'range' for git log commands and as 'two endpoints' for git diff commands
[user@pro13 schacon (master)]$ git log --oneline master...featureC
e7277bf (origin/jessica/featureC, featureC) featureC-2
9179f6b featureC-1
  • ‼️Incorrect Way‼️
    • Let's try to use git diff to show a full diff of what would happen if we were to merge topic branch (featureC) with master branch (see man git-diff)
    • What happen here are
      • We checkout to the branch featureC
      • We view the changes we have in branch featureC working tree relative to the named commit (commit where master branch points to)
        • These are changes in the current featureC branch relative to the master branch
        • These are changes which will be applied to master branch if we merge current featureC branch to it and only if master is a direct ancestor of featureC
      • Everything is OK till the moment master branch has moved forward (since the point when we created the topic branch from it) - see the further ‼️Incorrect Way: Explanation‼️ item below
[user@pro13 schacon (featureC)]$ git checkout featureC
Already on 'featureC'
Your branch is up to date with 'origin/jessica/featureC'.

[user@pro13 schacon (featureC)]$ git diff master
diff --git a/featureC.txt b/featureC.txt
new file mode 100644
index 0000000..5b41842
--- /dev/null
+++ b/featureC.txt
@@ -0,0 +1,2 @@
+1-st commit
+2-nd commit
  • Let's see what happen if we do the same in various directions

  • 1️⃣ Changes for <left commit>..<right commit>

    • git diff <left commit> <right commit> - changes between two arbitrary commits
      • These are changes in the right commit relative to the left commit
      • These are changes which will be applied to left commit if we merge right commit to it and only if left commit is a direct ancestor of right commit
    • git diff <left commit>..<right commit> - this is synonymous to the previous form
    • Changes for master..featureC
      • These are changes in the featureC branch relative to the master branch
      • These are changes which will be applied to master branch if we merge current featureC branch to it and only if master is a direct ancestor of featureC
[user@pro13 schacon (master)]$ git diff master..featureC # this is what we did above 
diff --git a/featureC.txt b/featureC.txt
new file mode 100644
index 0000000..5b41842
--- /dev/null
+++ b/featureC.txt
@@ -0,0 +1,2 @@
+1-st commit
+2-nd commit
  • 2️⃣ No changes for <the same commit>..<the same commit>
    • No changes for featureC..featureC
[user@pro13 schacon (featureC)]$ git diff featureC # 
[user@pro13 schacon (master)]$ git diff featureC..featureC # the same as above
  • 3️⃣ No changes for <the same commit>..<the same commit>
    • No changes for master..master
[user@pro13 schacon (featureC)]$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

[user@pro13 schacon (master)]$ git diff master # no changes for master..master
[user@pro13 schacon (master)]$ git diff master..master # the same as above
  • 4️⃣ Changes for <left commit>..<right commit> - see item 1️⃣ above
    • Changes for featureC..master
      • These are changes in the master branch relative to the featureC branch
      • These are changes which will be applied to featureC branch if we merge current master branch to it and only if featureC is a direct ancestor of master
[user@pro13 schacon (master)]$ git diff featureC..master # this is opposite of what we did initially above
diff --git a/featureC.txt b/featureC.txt
deleted file mode 100644
index 5b41842..0000000
--- a/featureC.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-1-st commit
-2-nd commit

[user@pro13 schacon (master)]$ git diff featureC # the same as above 
diff --git a/featureC.txt b/featureC.txt
deleted file mode 100644
index 5b41842..0000000
--- a/featureC.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-1-st commit
-2-nd commit

[user@pro13 schacon (master)]$ git checkout featureC
Switched to branch 'featureC'
Your branch is up to date with 'origin/jessica/featureC'.

[user@pro13 schacon (featureC)]$ git diff featureC..master # it doesn't depend on the current working tree
diff --git a/featureC.txt b/featureC.txt
deleted file mode 100644
index 5b41842..0000000
--- a/featureC.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-1-st commit
-2-nd commit
  • ‼️Incorrect Way: Explanation‼️
    • The command [user@pro13 schacon (featureC)]$ git diff master (run from checked out featureC branch) is equal to the git diff master..featureC command
    • The command compares the snapshots of the last commit of the topic branch (featureC) and the snapshot of the last commit on the master branch
    • If the master branch has diverged (for example, if we’ve added a line in a file on the master branch), the diff will look like we’re adding all the new stuff in the topic branch (featureC) and removing everything unique to the master branch
    • What we really want to see are the changes added to the topic branch (featureC) - the work we’ll introduce if we merge this branch with master
    • We do that by having Git compare the last commit on the topic branch (featureC) with the first common ancestor it has with the master branch
    • We can use git merge-base <commit> <commit> command to figure out the common ancestor for the provided commits (<common ancestor for master and featureC>)
    • We can use <common ancestor for master and featureC> running in git diff to determine the changes in the topic branch (featureC) relative to the <common ancestor for master and featureC> and these are changes which will be applied to master branch if we merge current featureC branch to it
    • The command [user@pro13 schacon (featureC)]$ git diff <common ancestor for master and featureC> (run from checked out featureC branch) is equal to the git diff <common ancestor for master and featureC>..featureC command
    • More concise command is git diff $(git merge-base featureC master) which shows the changes for <common ancestor for master and featureC>..featureC
      • These are changes in the featureC branch relative to the <common ancestor for master and featureC>
      • These are changes which will be applied to master branch if we merge current featureC branch to it whatever master branch is a direct ancestor of featureC or not
[user@pro13 schacon (master)]$ git log --oneline --all
e7277bf (origin/jessica/featureC, featureC) featureC-2
9179f6b featureC-1
6c4e96c (HEAD -> master, origin/master, origin/HEAD) Initial commit

[user@pro13 schacon (master)]$ vi README.md
[user@pro13 schacon (master ✗)]$ git add .
[user@pro13 schacon (master ✗)]$ git commit -m "master-3"
[master 796e082] master-3
 1 file changed, 2 insertions(+), 1 deletion(-)
 
[user@pro13 schacon (master)]$ git log --oneline --all --graph
* 796e082 (HEAD -> master) master-3
| * e7277bf (origin/jessica/featureC, featureC) featureC-2
| * 9179f6b featureC-1
|/
* 6c4e96c (origin/master, origin/HEAD) Initial commit

[user@pro13 schacon (master)]$ git diff master..featureC
diff --git a/README.md b/README.md
index 41a4412..bc15726 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1 @@
-# ch5
-3-rd commit
+# ch5
\ No newline at end of file
diff --git a/featureC.txt b/featureC.txt
new file mode 100644
index 0000000..5b41842
--- /dev/null
+++ b/featureC.txt
@@ -0,0 +1,2 @@
+1-st commit
+2-nd commit

[user@pro13 schacon (master)]$ git merge-base featureC master
6c4e96c097ad2645d5d8587f8338f94f1ae5ef87

[user@pro13 schacon (master)]$ git diff 6c4e96c097ad2645d5d8587f8338f94f1ae5ef87..featureC
diff --git a/featureC.txt b/featureC.txt
new file mode 100644
index 0000000..5b41842
--- /dev/null
+++ b/featureC.txt
@@ -0,0 +1,2 @@
+1-st commit
+2-nd commit

[user@pro13 schacon (master)]$ git checkout featureC
Switched to branch 'featureC'
Your branch is up to date with 'origin/jessica/featureC'.

[user@pro13 schacon (featureC)]$ git diff 6c4e96c097ad2645d5d8587f8338f94f1ae5ef87
diff --git a/featureC.txt b/featureC.txt
new file mode 100644
index 0000000..5b41842
--- /dev/null
+++ b/featureC.txt
@@ -0,0 +1,2 @@
+1-st commit
+2-nd commit

[user@pro13 schacon (featureC)]$ git diff $(git merge-base featureC master)
diff --git a/featureC.txt b/featureC.txt
new file mode 100644
index 0000000..5b41842
--- /dev/null
+++ b/featureC.txt
@@ -0,0 +1,2 @@
+1-st commit
+2-nd commit
  • Git shorthand for doing the above thing in the context of the git diff is the 'triple-dot syntax'
    • The command git diff A...B is equivalent to git diff $(git merge-base A B) B command
    • The command git diff master...featureC shows only the work the current topic branch (featureC) has introduced since its common ancestor with master
[user@pro13 schacon (featureC)]$ git diff master...featureC
diff --git a/featureC.txt b/featureC.txt
new file mode 100644
index 0000000..5b41842
--- /dev/null
+++ b/featureC.txt
@@ -0,0 +1,2 @@
+1-st commit
+2-nd commit
  • For git diff <commit>...<commit> omitting any one of <commits> has the same effect as using HEAD instead
[user@pro13 schacon (featureC)]$ cat .git/HEAD
ref: refs/heads/featureC

[user@pro13 schacon (featureC)]$ git diff master...
diff --git a/featureC.txt b/featureC.txt
new file mode 100644
index 0000000..5b41842
--- /dev/null
+++ b/featureC.txt
@@ -0,0 +1,2 @@
+1-st commit
+2-nd commit

[user@pro13 schacon (featureC)]$ git co master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)
  
[user@pro13 schacon (master)]$ git diff master...featureC
diff --git a/featureC.txt b/featureC.txt
new file mode 100644
index 0000000..5b41842
--- /dev/null
+++ b/featureC.txt
@@ -0,0 +1,2 @@
+1-st commit
+2-nd commit

[user@pro13 schacon (master)]$ cat .git/HEAD
ref: refs/heads/master

[user@pro13 schacon (master)]$ git diff ...featureC
diff --git a/featureC.txt b/featureC.txt
new file mode 100644
index 0000000..5b41842
--- /dev/null
+++ b/featureC.txt
@@ -0,0 +1,2 @@
+1-st commit
+2-nd commit
  • See the totally different meaning for git log triple-dot range notation
[user@pro13 schacon (master)]$ git log --oneline --graph --all
* 796e082 (HEAD -> master) master-3
| * e7277bf (origin/jessica/featureC, featureC) featureC-2
| * 9179f6b featureC-1
|/
* 6c4e96c (origin/master, origin/HEAD) Initial commit

[user@pro13 schacon (master)]$ git log --oneline --graph master...featureC
* 796e082 (HEAD -> master) master-3
* e7277bf (origin/jessica/featureC, featureC) featureC-2
* 9179f6b featureC-1

[user@pro13 schacon (master)]$ git log --oneline --graph featureC...master
* 796e082 (HEAD -> master) master-3
* e7277bf (origin/jessica/featureC, featureC) featureC-2
* 9179f6b featureC-1
References
...
<rev>
      Include commits that are reachable from <rev> (i.e. <rev> and its ancestors).

^<rev>
      Exclude commits that are reachable from <rev> (i.e. <rev> and its ancestors).

<rev1>..<rev2>
      Include commits that are reachable from <rev2> but exclude those that are reachable 
      from <rev1>. When either <rev1> or <rev2> is omitted, it defaults to HEAD.

<rev1>...<rev2>
      Include commits that are reachable from either <rev1> or <rev2> but exclude those 
      that are reachable from both. When either <rev1> or <rev2> is omitted, it defaults 
      to HEAD.
...
...
       git diff [<options>] <commit> [--] [<path>...]
           This form is to view the changes you have in your working tree relative to the 
           named <commit>. You can use HEAD to compare it with the latest commit, or a 
           branch name to compare with the tip of a different branch.

       git diff [<options>] <commit> <commit> [--] [<path>...]
           This is to view the changes between two arbitrary <commit>.

       git diff [<options>] <commit>..<commit> [--] [<path>...]
           This is synonymous to the previous form. If <commit> on one side is omitted, 
           it will have the same effect as using HEAD instead.

       git diff [<options>] <commit>...<commit> [--] [<path>...]
           This form is to view the changes on the branch containing and up to the 
           second <commit>, starting at a common ancestor of both <commit>. 
           "git diff A...B" is equivalent to "git diff $(git merge-base A B) B". 
           You can omit any one of <commit>, which has the same effect as using 
           HEAD instead.

       Just in case you are doing something exotic, it should be noted that all of 
       the <commit> in the above description, except in the last two forms that use 
       ".." notations, can be any <tree>.

       For a more complete list of ways to spell <commit>, see "SPECIFYING REVISIONS" 
       section in gitrevisions(7). However, "diff" is about comparing two endpoints, 
       not ranges, and the range notations ("<commit>..<commit>" and "<commit>...<commit>") 
       do not mean a range as defined in the "SPECIFYING RANGES" section in gitrevisions(7).
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment