]> Pileus Git - ~andy/git/blob - git-stash.sh
Implement "git stash branch <newbranch> <stash>"
[~andy/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 --ignore-submodules -- &&
19         git diff-files --quiet --ignore-submodules
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 --ignore-submodules ||
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 apply_to_branch () {
222         have_stash || die 'Nothing to apply'
223
224         test -n "$1" || die 'No branch name specified'
225         branch=$1
226
227         if test -z "$2"
228         then
229                 set x "$ref_stash@{0}"
230         fi
231         stash=$2
232
233         git-checkout -b $branch $stash^ &&
234         apply_stash --index $stash &&
235         drop_stash $stash
236 }
237
238 # Main command set
239 case "$1" in
240 list)
241         shift
242         if test $# = 0
243         then
244                 set x -n 10
245                 shift
246         fi
247         list_stash "$@"
248         ;;
249 show)
250         shift
251         show_stash "$@"
252         ;;
253 save)
254         shift
255         save_stash "$*" && git-reset --hard
256         ;;
257 apply)
258         shift
259         apply_stash "$@"
260         ;;
261 clear)
262         shift
263         clear_stash "$@"
264         ;;
265 create)
266         if test $# -gt 0 && test "$1" = create
267         then
268                 shift
269         fi
270         create_stash "$*" && echo "$w_commit"
271         ;;
272 drop)
273         shift
274         drop_stash "$@"
275         ;;
276 pop)
277         shift
278         if apply_stash "$@"
279         then
280                 test -z "$unstash_index" || shift
281                 drop_stash "$@"
282         fi
283         ;;
284 branch)
285         shift
286         apply_to_branch "$@"
287         ;;
288 *)
289         if test $# -eq 0
290         then
291                 save_stash &&
292                 echo '(To restore them type "git stash apply")' &&
293                 git-reset --hard
294         else
295                 usage
296         fi
297         ;;
298 esac