Skip to content

Instantly share code, notes, and snippets.

@dd32
Last active February 13, 2024 03:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dd32/9035a8ce5197735ff681cd73c456ddd2 to your computer and use it in GitHub Desktop.
Save dd32/9035a8ce5197735ff681cd73c456ddd2 to your computer and use it in GitHub Desktop.
svn ci --partial
#!/bin/bash
# SVN ci partial support
#
# This script can be enabled with a `svn()` wrapper defined in your profile.
#
# Props need to go to authors who have attempted this in the past, particularly to
# Karljohan Lundin Palmerius for his work with using kompare:
# https://www.itn.liu.se/~karlu20/div/blog/2013-05-31_SVNPartialCommit.php
#
# This script is based on the same idea, but uses a CLI staging function, similar to git.
# This has been written from scratch, with the aid of VSCode AI auto-complete.
#
##########
# WARNING: Use at your own risk. This may cause data loss. Always backup your work.
##########
verbose="1"
while getopts v flag; do
case "${flag}" in
v) verbose=1;
esac
done
shift $(($OPTIND - 1))
paths="$@"
function error() {
echo "$1" >&2
exit 1
}
function debug() {
if [[ -n "$verbose" ]]; then
echo "DEBUG: $1" >&2
fi
}
function early_exit() {
echo -n "Caught signal, files in $TMP_FOLDER left alone, restoring working copy, ... "
restore_working_copy "keep"
error "Done."
}
function restore_working_copy() {
cp -r $TMP_FOLDER/* . || error "Couldn't restore unstaged changes. You'll find your files in $TMP_FOLDER"
if [[ "$1" == "delete" ]]; then
rm -r $TMP_FOLDER || error "Couldn't remove temporary directory."
fi
}
function do_compare_and_patch {
CHANGES=$1
CLEAN=$2
IFS=$'\n' DIFF_LINES=( $(diff -U3 $CLEAN $CHANGES) )
chunks=()
chunk=-1
for line in "${DIFF_LINES[@]}"; do
if [[ $line =~ ^@@ ]]; then
chunk=$((chunk+1))
chunks[$chunk]=""
fi;
if [[ $chunk == "-1" ]]; then
# Headers.
echo $line | colordiff --color=always
else :
chunks[$chunk]+="$line"$'\n'
fi
done
diff_to_apply=""
for chunk in "${chunks[@]}"; do
echo "$chunk" | colordiff
# Do we want to stage this chunk?
stage=""
while [[ "$stage" != "y" && "$stage" != "n" ]]; do
read -p "Stage this chunk? (y/n) " stage
done
if [[ "$stage" == "y" ]]; then
echo "Staging chunk"
diff_to_apply+="$chunk"
fi
done
if [[ -n "$diff_to_apply" ]]; then
echo "Applying diff to $CLEAN"
patch $CLEAN <<< "$diff_to_apply" || error "Couldn't apply patch!!!!"
fi
}
debug "Pre-check; Checking for svn working copy"
svn info $paths > /dev/null 2>&1 || error "SVN Working Copy not detected."
debug "Pre-check; Is paths relative?"
[[ "$paths" = *..* ]] && error "Paths must be relative to the working copy."
debug "Finding modified files."
MODIFIED_FILES=`svn st $paths -qu 2>/dev/null | grep "^M" --color=none | sed -E "s/^M\s+[0-9]+\s+//g"`
NEW_FILES=`svn st $paths -qu 2>/dev/null | grep "^A" --color=none | sed -E "s/^A\s+-\s+//g"`
if [ -z "$MODIFIED_FILES" ]; then
error "No modified files detected."
elif [[ "$MODIFIED_FILES" =~ '*' ]]; then
echo "Out of date files detected. Please check these files:"
grep -E "^M\s*" --color=none <<< "$MODIFIED_FILES" | sed -E 's/^M\s+\*\s+[0-9]+\s+/ - /g'
error;
fi
debug "Create a backup folder, and copy modified files to it."
TMP_FOLDER=`mktemp -d svn-ci-partial-XXXXX`
debug "Temporary folder: $TMP_FOLDER"
trap early_exit INT TERM
cp --parents $MODIFIED_FILES $TMP_FOLDER || error "Couldn't move files"
debug "Cleaning svn working copy."
svn revert $MODIFIED_FILES > /dev/null 2> /dev/null || error "Couldn't revert files"
debug "Comparing files..."
for path in $MODIFIED_FILES; do
do_compare_and_patch "$TMP_FOLDER/$path" "./$path"
done
view_diff=""
while [[ "$view_diff" != "y" && "$view_diff" != "n" ]]; do
read -p "Review the Diff before commit? (y/n) " view_diff
done
if [[ "$view_diff" == "y" ]]; then
svn diff $MODIFIED_FILES $NEW_FILES | colordiff --color=always | less -RS
read -p "Enter to proceed with commit. Control+C otherwise."
fi
debug "svn ci..."
svn ci $MODIFIED_FILES $NEW_FILES || error "Commit Aborted."
debug "Restoring working copy."
restore_working_copy "delete"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment