e481913c91c83a19c5e10db1f66949c60d24b4f1
   1#!/bin/sh
   2# git-difftool-helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher.
   3# It supports kdiff3, kompare, tkdiff, xxdiff, meld, opendiff,
   4# emerge, ecmerge, vimdiff, gvimdiff, and custom user-configurable tools.
   5# This script is typically launched by using the 'git difftool'
   6# convenience command.
   7#
   8# Copyright (c) 2009 David Aguilar
   9
  10# Set GIT_DIFFTOOL_NO_PROMPT to bypass the per-file prompt.
  11should_prompt () {
  12        ! test -n "$GIT_DIFFTOOL_NO_PROMPT"
  13}
  14
  15# Should we keep the backup .orig file?
  16keep_backup_mode="$(git config --bool merge.keepBackup || echo true)"
  17keep_backup () {
  18        test "$keep_backup_mode" = "true"
  19}
  20
  21# This function manages the backup .orig file.
  22# A backup $MERGED.orig file is created if changes are detected.
  23cleanup_temp_files () {
  24        if test -n "$MERGED"; then
  25                if keep_backup && test "$MERGED" -nt "$BACKUP"; then
  26                        test -f "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
  27                else
  28                        rm -f -- "$BACKUP"
  29                fi
  30        fi
  31}
  32
  33# This is called when users Ctrl-C out of git-difftool-helper
  34sigint_handler () {
  35        cleanup_temp_files
  36        exit 1
  37}
  38
  39# This function prepares temporary files and launches the appropriate
  40# merge tool.
  41launch_merge_tool () {
  42        # Merged is the filename as it appears in the work tree
  43        # Local is the contents of a/filename
  44        # Remote is the contents of b/filename
  45        # Custom merge tool commands might use $BASE so we provide it
  46        MERGED="$1"
  47        LOCAL="$2"
  48        REMOTE="$3"
  49        BASE="$1"
  50        ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
  51        BACKUP="$MERGED.BACKUP.$ext"
  52
  53        # Create and ensure that we clean up $BACKUP
  54        test -f "$MERGED" && cp -- "$MERGED" "$BACKUP"
  55        trap sigint_handler INT
  56
  57        # $LOCAL and $REMOTE are temporary files so prompt
  58        # the user with the real $MERGED name before launching $merge_tool.
  59        if should_prompt; then
  60                printf "\nViewing: '$MERGED'\n"
  61                printf "Hit return to launch '%s': " "$merge_tool"
  62                read ans
  63        fi
  64
  65        # Run the appropriate merge tool command
  66        case "$merge_tool" in
  67        kdiff3)
  68                basename=$(basename "$MERGED")
  69                "$merge_tool_path" --auto \
  70                        --L1 "$basename (A)" \
  71                        --L2 "$basename (B)" \
  72                        -o "$MERGED" "$LOCAL" "$REMOTE" \
  73                        > /dev/null 2>&1
  74                ;;
  75
  76        kompare)
  77                "$merge_tool_path" "$LOCAL" "$REMOTE"
  78                ;;
  79
  80        tkdiff)
  81                "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"
  82                ;;
  83
  84        meld)
  85                "$merge_tool_path" "$LOCAL" "$REMOTE"
  86                ;;
  87
  88        vimdiff)
  89                "$merge_tool_path" -d -c "wincmd l" "$LOCAL" "$REMOTE"
  90                ;;
  91
  92        gvimdiff)
  93                "$merge_tool_path" -d -c "wincmd l" -f "$LOCAL" "$REMOTE"
  94                ;;
  95
  96        xxdiff)
  97                "$merge_tool_path" \
  98                        -X \
  99                        -R 'Accel.SaveAsMerged: "Ctrl-S"' \
 100                        -R 'Accel.Search: "Ctrl+F"' \
 101                        -R 'Accel.SearchForward: "Ctrl-G"' \
 102                        --merged-file "$MERGED" \
 103                        "$LOCAL" "$REMOTE"
 104                ;;
 105
 106        opendiff)
 107                "$merge_tool_path" "$LOCAL" "$REMOTE" \
 108                        -merge "$MERGED" | cat
 109                ;;
 110
 111        ecmerge)
 112                "$merge_tool_path" "$LOCAL" "$REMOTE" \
 113                        --default --mode=merge2 --to="$MERGED"
 114                ;;
 115
 116        emerge)
 117                "$merge_tool_path" -f emerge-files-command \
 118                        "$LOCAL" "$REMOTE" "$(basename "$MERGED")"
 119                ;;
 120
 121        *)
 122                if test -n "$merge_tool_cmd"; then
 123                        ( eval $merge_tool_cmd )
 124                fi
 125                ;;
 126        esac
 127
 128        cleanup_temp_files
 129}
 130
 131# Verifies that (difftool|mergetool).<tool>.cmd exists
 132valid_custom_tool() {
 133        merge_tool_cmd="$(git config difftool.$1.cmd)"
 134        test -z "$merge_tool_cmd" &&
 135        merge_tool_cmd="$(git config mergetool.$1.cmd)"
 136        test -n "$merge_tool_cmd"
 137}
 138
 139# Verifies that the chosen merge tool is properly setup.
 140# Built-in merge tools are always valid.
 141valid_tool() {
 142        case "$1" in
 143        kdiff3 | kompare | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
 144                ;; # happy
 145        *)
 146                if ! valid_custom_tool "$1"
 147                then
 148                        return 1
 149                fi
 150                ;;
 151        esac
 152}
 153
 154# Sets up the merge_tool_path variable.
 155# This handles the difftool.<tool>.path configuration.
 156# This also falls back to mergetool defaults.
 157init_merge_tool_path() {
 158        merge_tool_path=$(git config difftool."$1".path)
 159        test -z "$merge_tool_path" &&
 160        merge_tool_path=$(git config mergetool."$1".path)
 161        if test -z "$merge_tool_path"; then
 162                case "$1" in
 163                vimdiff)
 164                        merge_tool_path=vim
 165                        ;;
 166                gvimdiff)
 167                        merge_tool_path=gvim
 168                        ;;
 169                emerge)
 170                        merge_tool_path=emacs
 171                        ;;
 172                *)
 173                        merge_tool_path="$1"
 174                        ;;
 175                esac
 176        fi
 177}
 178
 179# Allow GIT_DIFF_TOOL and GIT_MERGE_TOOL to provide default values
 180test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL"
 181test -n "$GIT_DIFF_TOOL" && merge_tool="$GIT_DIFF_TOOL"
 182
 183# If merge tool was not specified then use the diff.tool
 184# configuration variable.  If that's invalid then reset merge_tool.
 185# Fallback to merge.tool.
 186if test -z "$merge_tool"; then
 187        merge_tool=$(git config diff.tool)
 188        test -z "$merge_tool" &&
 189        merge_tool=$(git config merge.tool)
 190        if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
 191                echo >&2 "git config option diff.tool set to unknown tool: $merge_tool"
 192                echo >&2 "Resetting to default..."
 193                unset merge_tool
 194        fi
 195fi
 196
 197# Try to guess an appropriate merge tool if no tool has been set.
 198if test -z "$merge_tool"; then
 199        # We have a $DISPLAY so try some common UNIX merge tools
 200        if test -n "$DISPLAY"; then
 201                # If gnome then prefer meld, otherwise, prefer kdiff3 or kompare
 202                if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
 203                        merge_tool_candidates="meld kdiff3 kompare tkdiff xxdiff gvimdiff"
 204                else
 205                        merge_tool_candidates="kdiff3 kompare tkdiff xxdiff meld gvimdiff"
 206                fi
 207        fi
 208        if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then
 209                # $EDITOR is emacs so add emerge as a candidate
 210                merge_tool_candidates="$merge_tool_candidates emerge opendiff vimdiff"
 211        elif echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
 212                # $EDITOR is vim so add vimdiff as a candidate
 213                merge_tool_candidates="$merge_tool_candidates vimdiff opendiff emerge"
 214        else
 215                merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
 216        fi
 217        echo "merge tool candidates: $merge_tool_candidates"
 218
 219        # Loop over each candidate and stop when a valid merge tool is found.
 220        for i in $merge_tool_candidates
 221        do
 222                init_merge_tool_path $i
 223                if type "$merge_tool_path" > /dev/null 2>&1; then
 224                        merge_tool=$i
 225                        break
 226                fi
 227        done
 228
 229        if test -z "$merge_tool" ; then
 230                echo "No known merge resolution program available."
 231                exit 1
 232        fi
 233
 234else
 235        # A merge tool has been set, so verify that it's valid.
 236        if ! valid_tool "$merge_tool"; then
 237                echo >&2 "Unknown merge tool $merge_tool"
 238                exit 1
 239        fi
 240
 241        init_merge_tool_path "$merge_tool"
 242
 243        if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then
 244                echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
 245                exit 1
 246        fi
 247fi
 248
 249
 250# Launch the merge tool on each path provided by 'git diff'
 251while test $# -gt 6
 252do
 253        launch_merge_tool "$1" "$2" "$5"
 254        shift 7
 255done