git-stash.shon commit stash: convert store to builtin (41e0dd5)
   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
  58prepare_fallback_ident () {
  59        if ! git -c user.useconfigonly=yes var GIT_COMMITTER_IDENT >/dev/null 2>&1
  60        then
  61                GIT_AUTHOR_NAME="git stash"
  62                GIT_AUTHOR_EMAIL=git@stash
  63                GIT_COMMITTER_NAME="git stash"
  64                GIT_COMMITTER_EMAIL=git@stash
  65                export GIT_AUTHOR_NAME
  66                export GIT_AUTHOR_EMAIL
  67                export GIT_COMMITTER_NAME
  68                export GIT_COMMITTER_EMAIL
  69        fi
  70}
  71
  72clear_stash () {
  73        if test $# != 0
  74        then
  75                die "$(gettext "git stash clear with parameters is unimplemented")"
  76        fi
  77        if current=$(git rev-parse --verify --quiet $ref_stash)
  78        then
  79                git update-ref -d $ref_stash $current
  80        fi
  81}
  82
  83create_stash () {
  84
  85        prepare_fallback_ident
  86
  87        stash_msg=
  88        untracked=
  89        while test $# != 0
  90        do
  91                case "$1" in
  92                -m|--message)
  93                        shift
  94                        stash_msg=${1?"BUG: create_stash () -m requires an argument"}
  95                        ;;
  96                -m*)
  97                        stash_msg=${1#-m}
  98                        ;;
  99                --message=*)
 100                        stash_msg=${1#--message=}
 101                        ;;
 102                -u|--include-untracked)
 103                        shift
 104                        untracked=${1?"BUG: create_stash () -u requires an argument"}
 105                        ;;
 106                --)
 107                        shift
 108                        break
 109                        ;;
 110                esac
 111                shift
 112        done
 113
 114        git update-index -q --refresh
 115        if no_changes "$@"
 116        then
 117                exit 0
 118        fi
 119
 120        # state of the base commit
 121        if b_commit=$(git rev-parse --verify HEAD)
 122        then
 123                head=$(git rev-list --oneline -n 1 HEAD --)
 124        else
 125                die "$(gettext "You do not have the initial commit yet")"
 126        fi
 127
 128        if branch=$(git symbolic-ref -q HEAD)
 129        then
 130                branch=${branch#refs/heads/}
 131        else
 132                branch='(no branch)'
 133        fi
 134        msg=$(printf '%s: %s' "$branch" "$head")
 135
 136        # state of the index
 137        i_tree=$(git write-tree) &&
 138        i_commit=$(printf 'index on %s\n' "$msg" |
 139                git commit-tree $i_tree -p $b_commit) ||
 140                die "$(gettext "Cannot save the current index state")"
 141
 142        if test -n "$untracked"
 143        then
 144                # Untracked files are stored by themselves in a parentless commit, for
 145                # ease of unpacking later.
 146                u_commit=$(
 147                        untracked_files -z "$@" | (
 148                                GIT_INDEX_FILE="$TMPindex" &&
 149                                export GIT_INDEX_FILE &&
 150                                rm -f "$TMPindex" &&
 151                                git update-index -z --add --remove --stdin &&
 152                                u_tree=$(git write-tree) &&
 153                                printf 'untracked files on %s\n' "$msg" | git commit-tree $u_tree  &&
 154                                rm -f "$TMPindex"
 155                ) ) || die "$(gettext "Cannot save the untracked files")"
 156
 157                untracked_commit_option="-p $u_commit";
 158        else
 159                untracked_commit_option=
 160        fi
 161
 162        if test -z "$patch_mode"
 163        then
 164
 165                # state of the working tree
 166                w_tree=$( (
 167                        git read-tree --index-output="$TMPindex" -m $i_tree &&
 168                        GIT_INDEX_FILE="$TMPindex" &&
 169                        export GIT_INDEX_FILE &&
 170                        git diff-index --name-only -z HEAD -- "$@" >"$TMP-stagenames" &&
 171                        git update-index -z --add --remove --stdin <"$TMP-stagenames" &&
 172                        git write-tree &&
 173                        rm -f "$TMPindex"
 174                ) ) ||
 175                        die "$(gettext "Cannot save the current worktree state")"
 176
 177        else
 178
 179                rm -f "$TMP-index" &&
 180                GIT_INDEX_FILE="$TMP-index" git read-tree HEAD &&
 181
 182                # find out what the user wants
 183                GIT_INDEX_FILE="$TMP-index" \
 184                        git add--interactive --patch=stash -- "$@" &&
 185
 186                # state of the working tree
 187                w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
 188                die "$(gettext "Cannot save the current worktree state")"
 189
 190                git diff-tree -p HEAD $w_tree -- >"$TMP-patch" &&
 191                test -s "$TMP-patch" ||
 192                die "$(gettext "No changes selected")"
 193
 194                rm -f "$TMP-index" ||
 195                die "$(gettext "Cannot remove temporary index (can't happen)")"
 196
 197        fi
 198
 199        # create the stash
 200        if test -z "$stash_msg"
 201        then
 202                stash_msg=$(printf 'WIP on %s' "$msg")
 203        else
 204                stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
 205        fi
 206        w_commit=$(printf '%s\n' "$stash_msg" |
 207        git commit-tree $w_tree -p $b_commit -p $i_commit $untracked_commit_option) ||
 208        die "$(gettext "Cannot record working tree state")"
 209}
 210
 211push_stash () {
 212        keep_index=
 213        patch_mode=
 214        untracked=
 215        stash_msg=
 216        while test $# != 0
 217        do
 218                case "$1" in
 219                -k|--keep-index)
 220                        keep_index=t
 221                        ;;
 222                --no-keep-index)
 223                        keep_index=n
 224                        ;;
 225                -p|--patch)
 226                        patch_mode=t
 227                        # only default to keep if we don't already have an override
 228                        test -z "$keep_index" && keep_index=t
 229                        ;;
 230                -q|--quiet)
 231                        GIT_QUIET=t
 232                        ;;
 233                -u|--include-untracked)
 234                        untracked=untracked
 235                        ;;
 236                -a|--all)
 237                        untracked=all
 238                        ;;
 239                -m|--message)
 240                        shift
 241                        test -z ${1+x} && usage
 242                        stash_msg=$1
 243                        ;;
 244                -m*)
 245                        stash_msg=${1#-m}
 246                        ;;
 247                --message=*)
 248                        stash_msg=${1#--message=}
 249                        ;;
 250                --help)
 251                        show_help
 252                        ;;
 253                --)
 254                        shift
 255                        break
 256                        ;;
 257                -*)
 258                        option="$1"
 259                        eval_gettextln "error: unknown option for 'stash push': \$option"
 260                        usage
 261                        ;;
 262                *)
 263                        break
 264                        ;;
 265                esac
 266                shift
 267        done
 268
 269        eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")"
 270
 271        if test -n "$patch_mode" && test -n "$untracked"
 272        then
 273                die "$(gettext "Can't use --patch and --include-untracked or --all at the same time")"
 274        fi
 275
 276        test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1
 277
 278        git update-index -q --refresh
 279        if no_changes "$@"
 280        then
 281                say "$(gettext "No local changes to save")"
 282                exit 0
 283        fi
 284
 285        git reflog exists $ref_stash ||
 286                clear_stash || die "$(gettext "Cannot initialize stash")"
 287
 288        create_stash -m "$stash_msg" -u "$untracked" -- "$@"
 289        git stash--helper store -m "$stash_msg" -q $w_commit ||
 290        die "$(gettext "Cannot save the current status")"
 291        say "$(eval_gettext "Saved working directory and index state \$stash_msg")"
 292
 293        if test -z "$patch_mode"
 294        then
 295                test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
 296                if test -n "$untracked" && test $# = 0
 297                then
 298                        git clean --force --quiet -d $CLEAN_X_OPTION
 299                fi
 300
 301                if test $# != 0
 302                then
 303                        test -z "$untracked" && UPDATE_OPTION="-u" || UPDATE_OPTION=
 304                        test "$untracked" = "all" && FORCE_OPTION="--force" || FORCE_OPTION=
 305                        git add $UPDATE_OPTION $FORCE_OPTION -- "$@"
 306                        git diff-index -p --cached --binary HEAD -- "$@" |
 307                        git apply --index -R
 308                else
 309                        git reset --hard -q
 310                fi
 311
 312                if test "$keep_index" = "t" && test -n "$i_tree"
 313                then
 314                        git read-tree --reset $i_tree
 315                        git ls-files -z --modified -- "$@" |
 316                        git checkout-index -z --force --stdin
 317                fi
 318        else
 319                git apply -R < "$TMP-patch" ||
 320                die "$(gettext "Cannot remove worktree changes")"
 321
 322                if test "$keep_index" != "t"
 323                then
 324                        git reset -q -- "$@"
 325                fi
 326        fi
 327}
 328
 329save_stash () {
 330        push_options=
 331        while test $# != 0
 332        do
 333                case "$1" in
 334                --)
 335                        shift
 336                        break
 337                        ;;
 338                -*)
 339                        # pass all options through to push_stash
 340                        push_options="$push_options $1"
 341                        ;;
 342                *)
 343                        break
 344                        ;;
 345                esac
 346                shift
 347        done
 348
 349        stash_msg="$*"
 350
 351        if test -z "$stash_msg"
 352        then
 353                push_stash $push_options
 354        else
 355                push_stash $push_options -m "$stash_msg"
 356        fi
 357}
 358
 359show_help () {
 360        exec git help stash
 361        exit 1
 362}
 363
 364#
 365# Parses the remaining options looking for flags and
 366# at most one revision defaulting to ${ref_stash}@{0}
 367# if none found.
 368#
 369# Derives related tree and commit objects from the
 370# revision, if one is found.
 371#
 372# stash records the work tree, and is a merge between the
 373# base commit (first parent) and the index tree (second parent).
 374#
 375#   REV is set to the symbolic version of the specified stash-like commit
 376#   IS_STASH_LIKE is non-blank if ${REV} looks like a stash
 377#   IS_STASH_REF is non-blank if the ${REV} looks like a stash ref
 378#   s is set to the SHA1 of the stash commit
 379#   w_commit is set to the commit containing the working tree
 380#   b_commit is set to the base commit
 381#   i_commit is set to the commit containing the index tree
 382#   u_commit is set to the commit containing the untracked files tree
 383#   w_tree is set to the working tree
 384#   b_tree is set to the base tree
 385#   i_tree is set to the index tree
 386#   u_tree is set to the untracked files tree
 387#
 388#   GIT_QUIET is set to t if -q is specified
 389#   INDEX_OPTION is set to --index if --index is specified.
 390#   FLAGS is set to the remaining flags (if allowed)
 391#
 392# dies if:
 393#   * too many revisions specified
 394#   * no revision is specified and there is no stash stack
 395#   * a revision is specified which cannot be resolve to a SHA1
 396#   * a non-existent stash reference is specified
 397#   * unknown flags were set and ALLOW_UNKNOWN_FLAGS is not "t"
 398#
 399
 400test "$1" = "-p" && set "push" "$@"
 401
 402PARSE_CACHE='--not-parsed'
 403# The default command is "push" if nothing but options are given
 404seen_non_option=
 405for opt
 406do
 407        case "$opt" in
 408        --) break ;;
 409        -*) ;;
 410        *) seen_non_option=t; break ;;
 411        esac
 412done
 413
 414test -n "$seen_non_option" || set "push" "$@"
 415
 416# Main command set
 417case "$1" in
 418list)
 419        shift
 420        git stash--helper list "$@"
 421        ;;
 422show)
 423        shift
 424        git stash--helper show "$@"
 425        ;;
 426save)
 427        shift
 428        save_stash "$@"
 429        ;;
 430push)
 431        shift
 432        push_stash "$@"
 433        ;;
 434apply)
 435        shift
 436        cd "$START_DIR"
 437        git stash--helper apply "$@"
 438        ;;
 439clear)
 440        shift
 441        git stash--helper clear "$@"
 442        ;;
 443create)
 444        shift
 445        create_stash -m "$*" && echo "$w_commit"
 446        ;;
 447store)
 448        shift
 449        git stash--helper store "$@"
 450        ;;
 451drop)
 452        shift
 453        git stash--helper drop "$@"
 454        ;;
 455pop)
 456        shift
 457        cd "$START_DIR"
 458        git stash--helper pop "$@"
 459        ;;
 460branch)
 461        shift
 462        cd "$START_DIR"
 463        git stash--helper branch "$@"
 464        ;;
 465*)
 466        case $# in
 467        0)
 468                push_stash &&
 469                say "$(gettext "(To restore them type \"git stash apply\")")"
 470                ;;
 471        *)
 472                usage
 473        esac
 474        ;;
 475esac