Composer: Missing Distributions For Explict References

A conductor conducting an orchestra

This came as a bit of a surprise to me today, so I wanted to post it here to help others, in case you run into similar problems.

For applications I control that are not distributed to the public, I like to hard-lock my Composer dependencies to a specific version. I don’t use tilde, asterisk, caret, ranges, etc. to specify dependencies. I use the specific version number I want (i.e. 3.7.1). This gives me piece of mind that application code I have already tested is not going to change behavior simply because underlying libraries I’m using have changed. It also gives me the flexibility to upgrade dependencies on my own terms.

Sometimes a library has updates that haven’t yet been released, and I need to use these right away. Composer allows me to specify the specific commit I want to use for a library. In this way, I can hard-lock a dependency to a specific state, even when there is not yet a release for the changes I need.

{
"require": {
"acme/foo": "dev-master#b96ab0ce4a"
}
}

This has worked well until today, when I tried to do a fresh composer install. After installing, one library contained files and methods that I did not expect. It turns out Composer was grabbing the HEAD of dev-master instead of the specific commit I referenced.

When I looked in vendor/composer/installed.json, it became clear what was happening. The relevant entry looked a little something like this:

{
"name": "acme/foo",
"version": "dev-master",
"version_normalized": "9999999-dev",
"source": {
"type": "git",
"url": "https://bitbucket.org/acme/foo.git",
"reference": "b96ab0ce4a"
},
"dist": {
"type": "zip",
"url": "https://bitbucket.org/acme/foo/get/03ae0dba64d1a902fb4d76004ef12c8391ededc8.zip",
"reference": "b96ab0ce4a",
"shasum": ""
},
"require": {
"php": ">=5.2.0"
},
"time": "2015-03-23 22:00:25",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-0": {
"Acme": "src/"
}
},
"description": "An example library"
}

Notice in line 21 that my installation-source is “dist,” and in the dist section it shows a zip file URL referring to a different commit hash than the reference (line 12 vs. line 13). This is because Bitbucket provides a distribution zip file for the current HEAD of the branch, but not the specific commit I need.

Since composer install uses --prefer-dist by default, I was getting the distribution zip file provided by Bitbucket for the HEAD of the master branch, rather than for the specific commit I needed.

When providing the --prefer-source option to composer install, I was able to retrieve the exact commit I wanted. Problem solved!

Unfortunately, using --prefer-source is slower and downloads VCS files (i.e. .git/), as well as files that are marked as export-ignore in .gitattributes (since Composer isn’t getting the exported distribution archive but is cloning the repository itself).