#!/bin/bash

# Create a pristine environment to minimize unnecessary fuzz when different
# users use git-ptx-patches on the same patch stack. That is, don't load any
# config files, and pin down environment variables which could influence git's
# behaviour or patch output.
pristine_git() {
	# Notes from the git(1) manpage:
	# - GIT_DIFF_OPTS takes precedence over -U command line parameter
	HOME=/nonexistent \
	XDG_CONFIG_HOME=/nonexistent \
	GIT_CONFIG_NOSYSTEM=true \
	GIT_DIFF_OPTS="-u3" \
	git "$@"
}
GIT="pristine_git"

PTX_PATCHES_HEADER="# generated by git-ptx-patches"

function _md5sum() {
	local sum=$(md5sum)
	echo "# $sum git-ptx-patches magic"
}

if [ ! -L .ptxdist/patches ]; then
	echo "Error: This is not patched by ptxdist. Aborting."
	exit 1
fi

if [ ! -L .ptxdist/series ]; then
	echo "Error: .ptxdist/series must be a symbolic link. Aborting."
	exit 1
fi

remove_old=no
tag=
numbered_patches=true
all_tags=false

if grep -q "$PTX_PATCHES_HEADER" .ptxdist/series; then
	echo "Found series file generated by git-ptx-patches."
	lines=$(wc -l < .ptxdist/series)
	lines=$[lines-1]
	magic=$(head -n$lines .ptxdist/series | _md5sum)
	if grep -q "^$magic" .ptxdist/series; then
		remove_old=yes
	else
		echo "Warning: .ptxdist/series was modified."
	fi
fi

if [ "x$1" = "x--force-remove" ]; then
	remove_old="force"
	shift
fi

while getopts "aft:n" opt; do
	case "${opt}" in
		a)
			all_tags=true
			;;
		f)
			remove_old="force"
			;;
		t)
			tag="${OPTARG}"
			;;
		n)
			numbered_patches=false
			;;
	esac
done
shift $((${OPTIND} - 1))


tags=( $(sed -n 's/^#tag:\([^ ]*\) .*/\1/p' .ptxdist/series 2>/dev/null) )

# default to the first tag if none was specified,
# use 'base' if there is no tag at all
if [ -z "${tag}" ]; then
	tag="${tags[0]}"
fi
if [ -z "${tag}" ]; then
	tag="base"
fi

