Git fork

mergetool: new config guiDefault supports auto-toggling gui by DISPLAY

When no merge.tool or diff.tool is configured or manually selected, the
selection of a default tool is sensitive to the DISPLAY variable; in a
GUI session a gui-specific tool will be proposed if found, and
otherwise a terminal-based one. This "GUI-optimizing" behavior is
important because a GUI can make a huge difference to a user's ability
to understand and correctly complete a non-trivial conflicting merge.

Some time ago the merge.guitool and diff.guitool config options were
introduced to enable users to configure both a GUI tool, and a non-GUI
tool (with fallback if no GUI tool configured), in the same environment.

Unfortunately, the --gui argument introduced to support the selection of
the guitool is still explicit. When using configured tools, there is no
equivalent of the no-tool-configured "propose a GUI tool if we are in a GUI
environment" behavior.

As proposed in <xmqqmtb8jsej.fsf@gitster.g>, introduce new configuration
options, difftool.guiDefault and mergetool.guiDefault, supporting a special
value "auto" which causes the corresponding tool or guitool to be selected
depending on the presence of a non-empty DISPLAY value. Also support "true"
to say "default to the guitool (unless --no-gui is passed on the
commandline)", and "false" as the previous default behavior when these new
configuration options are not specified.

Signed-off-by: Tao Klerks <tao@klerks.biz>
Acked-by: David Aguilar <davvid@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>

authored by

Tao Klerks and committed by
Junio C Hamano
42943b95 ae73b2c8

