git-mergetool.shon commit git-status: respect core.excludesFile (0ba956d)
   1#!/bin/sh
   2#
   3# This program resolves merge conflicts in git
   4#
   5# Copyright (c) 2006 Theodore Y. Ts'o
   6#
   7# This file is licensed under the GPL v2, or a later version
   8# at the discretion of Junio C Hammano.
   9#
  10
  11USAGE='[--tool=tool] [file to merge] ...'
  12SUBDIRECTORY_OK=Yes
  13. git-sh-setup
  14require_work_tree
  15
  16# Returns true if the mode reflects a symlink
  17is_symlink () {
  18    test "$1" = 120000
  19}
  20
  21local_present () {
  22    test -n "$local_mode"
  23}
  24
  25remote_present () {
  26    test -n "$remote_mode"
  27}
  28
  29base_present () {
  30    test -n "$base_mode"
  31}
  32
  33cleanup_temp_files () {
  34    if test "$1" = --save-backup ; then
  35        mv -- "$BACKUP" "$path.orig"
  36        rm -f -- "$LOCAL" "$REMOTE" "$BASE"
  37    else
  38        rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
  39    fi
  40}
  41
  42describe_file () {
  43    mode="$1"
  44    branch="$2"
  45    file="$3"
  46
  47    printf "  {%s}: " "$branch"
  48    if test -z "$mode"; then
  49        echo "deleted"
  50    elif is_symlink "$mode" ; then
  51        echo "a symbolic link -> '$(cat "$file")'"
  52    else
  53        if base_present; then
  54            echo "modified"
  55        else
  56            echo "created"
  57        fi
  58    fi
  59}
  60
  61
  62resolve_symlink_merge () {
  63    while true; do
  64        printf "Use (l)ocal or (r)emote, or (a)bort? "
  65        read ans
  66        case "$ans" in
  67            [lL]*)
  68                git-checkout-index -f --stage=2 -- "$path"
  69                git-add -- "$path"
  70                cleanup_temp_files --save-backup
  71                return
  72                ;;
  73            [rR]*)
  74                git-checkout-index -f --stage=3 -- "$path"
  75                git-add -- "$path"
  76                cleanup_temp_files --save-backup
  77                return
  78                ;;
  79            [aA]*)
  80                exit 1
  81                ;;
  82            esac
  83        done
  84}
  85
  86resolve_deleted_merge () {
  87    while true; do
  88        if base_present; then
  89            printf "Use (m)odified or (d)eleted file, or (a)bort? "
  90        else
  91            printf "Use (c)reated or (d)eleted file, or (a)bort? "
  92        fi
  93        read ans
  94        case "$ans" in
  95            [mMcC]*)
  96                git-add -- "$path"
  97                cleanup_temp_files --save-backup
  98                return
  99                ;;
 100            [dD]*)
 101                git-rm -- "$path" > /dev/null
 102                cleanup_temp_files
 103                return
 104                ;;
 105            [aA]*)
 106                exit 1
 107                ;;
 108            esac
 109        done
 110}
 111
 112check_unchanged () {
 113    if test "$path" -nt "$BACKUP" ; then
 114        status=0;
 115    else
 116        while true; do
 117            echo "$path seems unchanged."
 118            printf "Was the merge successful? [y/n] "
 119            read answer < /dev/tty
 120            case "$answer" in
 121                y*|Y*) status=0; break ;;
 122                n*|N*) status=1; break ;;
 123            esac
 124        done
 125    fi
 126}
 127
 128save_backup () {
 129    if test "$status" -eq 0; then
 130        mv -- "$BACKUP" "$path.orig"
 131    fi
 132}
 133
 134remove_backup () {
 135    if test "$status" -eq 0; then
 136        rm "$BACKUP"
 137    fi
 138}
 139
 140merge_file () {
 141    path="$1"
 142
 143    f=`git-ls-files -u -- "$path"`
 144    if test -z "$f" ; then
 145        if test ! -f "$path" ; then
 146            echo "$path: file not found"
 147        else
 148            echo "$path: file does not need merging"
 149        fi
 150        exit 1
 151    fi
 152
 153    BACKUP="$path.BACKUP.$$"
 154    LOCAL="$path.LOCAL.$$"
 155    REMOTE="$path.REMOTE.$$"
 156    BASE="$path.BASE.$$"
 157
 158    mv -- "$path" "$BACKUP"
 159    cp -- "$BACKUP" "$path"
 160
 161    base_mode=`git ls-files -u -- "$path" | awk '{if ($3==1) print $1;}'`
 162    local_mode=`git ls-files -u -- "$path" | awk '{if ($3==2) print $1;}'`
 163    remote_mode=`git ls-files -u -- "$path" | awk '{if ($3==3) print $1;}'`
 164
 165    base_present   && git cat-file blob ":1:$path" > "$BASE" 2>/dev/null
 166    local_present  && git cat-file blob ":2:$path" > "$LOCAL" 2>/dev/null
 167    remote_present && git cat-file blob ":3:$path" > "$REMOTE" 2>/dev/null
 168
 169    if test -z "$local_mode" -o -z "$remote_mode"; then
 170        echo "Deleted merge conflict for '$path':"
 171        describe_file "$local_mode" "local" "$LOCAL"
 172        describe_file "$remote_mode" "remote" "$REMOTE"
 173        resolve_deleted_merge
 174        return
 175    fi
 176
 177    if is_symlink "$local_mode" || is_symlink "$remote_mode"; then
 178        echo "Symbolic link merge conflict for '$path':"
 179        describe_file "$local_mode" "local" "$LOCAL"
 180        describe_file "$remote_mode" "remote" "$REMOTE"
 181        resolve_symlink_merge
 182        return
 183    fi
 184
 185    echo "Normal merge conflict for '$path':"
 186    describe_file "$local_mode" "local" "$LOCAL"
 187    describe_file "$remote_mode" "remote" "$REMOTE"
 188    printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
 189    read ans
 190
 191    case "$merge_tool" in
 192        kdiff3)
 193            if base_present ; then
 194                (kdiff3 --auto --L1 "$path (Base)" -L2 "$path (Local)" --L3 "$path (Remote)" \
 195                    -o "$path" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1)
 196            else
 197                (kdiff3 --auto -L1 "$path (Local)" --L2 "$path (Remote)" \
 198                    -o "$path" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1)
 199            fi
 200            status=$?
 201            remove_backup
 202            ;;
 203        tkdiff)
 204            if base_present ; then
 205                tkdiff -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE"
 206            else
 207                tkdiff -o "$path" -- "$LOCAL" "$REMOTE"
 208            fi
 209            status=$?
 210            save_backup
 211            ;;
 212        meld|vimdiff)
 213            touch "$BACKUP"
 214            $merge_tool -- "$LOCAL" "$path" "$REMOTE"
 215            check_unchanged
 216            save_backup
 217            ;;
 218        xxdiff)
 219            touch "$BACKUP"
 220            if base_present ; then
 221                xxdiff -X --show-merged-pane \
 222                    -R 'Accel.SaveAsMerged: "Ctrl-S"' \
 223                    -R 'Accel.Search: "Ctrl+F"' \
 224                    -R 'Accel.SearchForward: "Ctrl-G"' \
 225                    --merged-file "$path" -- "$LOCAL" "$BASE" "$REMOTE"
 226            else
 227                xxdiff -X --show-merged-pane \
 228                    -R 'Accel.SaveAsMerged: "Ctrl-S"' \
 229                    -R 'Accel.Search: "Ctrl+F"' \
 230                    -R 'Accel.SearchForward: "Ctrl-G"' \
 231                    --merged-file "$path" -- "$LOCAL" "$REMOTE"
 232            fi
 233            check_unchanged
 234            save_backup
 235            ;;
 236        opendiff)
 237            touch "$BACKUP"
 238            if base_present; then
 239                opendiff "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$path" | cat
 240            else
 241                opendiff "$LOCAL" "$REMOTE" -merge "$path" | cat
 242            fi
 243            check_unchanged
 244            save_backup
 245            ;;
 246        emerge)
 247            if base_present ; then
 248                emacs -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$path"
 249            else
 250                emacs -f emerge-files-command "$LOCAL" "$REMOTE" "$path"
 251            fi
 252            status=$?
 253            save_backup
 254            ;;
 255    esac
 256    if test "$status" -ne 0; then
 257        echo "merge of $path failed" 1>&2
 258        mv -- "$BACKUP" "$path"
 259        exit 1
 260    fi
 261    git add -- "$path"
 262    cleanup_temp_files
 263}
 264
 265while case $# in 0) break ;; esac
 266do
 267    case "$1" in
 268        -t|--tool*)
 269            case "$#,$1" in
 270                *,*=*)
 271                    merge_tool=`expr "z$1" : 'z-[^=]*=\(.*\)'`
 272                    ;;
 273                1,*)
 274                    usage ;;
 275                *)
 276                    merge_tool="$2"
 277                    shift ;;
 278            esac
 279            ;;
 280        --)
 281            break
 282            ;;
 283        -*)
 284            usage
 285            ;;
 286        *)
 287            break
 288            ;;
 289    esac
 290    shift
 291done
 292
 293if test -z "$merge_tool"; then
 294    merge_tool=`git-config merge.tool`
 295    case "$merge_tool" in
 296        kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | "")
 297            ;; # happy
 298        *)
 299            echo >&2 "git config option merge.tool set to unknown tool: $merge_tool"
 300            echo >&2 "Resetting to default..."
 301            unset merge_tool
 302            ;;
 303    esac
 304fi
 305
 306if test -z "$merge_tool" ; then
 307    if type kdiff3 >/dev/null 2>&1 && test -n "$DISPLAY"; then
 308        merge_tool="kdiff3";
 309    elif type tkdiff >/dev/null 2>&1 && test -n "$DISPLAY"; then
 310        merge_tool=tkdiff
 311    elif type xxdiff >/dev/null 2>&1 && test -n "$DISPLAY"; then
 312        merge_tool=xxdiff
 313    elif type meld >/dev/null 2>&1 && test -n "$DISPLAY"; then
 314        merge_tool=meld
 315    elif type opendiff >/dev/null 2>&1; then
 316        merge_tool=opendiff
 317    elif type emacs >/dev/null 2>&1; then
 318        merge_tool=emerge
 319    elif type vimdiff >/dev/null 2>&1; then
 320        merge_tool=vimdiff
 321    else
 322        echo "No available merge resolution programs available."
 323        exit 1
 324    fi
 325fi
 326
 327case "$merge_tool" in
 328    kdiff3|tkdiff|meld|xxdiff|vimdiff|opendiff)
 329        if ! type "$merge_tool" > /dev/null 2>&1; then
 330            echo "The merge tool $merge_tool is not available"
 331            exit 1
 332        fi
 333        ;;
 334    emerge)
 335        if ! type "emacs" > /dev/null 2>&1; then
 336            echo "Emacs is not available"
 337            exit 1
 338        fi
 339        ;;
 340    *)
 341        echo "Unknown merge tool: $merge_tool"
 342        exit 1
 343        ;;
 344esac
 345
 346if test $# -eq 0 ; then
 347        files=`git ls-files -u | sed -e 's/^[^  ]*      //' | sort -u`
 348        if test -z "$files" ; then
 349                echo "No files need merging"
 350                exit 0
 351        fi
 352        echo Merging the files: $files
 353        git ls-files -u | sed -e 's/^[^ ]*      //' | sort -u | while read i
 354        do
 355                printf "\n"
 356                merge_file "$i" < /dev/tty > /dev/tty
 357        done
 358else
 359        while test $# -gt 0; do
 360                printf "\n"
 361                merge_file "$1"
 362                shift
 363        done
 364fi
 365exit 0