Merge branch 'jc/diff-index-refactor'
[gitweb.git] / git-stash.sh
index 57892152c670423531f2d54399c85a661023b60f..f4e6f05ee212f452a1b3e13c492960a9222029f7 100755 (executable)
@@ -7,7 +7,8 @@ USAGE="list [<options>]
    or: $dashless drop [-q|--quiet] [<stash>]
    or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
    or: $dashless branch <branchname> [<stash>]
-   or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
+   or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
+                      [-u|--include-untracked] [-a|--all] [<message>]]
    or: $dashless clear"
 
 SUBDIRECTORY_OK=Yes
@@ -34,7 +35,14 @@ fi
 
 no_changes () {
        git diff-index --quiet --cached HEAD --ignore-submodules -- &&
-       git diff-files --quiet --ignore-submodules
+       git diff-files --quiet --ignore-submodules &&
+       (test -z "$untracked" || test -z "$(untracked_files)")
+}
+
+untracked_files () {
+       excl_opt=--exclude-standard
+       test "$untracked" = "all" && excl_opt=
+       git ls-files -o -z $excl_opt
 }
 
 clear_stash () {
@@ -50,6 +58,7 @@ clear_stash () {
 
 create_stash () {
        stash_msg="$1"
+       untracked="$2"
 
        git update-index -q --refresh
        if no_changes
@@ -79,6 +88,25 @@ create_stash () {
                git commit-tree $i_tree -p $b_commit) ||
                die "$(gettext "Cannot save the current index state")"
 
+       if test -n "$untracked"
+       then
+               # Untracked files are stored by themselves in a parentless commit, for
+               # ease of unpacking later.
+               u_commit=$(
+                       untracked_files | (
+                               export GIT_INDEX_FILE="$TMPindex"
+                               rm -f "$TMPindex" &&
+                               git update-index -z --add --remove --stdin &&
+                               u_tree=$(git write-tree) &&
+                               printf 'untracked files on %s\n' "$msg" | git commit-tree $u_tree  &&
+                               rm -f "$TMPindex"
+               ) ) || die "Cannot save the untracked files"
+
+               untracked_commit_option="-p $u_commit";
+       else
+               untracked_commit_option=
+       fi
+
        if test -z "$patch_mode"
        then
 
@@ -123,13 +151,14 @@ create_stash () {
                stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
        fi
        w_commit=$(printf '%s\n' "$stash_msg" |
-               git commit-tree $w_tree -p $b_commit -p $i_commit) ||
-               die "$(gettext "Cannot record working tree state")"
+       git commit-tree $w_tree -p $b_commit -p $i_commit $untracked_commit_option) ||
+       die "$(gettext "Cannot record working tree state")"
 }
 
 save_stash () {
        keep_index=
        patch_mode=
+       untracked=
        while test $# != 0
        do
                case "$1" in
@@ -147,13 +176,30 @@ save_stash () {
                -q|--quiet)
                        GIT_QUIET=t
                        ;;
+               -u|--include-untracked)
+                       untracked=untracked
+                       ;;
+               -a|--all)
+                       untracked=all
+                       ;;
                --)
                        shift
                        break
                        ;;
                -*)
-                       echo "error: unknown option for 'stash save': $1"
-                       echo "       To provide a message, use git stash save -- '$1'"
+                       option="$1"
+                       # TRANSLATORS: $option is an invalid option, like
+                       # `--blah-blah'. The 7 spaces at the beginning of the
+                       # second line correspond to "error: ". So you should line
+                       # up the second line with however many characters the
+                       # translation of "error: " takes in your language. E.g. in
+                       # English this is:
+                       #
+                       #    $ git stash save --blah-blah 2>&1 | head -n 2
+                       #    error: unknown option for 'stash save': --blah-blah
+                       #           To provide a message, use git stash save -- '--blah-blah'
+                       eval_gettext "$("error: unknown option for 'stash save': \$option
+       To provide a message, use git stash save -- '\$option'")"; echo
                        usage
                        ;;
                *)
