|
| 1 | +# Composer JSON Git Merge Driver |
| 2 | + |
| 3 | +The Composer JSON Git Merge Driver provides a mechanism to more effectively merge |
| 4 | +`composer.json` and `composer.lock` files that have been modified simultaneously |
| 5 | +in separate branches / development histories. The [custom git merge driver][merge driver] |
| 6 | +is activated when the composer JSON files require a merge more complex than a |
| 7 | +simple "fast forward". |
| 8 | + |
| 9 | +## How it Works |
| 10 | + |
| 11 | +The merge driver replaces git's standard merge algorithm only for composer's JSON files: |
| 12 | +instead of analyzing the files for changed lines, the JSON is parsed and the actual properties |
| 13 | +and values are compared for changes. As such, the merge driver is able to more gracefully |
| 14 | +handle most new, updated, and removed dependencies in your composer files. A merge conflict |
| 15 | +is only triggered when the version constraint, locked version number, or presence / absence |
| 16 | +of the same dependency has been modified in multiple development histories involved in the |
| 17 | +merge. |
| 18 | + |
| 19 | +For instance, if a certain dependency is updated in one branch and removed in another, |
| 20 | +a merge conflict is triggered because it is unclear which change for that dependency is |
| 21 | +desired following the merge. However, if a new, different dependency is appended to the |
| 22 | +`require` section in both branches, the merge driver will understand that both should |
| 23 | +be kept, whereas the standard git merge driver would trigger a merge conflict because |
| 24 | +the same line has been edited in both branches. |
| 25 | + |
| 26 | +More generally, all object data structures are merged gracefully and recursively, meaning |
| 27 | +other composer configuration properties in the composer JSON files (e.g. `extra` and |
| 28 | +`config`) will be merged properly as well—whenever changes are not ambiguous. In fact, |
| 29 | +the merge driver will work on any JSON file whose outermost data structure is an object. |
| 30 | + |
| 31 | +### Composer Lock Handling |
| 32 | + |
| 33 | +For the the lock file in particular, special handling is performed to minimize the potential |
| 34 | +for merge conflicts. For example, the locked dependency data is converted into an object |
| 35 | +for the purposes of merging and converted back to the proper data structure when the merge |
| 36 | +is complete. As well, the `content-hash` property is excluded from the merge, as the very |
| 37 | +nature of this property guarantees that two differing branches would have conflicting values. |
| 38 | +The content hash is used by composer to efficiently recognize that the `composer.json` file |
| 39 | +has changed, so the actual value is not necessarily important after a merge has occured. |
| 40 | +As a workaround for not being able to generate an accurate hash, the merge driver sets its |
| 41 | +own unique value for the hash to signal to composer that a change has occurred; the value is |
| 42 | +a simple message instructing you to run `composer update --lock` so composer can regenerate |
| 43 | +the real hash! |
| 44 | + |
| 45 | +[merge driver]: https://git-scm.com/docs/gitattributes#_defining_a_custom_merge_driver |
| 46 | + |
| 47 | +## Installation |
| 48 | + |
| 49 | +The merge driver can be installed globally or per repo and activated globally or per repo, |
| 50 | +i.e. you can install the driver globally but only activate it on certain projects. |
| 51 | + |
| 52 | +### 1. Download the merge driver. |
| 53 | + |
| 54 | +Download the `merge.php` file from this repo. For example, to download to your home directory: |
| 55 | + |
| 56 | +```sh |
| 57 | +$ curl -Lo ~/composer-git-merge-driver.php https://raw.githubusercontent.com/balbuf/composer-git-merge-driver/master/merge.php |
| 58 | +``` |
| 59 | + |
| 60 | +Make the file executable: |
| 61 | + |
| 62 | +```sh |
| 63 | +$ chmod +x ~/composer-git-merge-driver.php |
| 64 | +``` |
| 65 | + |
| 66 | +Optionally, you can move the file somewhere in your `$PATH`, e.g.: |
| 67 | + |
| 68 | +```sh |
| 69 | +$ mv ~/composer-git-merge-driver.php /usr/local/bin/composer-git-merge-driver |
| 70 | +``` |
| 71 | + |
| 72 | +### 2. Install the merge driver. |
| 73 | + |
| 74 | +The driver is installed by informing git of its existence via a [git config][git config] file: |
| 75 | + |
| 76 | +``` |
| 77 | +[merge "composer_json"] |
| 78 | + name = composer JSON file merge driver |
| 79 | + driver = ~/composer-git-merge-driver.php %O %A %B %L %P |
| 80 | + recursive = binary |
| 81 | +``` |
| 82 | + |
| 83 | +To open the git config file for editing in your default command line text editor: |
| 84 | + |
| 85 | +```sh |
| 86 | +$ git config -e |
| 87 | +``` |
| 88 | + |
| 89 | +By default, this allows you to edit the config file for the current repo, meaning the driver |
| 90 | +is only installed locally to the repo. To edit the global config file for your user, append the |
| 91 | +`--global` flag; to edit the system-wide config file, append the `--system` flag. |
| 92 | + |
| 93 | +Copy and paste the block above into the config file and save it. Be sure to update the path if |
| 94 | +you downloaded the file to somewhere other than `~/composer-git-merge-driver.php`. If you moved the |
| 95 | +file somewhere in your `$PATH`, you can simply replace the path with `composer-git-merge-driver`. |
| 96 | + |
| 97 | +For more information about git config files, refer to the [git documentation][git config]. |
| 98 | + |
| 99 | +[git config]: https://git-scm.com/docs/git-config |
| 100 | + |
| 101 | +### 3. Activate the merge driver. |
| 102 | + |
| 103 | +Finally, the merge driver must be activated for `composer.json` and `composer.lock` files via |
| 104 | +a [git attributes][git attributes] file: |
| 105 | + |
| 106 | +``` |
| 107 | +composer.json merge=composer_json |
| 108 | +composer.lock merge=composer_json |
| 109 | +``` |
| 110 | + |
| 111 | +To activate only for a specific repo, edit the `.git/info/attributes` file inside of the repo. |
| 112 | +To activate globally for your user, edit the `~/.gitattributes` file; to activate system-wide, |
| 113 | +edit the `$(prefix)/etc/gitattributes` file (e.g. `/usr/local/etc/gitattributes`). Copy and |
| 114 | +paste the block above into the config file and save it. In some cases the file may not yet exist, |
| 115 | +in which case you can simply create the file at the aforementioned path. |
| 116 | + |
| 117 | +For more information about git attributes files, refer to the [git documentation][git attributes]. |
| 118 | + |
| 119 | +[git attributes]: https://git-scm.com/docs/gitattributes |
| 120 | + |
| 121 | +## Usage |
| 122 | + |
| 123 | +The merge driver is automatically invoked any time a `composer.json` and/or `composer.lock` file |
| 124 | +must be merged in a repo where the driver is activated. If there are any merge conflicts within |
| 125 | +the files, the standard git merge conflict procedure kicks in: you will be alerted of which file(s) |
| 126 | +contain conflicts, and conflicts are denoted via the standard conflict markers (e.g. `<<<<<<<`). |
| 127 | + |
| 128 | +If there are no conflicts and the merge can be applied cleanly, the merge completes as normal. |
| 129 | +However, a changed lock file results in a non-standard `content-hash` value (whether there are |
| 130 | +conflicts or not). While there should be no harm in leaving the hash value as-is, it's best to |
| 131 | +let composer regenerate the hash. To do so, simply run: |
| 132 | + |
| 133 | +```sh |
| 134 | +$ composer update --lock |
| 135 | +``` |
| 136 | + |
| 137 | +The value of the hash actually informs you to take this action, but if the merge completes with |
| 138 | +no conflicts, you may not even notice the message. When there are conflicts, this step should |
| 139 | +be completed after fixing the conflicts but before finishing the merge. If there were no conflicts |
| 140 | +and the merge completed, you can avoid creating an additional commit by amending the merge commit: |
| 141 | + |
| 142 | +```sh |
| 143 | +$ composer update --lock |
| 144 | +$ git add composer.lock |
| 145 | +$ git commit --amend |
| 146 | +``` |
| 147 | + |
| 148 | +## Additional Information |
| 149 | + |
| 150 | +### Requirements |
| 151 | + |
| 152 | +The merge driver is written in PHP and requires at least version 5.4. |
| 153 | + |
| 154 | +### Known Limitations |
| 155 | + |
| 156 | +- The merge driver parses and regenerates the JSON to complete the merge. As such, some formatting |
| 157 | +may be lost or altered in the process. For the `composer.json` file, the driver attempts to detect |
| 158 | +the indentation preference of the file in the working tree and replicate that indentation when |
| 159 | +the JSON is regenerated, which should minimize formatting changes. However, some whitespace style |
| 160 | +cannot be preserved, e.g. fewer line breaks or more whitespace. While the interpreted contents |
| 161 | +of the file will not be compromised, the formatting could result in extraneous changes to the file |
| 162 | +as a result of the merge. (In keeping with the behavior of composer, the `composer.lock` file is |
| 163 | +regenerated by the default behavior of PHP's `json_encode` function and is consistently indented.) |
| 164 | + |
| 165 | +- When a merge conflict occurs where the last property of an object is removed, accepting the removed |
| 166 | +version will result in invalid syntax due to a trailing comma on the preceding property. Care should |
| 167 | +be taken to verify the syntax of the overall file and manually update accordingly. |
| 168 | + |
| 169 | +### Acknowledgements |
| 170 | + |
| 171 | +Thanks to [@christian-blades-cb](https://gist.github.com/christian-blades-cb/f75ec813f15393498b6c) |
| 172 | +and [@jphass](https://gist.github.com/jphaas/ad7823b3469aac112a52) for the inspiration and examples. |
0 commit comments