2.2 Some conflict

Let’s see what happens when there are conflicts. Checkout example9/branchB and merge example9/branchA:

Listing 2.9:Merging
$ git merge example9/branchA 
Auto-merging example.py 
CONFLICT (content): Merge conflict in example.py 
Automatic merge failed; fix conflicts and then commit the result. 
$ git status 
On branch example9/branchB 
You have unmerged paths. 
  (fix conflicts and run "git commit") 
  (use "git merge --abort" to abort the merge) 
 
Changes to be committed: 
        modified:   module.py 
 
Unmerged paths: 
  (use "git add <file>..." to mark resolution) 
        both modified:   example.py 
 
$ git ls-tree -r HEAD 
100644 blob 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f    .gitignore 
100755 blob 500616dc70f4847f244d29d827a192b7fa03de93    example.py 
100644 blob 2d1fe2ea52267ab6e75cca853c393fc6929a0e45    module.py 
$ git ls-files -s 
100644 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f 0       .gitignore 
100755 b0a7eae10629dec61246f86c08f2432f0e276675 1       example.py 
100755 500616dc70f4847f244d29d827a192b7fa03de93 2       example.py 
100755 ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b 3       example.py 
100644 475f980e6c6e24f8fc4a144e498fa1c1c59da370 0       module.py

We can see that example.py is showing up in Unmerged paths, and module.py is listed in Changes to be committed. .gitignore is not listed. .gitignore hasn’t changed between HEAD, index and what we have in the working tree so nothing to report on it. module.py has changed as part of the merge process without any conflicts and that’s why it is present in HEAD and in index with different ids. The file currently on the working tree is just like it is on index and that’s why the file is listed in Changes to be committed. The interesting stuff is on example.py. Instead of having a single item for it in index, there are 3 and each one of them has a different index stage number. When there is content conflict on a file (like in this case), git will hold 3 versions of the file in index. Stage number 1 is the file as it is in the common ancestor6 . Stage number 2 is the file as it is in HEAD. Stage number 3 is the file as it is in the other branch. See for yourselves:

Listing 2.10:Checking the ids of example.py in different revisions
$ git ls-tree $( git merge-base HEAD MERGE_HEAD ) example.py 
100755 blob b0a7eae10629dec61246f86c08f2432f0e276675    example.py 
$ git ls-tree HEAD example.py 
100755 blob 500616dc70f4847f244d29d827a192b7fa03de93    example.py 
$ git ls-tree MERGE_HEAD example.py 
100755 blob ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b    example.py

Stage number 0, as we had seen before, means that the file is either unchanged or added to index.

Let’s solve the conflict as we had done before (which means that both files have to be modified) and then let’s see the output of the git commands:

Listing 2.11:Status after modifying the files
$ git status 
On branch example9/branchB 
You have unmerged paths. 
  (fix conflicts and run "git commit") 
  (use "git merge --abort" to abort the merge) 
 
Changes to be committed: 
        modified:   module.py 
 
Unmerged paths: 
  (use "git add <file>..." to mark resolution) 
        both modified:   example.py 
 
Changes not staged for commit: 
  (use "git add <file>..." to update what will be committed) 
  (use "git restore <file>..." to discard changes in working directory) 
        modified:   module.py 
 
$ git ls-tree -r HEAD 
100644 blob 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f    .gitignore 
100755 blob 500616dc70f4847f244d29d827a192b7fa03de93    example.py 
100644 blob 2d1fe2ea52267ab6e75cca853c393fc6929a0e45    module.py 
$ git ls-files -s 
100644 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f 0       .gitignore 
100755 b0a7eae10629dec61246f86c08f2432f0e276675 1       example.py 
100755 500616dc70f4847f244d29d827a192b7fa03de93 2       example.py 
100755 ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b 3       example.py 
100644 475f980e6c6e24f8fc4a144e498fa1c1c59da370 0       module.py

Even though the ids of the objects in index and HEAD hasn’t changed, the content of the files has changed on the working tree. Given that module.py has an id that is different between index and HEAD, git lists it in Changes to be committed. Given that the content of the file on the working tree is different from index, it is also listed in Changes not staged for commit. example.py hasn’t been added to index and so it is still conflicted so we still have the same 3 versions of the file in index and is listed in Unmerged paths.

Let’s add module.py first and see what happens:

