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