Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
288 views
in Technique[技术] by (71.8m points)

maven - Git merge strategy for a specific file depending on rebase / merge

I'm looking for a way to make git use a specific merge strategy (ours / theirs) for a specific file depending whether I am merging or rebasing my feature branch.

Let me explain: imagine I have a feature branch 'fb' with changes to a file ".mvn/maven.config". If I merge master into 'fb' I want to keep my file so use the 'ours' strategy (I can define it in the .gitattributes file). But if I rebase 'fb' onto master (instead of merging), I still want to keep my ".mvn/maven.config" of the 'fb' branch so I should use the 'theirs' strategy because, as I understand it, the meaning of the strategies is reversed between merge & rebase.

Question is: is there an easy way to define an intelligent merge strategy ?

I've seen the "git merge driver" concept BUT to my knowledge a merge driver has no way to tell if it is a merge or a rebase thus cannot infer whether to use the ours VS theirs.

Do I have to tell my teammates that there is no solution and that they are forced to be cautious each time and manually select the appropriate strategy manually ?

NB: I have added the "maven" tag because anyone using git and maven may have the same problem with keeping the .mvn/maven.config file

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It's true that you would need "theirs" during rebase. This is because rebase operations are actually a series of cherry-picks, and each cherry-pick is a merge operation in which your HEAD (current commit) is on the target branch (being built), rather than pointing to a commit on your original branch (now being copied).

That is, a normal merge looks like this:

...--o--o--*--o--o    <-- yourbranch (HEAD)
            
             o--o--o   <-- theirbranch

You run git merge theirbranch; Git compares commit *—the merge base—to your final commit to see what you did, and then compares * to their final commit to see what they did. Git then combines these changes, applying them all to *, to produce the merge:

...--o--o--*--o--o---M    <-- yourbranch (HEAD)
                   /
             o--o--o   <-- theirbranch

If you provide a .gitattributes-defined merge driver, you can have Git take your version of .mvn/maven.config—but there is a huge caveat here; see below.

When you rebase, however, you start out with this:

...--o--o--*--o--o   <-- theirbranch
            
             A--B--C   <-- yourbranch (HEAD)

You run git rebase theirbranch, and Git finds the commits since *—these are your A, B, and C commits above—and lists their hash IDs into a temporary file. (If you use interactive rebase, you will see a series of pick commands using those hash IDs.) Now that rebase has the IDs, it starts the real work by first checking out their branch, as a detached HEAD:

...--o--o--*--o--o   <-- theirbranch, HEAD
            
             A--B--C   <-- yourbranch

Git then runs as many git cherry-pick calls as needed to copy all the commits—in this case, three of them. Each cherry-pick is a special kind of merge, doing the merge action without making a merge-style commit. The result of the merge is a copy of the commit being cherry-picked. So the first merge operates with * as the merge base as before, with the HEAD commit as the current commit as always, and with commit A as "their" commit to be merged!

Hence, in this case, you would want your custom .gitattributes-defined driver take "their" version of .mvn/maven.config, because "their" version (in commit A) is really your version. That huge caveat I mentioned before is getting even bigger now, though!

Assuming A is copied successfully, you are now in this state:

                   A'   <-- HEAD
                  /
...--o--o--*--o--o   <-- theirbranch
            
             A--B--C   <-- yourbranch

Git now runs a second cherry-pick, which means a second merge operation: the merge base this time is commit A; your HEAD commit is your own copy A'; and "their" commit is your commit B. It's now safe to take either .mvn/maven.config, as long as you took the right one in the first cherry-pick.

The rebase will repeat this for commit C as well, to build C' atop B'. The merge base this time will be commit B. As with the process of copying commit B, you could use either .mvn/maven.config here. When this copy is done you have:

                   A'-B'-C'   <-- HEAD
                  /
...--o--o--*--o--o   <-- theirbranch
            
             A--B--C   <-- yourbranch

and Git now finishes the rebase by peeling the name yourbranch off commit C and pasting it onto the last copy C', re-attaching your HEAD in the process:

                   A'-B'-C'   <-- yourbranch (HEAD)
                  /
...--o--o--*--o--o   <-- theirbranch
            
             A--B--C   [no longer needed]

The huge caveat

This particular idea, of using .gitattributes to control which .mvn/maven.config file is used, is at least partially) doomed from the start for a different reason: When Git is doing a merge, Git looks first at the raw hash IDs of each file from each of the three commits (merge base, ours, and theirs). There are a total of 5 possibilities here:

  • All three hash IDs are the same: the merged file is the base file, which is their file and our file. Everything is pretty groovy at this point: you did not even need a merge driver!

  • The base file is the same as theirs, but different from ours: Git just takes our file. Git completely ignores your merge driver. The idea of using a merge driver fails (well, unless you wanted "ours", but that happens even without the merge driver).

  • The base file is the same as ours, but different from theirs: Git just takes their file. Git completely ignores your merge driver. The idea of using a merge driver fails, as with the previous case.

  • The base file is different from ours or theirs, but ours and theirs are the same: Git just takes our file (it's already in place, so that's easier for Git). Git completely ignores your merge driver. This is OK for our case, since both of us (ours and theirs) made the same change, but a merge driver that was going to keep the base version would fail here.

  • Last, the base file differs from ours, and ours differs from theirs: Git uses your merge driver. Your merge driver succeeds! Well, it does as long as you figure out which version you want ("ours" or "theirs").

Going through the list, you will see that your .gitattributes driver only works in one out of the five possible cases. It's not needed in another two cases. It is needed in two out of the five in which it is ignored! So there are two (out of five total) situations where your merge driver won't be used, when you wanted it to be used.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...