Search code examples
gitmerge-conflict-resolutiongit-merge-conflict

Git make changes to single line on multiple branches and keep all changes


I have a really long line in my code that contains IDs for something. Here is an example of the line

const IDs="0,1,2,3,4"

I want to add to this line on multiple branches. Here's an example

Branch 1:

const IDs="0,1,2,3,4,87"

Branch 2:

const IDs="0,1,2,3,4,95"

On Branch 1 I added 87 to the line, and on Branch 2 I added 95. Once these are merged, there will be a merge conflict.

One easy way to resolve this would just be to keep one of the changes, and manually add the changes from the other branch. But in my actual case, this line is much longer, many more IDs have been added, and they haven't all been appended to the end. They are mostly scattered throughout the middle.

Is there a way to resolve the conflict such that both additions get added, without me manually having to add them myself such that it looks something like this?

const IDs="0,1,2,3,4,87,95"

Solution

  • You can write your own merge driver.

    When Git hits a case that could result in merge conflicts, it runs a merge driver. Git's default, built-in merge driver finds conflicts, declares them as conflicts, and leaves the conflict markers in your copy of the file in your work-tree. That's the one that you've been resolving by hand.

    If you use the gitattributes file to mark the source file as having a merge driver, though, Git uses your defined merge driver instead of its built-in one. Git already has, in the index, all three copies of the file:

    • the merge base copy, which is from the common, shared commit;
    • the --ours copy from the HEAD commit / current branch; and
    • the --theirs copy from the commit you're merging.

    Git will extract all three copies to temporary files, and invoke your merge driver with the names of these temporary files. Your merge driver must:

    • find the difference from the base file to the ours and theirs copies, and
    • combine those differences, writing the result to the ours copy.

    If your driver finds conflicts, your driver should report a nonzero exit status. If not, your driver should report success by exiting with a zero status.

    That is, you need to duplicate the work that the standard low-level driver would do. The key difference is that if there is a line that is changed in both, and it's one you can resolve by computer-program, that the standard low-level driver can't, you can go ahead and resolve it.

    If you don't want to write all of the standard low-level driver over again, you can invoke Git's low-level merge driver first. That is available as a stand-alone command, git merge-file. It will do the same thing that Git's built-in low-level driver would have done, and return the same kind of exit status as well. You can then inspect that status, and if it says there are merge conflicts, you can have your program attempt to resolve them in place. If your program succeeds, your program can report a zero exit status.

    (Writing good merge drivers is nontrivial, but for your particular case, it might not be that bad, if you use git merge-file to do most of the work.)