0e32126f04
This reverts commit 757c7f60a7
as an
unnecessary error message to pop up when the last stash entry is dropped.
It simply is not worth the aggravation.
321 lines
6.5 KiB
Bash
Executable File
321 lines
6.5 KiB
Bash
Executable File
#!/bin/sh
|
|
# Copyright (c) 2007, Nanako Shiraishi
|
|
|
|
dashless=$(basename "$0" | sed -e 's/-/ /')
|
|
USAGE="list [<options>]
|
|
or: $dashless (show | drop | pop ) [<stash>]
|
|
or: $dashless apply [--index] [<stash>]
|
|
or: $dashless branch <branchname> [<stash>]
|
|
or: $dashless [save [--keep-index] [<message>]]
|
|
or: $dashless clear"
|
|
|
|
SUBDIRECTORY_OK=Yes
|
|
OPTIONS_SPEC=
|
|
. git-sh-setup
|
|
require_work_tree
|
|
cd_to_toplevel
|
|
|
|
TMP="$GIT_DIR/.git-stash.$$"
|
|
trap 'rm -f "$TMP-*"' 0
|
|
|
|
ref_stash=refs/stash
|
|
|
|
no_changes () {
|
|
git diff-index --quiet --cached HEAD --ignore-submodules -- &&
|
|
git diff-files --quiet --ignore-submodules
|
|
}
|
|
|
|
clear_stash () {
|
|
if test $# != 0
|
|
then
|
|
die "git stash clear with parameters is unimplemented"
|
|
fi
|
|
if current=$(git rev-parse --verify $ref_stash 2>/dev/null)
|
|
then
|
|
git update-ref -d $ref_stash $current
|
|
fi
|
|
}
|
|
|
|
create_stash () {
|
|
stash_msg="$1"
|
|
|
|
git update-index -q --refresh
|
|
if no_changes
|
|
then
|
|
exit 0
|
|
fi
|
|
|
|
# state of the base commit
|
|
if b_commit=$(git rev-parse --verify HEAD)
|
|
then
|
|
head=$(git log --no-color --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\n' "$msg" |
|
|
git commit-tree $i_tree -p $b_commit) ||
|
|
die "Cannot save the current index state"
|
|
|
|
# state of the working tree
|
|
w_tree=$( (
|
|
rm -f "$TMP-index" &&
|
|
cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" &&
|
|
GIT_INDEX_FILE="$TMP-index" &&
|
|
export GIT_INDEX_FILE &&
|
|
git read-tree -m $i_tree &&
|
|
git add -u &&
|
|
git write-tree &&
|
|
rm -f "$TMP-index"
|
|
) ) ||
|
|
die "Cannot save the current worktree state"
|
|
|
|
# create the stash
|
|
if test -z "$stash_msg"
|
|
then
|
|
stash_msg=$(printf 'WIP on %s' "$msg")
|
|
else
|
|
stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
|
|
fi
|
|
w_commit=$(printf '%s\n' "$stash_msg" |
|
|
git commit-tree $w_tree -p $b_commit -p $i_commit) ||
|
|
die "Cannot record working tree state"
|
|
}
|
|
|
|
save_stash () {
|
|
keep_index=
|
|
case "$1" in
|
|
--keep-index)
|
|
keep_index=t
|
|
shift
|
|
esac
|
|
|
|
stash_msg="$*"
|
|
|
|
git update-index -q --refresh
|
|
if no_changes
|
|
then
|
|
echo 'No local changes to save'
|
|
exit 0
|
|
fi
|
|
test -f "$GIT_DIR/logs/$ref_stash" ||
|
|
clear_stash || die "Cannot initialize stash"
|
|
|
|
create_stash "$stash_msg"
|
|
|
|
# Make sure the reflog for stash is kept.
|
|
: >>"$GIT_DIR/logs/$ref_stash"
|
|
|
|
git update-ref -m "$stash_msg" $ref_stash $w_commit ||
|
|
die "Cannot save the current status"
|
|
printf 'Saved working directory and index state "%s"\n' "$stash_msg"
|
|
|
|
git reset --hard
|
|
|
|
if test -n "$keep_index" && test -n $i_tree
|
|
then
|
|
git read-tree --reset -u $i_tree
|
|
fi
|
|
}
|
|
|
|
have_stash () {
|
|
git rev-parse --verify $ref_stash >/dev/null 2>&1
|
|
}
|
|
|
|
list_stash () {
|
|
have_stash || return 0
|
|
git log --no-color --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
|
|
|
|
w_commit=$(git rev-parse --verify --default $ref_stash "$@") &&
|
|
b_commit=$(git rev-parse --verify "$w_commit^") &&
|
|
git diff $flags $b_commit $w_commit
|
|
}
|
|
|
|
apply_stash () {
|
|
git update-index -q --refresh &&
|
|
git diff-files --quiet --ignore-submodules ||
|
|
die 'Cannot apply to a dirty working tree, please stage your changes'
|
|
|
|
unstash_index=
|
|
case "$1" in
|
|
--index)
|
|
unstash_index=t
|
|
shift
|
|
esac
|
|
|
|
# current index state
|
|
c_tree=$(git write-tree) ||
|
|
die 'Cannot apply a stash in the middle of a merge'
|
|
|
|
# stash records the work tree, and is a merge between the
|
|
# base commit (first parent) and the index tree (second parent).
|
|
s=$(git rev-parse --verify --default $ref_stash "$@") &&
|
|
w_tree=$(git rev-parse --verify "$s:") &&
|
|
b_tree=$(git rev-parse --verify "$s^1:") &&
|
|
i_tree=$(git rev-parse --verify "$s^2:") ||
|
|
die "$*: no valid stashed state found"
|
|
|
|
unstashed_index_tree=
|
|
if test -n "$unstash_index" && test "$b_tree" != "$i_tree" &&
|
|
test "$c_tree" != "$i_tree"
|
|
then
|
|
git diff-tree --binary $s^2^..$s^2 | git apply --cached
|
|
test $? -ne 0 &&
|
|
die 'Conflicts in index. Try without --index.'
|
|
unstashed_index_tree=$(git-write-tree) ||
|
|
die 'Could not save index tree'
|
|
git reset
|
|
fi
|
|
|
|
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
|
|
if test -n "$unstashed_index_tree"
|
|
then
|
|
git read-tree "$unstashed_index_tree"
|
|
else
|
|
a="$TMP-added" &&
|
|
git diff-index --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"
|
|
rm -f "$a"
|
|
fi
|
|
git status || :
|
|
else
|
|
# Merge conflict; keep the exit status from merge-recursive
|
|
status=$?
|
|
if test -n "$unstash_index"
|
|
then
|
|
echo >&2 'Index was not unstashed.'
|
|
fi
|
|
exit $status
|
|
fi
|
|
}
|
|
|
|
drop_stash () {
|
|
have_stash || die 'No stash entries to drop'
|
|
|
|
if test $# = 0
|
|
then
|
|
set x "$ref_stash@{0}"
|
|
shift
|
|
fi
|
|
# Verify supplied argument looks like a stash entry
|
|
s=$(git rev-parse --verify "$@") &&
|
|
git rev-parse --verify "$s:" > /dev/null 2>&1 &&
|
|
git rev-parse --verify "$s^1:" > /dev/null 2>&1 &&
|
|
git rev-parse --verify "$s^2:" > /dev/null 2>&1 ||
|
|
die "$*: not a valid stashed state"
|
|
|
|
git reflog delete --updateref --rewrite "$@" &&
|
|
echo "Dropped $* ($s)" || die "$*: Could not drop stash entry"
|
|
|
|
# clear_stash if we just dropped the last stash entry
|
|
git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash
|
|
}
|
|
|
|
apply_to_branch () {
|
|
have_stash || die 'Nothing to apply'
|
|
|
|
test -n "$1" || die 'No branch name specified'
|
|
branch=$1
|
|
|
|
if test -z "$2"
|
|
then
|
|
set x "$ref_stash@{0}"
|
|
fi
|
|
stash=$2
|
|
|
|
git-checkout -b $branch $stash^ &&
|
|
apply_stash --index $stash &&
|
|
drop_stash $stash
|
|
}
|
|
|
|
# Main command set
|
|
case "$1" in
|
|
list)
|
|
shift
|
|
if test $# = 0
|
|
then
|
|
set x -n 10
|
|
shift
|
|
fi
|
|
list_stash "$@"
|
|
;;
|
|
show)
|
|
shift
|
|
show_stash "$@"
|
|
;;
|
|
save)
|
|
shift
|
|
save_stash "$@"
|
|
;;
|
|
apply)
|
|
shift
|
|
apply_stash "$@"
|
|
;;
|
|
clear)
|
|
shift
|
|
clear_stash "$@"
|
|
;;
|
|
create)
|
|
if test $# -gt 0 && test "$1" = create
|
|
then
|
|
shift
|
|
fi
|
|
create_stash "$*" && echo "$w_commit"
|
|
;;
|
|
drop)
|
|
shift
|
|
drop_stash "$@"
|
|
;;
|
|
pop)
|
|
shift
|
|
if apply_stash "$@"
|
|
then
|
|
test -z "$unstash_index" || shift
|
|
drop_stash "$@"
|
|
fi
|
|
;;
|
|
branch)
|
|
shift
|
|
apply_to_branch "$@"
|
|
;;
|
|
*)
|
|
if test $# -eq 0
|
|
then
|
|
save_stash &&
|
|
echo '(To restore them type "git stash apply")'
|
|
else
|
|
usage
|
|
fi
|
|
;;
|
|
esac
|