stash: convert show to builtin
[git] / git-stash.sh
1 #!/bin/sh
2 # Copyright (c) 2007, Nanako Shiraishi
3
4 dashless=$(basename "$0" | sed -e 's/-/ /')
5 USAGE="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
17 SUBDIRECTORY_OK=Yes
18 OPTIONS_SPEC=
19 START_DIR=$(pwd)
20 . git-sh-setup
21 require_work_tree
22 prefix=$(git rev-parse --show-prefix) || exit 1
23 cd_to_toplevel
24
25 TMP="$GIT_DIR/.git-stash.$$"
26 TMPindex=${GIT_INDEX_FILE-"$(git rev-parse --git-path index)"}.stash.$$
27 trap 'rm -f "$TMP-"* "$TMPindex"' 0
28
29 ref_stash=refs/stash
30
31 if 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)"
34 else
35        help_color=
36        reset_color=
37 fi
38
39 no_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
45 untracked_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
58 prepare_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
72 clear_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
83 create_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
211 store_stash () {
212         while test $# != 0
213         do
214                 case "$1" in
215                 -m|--message)
216                         shift
217                         stash_msg="$1"
218                         ;;
219                 -m*)
220                         stash_msg=${1#-m}
221                         ;;
222                 --message=*)
223                         stash_msg=${1#--message=}
224                         ;;
225                 -q|--quiet)
226                         quiet=t
227                         ;;
228                 *)
229                         break
230                         ;;
231                 esac
232                 shift
233         done
234         test $# = 1 ||
235         die "$(eval_gettext "\"$dashless store\" requires one <commit> argument")"
236
237         w_commit="$1"
238         if test -z "$stash_msg"
239         then
240                 stash_msg="Created via \"git stash store\"."
241         fi
242
243         git update-ref --create-reflog -m "$stash_msg" $ref_stash $w_commit
244         ret=$?
245         test $ret != 0 && test -z "$quiet" &&
246         die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")"
247         return $ret
248 }
249
250 push_stash () {
251         keep_index=
252         patch_mode=
253         untracked=
254         stash_msg=
255         while test $# != 0
256         do
257                 case "$1" in
258                 -k|--keep-index)
259                         keep_index=t
260                         ;;
261                 --no-keep-index)
262                         keep_index=n
263                         ;;
264                 -p|--patch)
265                         patch_mode=t
266                         # only default to keep if we don't already have an override
267                         test -z "$keep_index" && keep_index=t
268                         ;;
269                 -q|--quiet)
270                         GIT_QUIET=t
271                         ;;
272                 -u|--include-untracked)
273                         untracked=untracked
274                         ;;
275                 -a|--all)
276                         untracked=all
277                         ;;
278                 -m|--message)
279                         shift
280                         test -z ${1+x} && usage
281                         stash_msg=$1
282                         ;;
283                 -m*)
284                         stash_msg=${1#-m}
285                         ;;
286                 --message=*)
287                         stash_msg=${1#--message=}
288                         ;;
289                 --help)
290                         show_help
291                         ;;
292                 --)
293                         shift
294                         break
295                         ;;
296                 -*)
297                         option="$1"
298                         eval_gettextln "error: unknown option for 'stash push': \$option"
299                         usage
300                         ;;
301                 *)
302                         break
303                         ;;
304                 esac
305                 shift
306         done
307
308         eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")"
309
310         if test -n "$patch_mode" && test -n "$untracked"
311         then
312                 die "$(gettext "Can't use --patch and --include-untracked or --all at the same time")"
313         fi
314
315         test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1
316
317         git update-index -q --refresh
318         if no_changes "$@"
319         then
320                 say "$(gettext "No local changes to save")"
321                 exit 0
322         fi
323
324         git reflog exists $ref_stash ||
325                 clear_stash || die "$(gettext "Cannot initialize stash")"
326
327         create_stash -m "$stash_msg" -u "$untracked" -- "$@"
328         store_stash -m "$stash_msg" -q $w_commit ||
329         die "$(gettext "Cannot save the current status")"
330         say "$(eval_gettext "Saved working directory and index state \$stash_msg")"
331
332         if test -z "$patch_mode"
333         then
334                 test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
335                 if test -n "$untracked" && test $# = 0
336                 then
337                         git clean --force --quiet -d $CLEAN_X_OPTION
338                 fi
339
340                 if test $# != 0
341                 then
342                         test -z "$untracked" && UPDATE_OPTION="-u" || UPDATE_OPTION=
343                         test "$untracked" = "all" && FORCE_OPTION="--force" || FORCE_OPTION=
344                         git add $UPDATE_OPTION $FORCE_OPTION -- "$@"
345                         git diff-index -p --cached --binary HEAD -- "$@" |
346                         git apply --index -R
347                 else
348                         git reset --hard -q
349                 fi
350
351                 if test "$keep_index" = "t" && test -n "$i_tree"
352                 then
353                         git read-tree --reset $i_tree
354                         git ls-files -z --modified -- "$@" |
355                         git checkout-index -z --force --stdin
356                 fi
357         else
358                 git apply -R < "$TMP-patch" ||
359                 die "$(gettext "Cannot remove worktree changes")"
360
361                 if test "$keep_index" != "t"
362                 then
363                         git reset -q -- "$@"
364                 fi
365         fi
366 }
367
368 save_stash () {
369         push_options=
370         while test $# != 0
371         do
372                 case "$1" in
373                 --)
374                         shift
375                         break
376                         ;;
377                 -*)
378                         # pass all options through to push_stash
379                         push_options="$push_options $1"
380                         ;;
381                 *)
382                         break
383                         ;;
384                 esac
385                 shift
386         done
387
388         stash_msg="$*"
389
390         if test -z "$stash_msg"
391         then
392                 push_stash $push_options
393         else
394                 push_stash $push_options -m "$stash_msg"
395         fi
396 }
397
398 show_help () {
399         exec git help stash
400         exit 1
401 }
402
403 #
404 # Parses the remaining options looking for flags and
405 # at most one revision defaulting to ${ref_stash}@{0}
406 # if none found.
407 #
408 # Derives related tree and commit objects from the
409 # revision, if one is found.
410 #
411 # stash records the work tree, and is a merge between the
412 # base commit (first parent) and the index tree (second parent).
413 #
414 #   REV is set to the symbolic version of the specified stash-like commit
415 #   IS_STASH_LIKE is non-blank if ${REV} looks like a stash
416 #   IS_STASH_REF is non-blank if the ${REV} looks like a stash ref
417 #   s is set to the SHA1 of the stash commit
418 #   w_commit is set to the commit containing the working tree
419 #   b_commit is set to the base commit
420 #   i_commit is set to the commit containing the index tree
421 #   u_commit is set to the commit containing the untracked files tree
422 #   w_tree is set to the working tree
423 #   b_tree is set to the base tree
424 #   i_tree is set to the index tree
425 #   u_tree is set to the untracked files tree
426 #
427 #   GIT_QUIET is set to t if -q is specified
428 #   INDEX_OPTION is set to --index if --index is specified.
429 #   FLAGS is set to the remaining flags (if allowed)
430 #
431 # dies if:
432 #   * too many revisions specified
433 #   * no revision is specified and there is no stash stack
434 #   * a revision is specified which cannot be resolve to a SHA1
435 #   * a non-existent stash reference is specified
436 #   * unknown flags were set and ALLOW_UNKNOWN_FLAGS is not "t"
437 #
438
439 test "$1" = "-p" && set "push" "$@"
440
441 PARSE_CACHE='--not-parsed'
442 # The default command is "push" if nothing but options are given
443 seen_non_option=
444 for opt
445 do
446         case "$opt" in
447         --) break ;;
448         -*) ;;
449         *) seen_non_option=t; break ;;
450         esac
451 done
452
453 test -n "$seen_non_option" || set "push" "$@"
454
455 # Main command set
456 case "$1" in
457 list)
458         shift
459         git stash--helper list "$@"
460         ;;
461 show)
462         shift
463         git stash--helper show "$@"
464         ;;
465 save)
466         shift
467         save_stash "$@"
468         ;;
469 push)
470         shift
471         push_stash "$@"
472         ;;
473 apply)
474         shift
475         cd "$START_DIR"
476         git stash--helper apply "$@"
477         ;;
478 clear)
479         shift
480         git stash--helper clear "$@"
481         ;;
482 create)
483         shift
484         create_stash -m "$*" && echo "$w_commit"
485         ;;
486 store)
487         shift
488         store_stash "$@"
489         ;;
490 drop)
491         shift
492         git stash--helper drop "$@"
493         ;;
494 pop)
495         shift
496         cd "$START_DIR"
497         git stash--helper pop "$@"
498         ;;
499 branch)
500         shift
501         cd "$START_DIR"
502         git stash--helper branch "$@"
503         ;;
504 *)
505         case $# in
506         0)
507                 push_stash &&
508                 say "$(gettext "(To restore them type \"git stash apply\")")"
509                 ;;
510         *)
511                 usage
512         esac
513         ;;
514 esac