git-mergetool--lib.shon commit t6111: allow checking the parents as well (e16f434)
   1#!/bin/sh
   2# git-mergetool--lib is a library for common merge tool functions
   3
   4: ${MERGE_TOOLS_DIR=$(git --exec-path)/mergetools}
   5
   6mode_ok () {
   7        if diff_mode
   8        then
   9                can_diff
  10        elif merge_mode
  11        then
  12                can_merge
  13        else
  14                false
  15        fi
  16}
  17
  18is_available () {
  19        merge_tool_path=$(translate_merge_tool_path "$1") &&
  20        type "$merge_tool_path" >/dev/null 2>&1
  21}
  22
  23list_config_tools () {
  24        section=$1
  25        line_prefix=${2:-}
  26
  27        git config --get-regexp $section'\..*\.cmd' |
  28        while read -r key value
  29        do
  30                toolname=${key#$section.}
  31                toolname=${toolname%.cmd}
  32
  33                printf "%s%s\n" "$line_prefix" "$toolname"
  34        done
  35}
  36
  37show_tool_names () {
  38        condition=${1:-true} per_line_prefix=${2:-} preamble=${3:-}
  39        not_found_msg=${4:-}
  40        extra_content=${5:-}
  41
  42        shown_any=
  43        ( cd "$MERGE_TOOLS_DIR" && ls ) | {
  44                while read toolname
  45                do
  46                        if setup_tool "$toolname" 2>/dev/null &&
  47                                (eval "$condition" "$toolname")
  48                        then
  49                                if test -n "$preamble"
  50                                then
  51                                        printf "%s\n" "$preamble"
  52                                        preamble=
  53                                fi
  54                                shown_any=yes
  55                                printf "%s%s\n" "$per_line_prefix" "$toolname"
  56                        fi
  57                done
  58
  59                if test -n "$extra_content"
  60                then
  61                        if test -n "$preamble"
  62                        then
  63                                # Note: no '\n' here since we don't want a
  64                                # blank line if there is no initial content.
  65                                printf "%s" "$preamble"
  66                                preamble=
  67                        fi
  68                        shown_any=yes
  69                        printf "\n%s\n" "$extra_content"
  70                fi
  71
  72                if test -n "$preamble" && test -n "$not_found_msg"
  73                then
  74                        printf "%s\n" "$not_found_msg"
  75                fi
  76
  77                test -n "$shown_any"
  78        }
  79}
  80
  81diff_mode() {
  82        test "$TOOL_MODE" = diff
  83}
  84
  85merge_mode() {
  86        test "$TOOL_MODE" = merge
  87}
  88
  89translate_merge_tool_path () {
  90        echo "$1"
  91}
  92
  93check_unchanged () {
  94        if test "$MERGED" -nt "$BACKUP"
  95        then
  96                status=0
  97        else
  98                while true
  99                do
 100                        echo "$MERGED seems unchanged."
 101                        printf "Was the merge successful? [y/n] "
 102                        read answer || return 1
 103                        case "$answer" in
 104                        y*|Y*) status=0; break ;;
 105                        n*|N*) status=1; break ;;
 106                        esac
 107                done
 108        fi
 109}
 110
 111valid_tool () {
 112        setup_tool "$1" && return 0
 113        cmd=$(get_merge_tool_cmd "$1")
 114        test -n "$cmd"
 115}
 116
 117setup_tool () {
 118        tool="$1"
 119
 120        # Fallback definitions, to be overriden by tools.
 121        can_merge () {
 122                return 0
 123        }
 124
 125        can_diff () {
 126                return 0
 127        }
 128
 129        diff_cmd () {
 130                status=1
 131                return $status
 132        }
 133
 134        merge_cmd () {
 135                status=1
 136                return $status
 137        }
 138
 139        translate_merge_tool_path () {
 140                echo "$1"
 141        }
 142
 143        if ! test -f "$MERGE_TOOLS_DIR/$tool"
 144        then
 145                # Use a special return code for this case since we want to
 146                # source "defaults" even when an explicit tool path is
 147                # configured since the user can use that to override the
 148                # default path in the scriptlet.
 149                return 2
 150        fi
 151
 152        # Load the redefined functions
 153        . "$MERGE_TOOLS_DIR/$tool"
 154
 155        if merge_mode && ! can_merge
 156        then
 157                echo "error: '$tool' can not be used to resolve merges" >&2
 158                return 1
 159        elif diff_mode && ! can_diff
 160        then
 161                echo "error: '$tool' can only be used to resolve merges" >&2
 162                return 1
 163        fi
 164        return 0
 165}
 166
 167get_merge_tool_cmd () {
 168        merge_tool="$1"
 169        if diff_mode
 170        then
 171                git config "difftool.$merge_tool.cmd" ||
 172                git config "mergetool.$merge_tool.cmd"
 173        else
 174                git config "mergetool.$merge_tool.cmd"
 175        fi
 176}
 177
 178# Entry point for running tools
 179run_merge_tool () {
 180        # If GIT_PREFIX is empty then we cannot use it in tools
 181        # that expect to be able to chdir() to its value.
 182        GIT_PREFIX=${GIT_PREFIX:-.}
 183        export GIT_PREFIX
 184
 185        merge_tool_path=$(get_merge_tool_path "$1") || exit
 186        base_present="$2"
 187        status=0
 188
 189        # Bring tool-specific functions into scope
 190        setup_tool "$1"
 191        exitcode=$?
 192        case $exitcode in
 193        0)
 194                :
 195                ;;
 196        2)
 197                # The configured tool is not a built-in tool.
 198                test -n "$merge_tool_path" || return 1
 199                ;;
 200        *)
 201                return $exitcode
 202                ;;
 203        esac
 204
 205        if merge_mode
 206        then
 207                run_merge_cmd "$1"
 208        else
 209                run_diff_cmd "$1"
 210        fi
 211        return $status
 212}
 213
 214# Run a either a configured or built-in diff tool
 215run_diff_cmd () {
 216        merge_tool_cmd=$(get_merge_tool_cmd "$1")
 217        if test -n "$merge_tool_cmd"
 218        then
 219                ( eval $merge_tool_cmd )
 220                status=$?
 221                return $status
 222        else
 223                diff_cmd "$1"
 224        fi
 225}
 226
 227# Run a either a configured or built-in merge tool
 228run_merge_cmd () {
 229        merge_tool_cmd=$(get_merge_tool_cmd "$1")
 230        if test -n "$merge_tool_cmd"
 231        then
 232                trust_exit_code=$(git config --bool \
 233                        "mergetool.$1.trustExitCode" || echo false)
 234                if test "$trust_exit_code" = "false"
 235                then
 236                        touch "$BACKUP"
 237                        ( eval $merge_tool_cmd )
 238                        status=$?
 239                        check_unchanged
 240                else
 241                        ( eval $merge_tool_cmd )
 242                        status=$?
 243                fi
 244                return $status
 245        else
 246                merge_cmd "$1"
 247        fi
 248}
 249
 250list_merge_tool_candidates () {
 251        if merge_mode
 252        then
 253                tools="tortoisemerge"
 254        else
 255                tools="kompare"
 256        fi
 257        if test -n "$DISPLAY"
 258        then
 259                if test -n "$GNOME_DESKTOP_SESSION_ID"
 260                then
 261                        tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
 262                else
 263                        tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
 264                fi
 265                tools="$tools gvimdiff diffuse ecmerge p4merge araxis bc3 codecompare"
 266        fi
 267        case "${VISUAL:-$EDITOR}" in
 268        *vim*)
 269                tools="$tools vimdiff emerge"
 270                ;;
 271        *)
 272                tools="$tools emerge vimdiff"
 273                ;;
 274        esac
 275}
 276
 277show_tool_help () {
 278        tool_opt="'git ${TOOL_MODE}tool --tool-<tool>'"
 279
 280        tab='   '
 281        LF='
 282'
 283        any_shown=no
 284
 285        cmd_name=${TOOL_MODE}tool
 286        config_tools=$({
 287                diff_mode && list_config_tools difftool "$tab$tab"
 288                list_config_tools mergetool "$tab$tab"
 289        } | sort)
 290        extra_content=
 291        if test -n "$config_tools"
 292        then
 293                extra_content="${tab}user-defined:${LF}$config_tools"
 294        fi
 295
 296        show_tool_names 'mode_ok && is_available' "$tab$tab" \
 297                "$tool_opt may be set to one of the following:" \
 298                "No suitable tool for 'git $cmd_name --tool=<tool>' found." \
 299                "$extra_content" &&
 300                any_shown=yes
 301
 302        show_tool_names 'mode_ok && ! is_available' "$tab$tab" \
 303                "${LF}The following tools are valid, but not currently available:" &&
 304                any_shown=yes
 305
 306        if test "$any_shown" = yes
 307        then
 308                echo
 309                echo "Some of the tools listed above only work in a windowed"
 310                echo "environment. If run in a terminal-only session, they will fail."
 311        fi
 312        exit 0
 313}
 314
 315guess_merge_tool () {
 316        list_merge_tool_candidates
 317        cat >&2 <<-EOF
 318
 319        This message is displayed because '$TOOL_MODE.tool' is not configured.
 320        See 'git ${TOOL_MODE}tool --tool-help' or 'git help config' for more details.
 321        'git ${TOOL_MODE}tool' will now attempt to use one of the following tools:
 322        $tools
 323        EOF
 324
 325        # Loop over each candidate and stop when a valid merge tool is found.
 326        for tool in $tools
 327        do
 328                is_available "$tool" && echo "$tool" && return 0
 329        done
 330
 331        echo >&2 "No known ${TOOL_MODE} tool is available."
 332        return 1
 333}
 334
 335get_configured_merge_tool () {
 336        # Diff mode first tries diff.tool and falls back to merge.tool.
 337        # Merge mode only checks merge.tool
 338        if diff_mode
 339        then
 340                merge_tool=$(git config diff.tool || git config merge.tool)
 341        else
 342                merge_tool=$(git config merge.tool)
 343        fi
 344        if test -n "$merge_tool" && ! valid_tool "$merge_tool"
 345        then
 346                echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool"
 347                echo >&2 "Resetting to default..."
 348                return 1
 349        fi
 350        echo "$merge_tool"
 351}
 352
 353get_merge_tool_path () {
 354        # A merge tool has been set, so verify that it's valid.
 355        merge_tool="$1"
 356        if ! valid_tool "$merge_tool"
 357        then
 358                echo >&2 "Unknown merge tool $merge_tool"
 359                exit 1
 360        fi
 361        if diff_mode
 362        then
 363                merge_tool_path=$(git config difftool."$merge_tool".path ||
 364                                  git config mergetool."$merge_tool".path)
 365        else
 366                merge_tool_path=$(git config mergetool."$merge_tool".path)
 367        fi
 368        if test -z "$merge_tool_path"
 369        then
 370                merge_tool_path=$(translate_merge_tool_path "$merge_tool")
 371        fi
 372        if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
 373                ! type "$merge_tool_path" >/dev/null 2>&1
 374        then
 375                echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\
 376                         "'$merge_tool_path'"
 377                exit 1
 378        fi
 379        echo "$merge_tool_path"
 380}
 381
 382get_merge_tool () {
 383        # Check if a merge tool has been configured
 384        merge_tool=$(get_configured_merge_tool)
 385        # Try to guess an appropriate merge tool if no tool has been set.
 386        if test -z "$merge_tool"
 387        then
 388                merge_tool=$(guess_merge_tool) || exit
 389        fi
 390        echo "$merge_tool"
 391}