83

We maintain web application which has common master branch and many parallel branches, one for each installation, each have few specific changes. Source code is managed in git and it is terrific tool when we need transfer features and bugfixes from master branch into parallel ones. But are few files that are sensitive and automatic merging usually give bad results. So merging would be much easier if they can be somehow marked and every merge would result in conflict requiring manual merge.

I searched for an answer :

  1. I am using --no-commit and --no-ff merge options, but it is not the same.
  2. Here and here someone asks the same question but with no solution.
  3. Similar case seems to be how to prevent file being merged using .gitattributes containing: somefile.php merge=ours . I tried to find some merge option which would generate conflict or force manual merge but found none so far.
  4. .gitattributes containing: somefile.php -merge is never merged automatically and therefore forcing manual merge. It is 90% solution, but what I seek is to try automatic merge and mark it as conflict regardless it is successful or not. But this is so far closest to solution. (...thanks Charles Bailey for clarification...)
  5. Someone suggest to write custom merge driver (1, 2), but how to do it is far from clear to me.

edit: variant 4. description

Community
  • 1
  • 1
Stepan
  • 1,360
  • 2
  • 12
  • 11
  • 6
    This would not be the exact answer you are looking for, but for the same reason I do `git fetch` first, then use `git difftool FETCH_HEAD`, so I can manually apply the change in the remote branch to the local. – MHC Feb 22 '11 at 06:20
  • MHC: this is nice trick (especially if saved in script for each parallel branch + combined with preventing files from automatic merge). Main disadvantage is that in team workflow chances are someone forgets this step and just makes normal merge instead. – Stepan Feb 22 '11 at 06:39
  • 2
    Setting `-merge` doesn't prevent you from merging the files, it just forces you to do it manually, e.g. with a mergetool. Isn't this what you need? – CB Bailey Feb 22 '11 at 07:56
  • It is 90% of what I want. I would like that merge is done automatically, but for this sensitive file the merge is is considered conflict even when there is none so manual check is forced every time. – Stepan Feb 23 '11 at 12:37
  • @Stepan I'm in a similar situation so I want to clarify something. What you are saying that with `-merge` in `.gitatttributes` `git merge` does nothing, and all work must be down with the merge tool? So there is no <<<<< ===== >>>> for the merge tool to use, right? And Dan's solution does provide this? – Nero gris Aug 09 '18 at 16:40

3 Answers3

61

Option 5, a custom merge driver, is probably the way to get closest to what you want. It is surprisingly easy to do. Below is an example of one that I think should get you pretty close to the behavior you desire.

