13

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.

Piotr Dobrogost
  • 41,292
  • 40
  • 236
  • 366
kbro
  • 4,754
  • 7
  • 29
  • 40
  • From the gitattributes man page: When deciding what attributes are assigned to a path, git consults $GIT_DIR/info/attributes file (which has the highest precedence), .gitattributes file in the same directory as the path in question, and its parent directories up to the toplevel of the work tree (the further the directory that contains .gitattributes is from the path in question, the lower its precedence)... so put your .gitattribute at the top of the relevant tree. the custom merge driver will only apply for that tree. – VonC Dec 15 '09 at 22:01
  • `echo * merge=keepTheir > dirWithCopyMerge\.gitattributes` should do it for all files under `dirWithCopyMerge`, not for all files for the all repo. That should be what you are looking for. – VonC Dec 15 '09 at 22:04
  • I have just completed my answer to reflect my previous comments. – VonC Dec 15 '09 at 22:24
  • 1
    Does '*' glob in the same way as in bash? If so it will miss dot-files. I'm assuming your command line is for Windoze (msysGit?) as you've got a backslash for a directory separator (that had me confused for a moment!) I suppose setting a blanket copy-merge with "git config merge.default.driver" could have unforseen consequences, as I might conceivably need a normal merge in other parts of the repo, so a more focussed approach using a well-placed .gitattributes file might be less surprising. Thanks for the info about attributes file precedence - very useful. – kbro Dec 15 '09 at 22:30
  • 1
    See SO answer **[git command for making one branch like another](http://stackoverflow.com/questions/4911794/git-command-for-making-one-branch-like-another/4912267#4912267)** for *all* the current possible ways to **simulate `git merge -s their`**. – VonC Mar 13 '11 at 08:55

5 Answers5

9

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.

kbro
  • 4,754
  • 7
  • 29
  • 40
  • You have so many subtrees you do not want the .gitattributes files? And I do not understand the "is bound to lead to confusion, especially if you also use things like "*.bin -merge" part. Anyway, interesting feedback. +1 – VonC Dec 16 '09 at 04:54
  • For my purposes I only have one subtree - the entire repo! But I'm against .gitattributes files on principle as they pollute the workspace - I worked with CVS and Subversion for too long to want to put tool-specific cr*p in amongst my files :-) – kbro Dec 16 '09 at 09:15
  • 1
    The confusion I talk about could arise if you've already used .gitattributes to set up custom merge actions for other filetypes, and then you want to set things up for a copy-merge. Given the precedence rules for .gitattributes files, putting one in your top-level directory won't override the setting in any lower-level .gitattributes files. You'll therefore get copy-merge for previously default-merged files, but you'll get the previous override action for ones you've already defined. Definitely confusing. – kbro Dec 16 '09 at 09:16
  • The only way round this, if you want copy-merge throughout the repository (which I do) is to set the merge attribute in $GIT_DIR/info/attributes, as this overrides *everything*. Note that setting the merge.default config variable doesn't guarantee a complete copy-merge as this only catches files for which the merge attribute is *unspecified*. So @VonC was right after all - use attributes. But I was sorta right too, saying .gitattributes wasn't the best place to do it :-) – kbro Dec 16 '09 at 09:20
3

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
Sean Finney
  • 111
  • 1
  • 1
  • just tried this, works quite well except you can't limit the merge to specific paths. but other than that it is great – Tom Gruner Apr 27 '11 at 10:36
2

(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.

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • The solution will work but, since I want it to happen for every file, doing it with attributes is a bit ungainly. However, the gitattributes(5) manpage section on "merge" mentions the "merge.default" configuration variable. This defines the merge driver to be called for files where the merge attribute is unspecified, which will be my entire repository if I don't specify it! So it looks like I can do "git config merge.default.driver 'mv -f %B %A && exit 0'" and not worry about setting attributes. Does that make sense to you? I used mv rather than cp because it's quicker. – kbro Dec 15 '09 at 21:56
  • @kbro: "since I want it to happen for every file, doing it with attributes is a bit ungainly". I would say: quite "gainly" actually; a '*' would be enough. See my completed answer. – VonC Dec 15 '09 at 22:23
  • Will the '*' wildcard glob fully on U*IX systems, or should there be an extra attribute for '.*' assuming I want to copy-merge those files as well? Or is there a way to glob both dot-files and non-dot-files? Sad that I don't know! – kbro Dec 15 '09 at 23:24
2

In version 1.7.1 of Git, you can pass a "theirs" strategy to merge with and "-Xtheirs" argument.

git merge -Xtheirs otherBranch

Not sure if that applies to what you are trying to do, but it's probably worth a shot.

(See also this related question)

Community
  • 1
  • 1
Alan W. Smith
  • 24,647
  • 4
  • 70
  • 96
1

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

SETUP

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

USE

$ GIT_CONFIG_PARAMETERS="'merge.default=theirs'" git merge develop

BONUS: ALIAS

$ git config alias.merge-theirs \!GIT_CONFIG_PARAMETERS=\""'"merge.default=theirs"'"\"\ git\ merge

USE: ALIAS

$ 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)

lkraav
  • 2,696
  • 3
  • 26
  • 26