Merge branch 'sh/mergetool-hideresolved'

"git mergetool" feeds three versions (base, local and remote) of
a conflicted path unmodified.  The command learned to optionally
prepare these files with unconflicted parts already resolved.

* sh/mergetool-hideresolved:
  mergetool: add per-tool support and overrides for the hideResolved flag
  mergetool: break setup_tool out into separate initialization function
  mergetool: add hideResolved configuration
This commit is contained in:
Junio C Hamano 2021-02-17 17:21:41 -08:00
commit 78a26cb720
6 changed files with 101 additions and 3 deletions

View File

@ -13,6 +13,11 @@ mergetool.<tool>.cmd::
merged; 'MERGED' contains the name of the file to which the merge merged; 'MERGED' contains the name of the file to which the merge
tool should write the results of a successful merge. tool should write the results of a successful merge.
mergetool.<tool>.hideResolved::
Allows the user to override the global `mergetool.hideResolved` value
for a specific tool. See `mergetool.hideResolved` for the full
description.
mergetool.<tool>.trustExitCode:: mergetool.<tool>.trustExitCode::
For a custom merge command, specify whether the exit code of For a custom merge command, specify whether the exit code of
the merge command can be used to determine whether the merge was the merge command can be used to determine whether the merge was
@ -40,6 +45,16 @@ mergetool.meld.useAutoMerge::
value of `false` avoids using `--auto-merge` altogether, and is the value of `false` avoids using `--auto-merge` altogether, and is the
default value. default value.
mergetool.hideResolved::
During a merge Git will automatically resolve as many conflicts as
possible and write the 'MERGED' file containing conflict markers around
any conflicts that it cannot resolve; 'LOCAL' and 'REMOTE' normally
represent the versions of the file from before Git's conflict
resolution. This flag causes 'LOCAL' and 'REMOTE' to be overwriten so
that only the unresolved conflicts are presented to the merge tool. Can
be configured per-tool via the `mergetool.<tool>.hideResolved`
configuration variable. Defaults to `true`.
mergetool.keepBackup:: mergetool.keepBackup::
After performing a merge, the original file with conflict markers After performing a merge, the original file with conflict markers
can be saved as a file with a `.orig` extension. If this variable can be saved as a file with a `.orig` extension. If this variable

View File

@ -38,6 +38,10 @@ get_merge_tool_cmd::
get_merge_tool_path:: get_merge_tool_path::
returns the custom path for a merge tool. returns the custom path for a merge tool.
initialize_merge_tool::
bring merge tool specific functions into scope so they can be used or
overridden.
run_merge_tool:: run_merge_tool::
launches a merge tool given the tool name and a true/false launches a merge tool given the tool name and a true/false
flag to indicate whether a merge base is present. flag to indicate whether a merge base is present.

View File

@ -61,6 +61,9 @@ launch_merge_tool () {
export BASE export BASE
eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"' eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"'
else else
initialize_merge_tool "$merge_tool"
# ignore the error from the above --- run_merge_tool
# will diagnose unusable tool by itself
run_merge_tool "$merge_tool" run_merge_tool "$merge_tool"
fi fi
} }
@ -79,6 +82,9 @@ if test -n "$GIT_DIFFTOOL_DIRDIFF"
then then
LOCAL="$1" LOCAL="$1"
REMOTE="$2" REMOTE="$2"
initialize_merge_tool "$merge_tool"
# ignore the error from the above --- run_merge_tool
# will diagnose unusable tool by itself
run_merge_tool "$merge_tool" false run_merge_tool "$merge_tool" false
else else
# Launch the merge tool on each path provided by 'git diff' # Launch the merge tool on each path provided by 'git diff'

View File