First, create a merge driver script called merge-and-verify-driver. Make it executable and put it in a suitable location (you may want to consider checking this script into the repo, even, since the repo's config file is going to depend on it). Git is going to execute this shell script to perform the merge of the sensitive files:

#!/bin/bash
git merge-file "${1}" "${2}" "${3}"
exit 1

This just does the default merge behavior that Git itself normally does. The key difference is that the script always returns non-zero (to indicate that there was a conflict, even if the merge was actually resolved without conflicts).

Next, you need to tell Git about the existence of your custom merge driver. You do this in the repo's config file (.git/config):

[merge "verify"]
        name = merge and verify driver
        driver = ./merge-and-verify-driver %A %O %B

In this example, I've put merge-and-verify-driver in the repo's top level directory (./). You will need to specify the path to the script accordingly.

Now, you just need to give the sensitive files the proper attributes so that the custom merge driver is used when merging those files. Add this to your .gitattributes file:

*.sensitive merge=verify

Here, I've told Git that any file with a name matching the pattern *.sensitive should use the custom merge driver. Obviously, you need to use pattern that is appropriate for your file(s).

Dan Moulding
  • 183,296
  • 21
  • 89
  • 97
  • Thanks a lot for this detailed guide! – Stepan Feb 24 '11 at 10:03
  • 13
    It seems that the merge driver isn't called in "obvious" solutions. E.g, if the file was only changed on one branch and you do a merge, the "sensitive" file will be overwritten and the merge driver won't be called. Is there a way to fix that? – VitalyB Apr 09 '13 at 10:43
  • Does would this really behave such as @VitalyB suggests? thanks! – filippo Sep 06 '13 at 18:34
  • @Dan: This method does not seem to work when submodules are involved: I have a case where I have a master repo with a git submodule embedded in it. I tried to use your method to merge-verify all Makefiles in the master repo. I noticed your method will work only if the two branches being merged have the same version (i.e., git hash) of the submodule. If this is not the case (i.e, when both the submodule version and the Makefile changes), your merge-and-verify-driver.sh script is not hit. I verified this on both windows and Linux (git version 1.8.5). This is probably a bug. – rmccabe3701 Jan 25 '14 at 05:01
  • 1
    This works, but is there any way to make this a global option across all repositories on a machine? Also, any way to invoke it by command line? I wish there were a way to do this in something as simple as changing the merge strategy on the command line. – user1748155 Oct 01 '14 at 22:55
  • I am not sure what the point of running "git merge-file" is in your shell script since that is completely ignored. You can skip the shell script completely and just set driver=false instead of pointing to your shell script. false returns the non-zero exit code and accomplishes the same thing as "exit 1" in your shell script. – user1748155 Oct 02 '14 at 01:09
  • @user1748155 Setting the merge driver to false is going to indicate there was a conflict, but it's not actually going to merge anything. Maybe that's the behavior you want, but it's not the behavior OP wanted. – Dan Moulding Oct 02 '14 at 16:44
  • Well, with the above script I am not getting the merge to actually happen, so setting the driver to false results in the same outcome. I am using git version 1.8.1.2. I'm not sure if there could be some other setting (such as my choice of external merge tool) that I have set that could be causing git to ignore the results of merge-file if it results in a failure? – user1748155 Oct 03 '14 at 14:57
  • 5
    adding to @VitalyB comment. The merge also does not appear if _both_ branches made the same change to a file. For example, both branches increased the version by 1, and you would like to increase by 2 while trying to merge... Very practical problem, unsolved yet.:( – VasiliNovikov Aug 26 '15 at 18:31
  • I've already done the merge. Should `git checkout -m *filename*` invoke the merge driver? If so, it's not working for me. :( – Adrian Jan 15 '18 at 16:25
  • What cases does this work for which would fail in the OP's option 4? Does option 4 not produce the <<<<< ===== >>>> for instance? – Nero gris Aug 07 '18 at 17:33
2

These two commands seems to have the same effect as using the custom merge driver:

git merge --no-commit your_target_branch
git checkout --conflict merge .   (do not forget the . and run it in the top dir of the repository)

The first command stops the merge before the creation of the merge commit, and the second marks all the files modified in the two branches as a conflict to solve even if there was no conflict originally.

louisiuol
  • 43
  • 6
  • I like the idea of a "one time" solution (I just ran into such a case), however this did not work for me. Git did an FF resulting in a bad merge (it was essentially and ours merge). – Nero gris Aug 20 '18 at 12:00
  • @Nerogris Indeed it will not work for fast-forward merges. You can always add the --no-ff option but the second command will produce no conflict as there are no changes from the common ancestor in one of the two branches of the merge. – louisiuol Aug 21 '18 at 19:42
  • I have ff set to false in my global config. When I said it was an FF merge I mean that if I hadn't told Git not to do those, it would have. – Nero gris Aug 22 '18 at 19:33
  • I'm not convinced the 2nd line actually "creates" conflicts that didn't exist previously. Perhaps you can provide an example set of commands where it does work. – TTT Feb 05 '21 at 16:27
0

Note: this article "Writing a git merge driver for PO files" illustrates the kind of manipulation you can do when manually merging a file: you can pre-processed it in order for your manual merge to have certain data ready.

git merge-file can be used, for instance, to DECRYPT (and re-encrypt) files before merging (!)

In your case, exiting your merge driver with a non-0 status ensure that the merge will be a manual one.

VonC
  • 1,042,979
  • 435
  • 3,649
  • 4,283
  • 2
    Could you elaborate about the "merge driver for PO files" link because that seems to result in access denied for me? I'm hoping it would provide answer to question http://stackoverflow.com/questions/16214067/wheres-the-3-way-git-merge-driver-for-po-gettext-files – Mikko Rantalainen Apr 29 '13 at 05:13
  • @MikkoRantalainen blocked at work, so I will try this evening from home. – VonC Apr 29 '13 at 05:17
  • 1
    The "merge driver for PO files" doesn't work for me either :( –  Jun 10 '13 at 02:00
  • 1
    @sampablokuper I concur: the article is blocked for "non-authorized" reader. I wouldn't know why. – VonC Jun 10 '13 at 10:15
  • @VonC any chance you could quote the relevant parts of the article here, then? –  Jun 10 '13 at 11:32
  • 1
    @sampablokuper I don't have access to this blog right now. I'll keep looking for its content. – VonC Jun 10 '13 at 13:23