mergetool: add hideResolved configuration

The purpose of a mergetool is to help the user resolve any conflicts that Git cannot automatically resolve. If there is a conflict that must be resolved manually Git will write a file named MERGED which contains everything Git was able to resolve by itself and also everything that it was not able to resolve wrapped in conflict markers. One way to think of MERGED is as a two- or three-way diff. If each "side" of the conflict markers is separately extracted an external tool can represent those conflicts as a side-by-side diff. However many mergetools instead diff LOCAL and REMOTE both of which contain versions of the file from before the merge. Since the conflicts Git resolved automatically are not present it forces the user to manually re-resolve those conflicts. Some mergetools also show MERGED but often only for reference and not as the focal point to resolve the conflicts. This adds a `mergetool.hideResolved` flag that will overwrite LOCAL and REMOTE with each corresponding "side" of a conflicted file and thus hide all conflicts that Git was able to resolve itself. Overwriting these files will immediately benefit any mergetool that uses them without requiring any changes to the tool. No adverse effects were noted in a small survey of popular mergetools[1] so this behavior defaults to `true`. However it can be globally disabled by setting `mergetool.hideResolved` to `false`. [1] https://www.eseth.org/2020/mergetools.html https://github.com/whiteinge/eseth/blob/c884424769fffb05d87afb33b2cf80cecb4044c3/2020/mergetools.md Original-implementation-by: Felipe Contreras <felipe.contreras@gmail.com> Signed-off-by: Seth House <seth@eseth.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Seth House committed Feb 9, 2021 at 13:07 UTC 98ea309b3fa4818c1591b9071925ccb22c2e786b
3 files changed +42
Documentation/config/mergetool.txt
+10
@@ -40,6 +40,16 @@ mergetool.meld.useAutoMerge::
40 value of `false` avoids using `--auto-merge` altogether, and is the
41 default value.
42
43 +mergetool.hideResolved::
44 + During a merge Git will automatically resolve as many conflicts as
45 + possible and write the 'MERGED' file containing conflict markers around
46 + any conflicts that it cannot resolve; 'LOCAL' and 'REMOTE' normally
47 + represent the versions of the file from before Git's conflict
48 + resolution. This flag causes 'LOCAL' and 'REMOTE' to be overwriten so
49 + that only the unresolved conflicts are presented to the merge tool. Can
50 + be configured per-tool via the `mergetool.<tool>.hideResolved`
51 + configuration variable. Defaults to `true`.
52 +
53 mergetool.keepBackup::
54 After performing a merge, the original file with conflict markers
55 can be saved as a file with a `.orig` extension. If this variable
git-mergetool.sh
+14
@@ -239,6 +239,13 @@ checkout_staged_file () {
239 fi
240 }
241
242 +hide_resolved () {
243 + git merge-file --ours -q -p "$LOCAL" "$BASE" "$REMOTE" >"$LCONFL"
244 + git merge-file --theirs -q -p "$LOCAL" "$BASE" "$REMOTE" >"$RCONFL"
245 + mv -- "$LCONFL" "$LOCAL"
246 + mv -- "$RCONFL" "$REMOTE"
247 +}
248 +
249 merge_file () {
250 MERGED="$1"
251
@@ -276,7 +283,9 @@ merge_file () {
283
284 BACKUP="$MERGETOOL_TMPDIR/${BASE}_BACKUP_$$$ext"
285 LOCAL="$MERGETOOL_TMPDIR/${BASE}_LOCAL_$$$ext"
286 + LCONFL="$MERGETOOL_TMPDIR/${BASE}_LOCAL_LCONFL_$$$ext"
287 REMOTE="$MERGETOOL_TMPDIR/${BASE}_REMOTE_$$$ext"
288 + RCONFL="$MERGETOOL_TMPDIR/${BASE}_REMOTE_RCONFL_$$$ext"
289 BASE="$MERGETOOL_TMPDIR/${BASE}_BASE_$$$ext"
290
291 base_mode= local_mode= remote_mode=
@@ -322,6 +331,11 @@ merge_file () {
331 checkout_staged_file 2 "$MERGED" "$LOCAL"
332 checkout_staged_file 3 "$MERGED" "$REMOTE"
333
334 + if test "$(git config --type=bool mergetool.hideResolved)" != "false"
335 + then
336 + hide_resolved
337 + fi
338 +
339 if test -z "$local_mode" || test -z "$remote_mode"
340 then
341 echo "Deleted merge conflict for '$MERGED':"
t/t7610-mergetool.sh
+18
@@ -828,4 +828,22 @@ test_expect_success 'mergetool -Oorder-file is honored' '
828 test_cmp expect actual
829 '
830
831 +test_expect_success 'mergetool hideResolved' '
832 + test_config mergetool.hideResolved true &&
833 + test_when_finished "git reset --hard" &&
834 + git checkout -b test${test_count}_b master &&
835 + test_write_lines >file1 base "" a &&
836 + git commit -a -m "base" &&
837 + test_write_lines >file1 base "" c &&
838 + git commit -a -m "remote update" &&
839 + git checkout -b test${test_count}_a HEAD~ &&
840 + test_write_lines >file1 local "" b &&
841 + git commit -a -m "local update" &&
842 + test_must_fail git merge test${test_count}_b &&
843 + yes "" | git mergetool file1 &&
844 + test_write_lines >expect local "" c &&
845 + test_cmp expect file1 &&
846 + git commit -m "test resolved with mergetool"
847 +'
848 +
849 test_done