git-mergetool.shon commit Merge branch 'ms/daemon-doc-typo' into maint (c2e585f)
   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] [--tool-help] [-y|--no-prompt|--prompt] [file to merge] ...'
  12SUBDIRECTORY_OK=Yes
  13OPTIONS_SPEC=
  14TOOL_MODE=merge
  15. git-sh-setup
  16. git-mergetool--lib
  17require_work_tree
  18
  19# Returns true if the mode reflects a symlink
  20is_symlink () {
  21    test "$1" = 120000
  22}
  23
  24is_submodule () {
  25    test "$1" = 160000
  26}
  27
  28local_present () {
  29    test -n "$local_mode"
  30}
  31
  32remote_present () {
  33    test -n "$remote_mode"
  34}
  35
  36base_present () {
  37    test -n "$base_mode"
  38}
  39
  40cleanup_temp_files () {
  41    if test "$1" = --save-backup ; then
  42        rm -rf -- "$MERGED.orig"
  43        test -e "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
  44        rm -f -- "$LOCAL" "$REMOTE" "$BASE"
  45    else
  46        rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
  47    fi
  48}
  49
  50describe_file () {
  51    mode="$1"
  52    branch="$2"
  53    file="$3"
  54
  55    printf "  {%s}: " "$branch"
  56    if test -z "$mode"; then
  57        echo "deleted"
  58    elif is_symlink "$mode" ; then
  59        echo "a symbolic link -> '$(cat "$file")'"
  60    elif is_submodule "$mode" ; then
  61        echo "submodule commit $file"
  62    else
  63        if base_present; then
  64            echo "modified file"
  65        else
  66            echo "created file"
  67        fi
  68    fi
  69}
  70
  71
  72resolve_symlink_merge () {
  73    while true; do
  74        printf "Use (l)ocal or (r)emote, or (a)bort? "
  75        read ans || return 1
  76        case "$ans" in
  77            [lL]*)
  78                git checkout-index -f --stage=2 -- "$MERGED"
  79                git add -- "$MERGED"
  80                cleanup_temp_files --save-backup
  81                return 0
  82                ;;
  83            [rR]*)
  84                git checkout-index -f --stage=3 -- "$MERGED"
  85                git add -- "$MERGED"
  86                cleanup_temp_files --save-backup
  87                return 0
  88                ;;
  89            [aA]*)
  90                return 1
  91                ;;
  92            esac
  93        done
  94}
  95
  96resolve_deleted_merge () {
  97    while true; do
  98        if base_present; then
  99            printf "Use (m)odified or (d)eleted file, or (a)bort? "
 100        else
 101            printf "Use (c)reated or (d)eleted file, or (a)bort? "
 102        fi
 103        read ans || return 1
 104        case "$ans" in
 105            [mMcC]*)
 106                git add -- "$MERGED"
 107                cleanup_temp_files --save-backup
 108                return 0
 109                ;;
 110            [dD]*)
 111                git rm -- "$MERGED" > /dev/null
 112                cleanup_temp_files
 113                return 0
 114                ;;
 115            [aA]*)
 116                return 1
 117                ;;
 118            esac
 119        done
 120}
 121
 122resolve_submodule_merge () {
 123    while true; do
 124        printf "Use (l)ocal or (r)emote, or (a)bort? "
 125        read ans || return 1
 126        case "$ans" in
 127            [lL]*)
 128                if ! local_present; then
 129                    if test -n "$(git ls-tree HEAD -- "$MERGED")"; then
 130                        # Local isn't present, but it's a subdirectory
 131                        git ls-tree --full-name -r HEAD -- "$MERGED" | git update-index --index-info || exit $?
 132                    else
 133                        test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
 134                        git update-index --force-remove "$MERGED"
 135                        cleanup_temp_files --save-backup
 136                    fi
 137                elif is_submodule "$local_mode"; then
 138                    stage_submodule "$MERGED" "$local_sha1"
 139                else
 140                    git checkout-index -f --stage=2 -- "$MERGED"
 141                    git add -- "$MERGED"
 142                fi
 143                return 0
 144                ;;
 145            [rR]*)
 146                if ! remote_present; then
 147                    if test -n "$(git ls-tree MERGE_HEAD -- "$MERGED")"; then
 148                        # Remote isn't present, but it's a subdirectory
 149                        git ls-tree --full-name -r MERGE_HEAD -- "$MERGED" | git update-index --index-info || exit $?
 150                    else
 151                        test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
 152                        git update-index --force-remove "$MERGED"
 153                    fi
 154                elif is_submodule "$remote_mode"; then
 155                    ! is_submodule "$local_mode" && test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
 156                    stage_submodule "$MERGED" "$remote_sha1"
 157                else
 158                    test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
 159                    git checkout-index -f --stage=3 -- "$MERGED"
 160                    git add -- "$MERGED"
 161                fi
 162                cleanup_temp_files --save-backup
 163                return 0
 164                ;;
 165            [aA]*)
 166                return 1
 167                ;;
 168            esac
 169        done
 170}
 171
 172stage_submodule () {
 173    path="$1"
 174    submodule_sha1="$2"
 175    mkdir -p "$path" || die "fatal: unable to create directory for module at $path"
 176    # Find $path relative to work tree
 177    work_tree_root=$(cd_to_toplevel && pwd)
 178    work_rel_path=$(cd "$path" && GIT_WORK_TREE="${work_tree_root}" git rev-parse --show-prefix)
 179    test -n "$work_rel_path" || die "fatal: unable to get path of module $path relative to work tree"
 180    git update-index --add --replace --cacheinfo 160000 "$submodule_sha1" "${work_rel_path%/}" || die
 181}
 182
 183checkout_staged_file () {
 184    tmpfile=$(expr \
 185            "$(git checkout-index --temp --stage="$1" "$2" 2>/dev/null)" \
 186            : '\([^     ]*\)    ')
 187
 188    if test $? -eq 0 -a -n "$tmpfile" ; then
 189        mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3"
 190    else
 191        >"$3"
 192    fi
 193}
 194
 195merge_file () {
 196    MERGED="$1"
 197
 198    f=$(git ls-files -u -- "$MERGED")
 199    if test -z "$f" ; then
 200        if test ! -f "$MERGED" ; then
 201            echo "$MERGED: file not found"
 202        else
 203            echo "$MERGED: file does not need merging"
 204        fi
 205        return 1
 206    fi
 207
 208    ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
 209    BACKUP="./$MERGED.BACKUP.$ext"
 210    LOCAL="./$MERGED.LOCAL.$ext"
 211    REMOTE="./$MERGED.REMOTE.$ext"
 212    BASE="./$MERGED.BASE.$ext"
 213
 214    base_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}')
 215    local_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}')
 216    remote_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}')
 217
 218    if is_submodule "$local_mode" || is_submodule "$remote_mode"; then
 219        echo "Submodule merge conflict for '$MERGED':"
 220        local_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $2;}')
 221        remote_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $2;}')
 222        describe_file "$local_mode" "local" "$local_sha1"
 223        describe_file "$remote_mode" "remote" "$remote_sha1"
 224        resolve_submodule_merge
 225        return
 226    fi
 227
 228    mv -- "$MERGED" "$BACKUP"
 229    cp -- "$BACKUP" "$MERGED"
 230
 231    checkout_staged_file 1 "$MERGED" "$BASE"
 232    checkout_staged_file 2 "$MERGED" "$LOCAL"
 233    checkout_staged_file 3 "$MERGED" "$REMOTE"
 234
 235    if test -z "$local_mode" -o -z "$remote_mode"; then
 236        echo "Deleted merge conflict for '$MERGED':"
 237        describe_file "$local_mode" "local" "$LOCAL"
 238        describe_file "$remote_mode" "remote" "$REMOTE"
 239        resolve_deleted_merge
 240        return
 241    fi
 242
 243    if is_symlink "$local_mode" || is_symlink "$remote_mode"; then
 244        echo "Symbolic link merge conflict for '$MERGED':"
 245        describe_file "$local_mode" "local" "$LOCAL"
 246        describe_file "$remote_mode" "remote" "$REMOTE"
 247        resolve_symlink_merge
 248        return
 249    fi
 250
 251    echo "Normal merge conflict for '$MERGED':"
 252    describe_file "$local_mode" "local" "$LOCAL"
 253    describe_file "$remote_mode" "remote" "$REMOTE"
 254    if "$prompt" = true; then
 255        printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
 256        read ans || return 1
 257    fi
 258
 259    if base_present; then
 260            present=true
 261    else
 262            present=false
 263    fi
 264
 265    if ! run_merge_tool "$merge_tool" "$present"; then
 266        echo "merge of $MERGED failed" 1>&2
 267        mv -- "$BACKUP" "$MERGED"
 268
 269        if test "$merge_keep_temporaries" = "false"; then
 270            cleanup_temp_files
 271        fi
 272
 273        return 1
 274    fi
 275
 276    if test "$merge_keep_backup" = "true"; then
 277        mv -- "$BACKUP" "$MERGED.orig"
 278    else
 279        rm -- "$BACKUP"
 280    fi
 281
 282    git add -- "$MERGED"
 283    cleanup_temp_files
 284    return 0
 285}
 286
 287show_tool_help () {
 288        TOOL_MODE=merge
 289        list_merge_tool_candidates
 290        unavailable= available= LF='
 291'
 292        for i in $tools
 293        do
 294                merge_tool_path=$(translate_merge_tool_path "$i")
 295                if type "$merge_tool_path" >/dev/null 2>&1
 296                then
 297                        available="$available$i$LF"
 298                else
 299                        unavailable="$unavailable$i$LF"
 300                fi
 301        done
 302        if test -n "$available"
 303        then
 304                echo "'git mergetool --tool=<tool>' may be set to one of the following:"
 305                echo "$available" | sort | sed -e 's/^/ /'
 306        else
 307                echo "No suitable tool for 'git mergetool --tool=<tool>' found."
 308        fi
 309        if test -n "$unavailable"
 310        then
 311                echo
 312                echo 'The following tools are valid, but not currently available:'
 313                echo "$unavailable" | sort | sed -e 's/^/       /'
 314        fi
 315        if test -n "$unavailable$available"
 316        then
 317                echo
 318                echo "Some of the tools listed above only work in a windowed"
 319                echo "environment. If run in a terminal-only session, they will fail."
 320        fi
 321        exit 0
 322}
 323
 324prompt=$(git config --bool mergetool.prompt || echo true)
 325
 326while test $# != 0
 327do
 328    case "$1" in
 329        --tool-help)
 330                show_tool_help
 331                ;;
 332        -t|--tool*)
 333            case "$#,$1" in
 334                *,*=*)
 335                    merge_tool=$(expr "z$1" : 'z-[^=]*=\(.*\)')
 336                    ;;
 337                1,*)
 338                    usage ;;
 339                *)
 340                    merge_tool="$2"
 341                    shift ;;
 342            esac
 343            ;;
 344        -y|--no-prompt)
 345            prompt=false
 346            ;;
 347        --prompt)
 348            prompt=true
 349            ;;
 350        --)
 351            shift
 352            break
 353            ;;
 354        -*)
 355            usage
 356            ;;
 357        *)
 358            break
 359            ;;
 360    esac
 361    shift
 362done
 363
 364prompt_after_failed_merge() {
 365    while true; do
 366        printf "Continue merging other unresolved paths (y/n) ? "
 367        read ans || return 1
 368        case "$ans" in
 369
 370            [yY]*)
 371                return 0
 372                ;;
 373
 374            [nN]*)
 375                return 1
 376                ;;
 377        esac
 378    done
 379}
 380
 381if test -z "$merge_tool"; then
 382    merge_tool=$(get_merge_tool "$merge_tool") || exit
 383fi
 384merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
 385merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"
 386
 387last_status=0
 388rollup_status=0
 389files=
 390
 391if test $# -eq 0 ; then
 392    cd_to_toplevel
 393
 394    if test -e "$GIT_DIR/MERGE_RR"
 395    then
 396        files=$(git rerere remaining)
 397    else
 398        files=$(git ls-files -u | sed -e 's/^[^ ]*      //' | sort -u)
 399    fi
 400else
 401    files=$(git ls-files -u -- "$@" | sed -e 's/^[^     ]*      //' | sort -u)
 402fi
 403
 404if test -z "$files" ; then
 405    echo "No files need merging"
 406    exit 0
 407fi
 408
 409printf "Merging:\n"
 410printf "$files\n"
 411
 412IFS='
 413'
 414for i in $files
 415do
 416    if test $last_status -ne 0; then
 417        prompt_after_failed_merge || exit 1
 418    fi
 419    printf "\n"
 420    merge_file "$i"
 421    last_status=$?
 422    if test $last_status -ne 0; then
 423        rollup_status=1
 424    fi
 425done
 426
 427exit $rollup_status