@ -166,6 +166,10 @@ setup_tool () {
return 1 return 1
} }
hide_resolved_enabled () {
return 0
}
translate_merge_tool_path () { translate_merge_tool_path () {
echo "$1" echo "$1"
} }
@ -250,6 +254,10 @@ trust_exit_code () {
fi fi
} }
initialize_merge_tool () {
# Bring tool-specific functions into scope
setup_tool "$1" || return 1
}
# Entry point for running tools # Entry point for running tools
run_merge_tool () { run_merge_tool () {
@ -261,9 +269,6 @@ run_merge_tool () {
merge_tool_path=$(get_merge_tool_path "$1") || exit merge_tool_path=$(get_merge_tool_path "$1") || exit
base_present="$2" base_present="$2"
# Bring tool-specific functions into scope
setup_tool "$1" || return 1
if merge_mode if merge_mode
then then
run_merge_cmd "$1" run_merge_cmd "$1"

View File

@ -239,6 +239,13 @@ checkout_staged_file () {
fi fi
} }
hide_resolved () {
git merge-file --ours -q -p "$LOCAL" "$BASE" "$REMOTE" >"$LCONFL"
git merge-file --theirs -q -p "$LOCAL" "$BASE" "$REMOTE" >"$RCONFL"
mv -- "$LCONFL" "$LOCAL"
mv -- "$RCONFL" "$REMOTE"
}
merge_file () { merge_file () {
MERGED="$1" MERGED="$1"
@ -265,6 +272,8 @@ merge_file () {
ext= ext=
esac esac
initialize_merge_tool "$merge_tool" || return
mergetool_tmpdir_init mergetool_tmpdir_init
if test "$MERGETOOL_TMPDIR" != "." if test "$MERGETOOL_TMPDIR" != "."
@ -276,7 +285,9 @@ merge_file () {
BACKUP="$MERGETOOL_TMPDIR/${BASE}_BACKUP_$$$ext" BACKUP="$MERGETOOL_TMPDIR/${BASE}_BACKUP_$$$ext"
LOCAL="$MERGETOOL_TMPDIR/${BASE}_LOCAL_$$$ext" LOCAL="$MERGETOOL_TMPDIR/${BASE}_LOCAL_$$$ext"
LCONFL="$MERGETOOL_TMPDIR/${BASE}_LOCAL_LCONFL_$$$ext"
REMOTE="$MERGETOOL_TMPDIR/${BASE}_REMOTE_$$$ext" REMOTE="$MERGETOOL_TMPDIR/${BASE}_REMOTE_$$$ext"
RCONFL="$MERGETOOL_TMPDIR/${BASE}_REMOTE_RCONFL_$$$ext"
BASE="$MERGETOOL_TMPDIR/${BASE}_BASE_$$$ext" BASE="$MERGETOOL_TMPDIR/${BASE}_BASE_$$$ext"
base_mode= local_mode= remote_mode= base_mode= local_mode= remote_mode=
@ -322,6 +333,45 @@ merge_file () {
checkout_staged_file 2 "$MERGED" "$LOCAL" checkout_staged_file 2 "$MERGED" "$LOCAL"
checkout_staged_file 3 "$MERGED" "$REMOTE" checkout_staged_file 3 "$MERGED" "$REMOTE"
# hideResolved preferences hierarchy.
global_config="mergetool.hideResolved"
tool_config="mergetool.${merge_tool}.hideResolved"
if enabled=$(git config --type=bool "$tool_config")
then
# The user has a specific preference for a specific tool and no
# other preferences should override that.
: ;
elif enabled=$(git config --type=bool "$global_config")
then
# The user has a general preference for all tools.
#
# 'true' means the user likes the feature so we should use it
# where possible but tool authors can still override.
#
# 'false' means the user doesn't like the feature so we should
# not use it anywhere.
if test "$enabled" = true && hide_resolved_enabled
then
enabled=true
else
enabled=false
fi
else
# The user does not have a preference. Ask the tool.
if hide_resolved_enabled
then
enabled=true
else
enabled=false
fi
fi
if test "$enabled" = true
then
hide_resolved
fi
if test -z "$local_mode" || test -z "$remote_mode" if test -z "$local_mode" || test -z "$remote_mode"
then then
echo "Deleted merge conflict for '$MERGED':" echo "Deleted merge conflict for '$MERGED':"

View File

@ -842,4 +842,22 @@ test_expect_success 'mergetool --tool-help shows recognized tools' '
grep meld mergetools grep meld mergetools
' '
test_expect_success 'mergetool hideResolved' '
test_config mergetool.hideResolved true &&
test_when_finished "git reset --hard" &&
git checkout -b test${test_count}_b main &&
test_write_lines >file1 base "" a &&
git commit -a -m "base" &&
test_write_lines >file1 base "" c &&
git commit -a -m "remote update" &&
git checkout -b test${test_count}_a HEAD~ &&
test_write_lines >file1 local "" b &&
git commit -a -m "local update" &&
test_must_fail git merge test${test_count}_b &&
yes "" | git mergetool file1 &&
test_write_lines >expect local "" c &&
test_cmp expect file1 &&
git commit -m "test resolved with mergetool"
'
test_done test_done