git-am.shon commit git-svn: several graft-branches improvements (c1927a8)
   1#!/bin/sh
   2#
   3# Copyright (c) 2005, 2006 Junio C Hamano
   4
   5USAGE='[--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way]
   6  [--interactive] [--whitespace=<option>] <mbox>...
   7  or, when resuming [--skip | --resolved]'
   8. git-sh-setup
   9
  10git var GIT_COMMITTER_IDENT >/dev/null || exit
  11
  12stop_here () {
  13    echo "$1" >"$dotest/next"
  14    exit 1
  15}
  16
  17stop_here_user_resolve () {
  18    if [ -n "$resolvemsg" ]; then
  19            echo "$resolvemsg"
  20            stop_here $1
  21    fi
  22    cmdline=$(basename $0)
  23    if test '' != "$interactive"
  24    then
  25        cmdline="$cmdline -i"
  26    fi
  27    if test '' != "$threeway"
  28    then
  29        cmdline="$cmdline -3"
  30    fi
  31    if test '.dotest' != "$dotest"
  32    then
  33        cmdline="$cmdline -d=$dotest"
  34    fi
  35    echo "When you have resolved this problem run \"$cmdline --resolved\"."
  36    echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
  37
  38    stop_here $1
  39}
  40
  41go_next () {
  42        rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
  43                "$dotest/patch" "$dotest/info"
  44        echo "$next" >"$dotest/next"
  45        this=$next
  46}
  47
  48fall_back_3way () {
  49    O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
  50
  51    rm -fr "$dotest"/patch-merge-*
  52    mkdir "$dotest/patch-merge-tmp-dir"
  53
  54    # First see if the patch records the index info that we can use.
  55    if git-apply -z --index-info "$dotest/patch" \
  56        >"$dotest/patch-merge-index-info" 2>/dev/null &&
  57        GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
  58        git-update-index -z --index-info <"$dotest/patch-merge-index-info" &&
  59        GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
  60        git-write-tree >"$dotest/patch-merge-base+" &&
  61        # index has the base tree now.
  62        GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
  63        git-apply $binary --cached <"$dotest/patch"
  64    then
  65        echo Using index info to reconstruct a base tree...
  66        mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
  67        mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
  68    fi
  69
  70    test -f "$dotest/patch-merge-index" &&
  71    his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git-write-tree) &&
  72    orig_tree=$(cat "$dotest/patch-merge-base") &&
  73    rm -fr "$dotest"/patch-merge-* || exit 1
  74
  75    echo Falling back to patching base and 3-way merge...
  76
  77    # This is not so wrong.  Depending on which base we picked,
  78    # orig_tree may be wildly different from ours, but his_tree
  79    # has the same set of wildly different changes in parts the
  80    # patch did not touch, so resolve ends up cancelling them,
  81    # saying that we reverted all those changes.
  82
  83    git-merge-resolve $orig_tree -- HEAD $his_tree || {
  84            if test -d "$GIT_DIR/rr-cache"
  85            then
  86                git-rerere
  87            fi
  88            echo Failed to merge in the changes.
  89            exit 1
  90    }
  91}
  92
  93prec=4
  94dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws= resolvemsg=
  95
  96while case "$#" in 0) break;; esac
  97do
  98        case "$1" in
  99        -d=*|--d=*|--do=*|--dot=*|--dote=*|--dotes=*|--dotest=*)
 100        dotest=`expr "z$1" : 'z-[^=]*=\(.*\)'`; shift ;;
 101        -d|--d|--do|--dot|--dote|--dotes|--dotest)
 102        case "$#" in 1) usage ;; esac; shift
 103        dotest="$1"; shift;;
 104
 105        -i|--i|--in|--int|--inte|--inter|--intera|--interac|--interact|\
 106        --interacti|--interactiv|--interactive)
 107        interactive=t; shift ;;
 108
 109        -b|--b|--bi|--bin|--bina|--binar|--binary)
 110        binary=t; shift ;;
 111
 112        -3|--3|--3w|--3wa|--3way)
 113        threeway=t; shift ;;
 114        -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
 115        sign=t; shift ;;
 116        -u|--u|--ut|--utf|--utf8)
 117        utf8=t; shift ;;
 118        -k|--k|--ke|--kee|--keep)
 119        keep=t; shift ;;
 120
 121        -r|--r|--re|--res|--reso|--resol|--resolv|--resolve|--resolved)
 122        resolved=t; shift ;;
 123
 124        --sk|--ski|--skip)
 125        skip=t; shift ;;
 126
 127        --whitespace=*)
 128        ws=$1; shift ;;
 129
 130        --resolvemsg=*)
 131        resolvemsg=$(echo "$1" | sed -e "s/^--resolvemsg=//"); shift ;;
 132
 133        --)
 134        shift; break ;;
 135        -*)
 136        usage ;;
 137        *)
 138        break ;;
 139        esac
 140done
 141
 142# If the dotest directory exists, but we have finished applying all the
 143# patches in them, clear it out.
 144if test -d "$dotest" &&
 145   last=$(cat "$dotest/last") &&
 146   next=$(cat "$dotest/next") &&
 147   test $# != 0 &&
 148   test "$next" -gt "$last"
 149then
 150   rm -fr "$dotest"
 151fi
 152
 153if test -d "$dotest"
 154then
 155        test ",$#," = ",0," ||
 156        die "previous dotest directory $dotest still exists but mbox given."
 157        resume=yes
 158else
 159        # Make sure we are not given --skip nor --resolved
 160        test ",$skip,$resolved," = ,,, ||
 161                die "Resolve operation not in progress, we are not resuming."
 162
 163        # Start afresh.
 164        mkdir -p "$dotest" || exit
 165
 166        git-mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" ||  {
 167                rm -fr "$dotest"
 168                exit 1
 169        }
 170
 171        # -b, -s, -u, -k and --whitespace flags are kept for the
 172        # resuming session after a patch failure.
 173        # -3 and -i can and must be given when resuming.
 174        echo "$binary" >"$dotest/binary"
 175        echo " $ws" >"$dotest/whitespace"
 176        echo "$sign" >"$dotest/sign"
 177        echo "$utf8" >"$dotest/utf8"
 178        echo "$keep" >"$dotest/keep"
 179        echo 1 >"$dotest/next"
 180fi
 181
 182case "$resolved" in
 183'')
 184        files=$(git-diff-index --cached --name-only HEAD) || exit
 185        if [ "$files" ]; then
 186           echo "Dirty index: cannot apply patches (dirty: $files)" >&2
 187           exit 1
 188        fi
 189esac
 190
 191if test "$(cat "$dotest/binary")" = t
 192then
 193        binary=--allow-binary-replacement
 194fi
 195if test "$(cat "$dotest/utf8")" = t
 196then
 197        utf8=-u
 198fi
 199if test "$(cat "$dotest/keep")" = t
 200then
 201        keep=-k
 202fi
 203ws=`cat "$dotest/whitespace"`
 204if test "$(cat "$dotest/sign")" = t
 205then
 206        SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
 207                        s/>.*/>/
 208                        s/^/Signed-off-by: /'
 209                `
 210else
 211        SIGNOFF=
 212fi
 213
 214last=`cat "$dotest/last"`
 215this=`cat "$dotest/next"`
 216if test "$skip" = t
 217then
 218        this=`expr "$this" + 1`
 219        resume=
 220fi
 221
 222if test "$this" -gt "$last"
 223then
 224        echo Nothing to do.
 225        rm -fr "$dotest"
 226        exit
 227fi
 228
 229while test "$this" -le "$last"
 230do
 231        msgnum=`printf "%0${prec}d" $this`
 232        next=`expr "$this" + 1`
 233        test -f "$dotest/$msgnum" || {
 234                resume=
 235                go_next
 236                continue
 237        }
 238
 239        # If we are not resuming, parse and extract the patch information
 240        # into separate files:
 241        #  - info records the authorship and title
 242        #  - msg is the rest of commit log message
 243        #  - patch is the patch body.
 244        #
 245        # When we are resuming, these files are either already prepared
 246        # by the user, or the user can tell us to do so by --resolved flag.
 247        case "$resume" in
 248        '')
 249                git-mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
 250                        <"$dotest/$msgnum" >"$dotest/info" ||
 251                        stop_here $this
 252                git-stripspace < "$dotest/msg" > "$dotest/msg-clean"
 253                ;;
 254        esac
 255
 256        GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")"
 257        GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")"
 258        GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")"
 259
 260        if test -z "$GIT_AUTHOR_EMAIL"
 261        then
 262                echo "Patch does not have a valid e-mail address."
 263                stop_here $this
 264        fi
 265
 266        export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
 267
 268        SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")"
 269        case "$keep_subject" in -k)  SUBJECT="[PATCH] $SUBJECT" ;; esac
 270
 271        case "$resume" in
 272        '')
 273            if test '' != "$SIGNOFF"
 274            then
 275                LAST_SIGNED_OFF_BY=`
 276                    sed -ne '/^Signed-off-by: /p' \
 277                    "$dotest/msg-clean" |
 278                    tail -n 1
 279                `
 280                ADD_SIGNOFF=`
 281                    test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
 282                    test '' = "$LAST_SIGNED_OFF_BY" && echo
 283                    echo "$SIGNOFF"
 284                }`
 285            else
 286                ADD_SIGNOFF=
 287            fi
 288            {
 289                echo "$SUBJECT"
 290                if test -s "$dotest/msg-clean"
 291                then
 292                        echo
 293                        cat "$dotest/msg-clean"
 294                fi
 295                if test '' != "$ADD_SIGNOFF"
 296                then
 297                        echo "$ADD_SIGNOFF"
 298                fi
 299            } >"$dotest/final-commit"
 300            ;;
 301        *)
 302                case "$resolved$interactive" in
 303                tt)
 304                        # This is used only for interactive view option.
 305                        git-diff-index -p --cached HEAD >"$dotest/patch"
 306                        ;;
 307                esac
 308        esac
 309
 310        resume=
 311        if test "$interactive" = t
 312        then
 313            test -t 0 ||
 314            die "cannot be interactive without stdin connected to a terminal."
 315            action=again
 316            while test "$action" = again
 317            do
 318                echo "Commit Body is:"
 319                echo "--------------------------"
 320                cat "$dotest/final-commit"
 321                echo "--------------------------"
 322                printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
 323                read reply
 324                case "$reply" in
 325                [yY]*) action=yes ;;
 326                [aA]*) action=yes interactive= ;;
 327                [nN]*) action=skip ;;
 328                [eE]*) "${VISUAL:-${EDITOR:-vi}}" "$dotest/final-commit"
 329                       action=again ;;
 330                [vV]*) action=again
 331                       LESS=-S ${PAGER:-less} "$dotest/patch" ;;
 332                *)     action=again ;;
 333                esac
 334            done
 335        else
 336            action=yes
 337        fi
 338
 339        if test $action = skip
 340        then
 341                go_next
 342                continue
 343        fi
 344
 345        if test -x "$GIT_DIR"/hooks/applypatch-msg
 346        then
 347                "$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
 348                stop_here $this
 349        fi
 350
 351        echo
 352        echo "Applying '$SUBJECT'"
 353        echo
 354
 355        case "$resolved" in
 356        '')
 357                git-apply $binary --index $ws "$dotest/patch"
 358                apply_status=$?
 359                ;;
 360        t)
 361                # Resolved means the user did all the hard work, and
 362                # we do not have to do any patch application.  Just
 363                # trust what the user has in the index file and the
 364                # working tree.
 365                resolved=
 366                changed="$(git-diff-index --cached --name-only HEAD)"
 367                if test '' = "$changed"
 368                then
 369                        echo "No changes - did you forget update-index?"
 370                        stop_here_user_resolve $this
 371                fi
 372                unmerged=$(git-ls-files -u)
 373                if test -n "$unmerged"
 374                then
 375                        echo "You still have unmerged paths in your index"
 376                        echo "did you forget update-index?"
 377                        stop_here_user_resolve $this
 378                fi
 379                apply_status=0
 380                ;;
 381        esac
 382
 383        if test $apply_status = 1 && test "$threeway" = t
 384        then
 385                if (fall_back_3way)
 386                then
 387                    # Applying the patch to an earlier tree and merging the
 388                    # result may have produced the same tree as ours.
 389                    changed="$(git-diff-index --cached --name-only HEAD)"
 390                    if test '' = "$changed"
 391                    then
 392                            echo No changes -- Patch already applied.
 393                            go_next
 394                            continue
 395                    fi
 396                    # clear apply_status -- we have successfully merged.
 397                    apply_status=0
 398                fi
 399        fi
 400        if test $apply_status != 0
 401        then
 402                echo Patch failed at $msgnum.
 403                stop_here_user_resolve $this
 404        fi
 405
 406        if test -x "$GIT_DIR"/hooks/pre-applypatch
 407        then
 408                "$GIT_DIR"/hooks/pre-applypatch || stop_here $this
 409        fi
 410
 411        tree=$(git-write-tree) &&
 412        echo Wrote tree $tree &&
 413        parent=$(git-rev-parse --verify HEAD) &&
 414        commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
 415        echo Committed: $commit &&
 416        git-update-ref -m "am: $SUBJECT" HEAD $commit $parent ||
 417        stop_here $this
 418
 419        if test -x "$GIT_DIR"/hooks/post-applypatch
 420        then
 421                "$GIT_DIR"/hooks/post-applypatch
 422        fi
 423
 424        go_next
 425done
 426
 427rm -fr "$dotest"