简体   繁体   中英

How to automatically resolve all conflicts in git rebase, “git add .”

The project I'm working on is a maven application with parent and child modules. Changes to the parent module go to the development branch. Each child module has its own branch that is rebased off of development to inherit these changes.

As of right now, I'm trying to set up a rebase script. To this end, I'm trying to figure out a way automatically resolve any conflicts that come up with no interruptions. As part of my testing, I'm rebasing a test branch I made off of development, by always defaulting to 'their' changes:

git ls-files -z *_child | xargs -0 git update-index --skip-worktree #I do not want the child module to be touched

git rebase -X theirs development

However, the rebasing process is still being interrupted by conflict, usually for extremely old commits that have been resolved months ago. To resolve this, I use:

git add .
git rebase --continue

This works until I get to another conflict. Right now, I'm looking into using git rerere to record the resolution actions I took:

git config --local rerere.enabled true

However, I am still trying to figure out how this actually works with rebasing. Do you have any suggestions, either with git rerere or some other git command, to resolve this? Ideally, I would like to take out git add. altogether and always default conflicts to 'their' changes.

Important first note: your git update-index is not very functional here. Rebase will need to touch files marked --skip-worktree if the underlying merge operation needs to touch them. It will not touch them if not. Marking the files might get you a useful error during the cherry-pick operation (I have not tested this but you can test it yourself).


A rebase is repeated git cherry-pick (or sometimes the equivalent). Each cherry-pick uses the merge process—what I like to call merge as a verb , or to merge —and as you see, merges can have merge conflicts. The two ways in which git cherry-pick differs from a normal merge are:

  1. The merge base is just the parent of the commit being cherry-picked.
  2. The commit that will be made at the end is a regular (non-merge) commit.

Enabling rerere will have the same effect as on any other git merge , as the reuse of recorded resolutions happens in the same way. It won't help you with your case because you are already using -X theirs , which preempts the kind of conflict that rerere could resolve.

Conflicts during a merge arise either from what I call high level operations, on an entire tree of files, or from low-level merge work. I use these two phrases to distinguish between two different code sections in Git's merge engine. The merge works by, in effect, running two git diff operations:

git diff --find-renames <base> <tip1>
git diff --find-renames <base> <tip2>

These diffs are internal to git merge , which has difference engine compiled-in, and can pass various options that are hard to achieve via the command line, but the principle is the same:

  1. The rename-finding looks at files added and/or deleted and tries to match a left-hand-side (merge base side) "deleted" file against a right-hand-side "added" file. If so, Git decides that this file was renamed .

  2. Matching up file names like this produces identity mappings: the base file named F B has new name F L on the left side and F R on the right side. All three names may be equal, or two or all three may differ, but either way, this is "the same" file. It also leaves us with files that are added and/or deleted on either side, and files that may or may not be modified on one or both sides.

  3. Now we must combine all the changes... but this happens first at the file names and existence level:

    • If the left side deletes file F d and the right side does not change it, that's OK. But if the left side deletes it and the right side modifies it, we have a delete/modify conflict . (If the right side deletes it and the left side modifies it, we have the same conflict, reversed.)

    • If the left side renames a file and the right side deletes it (or same with sides swapped), we have a rename/delete conflict .

    • If both sides add a new file from scratch but with the same name, we have an add/add conflict .

    • If both sides rename base file F B , but to two different names *F L and F R , we have a rename/rename conflict .


    Git resolves none of these conflicts on its own. These are high level conflicts.

    Having resolved, or not, any high level tree operations, we now have cases where the same file has been carried from base to both branch tips—with "same file" accounting for renames as above—but has been modified by both sides. Git now runs code from a separate file named ll-merge.c : the low-level merge code.

    LLMerge allows users to define merge drivers . When using those, the entire operation is up to the merge driver, which just gets all three input files' data (base, left/HEAD, and right/theirs). When using the built-in merge drivers, resolution of conflicts can be done automatically, using the -X extended-options: -X theirs chooses the right-side change, discarding the left-side (base-vs-HEAD) change. -X ours chooses the left-side change. Without these options, Git just declares a conflict when the changes overlap or abut.

Since you are using -X theirs and getting conflicts, we can conclude that you must be getting high level conflicts. Git's rerere code does not record, and cannot re-use, these resolutions.

To discover these after a merge has stopped with a conflict, you can use git status or inspect the index / staging-area with git ls-files --stage . Unfortunately, Git does a poor (as in mostly nonexistent) job of recording what the high level conflicts actually were. It prints the conflicts, so if you capture the output, you can parse it; or, you can run your own git diff --find-renames --name-status command to see what the merge saw.

In the general case of merges, git diff --find-renames is problematic as git merge may build a temporary commit that holds what Git calls a virtual merge base . For cherry-pick, though, the merge base is simply the parent of the commit being picked at the time, so using this method could be reliable. You will have to write code either way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM