Git #4: Conflicts

Git conflicts

There is, in my opinion (and this is a very personal opinion) 3 barriers to learning Git: understanding the notion of c, knowing how to use the ribs, and manage the Conflicts.

A conflict occurs when two versions (branches, commits, etc.) of a project with incompatible modifications are merged. Basically, these are changes on the same lines of the same file. Git is able to resolve the simplest conflicts on its own, such as modifying the same file but in two separate places. On the other hand, when a file has been modified in two different branches on the same lines, only the affected developers can resolve the resulting conflict.

In the event of a conflict, the merger of two branches (such as a merge) will result in failure and a message from Git prompting us to resolve the conflicts before proceeding with the merge.

Creating the Git Conflict

For demonstration purposes, let's artificially create a conflict. Two branches will be used: conflict-branch et master. The file conflict-file.txt is created in each branch, with the line line from conflict branch on the branch conflict-branch et line from master on master.

Here's a quick tour of the repository after that.

# Let's see the difference in commits first
$ git logmaster
e58a6b9 (HEAD -> master) create conflict file on master Thu Feb 4 11:57:26 2021 +0100 Jules Chevalier 480b8fd (origin/master, origin/HEAD) Merge branch 'python-hello-world' Wed Oct 14 15:27:22 2020 +0200 Jules Chevalier

$ git log conflict-branch
e48246c (conflict-branch) create conflict file on conflict branch Thu Feb 4 11:51:34 2021 +0100 Jules Chevalier 480b8fd (origin/new-feature, origin/master, origin/HEAD) Merge branch 'python-hello-world' Wed Oct 14 15:27:22 2020 +0200 Jules Chevalier # Now in more detail the commits that differ between the two branches
$git show e58a6b9 
e58a6b9 (HEAD -> master) create conflict file on master diff --git a/conflict-file.txt b/conflict-file.txt @@ -0,0 +1 @@ +line from master

$git show e48246c
e48246c (conflict-branch) create conflict file on conflict branch diff --git a/conflict-file.txt b/conflict-file.txt @@ -0,0 +1 @@ +line from new branch

Now that everything is in place, let's unleash the heavens: it's time to merge the branches. As before, we return to master to repatriate the modifications of conflict-branch, thus creating a conflict since the same line of the same file was modified in both branches.

$ git checkout master 
Switched to branch 'master'

$ git merge conflict-branch 
CONFLICT (add/add): Merge conflict in conflict-file.txt Auto-merge conflict-file.txt Automatic merge failed; fix conflicts and then commit the result.

Conflict resolution

It is now a question of resolving the conflict, in other words of deciding between the opposing versions. To facilitate this work, git modifies the problematic file by adding the two contradictory possibilities, to the developer to make the choice. To represent these possibilities, Git uses marqueurs de conflits.

$cat conflict-file.txt 
<<<<<<< HEAD line from master ======= line from new branch >>>>>>> conflict-branch

Here, Git indicates that two versions are in conflict: the first, HEAD (which represents the current position of the repository, here master), and the second from conflict-branch. To resolve the conflict, you must choose the version you want to keep and remove the conflict markers, which will give this.

$cat conflict-file.txt
line from new branch

Then, git status tells us how to proceed.

$cat conflict-file.txt 
line from master

$ gitstatus
On branch master Your branch is ahead of 'origin/master' by 1 commit. (use "git push" to publish your local commits) You have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge) Unmerged paths: (use "git add ..." to mark resolution) both added: conflict-file.txt

Git gives us two new hints: fix conflicts and run git commit et use git add to mark resolution. This is exactly the procedure we are going to follow: use git add to mark the resolution of our conflict on the file conflict-file.txt then complete conflict resolution with git commit.

Note: Git offers at the time of git commit to modify the merge commit, which is not necessary.

$ git add conflict-file.txt

$ gitcommit
[master 8eada8a] Merge branch 'conflict-branch'

$ gitstatus
On branch master Your branch is ahead of 'origin/master' by 3 commits. (use "git push" to publish your local commits) nothing to commit, working tree clean

$ gitlog
8eada8a (HEAD -> master) Merge branch 'conflict-branch' Thu Feb 4 14:46:48 2021 +0100 Jules Chevalier e58a6b9 create conflict file on master Thu Feb 4 11:57:26 2021 +0100 Jules Chevalier e48246c (conflict-branch) create conflict file Thu Feb 4 11:51:34 2021 +0100 Jules Chevalier 480b8fd (origin/new-feature, origin/master, origin/HEAD) Merge branch 'python-hello-world' Wed Oct 14 15:27:22 2020 +0200 Jules Chevalier 

Branch master is now up to date! In all, we end up with 3 new commits: the commit already present on master, the commit retrieved from the branch conflict-branch, and finally the famous merge commit. This is a special commit, which carries the changes related to resolving the conflict. Its commit message makes it possible to locate the merger of the branches in the history, hence the interest of not modifying it.

As always, all that remains is to send the new version of master on the server using git push and the merger will be fully effective.

Git merge tools

Well, let's be honest, manually changing a one-line conflict by deleting 3 lines and 2 tags is fine. But when we have to deal with a real conflict, spread over several files, each containing several conflicting areas… We need a graphical tool more suitable for resolving such a conflict.

There are a number of them, with a similar operation: display the different versions in conflict side by side to help the user "visually" resolve the conflict, also displaying the "final" version which will be kept after the resolution of the conflict . Some tools also offer the "common ancestor" version, which represents the last commit in common with the two conflicting branches (in our example, this is the commit 480b8fd Merge branch 'python-hello-world'). These three versions provide a view of the entire conflict, with both the two versions offered, but also the "original" version to better understand the changes. Finally, these tools make it possible to graphically validate the version to keep and to move quickly from one conflict to the next.

Example of conflict resolution with Meld, showing the final version in the center
Example of conflict resolution with Report, showing the final version in the center
Example of conflict resolution with Kdiff3, showing the common ancestor on the left, and the final version at the bottom
Example of conflict resolution with Kdiff3, showing the common ancestor on the left, and the final version on the bottom

Git, conclusion

Conflict resolution can (no pun intended) be problematic. Beyond the difficulty of choosing the right version to keep, a bad resolution can break the existing code, for example by leaving conflict markers lying around which make the code useless...

By using graphical tools to resolve conflicts effectively and visually, we avoid the worst by ensuring that we do not lose code. It remains to have a clear idea of ​​the intention of the authors of the different versions of the project in order to keep the correct version of the code…

Marine

Check all
Career area
Subscribe to the newsletter :
These articles may interest you