|

Use zdiff3 for easier merge conflict resolution

24 Feb 2024


If you've worked with Git for some time, you've likely encountered some conflicts that are tricky to resolve. You may not have known that there are three different conflict styles to choose from: merge, diff3 and zdiff3. Let's take a look at an example to see how these styles differ, and why zdiff3 is really cool.

merge, the default conflict style

Let's consider an example where the diff preview with the default merge conflict style looks like this:

func getItemNames() -> [String] { doSomeCoolStuff() <<<<<<< HEAD let items = ItemStorage.getItems(onlyActive: true) ======= let items = ItemRepository.getItems(onlyActive: false) >>>>>>> develop return items.map { $0.name } }

This gives us a fairly basic overview of which lines are different between the two conflicting commits, but crucial information is missing here: what was this code replacing in the first place? How do we know if we should leave the onlyActive parameter as true or as false? Or whether to use ItemStorage or ItemRepository?

diff3 improvements

The diff3 algorithm improves upon the default merge algorithm by also showing us the original content between the two versions of the conflicting changes:

func getItemNames() -> [String] { <<<<<<< HEAD doSomeCoolStuff() let items = ItemStorage.getItems(onlyActive: true) ||||||| 3f0f000 let items = ItemStorage.getItems(onlyActive: false) ======= doSomeCoolStuff() let items = ItemRepository.getItems(onlyActive: false) >>>>>>> develop return items.map { $0.name } }

This is better in the sense that it gives us more information on which we can base our decisions of how to perform conflict resolution. Now we see that the original code had the onlyActive parameter set to false, so we would probably want it set to true. Also, it looks like ItemStorage was renamed to ItemRepository, so we would probably want to keep the latter when resolving the conflict.


However, the preview now has the annoying feature of including lines that have the same content into the conflict. In this small example it's only one line, but in more realistic scenarios this can hinder readability somewhat. Also, it's just kind of annoying.

How is zdiff3 different?

With the same example conflict as given above, the zdiff3 preview would look like this:

func getItemNames() -> [String] { doSomeCoolStuff() <<<<<<< HEAD let items = ItemStorage.getItems(onlyActive: true) ||||||| 3f0f000 let items = ItemStorage.getItems(onlyActive: false) ======= let items = ItemRepository.getItems(onlyActive: false) >>>>>>> develop return items.map { $0.name } }

Notice how the part that was shared between the two commits (the doSomeCoolStuff() function call) is now outside the indicated conflict zone. This makes the indicated conflict more compact and readable, giving us the best of both the merge and the diff3 worlds.

How to use it?

Run the following command:

git config --global merge.conflictStyle zdiff3

I found out about zdiff3 here. I also found a great talk titled So you think you know Git by Scott Chacon, one of the founders of GitHub. The talk has plenty of useful info about various other git tips and tricks, so I really recommend checking it out.