#! /bin/bash

#  This script is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License version 2 as
#  published by the Free Software Foundation.
#
#  See the COPYING and AUTHORS files for more details.

# Read in library functions
if [ "$(type -t patch_file_name)" != function ]
then
	if ! [ -r /usr/share/quilt/scripts/patchfns ]
	then
		echo "Cannot read library /usr/share/quilt/scripts/patchfns" >&2
		exit 1
	fi
	. /usr/share/quilt/scripts/patchfns
fi

usage()
{
	local redirect
	if [ x$1 != x-h ]
	then
		redirect='>&2'
	fi
	echo $"Usage: quilt diff [-p n] [-c patch|-z] [-R] [-P patch] [--diff=utility] [file ...]" $redirect
	
	if [ x$1 = x-h ]
	then
		echo $"

Produces a diff of the specified file(s) in the topmost or specified
patch.  If no files are specified, all files that are modified are
included.

-p n	Create a -p n style patch (-p0 or -p1 are supported).

-P patch
	Create a diff for the specified patch.  (Defaults to the topmost
	patch.)

-c patch
	Create a combined diff for all patches between this patch and
	the patch specified with -P. A patch name of \"-\" is equivalent
	to specifying the first applied patch.

-R	Create a reverse diff.

-z	Write to standard output the changes that have been made
	relative to the topmost or specified patch.

--diff=utility
	Use the specified utility for generating the diff. The utility
	is invoked with the original and new file name as arguments.

"
		exit 0
	else
		exit 1
	fi
}

do_diff()
{
	local file=$1 old_file=$2 new_file=$3

	if [ -n "$opt_reverse" ]
	then
		local f=$new_file
		new_file=$old_file
		old_file=$f
	fi
	
	if [ -n "$opt_diff" ]
	then
		[ -s "$old_file" ] || old_file=/dev/null
		[ -s "$new_file" ] || new_file=/dev/null
		if ! /usr/bin/diff -qN $old_file $new_file >/dev/null
		then
			$opt_diff $old_file $new_file
			true
		fi
	else
		diff_file $file $old_file $new_file
	fi
}

die ()
{
	local status=$1
	[ -e "$tmp_files" ] && rm -f $tmp_files
	[ -n "$workdir" ] && rm -rf $workdir
	exit $status
}

options=`getopt -o p:P:c:Rzh --long diff:,snapshot -- "$@"`

if [ $? -ne 0 ]
then
	usage
fi

eval set -- "$options"

while true
do
	case "$1" in
	-p)
		opt_strip_level=$2
		shift 2 ;;
	-P)
		last_patch=$(stripit $2)
		shift 2 ;;
	-c)
		opt_combine=1
		first_patch=$(stripit $2)
		shift 2 ;;
	-R)
		opt_reverse=1
		shift ;;
	-z)
		opt_relative=1
		shift ;;
	-h)
		usage -h ;;
	--snapshot)
		opt_snapshot=1
		snap_subdir=.snap
		shift ;;
	--diff)
		opt_diff="$2"
		shift 2 ;;
	--)
		shift
		break ;;
	esac
done

opt_files=( $(for file in "$@"; do echo "$SUBDIR$file" ; done) )

if [ $[0$opt_combine + 0$opt_snapshot + 0$opt_relative] -gt 1 ]
then
	echo $"Options \`-c patch', \`--snapshot', and \`-z' cannot be combined."
	die 1
fi

if [ -z "$last_patch" ]
then
	last_patch=$(top_patch)
	if [ -z "$last_patch" ]
	then
		echo $"No patch seem to be applied" >&2
		die 1
	fi
fi

if ! is_applied "$last_patch"
then
	echo $"Patch $last_patch is not applied" >&2
	die 1
fi

if [ -z "$opt_strip_level" ]
then
	opt_strip_level=$(patch_strip_level $last_patch)
fi
if [ "$opt_strip_level" != 0 -a "$opt_strip_level" != 1 ]
then
	echo $"Cannot diff patches with -p$opt_strip_level, please specify -p0 or -p1 instead" >&2
	die 1
fi

trap "die 1" SIGTERM

tmp_files=$(gen_tempfile)
exec 4> $tmp_files  # open $tmp_files

