23

I have a master and a test branch of my (web) application. These projects are almost the same, except for one file which sets up the application, say "setup".

Whenever I merge one branch into the other, I would like that branch to keep its version of setup. That is, git should not attempt to merge the changes to that file.

I followed the guidance from the Pro Git book and created a .gitattributes file, with the line "setup merge=ours". However, this does not work - neither with fast forward merges not if I introduce conflicts.

(To be precise:

$: mkdir gitest
$: cd gittest
$: git init
$: echo "setup merge=ours" >> .gitattributes 
$: echo "master" >> setup
$: git add setup .gitattributes
$: git commit -a -m ...
$: git branch test
$: git checkout test
$: echo "test" >> setup
$: git commit -a -m ...
$: git checkout master
$: git merge test

Expected result: setup contains the word "master", instead git performs a ff merge and setup is "test'.)

ax.
  • 58,560
  • 8
  • 81
  • 72
Ian
  • 231
  • 1
  • 2
  • 4
  • possible duplicate of [How do I tell git to always select my local version for conflicted merges on a specific file?](http://stackoverflow.com/questions/928646/how-do-i-tell-git-to-always-select-my-local-version-for-conflicted-merges-on-a-sp) – Karl Bielefeldt Mar 29 '11 at 01:44
  • 3
    I looked through the git source and I can't see how `merge=ours` ever worked. I think someone wrote that without trying it. That other answer provides a more complicated solution, but with the bonus that it actually appears to work. – Karl Bielefeldt Mar 29 '11 at 01:47
  • 1
    That seems to work indeed! Thanks Karl.As a slight modification of that answer: simply putting "exit 0" as the merge-driver is easier than a dedicated bash scripting doing just that. – Ian Mar 29 '11 at 15:21

4 Answers4

23

I had the same error and it can be solved just defining an "ours" merge driver in .git/config:

[merge "ours"]
    name = "Keep ours merge"
    driver = true

Since true always return 0 the temporary file holding the current state will not be changed and will stay as the final version.

You can read more about merge driver in here: http://www.kernel.org/pub/software/scm/git/docs/gitattributes.html#_defining_a_custom_merge_driver

Addendum:

This works any time the driver is actually called and this seems to occur only when there are commits changing the same files (git the merge attribute). If there are changes in a single branch the driver is not going to be called.

pixelistik
  • 7,541
  • 3
  • 32
  • 42
Jorge E. Cardona
  • 92,161
  • 3
  • 37
  • 44
  • 5
    @VitalyB I tried several ways, but it works only if there is a change in both branches. Have a conflict atleast once, then the merge strategy is invoked. From that point onwards, for all conflicts and changes (including ff-able ones), the merge strategy is invoked and the intended file is not affected. See Kevin's answer below – Aswin Kumar Aug 02 '13 at 11:59
  • 1
    Having a conflict at least once does not guarantee that the merge strategy will be invoked always in the future. Sadly my experience shows me otherwise. – Richard Le Mesurier Apr 10 '14 at 10:55
  • Your addendum is correct. Merge "ours" does not protect a file from changes. However it does protect *changes from changes*. Considering A->B->B', merging B'->A will just make A==B' even if merge "ours" is in place, because A has no changes while B' does, and A being a direct ancestor of B', it's considered to be "ours". However if also A->A', then merging B'->A' won't work for any files protected by merge "ours" that changed in A'. – CommaToast Sep 13 '14 at 21:18
8

I found that if I modified the files on both branches and committed the modifications to each branch, and then tried the merge, it would invoke the merge driver and listen to my .gitattributes which specifies merge=ours. After this, the two files always differ on the two branches and so the merge driver will always be invoked, so I didn't need to have a custom merge driver that touches the file. It was only necessary to have both modified initially.

Kevin Bullaughey
  • 2,556
  • 25
  • 37
  • Have you run into any problems with this technique over the last year? I have also found no need to implement a custom redefinition of the "ours" strategy as suggested by Jorge. But I'm wondering if there is anything that comes back to bite you when you are not looking. – Richard Le Mesurier Apr 08 '14 at 07:18
  • I haven't noticed any problems, then again I'm using it for the very simple purpose of keeping a few individual files (like Gemfile.lock and .bundle/config) in version control but different on different developers' branches. Not sure how kosher it is, but I've found it convenient. – Kevin Bullaughey Apr 08 '14 at 17:07
  • Kevin, my use case is similar. I depend all my Android debug code on a `public static boolean DEBUG_ON`. Being able to keep it as `false` in my production branches, and `true` in my development branches makes life (and the GIT log) much simpler. So thanks for pointing me in the right direction. For interest, here is the post I made which references your answer: [git smudge/clean filter between branches](http://stackoverflow.com/a/22914145/383414) – Richard Le Mesurier Apr 08 '14 at 17:52
  • A day later, and my files are no longer being kept separate. Git has decided to merge the changes across the branches now. Even though the 2 files differ across the branches, it seems the merge driver is no longer being called. Pity... – Richard Le Mesurier Apr 10 '14 at 10:54
  • 2
    I think this is because git deals with *changes to files*, not to *files themselves*. Case in point: lets say you have a single branch. Now you make a "test" branch, and add .gitattributes files in each that specify your "config.php merge=ours" (and of course make sure to have set `git config merge.ours.driver=true`). Now lets say after creating the new branch, you make some changes "test" to config.php, then check out "master" and merge "test" back into it. What will happen is that "test" has changes to config.php but master has none, so there is no "ours" since "we" do not have any changes! – CommaToast Sep 13 '14 at 21:03
5

The merge driver is only called in non-trivial cases, i.e. if both master and test have touched setup (and you need to define the merge driver ours first):

git init
git config merge.ours.name '"always keep ours" merge driver'
git config merge.ours.driver 'touch %A'
echo "setup merge=ours" >> .gitattributes 
echo "master" >> setup
git add setup .gitattributes
git commit -a -m ...
git branch test
git checkout test
echo "test" >> setup
git commit -a -m ...
git checkout master
echo "more content" >> setup
git commit -a -m ...
git merge test

Having said that I wonder if it's sensible to have setup in the repository at all. If you really want it under version control you could use submodules or the subtree merge strategy to keep common files in sync.

Uwe Kleine-König
  • 3,426
  • 1
  • 24
  • 20
1

Have a smudge clean script in stead of separate branches. The script can be different depending on what machine you are on.

Adam Dymitruk
  • 124,556
  • 26
  • 146
  • 141
  • 1
    While it is true that you could also use the smudge and clean git keyword expansion filters, since those are defined by a .gitattributes file for each branch, if you're tracking your .gitattributes files then you need to use merge=ours on the .gitattributes that define which different filters to use on each branch. What you're describing sounds like a single-branch setup. – CommaToast Sep 13 '14 at 21:23