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

Code & Download