# only look for the second tag if there are tags in the series
# otherwise assume all commits should be exported
if [ ${#tags[*]} -gt 0 ]; then
	tag2=$(awk -F "[ :]" "
/^#tag:/ {
	if (state == 1) {
		print \$2
		state=2
	}
}
/^#tag:${tag}( |\$)/ {
	state=1
}
END {
	if (!state)
		exit 1
}" < .ptxdist/series)

	if [ $? -ne 0 ]; then
		echo "Failed to find tag '${tag}' in the series"
		exit 1
	fi
else
	tags=( "${tag}" )
	tag2=""
fi

range="${tag}..${tag2}"

if "${all_tags}" ; then
	args=()
	if [ "${remove_old}" = "force" ]; then
		args[${#args[*]}]="-f"
	fi
	if ! "${numbered_patches}"; then
		args[${#args[*]}]="-n"
	fi
	for tag in "${tags[@]}"; do
		echo "Updating patches for '${tag}'..."
		"$0" "${args[@]}" -t "${tag}" || break
		echo
	done
	exit
fi

echo "$PTX_PATCHES_HEADER" > .ptxdist/series.0
:> .ptxdist/series.1
touch .ptxdist/series.append
if grep -q "^#tag:" .ptxdist/series .ptxdist/series.append; then
	tagline=$(cat .ptxdist/series .ptxdist/series.append | grep "#tag:${tag}")
	t=$(echo "${tagline}"|cut -d' ' -f1)
	if [ "#tag:${tag}" == "${t}" ]; then
		tagopt=$(echo "${tagline}"|cut -d' ' -s -f2-)
		sed -e "/$PTX_PATCHES_HEADER/d" -n \
			-e '/git-ptx-patches magic/d' \
			-e "0,/#tag:${tag}/p" \
			.ptxdist/series .ptxdist/series.append >> .ptxdist/series.0
		# Remove patches before #tag:${tag} so they don't get rm'd with remove_old=yes
		sed -i --follow-symlinks "0,/#tag:${tag}/d" .ptxdist/series
		if [ -n "${tag2}" ]; then
			sed -n -e "/#tag:${tag2}/,/git-ptx-patches magic/p" .ptxdist/series > .ptxdist/series.1
			sed -i "/git-ptx-patches magic/d" .ptxdist/series.1
			sed -i --follow-symlinks "/#tag:${tag2}/,/git-ptx-patches magic/d" .ptxdist/series
		fi
	else
		echo "series contains #tag:* lines, but could not find #tag:${tag} line in series. Aborting."
		exit 1
	fi
else
	if [ "${tag}" != "base" ]; then
		echo "When using series with no #tag:* lines, you must use base tag."
		exit 1
	fi
	echo "#tag:${tag} --start-number 1" >> .ptxdist/series.0
fi
rm .ptxdist/series.append

case "$remove_old" in
	"no") ;;
	"yes")
		echo "Removing old patches ..."
		while read patch para; do
			case "${patch}" in
				""|"#"*) continue ;;
				*) rm .ptxdist/patches/$patch ;;
			esac
		done < .ptxdist/series
		;;
	"force")
		echo "Removing old patches (forced) ..."
		find .ptxdist/patches/ | while read file; do
			case "$file" in
				".ptxdist/patches/") continue ;;
				".ptxdist/patches/series") continue ;;
				".ptxdist/patches/autogen.sh") continue ;;
				*)
					if grep -q "${file##\.ptxdist/patches/}" .ptxdist/series.{0,1}; then
						echo "Keep base patch ${file}"
					else
						rm -rf "$file"
					fi
					;;
			esac
		done
		;;
esac

# git-format-patch --no-signature is supported since git 1.7.2
if ${GIT} format-patch -h 2>&1 | grep -q signature; then
	GIT_EXTRA_ARGS="--no-signature"
fi

# git-format-patch --notes is supported since git 1.7.6, but actually you want
# git 1.8.1-rc0 to get the notes below the --- marker
if man git-format-patch | grep -e --notes > /dev/null; then
	GIT_EXTRA_ARGS="$GIT_EXTRA_ARGS --notes"
fi

GIT_EXTRA_ARGS="$GIT_EXTRA_ARGS --summary --stat=80"

${GIT} format-patch -N $GIT_EXTRA_ARGS ${tagopt} -o .ptxdist/patches/ ${range} | while read patch; do
	if "$numbered_patches"; then
		patchname="${patch#.ptxdist/patches/}"
	else
		patchname="${patch#.ptxdist/patches/[0-9][0-9][0-9][0-9]-}"
		mv -n "$patch" ".ptxdist/patches/$patchname"
	fi
	echo "$patchname"
done > .ptxdist/series.auto

cat .ptxdist/series.0 .ptxdist/series.auto .ptxdist/series.1 > .ptxdist/series
cat .ptxdist/series | _md5sum >> .ptxdist/series

# The first line of the patch is 'From <some-git-hash> ...'
# remove it to avoid unnecessary changes in the patch files.
while read patch para; do
	# There are no comments or empty lines in series.auto, so no need to
	# handle these. Also be a bit cautious to only remove lines matching
	# "^From ".
	sed -i '1{/^From /d}' ".ptxdist/patches/$patch"
done < .ptxdist/series.auto

find .ptxdist/patches/ ! -type d | sed -e 's,^.ptxdist/patches/,,' | \
while read patch para; do
	case "$patch" in
		"series"|"autogen.sh") continue ;;
		*) ;;
	esac
	if grep -q "$patch" .ptxdist/series.auto; then
		# ok, this is one of the patches we just touched
		:
	elif grep -q "$patch" .ptxdist/series.{0,1}; then
		echo "Base patch \"$patch\"!"
	else
		echo "Old patch \"$patch\"!"
	fi
done | sort
