1#!/bin/sh
2# Copyright (c) 2007, Nanako Shiraishi
34
USAGE='[ | save | list | show | apply | clear | drop | pop | create ]'
56
SUBDIRECTORY_OK=Yes
7OPTIONS_SPEC=
8. git-sh-setup
9require_work_tree
10cd_to_toplevel
1112
TMP="$GIT_DIR/.git-stash.$$"
13trap 'rm -f "$TMP-*"' 0
1415
ref_stash=refs/stash
1617
no_changes () {
18git diff-index --quiet --cached HEAD --ignore-submodules -- &&
19git diff-files --quiet --ignore-submodules
20}
2122
clear_stash () {
23if test $# != 0
24then
25die "git stash clear with parameters is unimplemented"
26fi
27if current=$(git rev-parse --verify $ref_stash 2>/dev/null)
28then
29git update-ref -d $ref_stash $current
30fi
31}
3233
create_stash () {
34stash_msg="$1"
3536
if no_changes
37then
38exit 0
39fi
4041
# state of the base commit
42if b_commit=$(git rev-parse --verify HEAD)
43then
44head=$(git log --no-color --abbrev-commit --pretty=oneline -n 1 HEAD --)
45else
46die "You do not have the initial commit yet"
47fi
4849
if branch=$(git symbolic-ref -q HEAD)
50then
51branch=${branch#refs/heads/}
52else
53branch='(no branch)'
54fi
55msg=$(printf '%s: %s' "$branch" "$head")
5657
# state of the index
58i_tree=$(git write-tree) &&
59i_commit=$(printf 'index on %s\n' "$msg" |
60git commit-tree $i_tree -p $b_commit) ||
61die "Cannot save the current index state"
6263
# state of the working tree
64w_tree=$( (
65rm -f "$TMP-index" &&
66cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" &&
67GIT_INDEX_FILE="$TMP-index" &&
68export GIT_INDEX_FILE &&
69git read-tree -m $i_tree &&
70git add -u &&
71git write-tree &&
72rm -f "$TMP-index"
73) ) ||
74die "Cannot save the current worktree state"
7576
# create the stash
77if test -z "$stash_msg"
78then
79stash_msg=$(printf 'WIP on %s' "$msg")
80else
81stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
82fi
83w_commit=$(printf '%s\n' "$stash_msg" |
84git commit-tree $w_tree -p $b_commit -p $i_commit) ||
85die "Cannot record working tree state"
86}
8788
save_stash () {
89stash_msg="$1"
9091
if no_changes
92then
93echo 'No local changes to save'
94exit 0
95fi
96test -f "$GIT_DIR/logs/$ref_stash" ||
97clear_stash || die "Cannot initialize stash"
9899
create_stash "$stash_msg"
100101
# Make sure the reflog for stash is kept.
102: >>"$GIT_DIR/logs/$ref_stash"
103104
git update-ref -m "$stash_msg" $ref_stash $w_commit ||
105die "Cannot save the current status"
106printf 'Saved working directory and index state "%s"\n' "$stash_msg"
107}
108109
have_stash () {
110git rev-parse --verify $ref_stash >/dev/null 2>&1
111}
112113
list_stash () {
114have_stash || return 0
115git log --no-color --pretty=oneline -g "$@" $ref_stash -- |
116sed -n -e 's/^[.0-9a-f]* refs\///p'
117}
118119
show_stash () {
120flags=$(git rev-parse --no-revs --flags "$@")
121if test -z "$flags"
122then
123flags=--stat
124fi
125s=$(git rev-parse --revs-only --no-flags --default $ref_stash "$@")
126127
w_commit=$(git rev-parse --verify "$s") &&
128b_commit=$(git rev-parse --verify "$s^") &&
129git diff $flags $b_commit $w_commit
130}
131132
apply_stash () {
133git diff-files --quiet --ignore-submodules ||
134die 'Cannot restore on top of a dirty state'
135136
unstash_index=
137case "$1" in
138--index)
139unstash_index=t
140shift
141esac
142143
# current index state
144c_tree=$(git write-tree) ||
145die 'Cannot apply a stash in the middle of a merge'
146147
# stash records the work tree, and is a merge between the
148# base commit (first parent) and the index tree (second parent).
149s=$(git rev-parse --revs-only --no-flags --default $ref_stash "$@") &&
150w_tree=$(git rev-parse --verify "$s:") &&
151b_tree=$(git rev-parse --verify "$s^1:") &&
152i_tree=$(git rev-parse --verify "$s^2:") ||
153die "$*: no valid stashed state found"
154155
unstashed_index_tree=
156if test -n "$unstash_index" && test "$b_tree" != "$i_tree"
157then
158git diff-tree --binary $s^2^..$s^2 | git apply --cached
159test $? -ne 0 &&
160die 'Conflicts in index. Try without --index.'
161unstashed_index_tree=$(git-write-tree) ||
162die 'Could not save index tree'
163git reset
164fi
165166
eval "
167GITHEAD_$w_tree='Stashed changes' &&
168GITHEAD_$c_tree='Updated upstream' &&
169GITHEAD_$b_tree='Version stash was based on' &&
170export GITHEAD_$w_tree GITHEAD_$c_tree GITHEAD_$b_tree
171"
172173
if git-merge-recursive $b_tree -- $c_tree $w_tree
174then
175# No conflict
176if test -n "$unstashed_index_tree"
177then
178git read-tree "$unstashed_index_tree"
179else
180a="$TMP-added" &&
181git diff-index --cached --name-only --diff-filter=A $c_tree >"$a" &&
182git read-tree --reset $c_tree &&
183git update-index --add --stdin <"$a" ||
184die "Cannot unstage modified files"
185rm -f "$a"
186fi
187git status || :
188else
189# Merge conflict; keep the exit status from merge-recursive
190status=$?
191if test -n "$unstash_index"
192then
193echo >&2 'Index was not unstashed.'
194fi
195exit $status
196fi
197}
198199
drop_stash () {
200have_stash || die 'No stash entries to drop'
201202
if test $# = 0
203then
204set x "$ref_stash@{0}"
205shift
206fi
207# Verify supplied argument looks like a stash entry
208s=$(git rev-parse --revs-only --no-flags "$@") &&
209git rev-parse --verify "$s:" > /dev/null 2>&1 &&
210git rev-parse --verify "$s^1:" > /dev/null 2>&1 &&
211git rev-parse --verify "$s^2:" > /dev/null 2>&1 ||
212die "$*: not a valid stashed state"
213214
git reflog delete --updateref --rewrite "$@" &&
215echo "Dropped $* ($s)" || die "$*: Could not drop stash entry"
216217
# clear_stash if we just dropped the last stash entry
218git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash
219}
220221
# Main command set
222case "$1" in
223list)
224shift
225if test $# = 0
226then
227set x -n 10
228shift
229fi
230list_stash "$@"
231;;
232show)
233shift
234show_stash "$@"
235;;
236save)
237shift
238save_stash "$*" && git-reset --hard
239;;
240apply)
241shift
242apply_stash "$@"
243;;
244clear)
245shift
246clear_stash "$@"
247;;
248create)
249if test $# -gt 0 && test "$1" = create
250then
251shift
252fi
253create_stash "$*" && echo "$w_commit"
254;;
255drop)
256shift
257drop_stash "$@"
258;;
259pop)
260shift
261if apply_stash "$@"
262then
263test -z "$unstash_index" || shift
264drop_stash "$@"
265fi
266;;
267*)
268if test $# -eq 0
269then
270save_stash &&
271echo '(To restore them type "git stash apply")' &&
272git-reset --hard
273else
274usage
275fi
276;;
277esac