#!/usr/bin/env bash
#
# Apply a diff generated by git diff.
# Copyright (c) Petr Baudis, 2005
#
# This is basically just a smart patch wrapper. It handles stuff like
# mode changes, removal of files vs. zero-size files etc.
#
# Takes the diff on stdin.

. cg-Xlib

gonefile=$(mktemp -t gitapply.XXXXXX)
todo=$(mktemp -t gitapply.XXXXXX)
patchfifo=$(mktemp -t gitapply.XXXXXX)
rm $patchfifo && mkfifo -m 600 $patchfifo

show-files --deleted >$gonefile

# patch file removal behaviour cannot be sensibly controlled, so we
# just handle it all ourselves.
patch -p1 -N <$patchfifo &

tee $patchfifo | egrep '^[+-]{3}[ \t]' | {
	victim=
	origmode=

	while read sign file attrs; do
		case $sign in
		"---")
			victim=$file
			mode=$(echo $attrs | sed 's/.*mode:[0-7]*\([0-7]\{3\}\).*/\1/')
			origmode=
			[ "$mode" != "$attrs" ] && origmode=$mode
			;;
		"+++")
			if [ "$file" = "/dev/null" ]; then
				torm=$(echo "$victim" | sed 's/[^\/]*\///') #-p1
				(show-files | fgrep -qx "$torm") && echo -ne "rm\0$torm\0"
				continue
			elif [ "$victim" = "/dev/null" ]; then
				toadd=$(echo "$file" | sed 's/^[^\/]*\///') #-p1
				(show-files | fgrep -qx "$toadd") || echo -ne "add\0$toadd\0"
			fi
			mode=$(echo $attrs | sed 's/.*mode:[0-7]*\([0-7]\{3\}\).*/\1/')
			if [ "$mode" ] && [ "$mode" != "$attrs" ] && [ "$origmode" != "$mode" ]; then
				tocm=$(echo "$file" | sed 's/[^\/]*\///') #-p1
				echo -ne "cm\0 $mode\0$tocm\0"
			fi
			;;
		*)
			echo "invalid operation: $sign" >&2
			;;
		esac
	done
} >$todo

wait

# Now we just recreate all the supposedly deleted files and
# kill only those who really are gone.
#
# This is done on the assumption that we are never going to have
# too many files deleted in the first place anyway.

touchfiles="$(show-files --deleted | join -v 2 $gonefile -)"
[ "$touchfiles" ] && touch $touchfiles

cat $todo | xargs -0 sh -c '
while [ "$1" ]; do
	op="$1"; shift;
	case "$op" in
	"add") cg-add "$1"; shift;;
	"rm")  rm -- "$1" && cg-rm "$1"; shift;;
	"cm")  chmod "$1" "$2"; shift; shift;;
	esac
done
' padding

rm $patchfifo $todo $gonefile