+179 -14
+7
Documentation/config/difftool.txt
··· 34 35 difftool.prompt:: 36 Prompt before each invocation of the diff tool.
··· 34 35 difftool.prompt:: 36 Prompt before each invocation of the diff tool. 37 + 38 + difftool.guiDefault:: 39 + Set `true` to use the `diff.guitool` by default (equivalent to specifying 40 + the `--gui` argument), or `auto` to select `diff.guitool` or `diff.tool` 41 + depending on the presence of a `DISPLAY` environment variable value. The 42 + default is `false`, where the `--gui` argument must be provided 43 + explicitly for the `diff.guitool` to be used.
+7
Documentation/config/mergetool.txt
··· 85 86 mergetool.prompt:: 87 Prompt before each invocation of the merge resolution program.
··· 85 86 mergetool.prompt:: 87 Prompt before each invocation of the merge resolution program. 88 + 89 + mergetool.guiDefault:: 90 + Set `true` to use the `merge.guitool` by default (equivalent to 91 + specifying the `--gui` argument), or `auto` to select `merge.guitool` 92 + or `merge.tool` depending on the presence of a `DISPLAY` environment 93 + variable value. The default is `false`, where the `--gui` argument 94 + must be provided explicitly for the `merge.guitool` to be used.
+6 -4
Documentation/git-difftool.txt
··· 97 --[no-]gui:: 98 When 'git-difftool' is invoked with the `-g` or `--gui` option 99 the default diff tool will be read from the configured 100 - `diff.guitool` variable instead of `diff.tool`. The `--no-gui` 101 - option can be used to override this setting. If `diff.guitool` 102 - is not set, we will fallback in the order of `merge.guitool`, 103 - `diff.tool`, `merge.tool` until a tool is found. 104 105 --[no-]trust-exit-code:: 106 'git-difftool' invokes a diff tool individually on each file.
··· 97 --[no-]gui:: 98 When 'git-difftool' is invoked with the `-g` or `--gui` option 99 the default diff tool will be read from the configured 100 + `diff.guitool` variable instead of `diff.tool`. This may be 101 + selected automatically using the configuration variable 102 + `difftool.guiDefault`. The `--no-gui` option can be used to 103 + override these settings. If `diff.guitool` is not set, we will 104 + fallback in the order of `merge.guitool`, `diff.tool`, 105 + `merge.tool` until a tool is found. 106 107 --[no-]trust-exit-code:: 108 'git-difftool' invokes a diff tool individually on each file.
+5 -4
Documentation/git-mergetool.txt
··· 85 the default merge tool will be read from the configured 86 `merge.guitool` variable instead of `merge.tool`. If 87 `merge.guitool` is not set, we will fallback to the tool 88 - configured under `merge.tool`. 89 90 --no-gui:: 91 - This overrides a previous `-g` or `--gui` setting and reads the 92 - default merge tool will be read from the configured `merge.tool` 93 - variable. 94 95 -O<orderfile>:: 96 Process files in the order specified in the
··· 85 the default merge tool will be read from the configured 86 `merge.guitool` variable instead of `merge.tool`. If 87 `merge.guitool` is not set, we will fallback to the tool 88 + configured under `merge.tool`. This may be autoselected using 89 + the configuration variable `mergetool.guiDefault`. 90 91 --no-gui:: 92 + This overrides a previous `-g` or `--gui` setting or 93 + `mergetool.guiDefault` configuration and reads the default merge 94 + tool from the configured `merge.tool` variable. 95 96 -O<orderfile>:: 97 Process files in the order specified in the
+12 -4
builtin/difftool.c
··· 685 686 int cmd_difftool(int argc, const char **argv, const char *prefix) 687 { 688 - int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, 689 tool_help = 0, no_index = 0; 690 static char *difftool_cmd = NULL, *extcmd = NULL; 691 struct option builtin_difftool_options[] = { ··· 735 } else if (dir_diff) 736 die(_("options '%s' and '%s' cannot be used together"), "--dir-diff", "--no-index"); 737 738 - die_for_incompatible_opt3(use_gui_tool, "--gui", 739 !!difftool_cmd, "--tool", 740 !!extcmd, "--extcmd"); 741 742 - if (use_gui_tool) 743 setenv("GIT_MERGETOOL_GUI", "true", 1); 744 - else if (difftool_cmd) { 745 if (*difftool_cmd) 746 setenv("GIT_DIFF_TOOL", difftool_cmd, 1); 747 else
··· 685 686 int cmd_difftool(int argc, const char **argv, const char *prefix) 687 { 688 + int use_gui_tool = -1, dir_diff = 0, prompt = -1, symlinks = 0, 689 tool_help = 0, no_index = 0; 690 static char *difftool_cmd = NULL, *extcmd = NULL; 691 struct option builtin_difftool_options[] = { ··· 735 } else if (dir_diff) 736 die(_("options '%s' and '%s' cannot be used together"), "--dir-diff", "--no-index"); 737 738 + die_for_incompatible_opt3(use_gui_tool == 1, "--gui", 739 !!difftool_cmd, "--tool", 740 !!extcmd, "--extcmd"); 741 742 + /* 743 + * Explicitly specified GUI option is forwarded to git-mergetool--lib.sh; 744 + * empty or unset means "use the difftool.guiDefault config or default to 745 + * false". 746 + */ 747 + if (use_gui_tool == 1) 748 setenv("GIT_MERGETOOL_GUI", "true", 1); 749 + else if (use_gui_tool == 0) 750 + setenv("GIT_MERGETOOL_GUI", "false", 1); 751 + 752 + if (difftool_cmd) { 753 if (*difftool_cmd) 754 setenv("GIT_DIFF_TOOL", difftool_cmd, 1); 755 else
+5
git-difftool--helper.sh
··· 75 merge_tool="$GIT_DIFF_TOOL" 76 else 77 merge_tool="$(get_merge_tool)" 78 fi 79 fi 80
··· 75 merge_tool="$GIT_DIFF_TOOL" 76 else 77 merge_tool="$(get_merge_tool)" 78 + subshell_exit_status=$? 79 + if test $subshell_exit_status -gt 1 80 + then 81 + exit $subshell_exit_status 82 + fi 83 fi 84 fi 85
+40
git-mergetool--lib.sh
··· 97 test "$TOOL_MODE" = merge 98 } 99 100 gui_mode () { 101 test "$GIT_MERGETOOL_GUI" = true 102 } 103 ··· 467 is_guessed=false 468 # Check if a merge tool has been configured 469 merge_tool=$(get_configured_merge_tool) 470 # Try to guess an appropriate merge tool if no tool has been set. 471 if test -z "$merge_tool" 472 then
··· 97 test "$TOOL_MODE" = merge 98 } 99 100 + get_gui_default () { 101 + if diff_mode 102 + then 103 + GUI_DEFAULT_KEY="difftool.guiDefault" 104 + else 105 + GUI_DEFAULT_KEY="mergetool.guiDefault" 106 + fi 107 + GUI_DEFAULT_CONFIG_LCASE=$(git config --default false --get "$GUI_DEFAULT_KEY" | tr 'A-Z' 'a-z') 108 + if test "$GUI_DEFAULT_CONFIG_LCASE" = "auto" 109 + then 110 + if test -n "$DISPLAY" 111 + then 112 + GUI_DEFAULT=true 113 + else 114 + GUI_DEFAULT=false 115 + fi 116 + else 117 + GUI_DEFAULT=$(git config --default false --bool --get "$GUI_DEFAULT_KEY") 118 + subshell_exit_status=$? 119 + if test $subshell_exit_status -ne 0 120 + then 121 + exit $subshell_exit_status 122 + fi 123 + fi 124 + echo $GUI_DEFAULT 125 + } 126 + 127 gui_mode () { 128 + if test -z "$GIT_MERGETOOL_GUI" 129 + then 130 + GIT_MERGETOOL_GUI=$(get_gui_default) 131 + if test $? -ne 0 132 + then 133 + exit 2 134 + fi 135 + fi 136 test "$GIT_MERGETOOL_GUI" = true 137 } 138 ··· 502 is_guessed=false 503 # Check if a merge tool has been configured 504 merge_tool=$(get_configured_merge_tool) 505 + subshell_exit_status=$? 506 + if test $subshell_exit_status -gt "1" 507 + then 508 + exit $subshell_exit_status 509 + fi 510 # Try to guess an appropriate merge tool if no tool has been set. 511 if test -z "$merge_tool" 512 then
+7 -2
git-mergetool.sh
··· 451 452 main () { 453 prompt=$(git config --bool mergetool.prompt) 454 - GIT_MERGETOOL_GUI=false 455 guessed_merge_tool=false 456 orderfile= 457 ··· 511 512 if test -z "$merge_tool" 513 then 514 - if ! merge_tool=$(get_merge_tool) 515 then 516 guessed_merge_tool=true 517 fi 518 fi 519 merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
··· 451 452 main () { 453 prompt=$(git config --bool mergetool.prompt) 454 + GIT_MERGETOOL_GUI= 455 guessed_merge_tool=false 456 orderfile= 457 ··· 511 512 if test -z "$merge_tool" 513 then 514 + merge_tool=$(get_merge_tool) 515 + subshell_exit_status=$? 516 + if test $subshell_exit_status = 1 517 then 518 guessed_merge_tool=true 519 + elif test $subshell_exit_status -gt 1 520 + then 521 + exit $subshell_exit_status 522 fi 523 fi 524 merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
+38
t/t7610-mergetool.sh
··· 860 git commit -m "test resolved with mergetool" 861 ' 862 863 test_done
··· 860 git commit -m "test resolved with mergetool" 861 ' 862 863 + test_expect_success 'mergetool with guiDefault' ' 864 + test_config merge.guitool myguitool && 865 + test_config mergetool.myguitool.cmd "(printf \"gui \" && cat \"\$REMOTE\") >\"\$MERGED\"" && 866 + test_config mergetool.myguitool.trustExitCode true && 867 + test_when_finished "git reset --hard" && 868 + git checkout -b test$test_count branch1 && 869 + git submodule update -N && 870 + test_must_fail git merge main && 871 + 872 + test_config mergetool.guiDefault auto && 873 + DISPLAY=SOMETHING && export DISPLAY && 874 + yes "" | git mergetool both && 875 + yes "" | git mergetool file1 file1 && 876 + 877 + DISPLAY= && export DISPLAY && 878 + yes "" | git mergetool file2 "spaced name" && 879 + 880 + test_config mergetool.guiDefault true && 881 + yes "" | git mergetool subdir/file3 && 882 + 883 + yes "d" | git mergetool file11 && 884 + yes "d" | git mergetool file12 && 885 + yes "l" | git mergetool submod && 886 + 887 + echo "gui main updated" >expect && 888 + test_cmp expect file1 && 889 + 890 + echo "main new" >expect && 891 + test_cmp expect file2 && 892 + 893 + echo "gui main new sub" >expect && 894 + test_cmp expect subdir/file3 && 895 + 896 + echo "branch1 submodule" >expect && 897 + test_cmp expect submod/bar && 898 + git commit -m "branch1 resolved with mergetool" 899 + ' 900 + 901 test_done
+52
t/t7800-difftool.sh
··· 155 test_cmp expect actual 156 ' 157 158 test_expect_success 'difftool --gui last setting wins' ' 159 difftool_test_setup && 160 : >expect &&
··· 155 test_cmp expect actual 156 ' 157 158 + test_expect_success 'difftool with guiDefault auto selects gui tool when there is DISPLAY' ' 159 + difftool_test_setup && 160 + test_config merge.tool bogus-tool && 161 + test_config diff.tool bogus-tool && 162 + test_config diff.guitool test-tool && 163 + test_config difftool.guiDefault auto && 164 + DISPLAY=SOMETHING && export DISPLAY && 165 + 166 + echo branch >expect && 167 + git difftool --no-prompt branch >actual && 168 + test_cmp expect actual 169 + ' 170 + test_expect_success 'difftool with guiDefault auto selects regular tool when no DISPLAY' ' 171 + difftool_test_setup && 172 + test_config diff.guitool bogus-tool && 173 + test_config diff.tool test-tool && 174 + test_config difftool.guiDefault Auto && 175 + DISPLAY= && export DISPLAY && 176 + 177 + echo branch >expect && 178 + git difftool --no-prompt branch >actual && 179 + test_cmp expect actual 180 + ' 181 + 182 + test_expect_success 'difftool with guiDefault true selects gui tool' ' 183 + difftool_test_setup && 184 + test_config diff.tool bogus-tool && 185 + test_config diff.guitool test-tool && 186 + test_config difftool.guiDefault true && 187 + 188 + DISPLAY= && export DISPLAY && 189 + echo branch >expect && 190 + git difftool --no-prompt branch >actual && 191 + test_cmp expect actual && 192 + 193 + DISPLAY=Something && export DISPLAY && 194 + echo branch >expect && 195 + git difftool --no-prompt branch >actual && 196 + test_cmp expect actual 197 + ' 198 + 199 + test_expect_success 'difftool --no-gui trumps config guiDefault' ' 200 + difftool_test_setup && 201 + test_config diff.guitool bogus-tool && 202 + test_config diff.tool test-tool && 203 + test_config difftool.guiDefault true && 204 + 205 + echo branch >expect && 206 + git difftool --no-prompt --no-gui branch >actual && 207 + test_cmp expect actual 208 + ' 209 + 210 test_expect_success 'difftool --gui last setting wins' ' 211 difftool_test_setup && 212 : >expect &&