Merge branch 'jc/mention-tracking-for-pull-default'
[git.git] / git-mergetool--lib.sh
index f013a0350665e8c11b52853b5794631dcf7694d0..e338be5e57e32f52cfa818fd44a6364c00c24cdd 100644 (file)
@@ -1,5 +1,83 @@
 #!/bin/sh
 # git-mergetool--lib is a library for common merge tool functions
+
+: ${MERGE_TOOLS_DIR=$(git --exec-path)/mergetools}
+
+mode_ok () {
+       if diff_mode
+       then
+               can_diff
+       elif merge_mode
+       then
+               can_merge
+       else
+               false
+       fi
+}
+
+is_available () {
+       merge_tool_path=$(translate_merge_tool_path "$1") &&
+       type "$merge_tool_path" >/dev/null 2>&1
+}
+
+list_config_tools () {
+       section=$1
+       line_prefix=${2:-}
+
+       git config --get-regexp $section'\..*\.cmd' |
+       while read -r key value
+       do
+               toolname=${key#$section.}
+               toolname=${toolname%.cmd}
+
+               printf "%s%s\n" "$line_prefix" "$toolname"
+       done
+}
+
+show_tool_names () {
+       condition=${1:-true} per_line_prefix=${2:-} preamble=${3:-}
+       not_found_msg=${4:-}
+       extra_content=${5:-}
+
+       shown_any=
+       ( cd "$MERGE_TOOLS_DIR" && ls ) | {
+               while read toolname
+               do
+                       if setup_tool "$toolname" 2>/dev/null &&
+                               (eval "$condition" "$toolname")
+                       then
+                               if test -n "$preamble"
+                               then
+                                       printf "%s\n" "$preamble"
+                                       preamble=
+                               fi
+                               shown_any=yes
+                               printf "%s%s\n" "$per_line_prefix" "$toolname"
+                       fi
+               done
+
+               if test -n "$extra_content"
+               then
+                       if test -n "$preamble"
+                       then
+                               # Note: no '\n' here since we don't want a
+                               # blank line if there is no initial content.
+                               printf "%s" "$preamble"
+                               preamble=
+                       fi
+                       shown_any=yes
+                       printf "\n%s\n" "$extra_content"
+               fi
+
+               if test -n "$preamble" && test -n "$not_found_msg"
+               then
+                       printf "%s\n" "$not_found_msg"
+               fi
+
+               test -n "$shown_any"
+       }
+}
+
 diff_mode() {
        test "$TOOL_MODE" = diff
 }
@@ -30,61 +108,70 @@ check_unchanged () {
        fi
 }
 
-valid_tool_config () {
-       if test -n "$(get_merge_tool_cmd "$1")"
-       then
-               return 0
-       else
-               return 1
-       fi
-}
-
 valid_tool () {
-       setup_tool "$1" || valid_tool_config "$1"
+       setup_tool "$1" && return 0
+       cmd=$(get_merge_tool_cmd "$1")
+       test -n "$cmd"
 }
 
 setup_tool () {
-       case "$1" in
-       vim*|gvim*)
-               tool=vim
-               ;;
-       *)
-               tool="$1"
-               ;;
-       esac
-       mergetools="$(git --exec-path)/mergetools"
+       tool="$1"
+
+       # Fallback definitions, to be overriden by tools.
+       can_merge () {
+               return 0
+       }
+
+       can_diff () {
+               return 0
+       }
+
+       diff_cmd () {
+               status=1
+               return $status
+       }
 
-       # Load the default definitions
-       . "$mergetools/defaults"
-       if ! test -f "$mergetools/$tool"
+       merge_cmd () {
+               status=1
+               return $status
+       }
+
+       translate_merge_tool_path () {
+               echo "$1"
+       }
+
+       if ! test -f "$MERGE_TOOLS_DIR/$tool"
        then
-               return 1
+               # Use a special return code for this case since we want to
+               # source "defaults" even when an explicit tool path is
+               # configured since the user can use that to override the
+               # default path in the scriptlet.
+               return 2
        fi
 
        # Load the redefined functions
-       . "$mergetools/$tool"
+       . "$MERGE_TOOLS_DIR/$tool"
 
        if merge_mode && ! can_merge
        then
                echo "error: '$tool' can not be used to resolve merges" >&2
-               exit 1
+               return 1
        elif diff_mode && ! can_diff
        then
                echo "error: '$tool' can only be used to resolve merges" >&2
-               exit 1
+               return 1
        fi
        return 0
 }
 
 get_merge_tool_cmd () {
-       # Prints the custom command for a merge tool
        merge_tool="$1"
        if diff_mode
        then
-               echo "$(git config difftool.$merge_tool.cmd ||
-                       git config mergetool.$merge_tool.cmd)"
+               git config "difftool.$merge_tool.cmd" ||
+               git config "mergetool.$merge_tool.cmd"
        else
-               echo "$(git config mergetool.$merge_tool.cmd)"
+               git config "mergetool.$merge_tool.cmd"
        fi
 }
 
