#!/bin/bash
# svn-commit-patch.sh
# Copyright (C) 2008 Jordan Crouse <jordan@cosmicpenguin.net>
# Released under the terms of GPLv2

# This script will commit a patch to the SVN tree

dryrun=0
patchlevel=1

# Apply the patch for the give file
apply_file()
{
	cat $1 | filterdiff -p$patchlevel -i $2 | patch -s -p$patchlevel || {
		echo "E: Error while applying $2"
		exit 1
	}
}

# Reverse the patch - try to get the system back to a sane state
reverse_patch()
{
	local patch=$1
	local file
	local flist
	local dlist
	for file in $(lsdiff --strip $patchlevel -s $patch | awk '{print $1":"$2}'); do
		s=`echo $file | cut -d ':' -f 1`
		f=`echo $file | cut -d ':' -f 2`

		flist="$flist $f"
		[ "$s" = "+" ] && dlist="$dlist $f"
	done

	svn revert -q $flist
	rm -f $dlist
}

usage()
{
	echo "Usage: ./svn-commit.patch.sh [ -p PATCHLEVEL] -t <patch>"
	echo "-p PATCHLEVEL:"
	echo "  Specify how many directories to strip from the patch"
	echo "-t"
	echo "  Test mode - don't do anything damaging"
	exit 0
}

while true
do
	case "$1" in
		-p)
			patchlevel=$2
			shift 2 ;;
		-t)
			dryrun=1
			shift ;;
		-h)
			usage ;;
		*)
			break ;;
	esac
done

PATCH=$1

[ -z "$PATCH" ] && usage

# Make sure the file exists

[ -f $PATCH ] || {
	echo "E:  $PATCH not found"
	exit 1
}

# Make sure we are in a SVN tree
svn info > /dev/null 2>&1 || {
	echo "E:  $PWD does not contain a SVN tree"
	exit 1
}

# Check to make sure the svn tree is clean

for line in $(svn status | awk '{print $1$2}'); do
	echo $line | egrep -q "^[MA]" && {
		echo "E:  The svn tree is not clean:"
		svn status
		exit 1
	}
done

# Make sure that the patch applies cleanly

cat $PATCH | patch -p $patchlevel --dry-run -f -s || {
	echo "E:  The patch does not apply cleanly"
	exit 1
}

# Apply the patch, adding and removing SVN files as needed

for file in $(lsdiff --strip $patchlevel -s $PATCH | awk '{print $1":"$2}'); do
	s=`echo $file | cut -d ':' -f 1`
	f=`echo $file | cut -d ':' -f 2`

	case $s in
		!)
			# Modified file - it should be picked up
			# Automatically

			[ $dryrun -eq 1 ] || apply_file $PATCH $f
			echo "M $f"
			;;
		+)
			# New file - apply it first then add it
			[ $dryrun -eq 1 ] || apply_file $PATCH $f
			[ $dryrun -eq 1 ] || svn add -q $f
			echo "A $f"
			;;
		-)
			# Deleted file - we don't need remove it,
			# svn will do that for us

			[ $dryrun -eq 1 ] || svn rm -q $f
			echo "D $f"
			;;
	esac
done

LOGTMP=$(mktemp) || {
	echo "E: Unable to make a temporary file.  Attempting to restore."
	reverse_patch $PATCH
	exit 1
}

cat $PATCH | filterdiff -x '*' | grep -v "Index" | grep -v "===" > $LOGTMP

# Output the log and see if the user is ready to commit
echo
echo "--- LOG ---"
cat $LOGTMP
echo "-----------"
echo
echo -n "Commit [yN]:"
read yesno
[ $yesno != "y" -a $yesno != 'Y' ] && {
	echo "Restoring..."
	[ $dryrun -eq 1 ] || reverse_patch $PATCH
	rm -f $LOGTMP
	exit 0
}

# No -q here - we want to see the result of the operation
[ $dryrun -eq 1 ] || svn commit -F $LOGTMP
rm -f $LOGTMP
