Today’s puzzle would make a great job interview question. It’s a question that I would’ve failed before today. Are you ready? Here it goes.

Consider the git branch feature-1, which is based on master. Is there a situation where merging the latest master into feature-1 before then merging feature-1 into master would result in a different software state than if you simply merged feature-1 directly into master? If so, describe how this could occur.

The answer is yes, surprisingly. There are probably more elegant ways to arrive at a proof, but here’s how this is possible. I’ll mostly just let you read through the logs, with an explanation at the two critical sections.

$ mkdir git-test
$ cd git-test
$ git init
Initialized empty Git repository in /Users/martynchamberlin/Desktop/git-test/.git/
$ touch master.md
$ git add .
$ git commit -m "First master commit"
[master (root-commit) 6bb6304] First master commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 master.md
$ git checkout -b feature-1
Switched to a new branch 'feature-1'
$ touch feature-1.md
$ git add .
$ git commit -m "feature 1 first commit"
[feature-1 17339cb] feature 1 first commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 feature-1.md
$ git checkout master; git checkout -b feature-2;
Switched to branch 'master'
Switched to a new branch 'feature-2'
$ touch feature-2.md
$ git add .
$ git commit -m "Feature 2 first commit"
[feature-2 0b3cb11] Feature 2 first commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 feature-2.md
$ git checkout master
Switched to branch 'master'
$ touch master-2.md
$ git add .
$ git commit -m "Master second commit"
[master 321e0e5] Master second commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 master-2.md
$ git merge feature-2
Merge made by the 'recursive' strategy.
 feature-2.md | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 feature-2.md

Here’s where things go wonky. The developer working on feature-1 hasn’t fetched the latest origin, and attempts to apply the latest changes that are in his latest local master manually.

$ git checkout feature-1
Switched to branch 'feature-1'
$ touch feature-2.md
$ git add .
$ git commit -m "Manually add feature-2 on feature-1"
[feature-1 9335baf] Manually add feature-2 on feature-1
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 feature-2.md
$ git merge master
Merge made by the 'recursive' strategy.
 master-2.md | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 master-2.md
$ git checkout master
Switched to branch 'master'
$ git revert -m 1 65a6f49 # top commit of `git log`
[master 151c8d7] Revert "Merge branch 'feature-2'"
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 feature-2.md
$ git checkout feature-1
Switched to branch 'feature-1'
$ git merge master
Removing feature-2.md
Merge made by the 'recursive' strategy.
 feature-2.md | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 feature-2.md
$ ls
feature-1.md	master-2.md	master.md

At this point, if we were to merge feature-1 into master, we would not have the feature-2.md file. However, what if we had not merged the master branch into the feature-1 branch before merging the feature-1 branch into master? In that case, we would have the feature-2.md file. I’ll prove it:

$ git revert -m 1 91bd90a # top commit of `git log`
[feature-1 4c713c6] Revert "Merge branch 'master' into feature-1"
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 feature-2.md
$ ls
feature-1.md	feature-2.md	master-2.md	master.md
$ git checkout master
Switched to branch 'master'
$ git merge feature-1
Updating 151c8d7..4c713c6
Fast-forward
 feature-1.md | 0
 feature-2.md | 0
 2 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 feature-1.md
 create mode 100644 feature-2.md
$ ls
feature-1.md	feature-2.md	master-2.md	master.md

The takeaway, of course, is to never fetch changes from external branches in a way that causes git to think that those changes occurred in the current branch as new code.