@@ -95,12 +182,25 @@ run_merge_tool () {
        GIT_PREFIX=${GIT_PREFIX:-.}
        export GIT_PREFIX
 
-       merge_tool_path="$(get_merge_tool_path "$1")" || exit
+       merge_tool_path=$(get_merge_tool_path "$1") || exit
        base_present="$2"
        status=0
 
        # Bring tool-specific functions into scope
        setup_tool "$1"
+       exitcode=$?
+       case $exitcode in
+       0)
+               :
+               ;;
+       2)
+               # The configured tool is not a built-in tool.
+               test -n "$merge_tool_path" || return 1
+               ;;
+       *)
+               return $exitcode
+               ;;
+       esac
 
        if merge_mode
        then
@@ -113,7 +213,7 @@ run_merge_tool () {
 
 # Run a either a configured or built-in diff tool
 run_diff_cmd () {
-       merge_tool_cmd="$(get_merge_tool_cmd "$1")"
+       merge_tool_cmd=$(get_merge_tool_cmd "$1")
        if test -n "$merge_tool_cmd"
        then
                ( eval $merge_tool_cmd )
@@ -126,11 +226,11 @@ run_diff_cmd () {
 
 # Run a either a configured or built-in merge tool
 run_merge_cmd () {
-       merge_tool_cmd="$(get_merge_tool_cmd "$1")"
+       merge_tool_cmd=$(get_merge_tool_cmd "$1")
        if test -n "$merge_tool_cmd"
        then
-               trust_exit_code="$(git config --bool \
-                       mergetool."$1".trustExitCode || echo false)"
+               trust_exit_code=$(git config --bool \
+                       "mergetool.$1.trustExitCode" || echo false)
                if test "$trust_exit_code" = "false"
                then
                        touch "$BACKUP"
@@ -174,22 +274,61 @@ list_merge_tool_candidates () {
        esac
 }
 
+show_tool_help () {
+       tool_opt="'git ${TOOL_MODE}tool --tool-<tool>'"
+
+       tab='   '
+       LF='
+'
+       any_shown=no
+
+       cmd_name=${TOOL_MODE}tool
+       config_tools=$({
+               diff_mode && list_config_tools difftool "$tab$tab"
+               list_config_tools mergetool "$tab$tab"
+       } | sort)
+       extra_content=
+       if test -n "$config_tools"
+       then
+               extra_content="${tab}user-defined:${LF}$config_tools"
+       fi
+
+       show_tool_names 'mode_ok && is_available' "$tab$tab" \
+               "$tool_opt may be set to one of the following:" \
+               "No suitable tool for 'git $cmd_name --tool=<tool>' found." \
+               "$extra_content" &&
+               any_shown=yes
+
+       show_tool_names 'mode_ok && ! is_available' "$tab$tab" \
+               "${LF}The following tools are valid, but not currently available:" &&
+               any_shown=yes
+
+       if test "$any_shown" = yes
+       then
+               echo
+               echo "Some of the tools listed above only work in a windowed"
+               echo "environment. If run in a terminal-only session, they will fail."
+       fi
+       exit 0
+}
+
 guess_merge_tool () {
        list_merge_tool_candidates
-       echo >&2 "merge tool candidates: $tools"
+       cat >&2 <<-EOF
+
+       This message is displayed because '$TOOL_MODE.tool' is not configured.
+       See 'git ${TOOL_MODE}tool --tool-help' or 'git help config' for more details.
+       'git ${TOOL_MODE}tool' will now attempt to use one of the following tools:
+       $tools
+       EOF
 
        # Loop over each candidate and stop when a valid merge tool is found.
-       for i in $tools
+       for tool in $tools
        do
-               merge_tool_path="$(translate_merge_tool_path "$i")"
-               if type "$merge_tool_path" >/dev/null 2>&1
-               then
-                       echo "$i"
-                       return 0
-               fi
+               is_available "$tool" && echo "$tool" && return 0
        done
 
-       echo >&2 "No known merge resolution program available."
+       echo >&2 "No known ${TOOL_MODE} tool is available."
        return 1
 }
 
@@ -228,7 +367,7 @@ get_merge_tool_path () {
        fi
        if test -z "$merge_tool_path"
        then
-               merge_tool_path="$(translate_merge_tool_path "$merge_tool")"
+               merge_tool_path=$(translate_merge_tool_path "$merge_tool")
        fi
        if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
                ! type "$merge_tool_path" >/dev/null 2>&1
@@ -242,11 +381,11 @@ get_merge_tool_path () {
 
 get_merge_tool () {
        # Check if a merge tool has been configured
-       merge_tool="$(get_configured_merge_tool)"
+       merge_tool=$(get_configured_merge_tool)
        # Try to guess an appropriate merge tool if no tool has been set.
        if test -z "$merge_tool"
        then
-               merge_tool="$(guess_merge_tool)" || exit
+               merge_tool=$(guess_merge_tool) || exit
        fi
        echo "$merge_tool"
 }