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.
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 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:
- Find what changes were made in the base branch and my feature branch
- Get composer.json and composer.lock from the base branch
- Apply the changes I made in my branch again using Composer
- Stage the updated composer.json / composer.lock and continue the rebase
The same goes for merging any branch into my branch:
- Find what changes were made in the branch I try to merge into mine
- Get composer.json and composer.lock from my own branch
- Apply the changes from the other branch again using Composer
- 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.
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!
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.
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!
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?