Merge branch 'ns/stash'
* ns/stash: Documentation: quote {non-attributes} for asciidoc git-stash: don't complain when listing in a repo with no stash git-stash: fix "can't shift that many" with no arguments git-stash: fix "no arguments" case in documentation git-stash: require "save" to be explicit and update documentation Document git-stash Add git-stash script
This commit is contained in:
commit
7425dcc95e
1
.gitignore
vendored
1
.gitignore
vendored
@ -124,6 +124,7 @@ git-ssh-fetch
|
|||||||
git-ssh-pull
|
git-ssh-pull
|
||||||
git-ssh-push
|
git-ssh-push
|
||||||
git-ssh-upload
|
git-ssh-upload
|
||||||
|
git-stash
|
||||||
git-status
|
git-status
|
||||||
git-stripspace
|
git-stripspace
|
||||||
git-submodule
|
git-submodule
|
||||||
|
@ -178,6 +178,7 @@ git-show-ref plumbinginterrogators
|
|||||||
git-sh-setup purehelpers
|
git-sh-setup purehelpers
|
||||||
git-ssh-fetch synchingrepositories
|
git-ssh-fetch synchingrepositories
|
||||||
git-ssh-upload synchingrepositories
|
git-ssh-upload synchingrepositories
|
||||||
|
git-stash mainporcelain
|
||||||
git-status mainporcelain
|
git-status mainporcelain
|
||||||
git-stripspace purehelpers
|
git-stripspace purehelpers
|
||||||
git-submodule mainporcelain
|
git-submodule mainporcelain
|
||||||
|
161
Documentation/git-stash.txt
Normal file
161
Documentation/git-stash.txt
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
git-stash(1)
|
||||||
|
============
|
||||||
|
|
||||||
|
NAME
|
||||||
|
----
|
||||||
|
git-stash - Stash the changes in a dirty working directory away
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
--------
|
||||||
|
[verse]
|
||||||
|
'git-stash' (save | list | show [<stash>] | apply [<stash>] | clear)
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Use 'git-stash save' when you want to record the current state of the
|
||||||
|
working directory and the index, but want to go back to a clean
|
||||||
|
working directory. The command saves your local modifications away
|
||||||
|
and reverts the working directory to match the `HEAD` commit.
|
||||||
|
|
||||||
|
The modifications stashed away by this command can be listed with
|
||||||
|
`git-stash list`, inspected with `git-stash show`, and restored
|
||||||
|
(potentially on top of a different commit) with `git-stash apply`.
|
||||||
|
Calling git-stash without any arguments is equivalent to `git-stash
|
||||||
|
list`.
|
||||||
|
|
||||||
|
The latest stash you created is stored in `$GIT_DIR/refs/stash`; older
|
||||||
|
stashes are found in the reflog of this reference and can be named using
|
||||||
|
the usual reflog syntax (e.g. `stash@\{1}` is the most recently
|
||||||
|
created stash, `stash@\{2}` is the one before it, `stash@\{2.hours.ago}`
|
||||||
|
is also possible).
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
-------
|
||||||
|
|
||||||
|
save::
|
||||||
|
|
||||||
|
Save your local modifications to a new 'stash', and run `git-reset
|
||||||
|
--hard` to revert them.
|
||||||
|
|
||||||
|
list::
|
||||||
|
|
||||||
|
List the stashes that you currently have. Each 'stash' is listed
|
||||||
|
with its name (e.g. `stash@\{0}` is the latest stash, `stash@\{1} is
|
||||||
|
the one before, etc.), the name of the branch that was current when the
|
||||||
|
stash was made, and a short description of the commit the stash was
|
||||||
|
based on.
|
||||||
|
+
|
||||||
|
----------------------------------------------------------------
|
||||||
|
stash@{0}: submit: 6ebd0e2... Add git-stash
|
||||||
|
stash@{1}: master: 9cc0589... Merge branch 'master' of gfi
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
show [<stash>]::
|
||||||
|
|
||||||
|
Show the changes recorded in the stash as a diff between the the
|
||||||
|
stashed state and its original parent. When no `<stash>` is given,
|
||||||
|
shows the latest one. By default, the command shows the diffstat, but
|
||||||
|
it will accept any format known to `git-diff` (e.g., `git-stash show
|
||||||
|
-p stash@\{2}` to view the second most recent stash in patch form).
|
||||||
|
|
||||||
|
apply [<stash>]::
|
||||||
|
|
||||||
|
Restore the changes recorded in the stash on top of the current
|
||||||
|
working tree state. When no `<stash>` is given, applies the latest
|
||||||
|
one. The working directory must match the index.
|
||||||
|
+
|
||||||
|
This operation can fail with conflicts; you need to resolve them
|
||||||
|
by hand in the working tree.
|
||||||
|
|
||||||
|
clear::
|
||||||
|
Remove all the stashed states. Note that those states will then
|
||||||
|
be subject to pruning, and may be difficult or impossible to recover.
|
||||||
|
|
||||||
|
|
||||||
|
DISCUSSION
|
||||||
|
----------
|
||||||
|
|
||||||
|
A stash is represented as a commit whose tree records the state of the
|
||||||
|
working directory, and its first parent is the commit at `HEAD` when
|
||||||
|
the stash was created. The tree of the second parent records the
|
||||||
|
state of the index when the stash is made, and it is made a child of
|
||||||
|
the `HEAD` commit. The ancestry graph looks like this:
|
||||||
|
|
||||||
|
.----W
|
||||||
|
/ /
|
||||||
|
...--H----I
|
||||||
|
|
||||||
|
where `H` is the `HEAD` commit, `I` is a commit that records the state
|
||||||
|
of the index, and `W` is a commit that records the state of the working
|
||||||
|
tree.
|
||||||
|
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
--------
|
||||||
|
|
||||||
|
Pulling into a dirty tree::
|
||||||
|
|
||||||
|
When you are in the middle of something, you learn that there are
|
||||||
|
upstream changes that are possibly relevant to what you are
|
||||||
|
doing. When your local changes do not conflict with the changes in
|
||||||
|
the upstream, a simple `git pull` will let you move forward.
|
||||||
|
+
|
||||||
|
However, there are cases in which your local changes do conflict with
|
||||||
|
the upstream changes, and `git pull` refuses to overwrite your
|
||||||
|
changes. In such a case, you can stash your changes away,
|
||||||
|
perform a pull, and then unstash, like this:
|
||||||
|
+
|
||||||
|
----------------------------------------------------------------
|
||||||
|
$ git pull
|
||||||
|
...
|
||||||
|
file foobar not up to date, cannot merge.
|
||||||
|
$ git stash
|
||||||
|
$ git pull
|
||||||
|
$ git stash apply
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
Interrupted workflow::
|
||||||
|
|
||||||
|
When you are in the middle of something, your boss comes in and
|
||||||
|
demands that you fix something immediately. Traditionally, you would
|
||||||
|
make a commit to a temporary branch to store your changes away, and
|
||||||
|
return to your original branch to make the emergency fix, like this:
|
||||||
|
+
|
||||||
|
----------------------------------------------------------------
|
||||||
|
... hack hack hack ...
|
||||||
|
$ git checkout -b my_wip
|
||||||
|
$ git commit -a -m "WIP"
|
||||||
|
$ git checkout master
|
||||||
|
$ edit emergency fix
|
||||||
|
$ git commit -a -m "Fix in a hurry"
|
||||||
|
$ git checkout my_wip
|
||||||
|
$ git reset --soft HEAD^
|
||||||
|
... continue hacking ...
|
||||||
|
----------------------------------------------------------------
|
||||||
|
+
|
||||||
|
You can use `git-stash` to simplify the above, like this:
|
||||||
|
+
|
||||||
|
----------------------------------------------------------------
|
||||||
|
... hack hack hack ...
|
||||||
|
$ git stash
|
||||||
|
$ edit emergency fix
|
||||||
|
$ git commit -a -m "Fix in a hurry"
|
||||||
|
$ git stash apply
|
||||||
|
... continue hacking ...
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
SEE ALSO
|
||||||
|
--------
|
||||||
|
gitlink:git-checkout[1],
|
||||||
|
gitlink:git-commit[1],
|
||||||
|
gitlink:git-reflog[1],
|
||||||
|
gitlink:git-reset[1]
|
||||||
|
|
||||||
|
AUTHOR
|
||||||
|
------
|
||||||
|
Written by Nanako Shiraishi <nanako3@bluebottle.com>
|
||||||
|
|
||||||
|
GIT
|
||||||
|
---
|
||||||
|
Part of the gitlink:git[7] suite
|
3
Makefile
3
Makefile
@ -212,7 +212,8 @@ SCRIPT_SH = \
|
|||||||
git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
|
git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
|
||||||
git-merge-resolve.sh git-merge-ours.sh \
|
git-merge-resolve.sh git-merge-ours.sh \
|
||||||
git-lost-found.sh git-quiltimport.sh git-submodule.sh \
|
git-lost-found.sh git-quiltimport.sh git-submodule.sh \
|
||||||
git-filter-branch.sh
|
git-filter-branch.sh \
|
||||||
|
git-stash.sh
|
||||||
|
|
||||||
SCRIPT_PERL = \
|
SCRIPT_PERL = \
|
||||||
git-add--interactive.perl \
|
git-add--interactive.perl \
|
||||||
|
165
git-stash.sh
Executable file
165
git-stash.sh
Executable file
@ -0,0 +1,165 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Copyright (c) 2007, Nanako Shiraishi
|
||||||
|
|
||||||
|
USAGE='[ | list | show | apply | clear]'
|
||||||
|
|
||||||
|
. git-sh-setup
|
||||||
|
require_work_tree
|
||||||
|
|
||||||
|
TMP="$GIT_DIR/.git-stash.$$"
|
||||||
|
trap 'rm -f "$TMP-*"' 0
|
||||||
|
|
||||||
|
ref_stash=refs/stash
|
||||||
|
|
||||||
|
no_changes () {
|
||||||
|
git-diff-index --quiet --cached HEAD &&
|
||||||
|
git-diff-files --quiet
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_stash () {
|
||||||
|
logfile="$GIT_DIR/logs/$ref_stash" &&
|
||||||
|
mkdir -p "$(dirname "$logfile")" &&
|
||||||
|
: >"$logfile"
|
||||||
|
}
|
||||||
|
|
||||||
|
save_stash () {
|
||||||
|
if no_changes
|
||||||
|
then
|
||||||
|
echo >&2 'No local changes to save'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
test -f "$GIT_DIR/logs/$ref_stash" ||
|
||||||
|
clear_stash || die "Cannot initialize stash"
|
||||||
|
|
||||||
|
# state of the base commit
|
||||||
|
if b_commit=$(git-rev-parse --verify HEAD)
|
||||||
|
then
|
||||||
|
head=$(git-log --abbrev-commit --pretty=oneline -n 1 HEAD)
|
||||||
|
else
|
||||||
|
die "You do not have the initial commit yet"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if branch=$(git-symbolic-ref -q HEAD)
|
||||||
|
then
|
||||||
|
branch=${branch#refs/heads/}
|
||||||
|
else
|
||||||
|
branch='(no branch)'
|
||||||
|
fi
|
||||||
|
msg=$(printf '%s: %s' "$branch" "$head")
|
||||||
|
|
||||||
|
# state of the index
|
||||||
|
i_tree=$(git-write-tree) &&
|
||||||
|
i_commit=$(printf 'index on %s' "$msg" |
|
||||||
|
git-commit-tree $i_tree -p $b_commit) ||
|
||||||
|
die "Cannot save the current index state"
|
||||||
|
|
||||||
|
# state of the working tree
|
||||||
|
w_tree=$( (
|
||||||
|
GIT_INDEX_FILE="$TMP-index" &&
|
||||||
|
export GIT_INDEX_FILE &&
|
||||||
|
|
||||||
|
rm -f "$TMP-index" &&
|
||||||
|
git-read-tree $i_tree &&
|
||||||
|
git-add -u &&
|
||||||
|
git-write-tree &&
|
||||||
|
rm -f "$TMP-index"
|
||||||
|
) ) ||
|
||||||
|
die "Cannot save the current worktree state"
|
||||||
|
|
||||||
|
# create the stash
|
||||||
|
w_commit=$(printf 'WIP on %s' "$msg" |
|
||||||
|
git-commit-tree $w_tree -p $b_commit -p $i_commit) ||
|
||||||
|
die "Cannot record working tree state"
|
||||||
|
|
||||||
|
git-update-ref -m "$msg" $ref_stash $w_commit ||
|
||||||
|
die "Cannot save the current status"
|
||||||
|
printf >&2 'Saved WIP on %s\n' "$msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
have_stash () {
|
||||||
|
git-rev-parse --verify $ref_stash >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
list_stash () {
|
||||||
|
have_stash || return 0
|
||||||
|
git-log --pretty=oneline -g "$@" $ref_stash |
|
||||||
|
sed -n -e 's/^[.0-9a-f]* refs\///p'
|
||||||
|
}
|
||||||
|
|
||||||
|
show_stash () {
|
||||||
|
flags=$(git-rev-parse --no-revs --flags "$@")
|
||||||
|
if test -z "$flags"
|
||||||
|
then
|
||||||
|
flags=--stat
|
||||||
|
fi
|
||||||
|
s=$(git-rev-parse --revs-only --no-flags --default $ref_stash "$@")
|
||||||
|
|
||||||
|
w_commit=$(git-rev-parse --verify "$s") &&
|
||||||
|
b_commit=$(git-rev-parse --verify "$s^") &&
|
||||||
|
git-diff $flags $b_commit $w_commit
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_stash () {
|
||||||
|
git-diff-files --quiet ||
|
||||||
|
die 'Cannot restore on top of a dirty state'
|
||||||
|
|
||||||
|
# current index state
|
||||||
|
c_tree=$(git-write-tree) ||
|
||||||
|
die 'Cannot apply a stash in the middle of a merge'
|
||||||
|
|
||||||
|
s=$(git-rev-parse --revs-only --no-flags --default $ref_stash "$@") &&
|
||||||
|
w_tree=$(git-rev-parse --verify "$s:") &&
|
||||||
|
b_tree=$(git-rev-parse --verify "$s^:") ||
|
||||||
|
die "$*: no valid stashed state found"
|
||||||
|
|
||||||
|
eval "
|
||||||
|
GITHEAD_$w_tree='Stashed changes' &&
|
||||||
|
GITHEAD_$c_tree='Updated upstream' &&
|
||||||
|
GITHEAD_$b_tree='Version stash was based on' &&
|
||||||
|
export GITHEAD_$w_tree GITHEAD_$c_tree GITHEAD_$b_tree
|
||||||
|
"
|
||||||
|
|
||||||
|
if git-merge-recursive $b_tree -- $c_tree $w_tree
|
||||||
|
then
|
||||||
|
# No conflict
|
||||||
|
a="$TMP-added" &&
|
||||||
|
git-diff --cached --name-only --diff-filter=A $c_tree >"$a" &&
|
||||||
|
git-read-tree --reset $c_tree &&
|
||||||
|
git-update-index --add --stdin <"$a" ||
|
||||||
|
die "Cannot unstage modified files"
|
||||||
|
git-status
|
||||||
|
rm -f "$a"
|
||||||
|
else
|
||||||
|
# Merge conflict; keep the exit status from merge-recursive
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main command set
|
||||||
|
case "$1" in
|
||||||
|
list | '')
|
||||||
|
test $# -gt 0 && shift
|
||||||
|
if test $# = 0
|
||||||
|
then
|
||||||
|
set x -n 10
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
list_stash "$@"
|
||||||
|
;;
|
||||||
|
show)
|
||||||
|
shift
|
||||||
|
show_stash "$@"
|
||||||
|
;;
|
||||||
|
apply)
|
||||||
|
shift
|
||||||
|
apply_stash "$@"
|
||||||
|
;;
|
||||||
|
clear)
|
||||||
|
clear_stash
|
||||||
|
;;
|
||||||
|
save)
|
||||||
|
save_stash && git-reset --hard
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
esac
|
Loading…
Reference in New Issue
Block a user