git-am.shon commit gitweb.cgi: git_blame2: an alternative simple working git blame (1f2857e)
   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 canceling 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
  94rloga=am
  95dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws= resolvemsg=
  96
  97while case "$#" in 0) break;; esac
  98do
  99        case "$1" in
 100        -d=*|--d=*|--do=*|--dot=*|--dote=*|--dotes=*|--dotest=*)
 101        dotest=`expr "z$1" : 'z-[^=]*=\(.*\)'`; shift ;;
 102        -d|--d|--do|--dot|--dote|--dotes|--dotest)
 103        case "$#" in 1) usage ;; esac; shift
 104        dotest="$1"; shift;;
 105
 106        -i|--i|--in|--int|--inte|--inter|--intera|--interac|--interact|\
 107        --interacti|--interactiv|--interactive)
 108        interactive=t; shift ;;
 109
 110        -b|--b|--bi|--bin|--bina|--binar|--binary)
 111        binary=t; shift ;;
 112
 113        -3|--3|--3w|--3wa|--3way)
 114        threeway=t; shift ;;
 115        -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
 116        sign=t; shift ;;
 117        -u|--u|--ut|--utf|--utf8)
 118        utf8=t; shift ;;
 119        -k|--k|--ke|--kee|--keep)
 120        keep=t; shift ;;
 121
 122        -r|--r|--re|--res|--reso|--resol|--resolv|--resolve|--resolved)
 123        resolved=t; shift ;;
 124
 125        --sk|--ski|--skip)
 126        skip=t; shift ;;
 127
 128        --whitespace=*)
 129        ws=$1; shift ;;
 130
 131        --resolvemsg=*)
 132        resolvemsg=$(echo "$1" | sed -e "s/^--resolvemsg=//"); shift ;;
 133
 134        --reflog-action=*)
 135        rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`; shift ;;
 136
 137        --)
 138        shift; break ;;
 139        -*)
 140        usage ;;
 141        *)
 142        break ;;
 143        esac
 144done
 145
 146# If the dotest directory exists, but we have finished applying all the
 147# patches in them, clear it out.
 148if test -d "$dotest" &&
 149   last=$(cat "$dotest/last") &&
 150   next=$(cat "$dotest/next") &&
 151   test $# != 0 &&
 152   test "$next" -gt "$last"
 153then
 154   rm -fr "$dotest"
 155fi
 156
 157if test -d "$dotest"
 158then
 159        if test ",$#," != ",0," || ! tty -s
 160        then
 161                die "previous dotest directory $dotest still exists but mbox given."
 162        fi
 163        resume=yes
 164else
 165        # Make sure we are not given --skip nor --resolved
 166        test ",$skip,$resolved," = ,,, ||
 167                die "Resolve operation not in progress, we are not resuming."
 168
 169        # Start afresh.
 170        mkdir -p "$dotest" || exit
 171
 172        git-mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" ||  {
 173                rm -fr "$dotest"
 174                exit 1
 175        }
 176
 177        # -b, -s, -u, -k and --whitespace flags are kept for the
 178        # resuming session after a patch failure.
 179        # -3 and -i can and must be given when resuming.
 180        echo "$binary" >"$dotest/binary"
 181        echo " $ws" >"$dotest/whitespace"
 182        echo "$sign" >"$dotest/sign"
 183        echo "$utf8" >"$dotest/utf8"
 184        echo "$keep" >"$dotest/keep"
 185        echo 1 >"$dotest/next"
 186fi
 187
 188case "$resolved" in
 189'')
 190        files=$(git-diff-index --cached --name-only HEAD) || exit
 191        if [ "$files" ]; then
 192           echo "Dirty index: cannot apply patches (dirty: $files)" >&2
 193           exit 1
 194        fi
 195esac
 196
 197if test "$(cat "$dotest/binary")" = t
 198then
 199        binary=--allow-binary-replacement
 200fi
 201if test "$(cat "$dotest/utf8")" = t
 202then
 203        utf8=-u
 204fi
 205if test "$(cat "$dotest/keep")" = t
 206then
 207        keep=-k
 208fi
 209ws=`cat "$dotest/whitespace"`
 210if test "$(cat "$dotest/sign")" = t
 211then
 212        SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
 213                        s/>.*/>/
 214                        s/^/Signed-off-by: /'
 215                `
 216else
 217        SIGNOFF=
 218fi
 219
 220last=`cat "$dotest/last"`
 221this=`cat "$dotest/next"`
 222if test "$skip" = t
 223then
 224        this=`expr "$this" + 1`
 225        resume=
 226fi
 227
 228if test "$this" -gt "$last"
 229then
 230        echo Nothing to do.
 231        rm -fr "$dotest"
 232        exit
 233fi
 234
 235while test "$this" -le "$last"
 236do
 237        msgnum=`printf "%0${prec}d" $this`
 238        next=`expr "$this" + 1`
 239        test -f "$dotest/$msgnum" || {
 240                resume=
 241                go_next
 242                continue
 243        }
 244
 245        # If we are not resuming, parse and extract the patch information
 246        # into separate files:
 247        #  - info records the authorship and title
 248        #  - msg is the rest of commit log message
 249        #  - patch is the patch body.
 250        #
 251        # When we are resuming, these files are either already prepared
 252        # by the user, or the user can tell us to do so by --resolved flag.
 253        case "$resume" in
 254        '')
 255                git-mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
 256                        <"$dotest/$msgnum" >"$dotest/info" ||
 257                        stop_here $this
 258                git-stripspace < "$dotest/msg" > "$dotest/msg-clean"
 259                ;;
 260        esac
 261
 262        GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")"
 263        GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")"
 264        GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")"
 265
 266        if test -z "$GIT_AUTHOR_EMAIL"
 267        then
 268                echo "Patch does not have a valid e-mail address."
 269                stop_here $this
 270        fi
 271
 272        export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
 273
 274        SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")"
 275        case "$keep_subject" in -k)  SUBJECT="[PATCH] $SUBJECT" ;; esac
 276
 277        case "$resume" in
 278        '')
 279            if test '' != "$SIGNOFF"
 280            then
 281                LAST_SIGNED_OFF_BY=`
 282                    sed -ne '/^Signed-off-by: /p' \
 283                    "$dotest/msg-clean" |
 284                    tail -n 1
 285                `
 286                ADD_SIGNOFF=`
 287                    test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
 288                    test '' = "$LAST_SIGNED_OFF_BY" && echo
 289                    echo "$SIGNOFF"
 290                }`
 291            else
 292                ADD_SIGNOFF=
 293            fi
 294            {
 295                echo "$SUBJECT"
 296                if test -s "$dotest/msg-clean"
 297                then
 298                        echo
 299                        cat "$dotest/msg-clean"
 300                fi
 301                if test '' != "$ADD_SIGNOFF"
 302                then
 303                        echo "$ADD_SIGNOFF"
 304                fi
 305            } >"$dotest/final-commit"
 306            ;;
 307        *)
 308                case "$resolved$interactive" in
 309                tt)
 310                        # This is used only for interactive view option.
 311                        git-diff-index -p --cached HEAD >"$dotest/patch"
 312                        ;;
 313                esac
 314        esac
 315
 316        resume=
 317        if test "$interactive" = t
 318        then
 319            test -t 0 ||
 320            die "cannot be interactive without stdin connected to a terminal."
 321            action=again
 322            while test "$action" = again
 323            do
 324                echo "Commit Body is:"
 325                echo "--------------------------"
 326                cat "$dotest/final-commit"
 327                echo "--------------------------"
 328                printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
 329                read reply
 330                case "$reply" in
 331                [yY]*) action=yes ;;
 332                [aA]*) action=yes interactive= ;;
 333                [nN]*) action=skip ;;
 334                [eE]*) "${VISUAL:-${EDITOR:-vi}}" "$dotest/final-commit"
 335                       action=again ;;
 336                [vV]*) action=again
 337                       LESS=-S ${PAGER:-less} "$dotest/patch" ;;
 338                *)     action=again ;;
 339                esac
 340            done
 341        else
 342            action=yes
 343        fi
 344
 345        if test $action = skip
 346        then
 347                go_next
 348                continue
 349        fi
 350
 351        if test -x "$GIT_DIR"/hooks/applypatch-msg
 352        then
 353                "$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
 354                stop_here $this
 355        fi
 356
 357        echo
 358        echo "Applying '$SUBJECT'"
 359        echo
 360
 361        case "$resolved" in
 362        '')
 363                git-apply $binary --index $ws "$dotest/patch"
 364                apply_status=$?
 365                ;;
 366        t)
 367                # Resolved means the user did all the hard work, and
 368                # we do not have to do any patch application.  Just
 369                # trust what the user has in the index file and the
 370                # working tree.
 371                resolved=
 372                changed="$(git-diff-index --cached --name-only HEAD)"
 373                if test '' = "$changed"
 374                then
 375                        echo "No changes - did you forget update-index?"
 376                        stop_here_user_resolve $this
 377                fi
 378                unmerged=$(git-ls-files -u)
 379                if test -n "$unmerged"
 380                then
 381                        echo "You still have unmerged paths in your index"
 382                        echo "did you forget update-index?"
 383                        stop_here_user_resolve $this
 384                fi
 385                apply_status=0
 386                ;;
 387        esac
 388
 389        if test $apply_status = 1 && test "$threeway" = t
 390        then
 391                if (fall_back_3way)
 392                then
 393                    # Applying the patch to an earlier tree and merging the
 394                    # result may have produced the same tree as ours.
 395                    changed="$(git-diff-index --cached --name-only HEAD)"
 396                    if test '' = "$changed"
 397                    then
 398                            echo No changes -- Patch already applied.
 399                            go_next
 400                            continue
 401                    fi
 402                    # clear apply_status -- we have successfully merged.
 403                    apply_status=0
 404                fi
 405        fi
 406        if test $apply_status != 0
 407        then
 408                echo Patch failed at $msgnum.
 409                stop_here_user_resolve $this
 410        fi
 411
 412        if test -x "$GIT_DIR"/hooks/pre-applypatch
 413        then
 414                "$GIT_DIR"/hooks/pre-applypatch || stop_here $this
 415        fi
 416
 417        tree=$(git-write-tree) &&
 418        echo Wrote tree $tree &&
 419        parent=$(git-rev-parse --verify HEAD) &&
 420        commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
 421        echo Committed: $commit &&
 422        git-update-ref -m "$rloga: $SUBJECT" HEAD $commit $parent ||
 423        stop_here $this
 424
 425        if test -x "$GIT_DIR"/hooks/post-applypatch
 426        then
 427                "$GIT_DIR"/hooks/post-applypatch
 428        fi
 429
 430        go_next
 431done
 432
 433rm -fr "$dotest"