How can I teach `git-rerere` about the resolutions in an existing merge commit, with the least manual intervention

295 Views Asked by At

git-rerere is the exact solution to a problem I have ... except that the merges that need to be "learnt" have already been performed and are non-trivial to repeat.

How can I "teach" rerere about those commits with the minimum manual intervention.

Supposing that existing, but conflicting, branches A and B were merged together to form commit C with resolved conflicts. Then I was hoping to do:

  • git checkout A
  • git merge B
    • merge pauses when conflicts observed.
  • git reset --soft C
    • working files are now in the desired final state - i.e. the conflicts were "resolved".
  • git merge --continue
    • rerere sees the updated files as the appropriate resolution, and "learns" this resolution.

But the reset step doesn't work :(

  • A --soft reset isn't permitted during a merge.
  • A --mixed reset nukes MERGE_HEAD (and I assume generally screws around with the git state, such that the merge cannot be resumed)

EDIT:

Manually "resetting" the files, by checking out C before hand and copying over the relevant files works but is quite slow and tedious due to the large and complex nature of the repositor.

(lots of projects in separate folders, each with their own node_modules/packages folders that will tank the copy time, unless I target specific folders :( )

I'm hoping for something automated. #optimism

2

There are 2 best solutions below

0
philb On

In Git 2.36 and later, you can use git show --remerge-diff to create a diff showing how a merge was resolved. This can be used to do exactly what you want, with a few tricks.

  1. First, we recreate the merge conflict, but with predictable conflict markers. Just after the failed git merge B, you can run:

    git checkout --merge :/
    

    This will recreate the merge conflict, but use ours and theirs on the merge conflict markers lines. The top pathspec (:/) is used so this works from any directory in your repository.

  2. Then we use git show --remerge-diff, pass its output to sed to also use ours and theirs as conflict markers, and finally apply this diff:

    git show --format= --remerge-diff C | \
    sed -e 's/<<<<<<< .*/<<<<<<< ours/g' -e 's/>>>>>>> .*/>>>>>>> theirs/g' | \
    git apply
    

Note that in contrast to rerere, this approach allows you to also reapply any changes that were done in the merge outside of conflict regions.

0
jthill On

You've got the right idea, but reset --soft changes your commit parent, not your index. You want

git worktree add scratch A  # or C^1
git -C scratch merge B      # or C^2
git -C scratch read-tree C
git -C scratch rerere
git worktree remove -f scratch

and you're done. git read-tree has a ton of options, it's at the core of basically every index/worktree operation Git ever does, the one-tree version just loads the index, here replacing the inflight merge content pointers with the final merge result pointers.