#!/bin/sh

readonly LANG=C
readonly PAGER=${PAGER:-less}
readonly PAGER_DIFF=${PAGER_DIFF:-$PAGER}
readonly PSVN=${PSVN:-svn}

clean_only=0	# only clean temporary files
dir_cache=""	# directories to remove
dir_cache_ignore="" # directories not to be removed
ignore_empty=0	# ignore empty file during clean and commit phase
non_interactive=0 # don't ask user any questions
removed_dirs=""	# already removed directories

ask_yesno() {
	local answer

	[ $non_interactive -eq 0 ] || return 0

	while true; do
		read -r -p "$1 (y/n)? [y] " answer
		[ "$answer" ] || answer="y"

		case $answer in
		[Nn])
			return 1 ;;
		[Yy])
			return 0
		esac
	done
}

cache_concat() {
	local str=$(eval echo $"${1}")

	[ ! "$str" ] && str="$2" || str="$str $2"
	setvar ${1} "${str}"
}

dir_cache_add() {
	local dir=$1

	case $dir_cache in
	*$dir*)
		;;
	*)
		cache_concat dir_cache "$dir"
	esac
}

display_usage() {
	<< EOF >&2 cat
Usage: ${0##*/} -c
       ${0##*/} [-en]
       ${0##*/} -h

    -c - clean temp files only
    -e - ignore empty files
    -h - show this help
    -n - non-interactive mode

EOF
	exit 1
}

file_add() {
	local child file=$1 n_kind="file"

	case $file in *svn-commit*) return 1 ;; esac
	[ $ignore_empty -eq 1 -a ! -s "$file" ] && return 1
	[ -d "$file" ] && n_kind="directory"

	if ask_yesno "Add $n_kind $file"; then
		[ $non_interactive -eq 0 ] || echo "===> Adding $n_kind $file"
		if ! $PSVN -q add "$file"; then
			echo "===> File addition failed" >&2
			pcommit_exit 1
		fi

		if [ "$n_kind" = "directory" ]; then
			for child in $(ls "$file"); do
				file_set_props "$file/$child"
			done
		fi
	else
		return 1
	fi
}

file_remove() {
	local file=$1
	local n_kind=$($PSVN info "$1"|awk '/^Node Kind/ { print $3 }')

	if [ "$n_kind" = "file" ]; then
		for dir in $removed_dirs; do
			case $file in $dir/*)
				return 0
			esac
		done
	fi

	if ask_yesno "Remove $n_kind $file"; then
		[ $non_interactive -eq 0 ] || echo "===> Removing $n_kind $file"
		if ! $PSVN -q --force rm "$file"; then
			echo "===> File removal failed" >&2
			pcommit_exit 1
		fi

		if [ "$n_kind" = "file" ]; then
			case $file in *files/*|*scripts/*)
				dir_cache_add "$(dirname "$file")"
			esac
		else
			cache_concat removed_dirs "$file"
		fi
	else
		[ "$n_kind" = "file" ] &&
			cache_concat dir_cache_ignore "$(dirname "$file")"
		return 1
	fi
}

file_set_props() {
	local file=$1

	[ -d "$file" -o ! -e "$file" ] && return 1

	case $(grep -E -- '\$FreeBSD\$|\$[BDFSer]+:' "$file" >/dev/null || echo $?) in
	"")	# matched pattern
		$PSVN -q -- propset svn:keywords "FreeBSD=%H" "$file"
		$PSVN -q -- propdel fbsd:nokeywords "$file" ;;
	1)	# no match
		$PSVN -q -- propset fbsd:nokeywords yes "$file"
		$PSVN -q -- propdel svn:keywords "$file" ;;
	esac

	[ "${file##/*}" != "bsd.port.mk" ] &&
		$PSVN -q -- propset svn:eol-style native "$file"
	$PSVN -q -- propset svn:mime-type text/plain "$file"
	$PSVN -q -- propdel cvs2svn:cvs-rev "$file"
}

pcommit_exit() {
	[ ! "$tempfile" ] || rm -f "$tempfile"
	exit $1
}

remove_empty_dirs() {
	local dir

	for dir in $dir_cache; do
		[ -d $dir ] || continue
		case $dir_cache_ignore in *$dir*) continue ;; esac

		if [ ! "$(ls $dir 2>&1)" ]; then
			echo "===> Removing empty directory $dir"
			$PSVN -q rm $dir
		fi
	done
}

while getopts "cehn" option; do
	case $option in
	c)
		clean_only=1 ;;
	e)
		ignore_empty=1 ;;
	n)
		non_interactive=1 ;;
	*)
		display_usage
	esac
done

if [ "$(make -V PORTNAME)" ]; then
	depth=2
else
	[ "$(make -V PORTSTOP)" ] && depth=4 || depth=3
fi

wrk_dirs=$(find . -name "work*" -type d -depth $((depth-1)) | sed 's|^./||g')
if [ "$wrk_dirs" ]; then
	for wdir in $wrk_dirs; do
		if [ $depth -ne 2 ]; then
			dirname=$(dirname "$wdir")
			echo "===> Running 'make clean' in $dirname..."
			make_args="-C $dirname"
		else
			echo "===> Running 'make clean'..."
			make_args=""
		fi

		ret=$(make $make_args clean 2>/dev/null)
		if echo "$ret" | grep -q 'not writable'; then
			if [ $depth -ne 2 ]; then
				echo "===> Failed to 'make clean' in $dirname" >&2
			else
				echo "===> Failed to 'make clean'" >&2
			fi
			exit 1
		fi
	done
fi

rej_files=$(find . -name "*.rej" -maxdepth $depth | sed 's|^./||g')
if [ "$rej_files" ]; then
	echo "===> Found rejected patch files" >&2
	echo "$rej_files" | tr ' ' '\n'
	exit 1
fi

old_files=$(find -E . \( -name "*.orig" -or -name "svn-commit.*" \) \
	! -regex '.*(\.svn|work)/.*' -delete -print -maxdepth $depth |
	sed 's|^./||g')
if [ "$old_files" ]; then
	echo "===> Removed old temporary files"
	echo "$old_files" | tr ' ' '\n'
fi
[ $clean_only -eq 0 ] || exit 0

if [ $ignore_empty -eq 0 ]; then
	empty_files=$(find . -maxdepth $depth -type f -size 0 \
			! -regex '.*\.svn/.*' | sed 's|^./||')
	for file in $empty_files; do
		if $PSVN info "$file" >/dev/null 2>&1; then
			file_remove "$file"
		else
			rm -f "$file" &&
				echo "===> Removed empty untracked file $file"
		fi
	done
fi

tempfile=$(mktemp -t "${0##*/}")
if [ ! "$tempfile" ]; then
	echo "===> Failed to create temporary file" >&2
	exit 1
fi
trap 'pcommit_exit 1' INT

if ! $PSVN --show-updates status > "$tempfile"; then
	echo "===> '$PSVN status' failed" >&2
	pcommit_exit 1
fi

old_IFS=$IFS
IFS=$'\n'

for line in $(cat "$tempfile"); do
	change=$(echo "$line" | awk '{
		if (match($0, "^Status"))
			next;

		mod = substr($0,1,1);
		if (mod == "?")
			mod="N";

		upg = substr($0,9,1);
		if (mod == "M" && upg == "*")
			mod="U";

		print mod;
	}')
	[ "$change" = " " -o -z "$change" ] && continue
	file=$(echo "$line" | awk '{ print $NF }')
	tracked=0

	case $change in
	!)
		file_remove "$file"
		[ $? -eq 1 ] || tracked=1 ;;
	N)
		file_add "$file"
		tracked=$? ;;
	U)
		echo "===> Updating $file"
		$PSVN -q update "$file"
	esac

	[ $tracked -eq 1 ] || file_set_props "$file"
done

rm "$tempfile"
IFS=$old_IFS

remove_empty_dirs

if [ $non_interactive -eq 1 ]; then
	$PSVN commit
	exit
fi

while true; do
	printf "\n[c]ommit [v]iew diff [s]vn status [q]uit"
	read -r -p "> " cmd
	case $cmd in
	[Cc])
		$PSVN commit
		exit ;;
	[Qq])
		exit 0 ;;
	[Ss])
		$PSVN status ;;
	[Vv])
		$PSVN diff | $PAGER_DIFF ;;
	*)
		echo "===> No such command: $cmd"
	esac
done