@@ -163,6 +209,11 @@ save_stash () {
                shift
        done
 
+       if test -n "$patch_mode" && test -n "$untracked"
+       then
+           die "Can't use --patch and ---include-untracked or --all at the same time"
+       fi
+
        stash_msg="$*"
 
        git update-index -q --refresh
@@ -174,7 +225,7 @@ save_stash () {
        test -f "$GIT_DIR/logs/$ref_stash" ||
                clear_stash || die "$(gettext "Cannot initialize stash")"
 
-       create_stash "$stash_msg"
+       create_stash "$stash_msg" $untracked
 
        # Make sure the reflog for stash is kept.
        : >>"$GIT_DIR/logs/$ref_stash"
@@ -186,6 +237,11 @@ save_stash () {
        if test -z "$patch_mode"
        then
                git reset --hard ${GIT_QUIET:+-q}
+               test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
+               if test -n "$untracked"
+               then
+                       git clean --force --quiet $CLEAN_X_OPTION
+               fi
 
                if test "$keep_index" = "t" && test -n $i_tree
                then
@@ -235,9 +291,11 @@ show_stash () {
 #   w_commit is set to the commit containing the working tree
 #   b_commit is set to the base commit
 #   i_commit is set to the commit containing the index tree
+#   u_commit is set to the commit containing the untracked files tree
 #   w_tree is set to the working tree
 #   b_tree is set to the base tree
 #   i_tree is set to the index tree
+#   u_tree is set to the untracked files tree
 #
 #   GIT_QUIET is set to t if -q is specified
 #   INDEX_OPTION is set to --index if --index is specified.
@@ -262,9 +320,11 @@ parse_flags_and_rev()
        w_commit=
        b_commit=
        i_commit=
+       u_commit=
        w_tree=
        b_tree=
        i_tree=
+       u_tree=
 
        REV=$(git rev-parse --no-flags --symbolic "$@") || exit 1
 
@@ -295,11 +355,14 @@ parse_flags_and_rev()
                        :
                ;;
                *)
-                       die "Too many revisions specified: $REV"
+                       die "$(eval_gettext "Too many revisions specified: \$REV")"
                ;;
        esac
 
-       REV=$(git rev-parse --quiet --symbolic --verify $1 2>/dev/null) || die "$1 is not valid reference"
+       REV=$(git rev-parse --quiet --symbolic --verify $1 2>/dev/null) || {
+               reference="$1"
+               die "$(eval_gettext "\$reference is not valid reference")"
+       }
 
        i_commit=$(git rev-parse --quiet --verify $REV^2 2>/dev/null) &&
        set -- $(git rev-parse $REV $REV^1 $REV: $REV^1: $REV^2: 2>/dev/null) &&
@@ -312,6 +375,9 @@ parse_flags_and_rev()
        IS_STASH_LIKE=t &&
        test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
        IS_STASH_REF=t
+
+       u_commit=$(git rev-parse --quiet --verify $REV^3 2>/dev/null) &&
+       u_tree=$(git rev-parse $REV^3: 2>/dev/null)
 }
 
 is_stash_like()
@@ -321,7 +387,10 @@ is_stash_like()
 }
 
 assert_stash_like() {
-       is_stash_like "$@" || die "'$*' is not a stash-like commit"
+       is_stash_like "$@" || {
+               args="$*"
+               die "$(eval_gettext "'\$args' is not a stash-like commit")"
+       }
 }
 
 is_stash_ref() {
@@ -329,7 +398,10 @@ is_stash_ref() {
 }
 
 assert_stash_ref() {
-       is_stash_ref "$@" || die "'$*' is not a stash reference"
+       is_stash_ref "$@" || {
+               args="$*"
+               die "$(eval_gettext "'\$args' is not a stash reference")"
+       }
 }
 
 apply_stash () {
@@ -354,6 +426,14 @@ apply_stash () {
                git reset
        fi
 
+       if test -n "$u_tree"
+       then
+               GIT_INDEX_FILE="$TMPindex" git-read-tree "$u_tree" &&
+               GIT_INDEX_FILE="$TMPindex" git checkout-index --all &&
+               rm -f "$TMPindex" ||
+               die 'Could not restore untracked files from stash'
+       fi
+
        eval "
                GITHEAD_$w_tree='Stashed changes' &&
                GITHEAD_$c_tree='Updated upstream' &&
@@ -410,7 +490,8 @@ drop_stash () {
        assert_stash_ref "$@"
 
        git reflog delete --updateref --rewrite "${REV}" &&
-               say "Dropped ${REV} ($s)" || die "${REV}: Could not drop stash entry"
+               say "$(eval_gettext "Dropped \${REV} (\$s)")" ||
+               die "$(eval_gettext "\${REV}: Could not drop stash entry")"
 
        # clear_stash if we just dropped the last stash entry
        git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash