1

I'm currently cherry-picking a number of commits into another (release) branch. I know about the --strategy-option theirs when cherry-picking, but I'm afraid that this option isn't suitable for every commit I'm picking.

I'd prefer running with the default strategy generating conflicts, then manually resolve those for each file.

However, there are some files where I'd like to batch-accept all ours or their hunks, which is getting cumbersome doing it by hand. Is there a way to just accept all ours or their hunks in a conflicted file?

Note: git checkout --ours/--theirs seems unsuitable for cherry-picking as it checks out the whole file from the given branch. I only want to accept conflicting hunks from either version.

resi
  • 1,738
  • 2
  • 13
  • 14
  • I was just thinking that `git checkout -m --theirs` would be a good fit, but unfortunately that combination isn't supported. – resi Jan 17 '19 at 10:32

1 Answers1

2

There is not a really convenient way to do this, but you can build one from the tools Git provides. The main tool required is this git merge-file, which performs a three-way merge on a single trio of file-versions, i.e., base + ours + theirs. It accepts --ours and --theirs options to resolve conflicts the same way that -X ours and -X theirs does for an overall merge, i.e., it doesn't just take our file or their file, it only takes ours or theirs at conflict-points.

That's all great, but, where you do get the three versions? Git has stopped with a merge conflict on, say, main.py. In your work-tree, main.py contains the mess that Git left behind, with the <<<<<<< ... >>>>>>> markers around the conflicted lines. But git merge-file needs three un-marked-up input files, for the merge base version, the "ours" version, and the "theirs" version. But those three files are in the index! If file F had a conflict, there is a :1:F with the merge base version, :2:F with ours, and :3:F with theirs.

To get them, you can either use git show or git checkout-index. The latter is actually the right tool: git mergetool uses git checkout-index, with this little shell function:

checkout_staged_file () {
        tmpfile=$(expr \
                "$(git checkout-index --temp --stage="$1" "$2" 2>/dev/null)" \
                : '\([^ ]*\)    ')

        if test $? -eq 0 && test -n "$tmpfile"
        then
                mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3"
        else
                >"$3"
        fi
}

Invoked as checkout_staged_file 1 main.py main.py.base, for instance, it extracts the stage-1 (merge base) copy of main.py to main.py.base. Repeat with 2 and 3, and suitable variants on the third argument, to get all three files out. Then, run git merge-file on the three files in the way described in the git merge-file documentation.

(See the git mergetool source code for more. It's just a big shell script, so it's pretty easy to read and modify for your own purposes.)

torek
  • 448,244
  • 59
  • 642
  • 775
  • That sounds great, thanks! I'll try it tomorrow. Still sounds like a missing piece in the toolchain tough. – resi Jan 17 '19 at 20:09
  • @resi: it really is—there should be a `git remerge ` that accepts ours/theirs/union arguments, and otherwise is like doing all of the fiddling described above and then running `git merge-file`. (It might even include the `git checkout -m` action that uses the REUC record in the index to "unresolve" a previously resolved file.) – torek Jan 18 '19 at 05:55
  • I just tested it out, works like a charm. Thanks @torek! – resi Jan 18 '19 at 08:23
  • It turns out this can be easily configured as custom `git mergetool`, see https://gist.github.com/diresi/a9f0bd3f68e76d0576be4a709ee421ba – resi Jan 18 '19 at 09:20
  • just so you know, I just remembered this post+answer and I'm happy it's all still working and useful. thx! – resi May 25 '21 at 12:04