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