git-stash.shon commit rebase -i: rewrite init_basic_state() in C (d59cd14)
   1#!/bin/sh
   2# Copyright (c) 2007, Nanako Shiraishi
   3
   4dashless=$(basename "$0" | sed -e 's/-/ /')
   5USAGE="list [<options>]
   6   or: $dashless show [<stash>]
   7   or: $dashless drop [-q|--quiet] [<stash>]
   8   or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
   9   or: $dashless branch <branchname> [<stash>]
  10   or: $dashless save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
  11                      [-u|--include-untracked] [-a|--all] [<message>]
  12   or: $dashless [push [--patch] [-k|--[no-]keep-index] [-q|--quiet]
  13                       [-u|--include-untracked] [-a|--all] [-m <message>]
  14                       [-- <pathspec>...]]
  15   or: $dashless clear"
  16
  17SUBDIRECTORY_OK=Yes
  18OPTIONS_SPEC=
  19START_DIR=$(pwd)
  20. git-sh-setup
  21require_work_tree
  22prefix=$(git rev-parse --show-prefix) || exit 1
  23cd_to_toplevel
  24
  25TMP="$GIT_DIR/.git-stash.$$"
  26TMPindex=${GIT_INDEX_FILE-"$(git rev-parse --git-path index)"}.stash.$$
  27trap 'rm -f "$TMP-"* "$TMPindex"' 0
  28
  29ref_stash=refs/stash
  30
  31if git config --get-colorbool color.interactive; then
  32       help_color="$(git config --get-color color.interactive.help 'red bold')"
  33       reset_color="$(git config --get-color '' reset)"
  34else
  35       help_color=
  36       reset_color=
  37fi
  38
  39no_changes () {
  40        git diff-index --quiet --cached HEAD --ignore-submodules -- "$@" &&
  41        git diff-files --quiet --ignore-submodules -- "$@" &&
  42        (test -z "$untracked" || test -z "$(untracked_files "$@")")
  43}
  44
  45untracked_files () {
  46        if test "$1" = "-z"
  47        then
  48                shift
  49                z=-z
  50        else
  51                z=
  52        fi
  53        excl_opt=--exclude-standard
  54        test "$untracked" = "all" && excl_opt=
  55        git ls-files -o $z $excl_opt -- "$@"
  56}
  57
  58clear_stash () {
  59        if test $# != 0
  60        then
  61                die "$(gettext "git stash clear with parameters is unimplemented")"
  62        fi
  63        if current=$(git rev-parse --verify --quiet $ref_stash)
  64        then
  65                git update-ref -d $ref_stash $current
  66        fi
  67}
  68
  69create_stash () {
  70        stash_msg=
  71        untracked=
  72        while test $# != 0
  73        do
  74                case "$1" in
  75                -m|--message)
  76                        shift
  77                        stash_msg=${1?"BUG: create_stash () -m requires an argument"}
  78                        ;;
  79                -m*)
  80                        stash_msg=${1#-m}
  81                        ;;
  82                --message=*)
  83                        stash_msg=${1#--message=}
  84                        ;;
  85                -u|--include-untracked)
  86                        shift
  87                        untracked=${1?"BUG: create_stash () -u requires an argument"}
  88                        ;;
  89                --)
  90                        shift
  91                        break
  92                        ;;
  93                esac
  94                shift
  95        done
  96
  97        git update-index -q --refresh
  98        if no_changes "$@"
  99        then
 100                exit 0
 101        fi
 102
 103        # state of the base commit
 104        if b_commit=$(git rev-parse --verify HEAD)
 105        then
 106                head=$(git rev-list --oneline -n 1 HEAD --)
 107        else
 108                die "$(gettext "You do not have the initial commit yet")"
 109        fi
 110
 111        if branch=$(git symbolic-ref -q HEAD)
 112        then
 113                branch=${branch#refs/heads/}
 114        else
 115                branch='(no branch)'
 116        fi
 117        msg=$(printf '%s: %s' "$branch" "$head")
 118
 119        # state of the index
 120        i_tree=$(git write-tree) &&
 121        i_commit=$(printf 'index on %s\n' "$msg" |
 122                git commit-tree $i_tree -p $b_commit) ||
 123                die "$(gettext "Cannot save the current index state")"
 124
 125        if test -n "$untracked"
 126        then
 127                # Untracked files are stored by themselves in a parentless commit, for
 128                # ease of unpacking later.
 129                u_commit=$(
 130                        untracked_files -z "$@" | (
 131                                GIT_INDEX_FILE="$TMPindex" &&
 132                                export GIT_INDEX_FILE &&
 133                                rm -f "$TMPindex" &&
 134                                git update-index -z --add --remove --stdin &&
 135                                u_tree=$(git write-tree) &&
 136                                printf 'untracked files on %s\n' "$msg" | git commit-tree $u_tree  &&
 137                                rm -f "$TMPindex"
 138                ) ) || die "$(gettext "Cannot save the untracked files")"
 139
 140                untracked_commit_option="-p $u_commit";
 141        else
 142                untracked_commit_option=
 143        fi
 144
 145        if test -z "$patch_mode"
 146        then
 147
 148                # state of the working tree
 149                w_tree=$( (
 150                        git read-tree --index-output="$TMPindex" -m $i_tree &&
 151                        GIT_INDEX_FILE="$TMPindex" &&
 152                        export GIT_INDEX_FILE &&
 153                        git diff-index --name-only -z HEAD -- "$@" >"$TMP-stagenames" &&
 154                        git update-index -z --add --remove --stdin <"$TMP-stagenames" &&
 155                        git write-tree &&
 156                        rm -f "$TMPindex"
 157                ) ) ||
 158                        die "$(gettext "Cannot save the current worktree state")"
 159
 160        else
 161
 162                rm -f "$TMP-index" &&
 163                GIT_INDEX_FILE="$TMP-index" git read-tree HEAD &&
 164
 165                # find out what the user wants
 166                GIT_INDEX_FILE="$TMP-index" \
 167                        git add--interactive --patch=stash -- "$@" &&
 168
 169                # state of the working tree
 170                w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
 171                die "$(gettext "Cannot save the current worktree state")"
 172
 173                git diff-tree -p HEAD $w_tree -- >"$TMP-patch" &&
 174                test -s "$TMP-patch" ||
 175                die "$(gettext "No changes selected")"
 176
 177                rm -f "$TMP-index" ||
 178                die "$(gettext "Cannot remove temporary index (can't happen)")"
 179
 180        fi
 181
 182        # create the stash
 183        if test -z "$stash_msg"
 184        then
 185                stash_msg=$(printf 'WIP on %s' "$msg")
 186        else
 187                stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
 188        fi
 189        w_commit=$(printf '%s\n' "$stash_msg" |
 190        git commit-tree $w_tree -p $b_commit -p $i_commit $untracked_commit_option) ||
 191        die "$(gettext "Cannot record working tree state")"
 192}
 193
 194store_stash () {
 195        while test $# != 0
 196        do
 197                case "$1" in
 198                -m|--message)
 199                        shift
 200                        stash_msg="$1"
 201                        ;;
 202                -m*)
 203                        stash_msg=${1#-m}
 204                        ;;
 205                --message=*)
 206                        stash_msg=${1#--message=}
 207                        ;;
 208                -q|--quiet)
 209                        quiet=t
 210                        ;;
 211                *)
 212                        break
 213                        ;;
 214                esac
 215                shift
 216        done
 217        test $# = 1 ||
 218        die "$(eval_gettext "\"$dashless store\" requires one <commit> argument")"
 219
 220        w_commit="$1"
 221        if test -z "$stash_msg"
 222        then
 223                stash_msg="Created via \"git stash store\"."
 224        fi
 225
 226        git update-ref --create-reflog -m "$stash_msg" $ref_stash $w_commit
 227        ret=$?
 228        test $ret != 0 && test -z "$quiet" &&
 229        die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")"
 230        return $ret
 231}
 232
 233push_stash () {
 234        keep_index=
 235        patch_mode=
 236        untracked=
 237        stash_msg=
 238        while test $# != 0
 239        do
 240                case "$1" in
 241                -k|--keep-index)
 242                        keep_index=t
 243                        ;;
 244                --no-keep-index)
 245                        keep_index=n
 246                        ;;
 247                -p|--patch)
 248                        patch_mode=t
 249                        # only default to keep if we don't already have an override
 250                        test -z "$keep_index" && keep_index=t
 251                        ;;
 252                -q|--quiet)
 253                        GIT_QUIET=t
 254                        ;;
 255                -u|--include-untracked)
 256                        untracked=untracked
 257                        ;;
 258                -a|--all)
 259                        untracked=all
 260                        ;;
 261                -m|--message)
 262                        shift
 263                        test -z ${1+x} && usage
 264                        stash_msg=$1
 265                        ;;
 266                -m*)
 267                        stash_msg=${1#-m}
 268                        ;;
 269                --message=*)
 270                        stash_msg=${1#--message=}
 271                        ;;
 272                --help)
 273                        show_help
 274                        ;;
 275                --)
 276                        shift
 277                        break
 278                        ;;
 279                -*)
 280                        option="$1"
 281                        eval_gettextln "error: unknown option for 'stash push': \$option"
 282                        usage
 283                        ;;
 284                *)
 285                        break
 286                        ;;
 287                esac
 288                shift
 289        done
 290
 291        eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")"
 292
 293        if test -n "$patch_mode" && test -n "$untracked"
 294        then
 295                die "$(gettext "Can't use --patch and --include-untracked or --all at the same time")"
 296        fi
 297
 298        test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1
 299
 300        git update-index -q --refresh
 301        if no_changes "$@"
 302        then
 303                say "$(gettext "No local changes to save")"
 304                exit 0
 305        fi
 306
 307        git reflog exists $ref_stash ||
 308                clear_stash || die "$(gettext "Cannot initialize stash")"
 309
 310        create_stash -m "$stash_msg" -u "$untracked" -- "$@"
 311        store_stash -m "$stash_msg" -q $w_commit ||
 312        die "$(gettext "Cannot save the current status")"
 313        say "$(eval_gettext "Saved working directory and index state \$stash_msg")"
 314
 315        if test -z "$patch_mode"
 316        then
 317                test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
 318                if test -n "$untracked" && test $# = 0
 319                then
 320                        git clean --force --quiet -d $CLEAN_X_OPTION
 321                fi
 322
 323                if test $# != 0
 324                then
 325                        test -z "$untracked" && UPDATE_OPTION="-u" || UPDATE_OPTION=
 326                        test "$untracked" = "all" && FORCE_OPTION="--force" || FORCE_OPTION=
 327                        git add $UPDATE_OPTION $FORCE_OPTION -- "$@"
 328                        git diff-index -p --cached --binary HEAD -- "$@" |
 329                        git apply --index -R
 330                else
 331                        git reset --hard -q
 332                fi
 333
 334                if test "$keep_index" = "t" && test -n "$i_tree"
 335                then
 336                        git read-tree --reset $i_tree
 337                        git ls-files -z --modified -- "$@" |
 338                        git checkout-index -z --force --stdin
 339                fi
 340        else
 341                git apply -R < "$TMP-patch" ||
 342                die "$(gettext "Cannot remove worktree changes")"
 343
 344                if test "$keep_index" != "t"
 345                then
 346                        git reset -q -- "$@"
 347                fi
 348        fi
 349}
 350
 351save_stash () {
 352        push_options=
 353        while test $# != 0
 354        do
 355                case "$1" in
 356                --)
 357                        shift
 358                        break
 359                        ;;
 360                -*)
 361                        # pass all options through to push_stash
 362                        push_options="$push_options $1"
 363                        ;;
 364                *)
 365                        break
 366                        ;;
 367                esac
 368                shift
 369        done
 370
 371        stash_msg="$*"
 372
 373        if test -z "$stash_msg"
 374        then
 375                push_stash $push_options
 376        else
 377                push_stash $push_options -m "$stash_msg"
 378        fi
 379}
 380
 381have_stash () {
 382        git rev-parse --verify --quiet $ref_stash >/dev/null
 383}
 384
 385list_stash () {
 386        have_stash || return 0
 387        git log --format="%gd: %gs" -g --first-parent -m "$@" $ref_stash --
 388}
 389
 390show_stash () {
 391        ALLOW_UNKNOWN_FLAGS=t
 392        assert_stash_like "$@"
 393
 394        if test -z "$FLAGS"
 395        then
 396                if test "$(git config --bool stash.showStat || echo true)" = "true"
 397                then
 398                        FLAGS=--stat
 399                fi
 400
 401                if test "$(git config --bool stash.showPatch || echo false)" = "true"
 402                then
 403                        FLAGS=${FLAGS}${FLAGS:+ }-p
 404                fi
 405
 406                if test -z "$FLAGS"
 407                then
 408                        return 0
 409                fi
 410        fi
 411
 412        git diff ${FLAGS} $b_commit $w_commit
 413}
 414
 415show_help () {
 416        exec git help stash
 417        exit 1
 418}
 419
 420#
 421# Parses the remaining options looking for flags and
 422# at most one revision defaulting to ${ref_stash}@{0}
 423# if none found.
 424#
 425# Derives related tree and commit objects from the
 426# revision, if one is found.
 427#
 428# stash records the work tree, and is a merge between the
 429# base commit (first parent) and the index tree (second parent).
 430#
 431#   REV is set to the symbolic version of the specified stash-like commit
 432#   IS_STASH_LIKE is non-blank if ${REV} looks like a stash
 433#   IS_STASH_REF is non-blank if the ${REV} looks like a stash ref
 434#   s is set to the SHA1 of the stash commit
 435#   w_commit is set to the commit containing the working tree
 436#   b_commit is set to the base commit
 437#   i_commit is set to the commit containing the index tree
 438#   u_commit is set to the commit containing the untracked files tree
 439#   w_tree is set to the working tree
 440#   b_tree is set to the base tree
 441#   i_tree is set to the index tree
 442#   u_tree is set to the untracked files tree
 443#
 444#   GIT_QUIET is set to t if -q is specified
 445#   INDEX_OPTION is set to --index if --index is specified.
 446#   FLAGS is set to the remaining flags (if allowed)
 447#
 448# dies if:
 449#   * too many revisions specified
 450#   * no revision is specified and there is no stash stack
 451#   * a revision is specified which cannot be resolve to a SHA1
 452#   * a non-existent stash reference is specified
 453#   * unknown flags were set and ALLOW_UNKNOWN_FLAGS is not "t"
 454#
 455
 456parse_flags_and_rev()
 457{
 458        test "$PARSE_CACHE" = "$*" && return 0 # optimisation
 459        PARSE_CACHE="$*"
 460
 461        IS_STASH_LIKE=
 462        IS_STASH_REF=
 463        INDEX_OPTION=
 464        s=
 465        w_commit=
 466        b_commit=
 467        i_commit=
 468        u_commit=
 469        w_tree=
 470        b_tree=
 471        i_tree=
 472        u_tree=
 473
 474        FLAGS=
 475        REV=
 476        for opt
 477        do
 478                case "$opt" in
 479                        -q|--quiet)
 480                                GIT_QUIET=-t
 481                        ;;
 482                        --index)
 483                                INDEX_OPTION=--index
 484                        ;;
 485                        --help)
 486                                show_help
 487                        ;;
 488                        -*)
 489                                test "$ALLOW_UNKNOWN_FLAGS" = t ||
 490                                        die "$(eval_gettext "unknown option: \$opt")"
 491                                FLAGS="${FLAGS}${FLAGS:+ }$opt"
 492                        ;;
 493                        *)
 494                                REV="${REV}${REV:+ }'$opt'"
 495                        ;;
 496                esac
 497        done
 498
 499        eval set -- $REV
 500
 501        case $# in
 502                0)
 503                        have_stash || die "$(gettext "No stash entries found.")"
 504                        set -- ${ref_stash}@{0}
 505                ;;
 506                1)
 507                        :
 508                ;;
 509                *)
 510                        die "$(eval_gettext "Too many revisions specified: \$REV")"
 511                ;;
 512        esac
 513
 514        case "$1" in
 515                *[!0-9]*)
 516                        :
 517                ;;
 518                *)
 519                        set -- "${ref_stash}@{$1}"
 520                ;;
 521        esac
 522
 523        REV=$(git rev-parse --symbolic --verify --quiet "$1") || {
 524                reference="$1"
 525                die "$(eval_gettext "\$reference is not a valid reference")"
 526        }
 527
 528        i_commit=$(git rev-parse --verify --quiet "$REV^2") &&
 529        set -- $(git rev-parse "$REV" "$REV^1" "$REV:" "$REV^1:" "$REV^2:" 2>/dev/null) &&
 530        s=$1 &&
 531        w_commit=$1 &&
 532        b_commit=$2 &&
 533        w_tree=$3 &&
 534        b_tree=$4 &&
 535        i_tree=$5 &&
 536        IS_STASH_LIKE=t &&
 537        test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
 538        IS_STASH_REF=t
 539
 540        u_commit=$(git rev-parse --verify --quiet "$REV^3") &&
 541        u_tree=$(git rev-parse "$REV^3:" 2>/dev/null)
 542}
 543
 544is_stash_like()
 545{
 546        parse_flags_and_rev "$@"
 547        test -n "$IS_STASH_LIKE"
 548}
 549
 550assert_stash_like() {
 551        is_stash_like "$@" || {
 552                args="$*"
 553                die "$(eval_gettext "'\$args' is not a stash-like commit")"
 554        }
 555}
 556
 557is_stash_ref() {
 558        is_stash_like "$@" && test -n "$IS_STASH_REF"
 559}
 560
 561assert_stash_ref() {
 562        is_stash_ref "$@" || {
 563                args="$*"
 564                die "$(eval_gettext "'\$args' is not a stash reference")"
 565        }
 566}
 567
 568apply_stash () {
 569
 570        assert_stash_like "$@"
 571
 572        git update-index -q --refresh || die "$(gettext "unable to refresh index")"
 573
 574        # current index state
 575        c_tree=$(git write-tree) ||
 576                die "$(gettext "Cannot apply a stash in the middle of a merge")"
 577
 578        unstashed_index_tree=
 579        if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" &&
 580                        test "$c_tree" != "$i_tree"
 581        then
 582                git diff-tree --binary $s^2^..$s^2 | git apply --cached
 583                test $? -ne 0 &&
 584                        die "$(gettext "Conflicts in index. Try without --index.")"
 585                unstashed_index_tree=$(git write-tree) ||
 586                        die "$(gettext "Could not save index tree")"
 587                git reset
 588        fi
 589
 590        if test -n "$u_tree"
 591        then
 592                GIT_INDEX_FILE="$TMPindex" git read-tree "$u_tree" &&
 593                GIT_INDEX_FILE="$TMPindex" git checkout-index --all &&
 594                rm -f "$TMPindex" ||
 595                die "$(gettext "Could not restore untracked files from stash entry")"
 596        fi
 597
 598        eval "
 599                GITHEAD_$w_tree='Stashed changes' &&
 600                GITHEAD_$c_tree='Updated upstream' &&
 601                GITHEAD_$b_tree='Version stash was based on' &&
 602                export GITHEAD_$w_tree GITHEAD_$c_tree GITHEAD_$b_tree
 603        "
 604
 605        if test -n "$GIT_QUIET"
 606        then
 607                GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY
 608        fi
 609        if git merge-recursive $b_tree -- $c_tree $w_tree
 610        then
 611                # No conflict
 612                if test -n "$unstashed_index_tree"
 613                then
 614                        git read-tree "$unstashed_index_tree"
 615                else
 616                        a="$TMP-added" &&
 617                        git diff-index --cached --name-only --diff-filter=A $c_tree >"$a" &&
 618                        git read-tree --reset $c_tree &&
 619                        git update-index --add --stdin <"$a" ||
 620                                die "$(gettext "Cannot unstage modified files")"
 621                        rm -f "$a"
 622                fi
 623                squelch=
 624                if test -n "$GIT_QUIET"
 625                then
 626                        squelch='>/dev/null 2>&1'
 627                fi
 628                (cd "$START_DIR" && eval "git status $squelch") || :
 629        else
 630                # Merge conflict; keep the exit status from merge-recursive
 631                status=$?
 632                git rerere
 633                if test -n "$INDEX_OPTION"
 634                then
 635                        gettextln "Index was not unstashed." >&2
 636                fi
 637                exit $status
 638        fi
 639}
 640
 641pop_stash() {
 642        assert_stash_ref "$@"
 643
 644        if apply_stash "$@"
 645        then
 646                drop_stash "$@"
 647        else
 648                status=$?
 649                say "$(gettext "The stash entry is kept in case you need it again.")"
 650                exit $status
 651        fi
 652}
 653
 654drop_stash () {
 655        assert_stash_ref "$@"
 656
 657        git reflog delete --updateref --rewrite "${REV}" &&
 658                say "$(eval_gettext "Dropped \${REV} (\$s)")" ||
 659                die "$(eval_gettext "\${REV}: Could not drop stash entry")"
 660
 661        # clear_stash if we just dropped the last stash entry
 662        git rev-parse --verify --quiet "$ref_stash@{0}" >/dev/null ||
 663        clear_stash
 664}
 665
 666apply_to_branch () {
 667        test -n "$1" || die "$(gettext "No branch name specified")"
 668        branch=$1
 669        shift 1
 670
 671        set -- --index "$@"
 672        assert_stash_like "$@"
 673
 674        git checkout -b $branch $REV^ &&
 675        apply_stash "$@" && {
 676                test -z "$IS_STASH_REF" || drop_stash "$@"
 677        }
 678}
 679
 680test "$1" = "-p" && set "push" "$@"
 681
 682PARSE_CACHE='--not-parsed'
 683# The default command is "push" if nothing but options are given
 684seen_non_option=
 685for opt
 686do
 687        case "$opt" in
 688        --) break ;;
 689        -*) ;;
 690        *) seen_non_option=t; break ;;
 691        esac
 692done
 693
 694test -n "$seen_non_option" || set "push" "$@"
 695
 696# Main command set
 697case "$1" in
 698list)
 699        shift
 700        list_stash "$@"
 701        ;;
 702show)
 703        shift
 704        show_stash "$@"
 705        ;;
 706save)
 707        shift
 708        save_stash "$@"
 709        ;;
 710push)
 711        shift
 712        push_stash "$@"
 713        ;;
 714apply)
 715        shift
 716        apply_stash "$@"
 717        ;;
 718clear)
 719        shift
 720        clear_stash "$@"
 721        ;;
 722create)
 723        shift
 724        create_stash -m "$*" && echo "$w_commit"
 725        ;;
 726store)
 727        shift
 728        store_stash "$@"
 729        ;;
 730drop)
 731        shift
 732        drop_stash "$@"
 733        ;;
 734pop)
 735        shift
 736        pop_stash "$@"
 737        ;;
 738branch)
 739        shift
 740        apply_to_branch "$@"
 741        ;;
 742*)
 743        case $# in
 744        0)
 745                push_stash &&
 746                say "$(gettext "(To restore them type \"git stash apply\")")"
 747                ;;
 748        *)
 749                usage
 750        esac
 751        ;;
 752esac