if [ -n "$opt_snapshot" -a ${#opt_files[@]} -eq 0 ]
then
	# Add all files in the snapshot into the file list (they may all
	# have changed).
	while read file
	do
		echo "${file#$QUILT_PC/$snap_subdir/}" >&4
	done \
	< <(find $QUILT_PC/$snap_subdir -type f)
	# Also look at all patches that are currently applied.
	opt_combine=1
	first_patch="$(applied_patches | head -n 1)"
fi

if [ -n "$opt_combine" ]
then
	set -- $(patches_before $last_patch) $last_patch
	if [ "$first_patch" != "-" ]
	then
		while [ $# -ge 1 -a "$1" != "$first_patch" ]
		do
			shift
		done
		if [ $# -eq 0 ]
		then
			echo $"Patch $first_patch not applied before $last_patch."
			die 1
		fi
	fi
	patches=( $@ )
else
	patches=( $last_patch )
fi

for patch in ${patches[@]}
do
	for file in $(files_in_patch $patch)
	do
		if [ ${#opt_files[@]} -gt 0 ] && \
		   ! in_array "$file" "${opt_files[@]#./}"
		then
			continue
		fi
		echo "$file" >&4
	done
done

exec 4>&-  # close $tmp_files
files=( $(sort -u $tmp_files) )

if [ -n "$opt_relative" ]
then
	patch_file=$(patch_file_name $last_patch)
	patch_args=$(patch_args $last_patch)
	workdir=$(gen_tempfile -d $PWD/quilt)
	pwd=$PWD

	if ! cd $QUILT_PC/$last_patch
	then
		echo $"Cannot change into $QUILT_PC/$last_patch"
		die 1
	fi
	if [ ${#files[@]} -gt 0 ] \
	   && ! cp -l --parents "${files[@]}" $workdir/
	then
		echo $"Failed to copy files to temporary directory"
		die 1
	fi
	# Now we may have some zero-size files that have no permissions
	# (which represent files that the patch creates). Those may have
	# been created in the meantime, but patch would refuse to touch
	# them: We must remove them here.
	find $workdir -type f -size 0 -exec rm -f '{}' ';'

	if ! cd $workdir
	then
		echo $"Cannot change to temporary directory"
		die 1
	fi
	
	if [ -s $pwd/$patch_file ]
	then
		if ! cat_file $pwd/$patch_file \
		     | /usr/bin/patch $QUILT_PATCH_OPTS $patch_args --no-backup-if-mismatch \
			     -Ef >/dev/null 2>/dev/null
		then
			# If a patch was force applied (and therefore needs
			# refreshing), we know that patching the temporary
			# files won't succeed, either. So, ignore the error
			# in that particular case. Also, generating a
			# relative diff for a subset of files in the patch
			# will fail.
			
			if [ ! -e $pwd/$QUILT_PC/$last_patch~refresh -a \
			       ${#opt_files[@]} -eq 0 ]
			then
				echo $"Failed to patch temporary files"
				die 1
			fi
		fi
	fi
	if ! cd $pwd
	then
		echo $"Cannot change to source directory"
		die 1
	fi
fi

for file in "${files[@]}"
do
	if [ -n "$opt_snapshot" -a -e "$QUILT_PC/$snap_subdir/$file" ]
	then
		old_file="$QUILT_PC/$snap_subdir/$file"
	elif [ -n "$opt_relative" ]
	then
		old_file="$workdir/$file"
	else
		patch="$(first_modified_by $file ${patches[@]})"
		if [ -z "$patch" ]
		then
			[ -z "$opt_snapshot" ] \
			&& echo $"File $file is not being modified."
			continue
		fi
		old_file="$(backup_file_name $patch $file)"
	fi

	next_patch=$(next_patch_for_file $last_patch $file)
	if [ -z "$next_patch" ]
	then
		new_file="$file"
	else
		new_file="$(backup_file_name $next_patch $file)"
		files_were_shadowed=1
	fi

	do_diff "$file" "$old_file" "$new_file"

	if [ $? -ne 0 ]
	then
		echo $"Diff failed, aborting." >&2
		die 1
	fi
done

if [ -n "$files_were_shadowed" ]
then
	echo $"More recent patches modify files in $last_patch."
	die 1
fi
die 0
### Local Variables:
### mode: shell-script
### End:
# vim:filetype=sh