Listing 2.12:After adding module.py
$ git add module.py 
$ git status 
On branch example9/branchB 
You have unmerged paths. 
  (fix conflicts and run "git commit") 
  (use "git merge --abort" to abort the merge) 
 
Changes to be committed: 
        modified:   module.py 
 
Unmerged paths: 
  (use "git add <file>..." to mark resolution) 
        both modified:   example.py 
 
$ git ls-tree -r HEAD 
100644 blob 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f    .gitignore 
100755 blob 500616dc70f4847f244d29d827a192b7fa03de93    example.py 
100644 blob 2d1fe2ea52267ab6e75cca853c393fc6929a0e45    module.py 
$ git ls-files -s 
100644 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f 0       .gitignore 
100755 b0a7eae10629dec61246f86c08f2432f0e276675 1       example.py 
100755 500616dc70f4847f244d29d827a192b7fa03de93 2       example.py 
100755 ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b 3       example.py 
100644 42004985f8888627d3985174325a235401568e0b 0       module.py

For this file, it behaved like it did when there was no conflict. The ids for the object are different between HEAD and index and that’s why it is listed in Changes to be committed. The file in the working tree is like it is in index now and that’s why git knows there’s nothing else to report about the file. example.py is still conflicted and so there are no changes for it from what we had seen before.

Let’s add example.py now:

Listing 2.13:After adding example.py
$ git add example.py 
$ git status 
On branch example10/branchB 
All conflicts fixed but you are still merging. 
  (use "git commit" to conclude merge) 
 
Changes to be committed: 
        modified:   example.py 
        modified:   module.py 
 
$ git ls-tree -r HEAD 
100644 blob 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f    .gitignore 
100755 blob 500616dc70f4847f244d29d827a192b7fa03de93    example.py 
100644 blob 2d1fe2ea52267ab6e75cca853c393fc6929a0e45    module.py 
$ git ls-files -s 
100644 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f 0       .gitignore 
100755 ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b 0       example.py 
100644 42004985f8888627d3985174325a235401568e0b 0       module.py

Now that the (originally) conflicted file has been added to index, we have a single item for it in index. Given that the ids are not the same between HEAD and index, git reports it in Changed to be committed and given that the content of the files in the working tree is the same as it is in index, git doesn’t say anything else about the files. Let’s create a new revision at this moment and see what happens:

Listing 2.14:Committing after conflict
$ git commit -m "A new revision" 
[example9/branchB 9f80666] A new revision 
$ git status 
On branch example9/branchB 
nothing to commit, working tree clean 
$ git ls-tree -r HEAD 
100644 blob 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f    .gitignore 
100755 blob ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b    example.py 
100644 blob 42004985f8888627d3985174325a235401568e0b    module.py 
$ git ls-files -s 
100644 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f 0       .gitignore 
100755 ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b 0       example.py 
100644 42004985f8888627d3985174325a235401568e0b 0       module.py

As it had happened before when there was no conflict, when we create a new revision, the ids in HEAD are updated to the same ids of the objects that index had right before the revision was created. Given that now index and HEAD have the same ids, there are no Changes to be committed and given that there are no differences between index and working tree, there’s nothing else to report.

If you stop to think about it, other than having the 3 items listed in index while the file was conflicted, there were no other differences in terms of what was held in index and the revisions. All in all, git does not save any information whatsoever related to the conflicts that happened on a merge when a new revision is created. It simply creates a new revision with multiple parents and the files and their (final) contents on the revision just like it would do on any other revision:

Listing 2.15:Checking revision information
$ git cat-file -p HEAD 
tree 396032b1546d75672f3a85c13a858d3b187d2046 
parent 3cd9cfd24b13fa2381c5cc8009275b961ea7a26b 
parent dfde76c316ff0a070ddc7560f86b7279b73ed807 
author Developer A <dev.a@localhost> 1592110970 -0600 
committer Developer A <dev.a@localhost> 1592111469 -0600 
 
A new revision 
$ git cat-file -p HEAD^{tree} 
100644 blob 2f78cf5b66514f2506d9af5f3dadf3dee7aa6d9f    .gitignore 
100755 blob ad957360597fb9bd3e83d4bfa869f6e19b7fbf2b    example.py 
100644 blob 42004985f8888627d3985174325a235401568e0b    module.py

Copyright 2020 Edmundo Carmona Antoranz