SVN stash
Simple script that allows for stashing in SVN as e.g. supported by git. Working changes can be shelved interactively and thus committed selectively.
When working under version control, a selective commit or revert can require local changes to be split up into chunks. For example, git add allows to pick certain changes (“patches”) of a file and git stash (or similarly bzr shelve) can be used to select changes that are temporarily put away. The still common svn lacks comparable features, though.
Fortunately, a single bash script suffices for supporting this workflow using SVN.
By per-change stashing, splitted commits and partial reverts are possible – even of the same file.
Similar to git’s -p
flag, this script interactively guides though each (colored) diff to be acknowledged.
Stashing Changes with SVN: Usage
Called from within an SVN repo’s root without arguments, svnstash
first checks for an existing
stash and will accordingly either propose to unstash it or to create a new one.
For stashing, each diff of a modified file is shown and can be excluded or accepted. If the stashing operation gets confirmed as a whole afterwards, the patches are applied in reverse mode, undoing their particular changes in the working copy. For unstashing lateron, the forward patch line offsets are recalculated accordingly and stored in a hidden directory, which represents the actual stash. The unstashing operation simply prints the affected files of those patches, asks for confirmation, and applies them.
Example
This example shows an interactive run on an SVN repository with three pending changes across two files.
svn status
M test/bar
M test/foo
Two of the changes are selected to be stashed, leaving a partially changed single file:
svnstash.sh
Index: test/bar
===================================================================
--- test/bar (revision 192)
+++ test/bar (working copy)
@@ -30,6 +30,10 @@
exit 1
elif [ -d "$WORKDIR" ]; then
exit 1
+ if ! _ask "Partial stash detected. Start from scratch"; then
+ exit 1
+ fi
+ rm -rf "$WORKDIR" || exit 1
elif [ -d "$STASHDIR" ]; then
grep --color=never --no-filename '^+++ ' "$STASHDIR/"*.patch | cut -b 5- | sort | uniq -c
if ! _ask "Unstash all hunks"; then
Stash this hunk [y/n]? y
Index: test/foo
===================================================================
--- test/foo (revision 191)
+++ test/foo (working copy)
@@ -4,8 +4,8 @@
local A=""
while true; do
read -p $'\x1b\x5b1;34m'"$1 [y/n]? "$'\x1b\x5b0m' A || continue
- [ "$A" = "y" ] && return 0
- [ "$A" = "n" ] && return 1
+ [ "${A,,}" = "y" ] && return 0
+ [ "${A,,}" = "n" ] && return 1
done
}
Stash this hunk [y/n]? n
Index: test/foo
===================================================================
--- test/foo (revision 191)
+++ test/foo (working copy)
@@ -164,5 +164,3 @@
rm -rf "$WORKDIR"
-echo "Done." >&2
-exit 0
Stash this hunk [y/n]? y
A final confirmation is needed to perform the actual changes. The isolated patch can then e.g. be committed afterwards.
Stash 2 hunks [y/n]? y
patching file test/bar
patching file test/foo
Done.
svn status
? .svnstash
M test/foo
svn commit
Committed revision 193.
The patches for restoring the previous state are stored in a hidden directory (and can thus also be deleted or used for manual inspection).
After working on the partial changes (i.e. commit or revert), the two previously excluded changes can be restored by calling the script once again. The presence of stored patches gets detected and the work will be re-applied.
svnstash.sh
1 test/bar
1 test/foo
Unstash all hunks [y/n]? y
patching file test/bar
patching file test/foo
removed directory '.svnstash'
Done.
svn status
M test/bar
M test/foo
This example assumed the script being locally available via the $PATH
variable.
For system-wide installation, you can use e.g. (the equivalent of): install -o root -g root -m 0755 -T svnstash.sh /usr/local/bin/svnstash