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