Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

`git merge -s theirs` needed but it does not exist

Tags:

git

merge

copy

I have a number of remote repositories that I want to merge together. Some of the subtrees in those repositories are unique to the remote (they contain data that is host-specific), other subtrees contain data that is (supposed to be) common across all remotes.

What I want to do, essentially, is run "git pull " for each remote. This will fast-forward the local master branch along the tracking branch for the remote master for host-specific files that have changed on the remote, and will do nothing for the common files because they will not have changed.

A change in a common file (call it F, with the change being F') shouldn't be a problem, even if it only happens on one remote at first. git-merge will Do The Right Thing and give me a copy of F' in my composite workspace, which is what I want. The problem comes if the same common file changes in a different way on another remote (call it F"). git-merge will give me a composite of F' and F", which isn't what I want. All I want is F".

When I worked with ClearCase we called this a copy-merge. The result of the merge was always an exact copy of the contributor. This sounds a lot like "git merge -s theirs" would be, except that it doesn't exist.

I wondering whether I can cook something up with "git-read-tree -m --trivial" to get the fast-forward merges out of the way, then do some magic with git-merge and a custom mergetool that simply copies the $REMOTE file to $MERGED. But even with that I don't see how I can stop git-merge from compositing F' and F" if it things the merge is trivial.

I've read the link Is there a "theirs" version of "git merge -s ours"? on this site, and the post by Junio Hamano it references explaining why "git merge -s theirs" is such a bad idea, but this isn't the case for me. I do value the old history, but I need to jump ship and follow the change on the remote site when one happens. No new work is done on the local site. It simply needs to form a composite of all the remote sites, taking the latest "common" file from the last polled remote when one changes.

Thanks in advance for any help you can give me.

like image 896
kbro Avatar asked Dec 15 '09 21:12

kbro


4 Answers

Many thanks to @VonC for suggesting the use of the merge=custom-driver attribute in the .gitattributes file. While this will work, I'm reluctant to pollute my workspace with .git files, and while I could use $GIT_DIR/info/attributes to avoid the pollution, I'm bothered by the need for 2 rules to catch dot-files and non-dot files.

After a bit of experimentation I managed to get a solution with the merge.default configuration variable (mentioned in the gitattributes(5) manpage) working. The trick I missed was that merge.default takes the name of a custom driver you have defined previously; you don't give it the custom command directly. Here's what works for me...

First define your copy-merge custom driver. You can use shell commands directly; there's no need for an external script (just make sure you get your shell meta-character quoting right):

git config merge.copy-merge.name   'Copy Merge'
git config merge.copy-merge.driver 'mv %B %A'

Note that mv returns 0 on success, 1 on failure, meeting the criteria for reporting merge "success" back to git.

Now tell git that ALL merges are copy-merges:

git config merge.default copy-merge

Hey Presto! Job done. git merge <branch> will now copy-merge everything so the branch you're on contains exact copies of all files on <branch>. QED.

If you want to do a non-copy-merge then simply reset the default merge driver:

git config --unset merge.default

If you do want to be more selective then leave merge.default unset and use attributes as @VonC says:

cd path/to/copy-merge/in
echo '* merge=copy-merge'  >  .gitattributes
echo '.* merge=copy-merge' >> .gitattributes

Do this at the top of every subtree you want to copy-merge in. If there's a sub-subtree that you DON'T want to copy-merge in, you can turn it off again:

cd path/to/copy-merge/in/path/to/normal-merge/in
echo '* merge'  >  .gitattributes
echo '.* merge' >> .gitattributes

WARNING: littering your working tree with lots of .gitattributes files is bound to lead to confusion, especially if you also use things like "*.bin -merge" in other directories to force all merges of .bin files to fail with conflicts. It may be better to use $GIT_DIR/info/attributes for this sort of thing, as it has the highest precedence.

like image 197
kbro Avatar answered Oct 17 '22 23:10

kbro


After a ton of research, going through all the SO noise, and learning more about git again (does it ever end?), I believe this answer is the most noise-free and optimal way to achieve a custom --strategy theirs simulation merge driver. Usable on demand, no gitattributes litter needed.

I'm actually still a bit dizzy on whether this is a --strategy theirs simulation, or the smoothest usable checkout --theirs . conflict resolution method. Maybe they're equivalents, but I'd have to diagnose a few result graphs to fully understand, which there's no time for right now.

Props to @kbro doing the right thing chasing the fine details, and getting quite close in https://stackoverflow.com/a/1911370/35946

[git:master] is shell prompt display.

SETUP

$ [git:master] git config merge.theirs.name 'simulate `-s theirs`'
$ [git:master] git config merge.theirs.driver 'cat %B > %A' # same as `mv` or `cp`, matter of taste

USE

$ [git:master] GIT_CONFIG_PARAMETERS="'merge.default=theirs'" git merge develop

BONUS: ALIAS

$ [git:master] git config alias.merge-theirs \!GIT_CONFIG_PARAMETERS=\""'"merge.default=theirs"'"\"\ git\ merge

USE: ALIAS

$ [git:master] git merge-theirs develop

Notice the quotes structure of GIT_CONFIG_PARAMETERS, because it must be able to take multiple complex values. Getting that BONUS command line took effort to figure out.

PS GIT_CONFIG_PARAMETERS must be the best kept secret on SO, we're almost done with 2016, https://stackoverflow.com/search?q=git_config_parameters has 1 (one) result (1)

like image 43
lkraav Avatar answered Oct 05 '22 18:10

lkraav


Ran into this problem the other day:

http://seanius.net/blog/2011/02/git-merge-s-theirs/

git merge -s ours ref-to-be-merged
git diff --binary ref-to-be-merged | git apply -R --index
git commit -F .git/COMMIT_EDITMSG --amend
like image 3
Sean Finney Avatar answered Oct 17 '22 21:10

Sean Finney


(update 2011:
The answer " git command for making one branch like another " lists all the possible ways to simulate a git merge -s theirs`)


For the specific files/trees you want to copy-merge, you could setting up a gitattributes value like the one I mention in this SO question, defining a custom merge driver.
The script associated with the merge attribute would ensure always keeping the remote file as the merge result (see this SO answer for an illustration, albeit for the opposite scenario -- keeping the local version).

echo * merge=keepTheir > dirWithCopyMerge\.gitattributes
git config merge.keepTheir.name "always keep theirduring merge"
git config merge.keepTheir.driver "keepTheir.sh %O %A %B"

By setting a .gitattribute on top of the sub-tree you want to be copy-merged, with '* merge=keepTheir' as its content, you effectively attribute a custom merge driver to all files of that subtree (note the use of the '*' wildcard here).

With keepTheir.sh as:

mv -f $3 $2
exit 0

you do not have to modify any "default" merge driver, and you apply your own only on the files you want.

like image 2
VonC Avatar answered Oct 17 '22 21:10

VonC