eXistenZ | Stefan

Resolving composer.lock merge conflicts

For developers merging and rebasing git branches is rather straight forward most of the time, but it can get complex when dealing with PHP’s package manager Composer, and its lock file.

In this blogpost I go over my workflow when encountering such a scenario.

The issue

Most files can be resolved when rebasing git branches, by simply looking at them.

What was changed in the base branch, what was changed in your feature branch, and what should the changes in your feature branch look like given the new base branch?

Any developer can look at this, and given enough knowledge of what was changed in the base branch and what is done in your feature branch, these merge issues can be resolved.

This is much harder to do with generated files that hold hashes, like Composer’s lock file (composer.lock). Do you pick generated lock file 1 or generated lock file 2? Whatever you do, Composer will most likely still complain that your changes do not result in the correct hash and that your lock file is out of date.

My workflow

Git merge

My workflow consists of staying as far away of composer.lock as possible, and let Composer generate a new correct lock file for me.

These are the steps I take when rebasing my branch upon the base branch:

  1. Find what changes were made in the base branch and my feature branch
  2. Get composer.json and composer.lock from the base branch
  3. Apply the changes I made in my branch again using Composer
  4. Stage the updated composer.json / composer.lock and continue the rebase

The same goes for merging any branch into my branch:

  1. Find what changes were made in the branch I try to merge into mine
  2. Get composer.json and composer.lock from my own branch
  3. Apply the changes from the other branch again using Composer
  4. Stage the updated composer.json / composer.lock and finish the merge

By re-running Composer commands via the CLI I end up with a completely new composer.lock that is different from the lock file in either branch, but functionally holds the combination of changes made in both branches!

Do it yourself

See how easy it is to resolve Composer dependencies during a merge conflict by following my little tutorial! In this tutorial I assume you have access to a Linux/OSX terminal with working git and Composer binaries.

Preparation

1. First, create a new directory somewhere. Cd into the dir and type git init followed by composer init. Go with the default options by hitting enter a few times, and do not define your (dev) dependencies. You should end up with only a composer.json and nothing else.

2. Now, run composer require symfony/console --prefer-lowest. This downloads the lowest possible version of the Symfony console component and creates a lock file.

3. Add composer.json and composer.lock to git and commit the changes. Then run git tag v1.0 to create a new tag.

Congratulations, we now have a starting point from which we can create two new branches, each with their own changes!

The first branch

1. Create a new branch called branch-1 with git checkout -b branch-1.

2. With the new branch checked out, run composer update to update the Symfony console component to the latest version. When running git status, you should see that composer.lock has been updated.

3. Add the lock file to git and make a new commit.

The second branch

Starting from branch-1, run git checkout tags/v1.0 -b branch-2 to check out a new branch called branch-2, based on the previously created tag. With the new branch checked out, run composer require symfony/var-dumper to add the Symfony var-dumper component. When running git status, you should see that both composer.json and composer.lock have been modified. Add them to git and make a new commit.

Someone set up us the bomb!

So far, you created two branches, both with their own changes to the lock file, that cannot be simply merged by git. Time to defuse this situation!

1. Run git rebase branch-1 to rebase branch-2 upon branch-1. This will result in a merge conflict and running git status will show that both branched modified composer.lock.

2. Unstage composer.json and check the difference we made in branch-2. Make a (mental) note then run git checkout composer.json so the changes are gone.

3. With git checkout --theirs composer.lock you grab the lock file from branch-1, this is the lock file that goes with the original composer.json. You can validate this by running composer validate and checking for any warnings regarding an out of date lock file, there should be none.

4. Now redo the changes that we learned by inspecting: composer require symfony/var-dumper.

5. Composer re-did the changes from branch-2 against the situation of branch-1, so we end up with the combination of both. You can now stage the end result, continue the rebase, and you are all done.

Tada! in this whole process, we didn’t made any manual changes to the lock file, but instead Composer did all the heavy lifting for us!

Composer logo

Conclusion

Now that you’ve learned my process of resolving Composer conflicts, you might think that this sounds a bit familiar.

You would be right, because if you dive a bit deeper into the documentation of Composer, you will find this page which explains this procedure, albeit a bit more sparsely.

However, it seems that this is not a well known procedure however, so I wrote this blogpost about it combined with a little DIY section, so more people can get the hang of this. The more you know, right?