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