#! /usr/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

setup_colors

usage()
{
	printf $"Usage: quilt diff [-p n] [-u|-U num|-c|-C num] [--combine patch|-z] [-R] [-P patch] [--snapshot] [--diff=utility] [--no-timestamps] [--no-index] [--sort] [--color] [file ...]\n"
	
	if [ x$1 = x-h ]
	then
		printf $"
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).

-u, -U num, -c, -C num
	Create a unified diff (-u, -U) with num lines of context. Create
	a context diff (-c, -C) with num lines of context. The number of
	context lines defaults to 3.

--no-timestamps
	Do not include file timestamps in patch headers.

--no-index
	Do not output Index: lines.

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

-R	Create a reverse diff.

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

--combine 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.

--snapshot
	Diff against snapshot (see \`quilt snapshot -h').

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

--color[=always|auto|never]
	Use syntax coloring.

--sort	Sort files by their name instead of preserving the original order.
"
		exit 0
	else
		exit 1
	fi
}

colorize() {
	if [ -n "$opt_color" ]; then
		/usr/bin/gawk '
		{ if (/^(Index:|\-\-\-|\+\+\+|\*\*\*) /)
		    print "'$color_diff_hdr'" $0 "'$color_clear'"
		  else if (/^\+/)
		    print "'$color_diff_add'" $0 "'$color_clear'"
		  else if (/^-/)
		    print "'$color_diff_rem'" $0 "'$color_clear'"
		  else if (/^!/)
		    print "'$color_diff_mod'" $0 "'$color_clear'"
		  else if (/^@@ \-[0-9]+(,[0-9]+)? \+[0-9]+(,[0-9]+)? @@/)
		    print gensub(/^(@@[^@]*@@)([ \t]*)(.*)/,
		         "'$color_diff_hunk'" "\\1" "'$color_clear'" \
			 "\\2" \
			 "'$color_diff_ctx'" "\\3" "'$color_clear'", "")
		  else if (/^\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/)
		    print "'$color_diff_cctx'" $0 "'$color_clear'"
		  else
		    print
		}'
	else
		cat
	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
			export LANG=$ORIGINAL_LANG
			$opt_diff $old_file $new_file
			export LANG=POSIX
			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:RuU:cC:zh --long diff:,snapshot,no-timestamps \
				 --long no-index,combine:,color:: \
				 --long sort -- "$@"`

if [ $? -ne 0 ]
then
	usage
fi

eval set -- "$options"

opt_format=-u
while true
do
	case "$1" in
	-p)
		opt_strip_level=$2
		shift 2 ;;
	-P)
		if ! last_patch=$(find_patch $2)
		then
			printf $"Patch %s is not in series\n" "$2" >&2
			exit 1
		fi
		shift 2 ;;
	--combine)
		opt_combine=1
		if [ "$2" = - ]
		then
			first_patch=-
		elif ! first_patch=$(find_patch $2)
		then
			printf $"Patch %s is not in series\n" \
			       "$(print_patch $2)" >&2
			exit 1
		fi
		shift 2 ;;
	-R)
		opt_reverse=1
		shift ;;
	-z)
		opt_relative=1
		shift ;;
	-u|-c)
		opt_format=$1
		shift ;;
	-U|-C)
		opt_format="$1 $2"
		shift 2 ;;
	-h)
		usage -h ;;
	--snapshot)
		opt_snapshot=1
		snap_subdir=.snap
		shift ;;
	--diff)
		opt_diff="$2"
		shift 2 ;;
	--no-timestamps)
		QUILT_NO_DIFF_TIMESTAMPS=1
		shift ;;
	--no-index)
		QUILT_NO_DIFF_INDEX=1
		shift ;;
	--sort)
		opt_sort=1
		shift ;;
	--color)
		case "$2" in
		"" | always)
			opt_color=1 ;;
		auto | tty)
			opt_color=
			[ -t 1 ] && opt_color=1 ;;
		never)
			opt_color= ;;
		*)
			usage ;;
		esac
		shift 2 ;;
	--)
		shift
		break ;;
	esac
done

QUILT_DIFF_OPTS="$QUILT_DIFF_OPTS $opt_format"

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

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

if [ -n "$last_patch" ]
then
	if ! is_applied "$last_patch"
	then
		printf $"Patch %s is not applied\n" \
		       "$(print_patch $last_patch)" >&2
		die 1
	fi
else
	last_patch=$(top_patch)
	if [ -z "$last_patch" ]
	then
		printf $"No patches applied\n" >&2
		die 1
	fi
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
	printf $"Cannot diff patches with -p%s, please specify -p0 or -p1 instead\n" \
	       "$opt_strip_level" >&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
			printf $"Patch %s not applied before patch %s\n" \
			       "$(print_patch $first_patch)" \
			       "$(print_patch $last_patch)" >&2
			die 1
		fi
	fi
	patches=( $@ )
else
	patches=( $last_patch )
fi

for patch in ${patches[@]}
do
	for file in $(files_in_patch_ordered $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

if [ -z "$opt_sort" ]
then
	files=( $(
	    awk '
	    { if ($0 in seen)
		next
	      seen[$0]=1
	      print
	    }
	    ' $tmp_files) )
else
	files=( $(sort -u $tmp_files) )
fi

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

	if [ ${#files[@]} -gt 0 ] \
	   && ! ( cd $QUILT_PC/$last_patch &&
		  cp -l --parents "${files[@]}" $workdir/ )
	then
		printf $"Failed to copy files to temporary directory\n" >&2
		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 [ -s $patch_file ]
	then
		if ! cat_file $patch_file \
		     | /usr/bin/patch -d $workdir $QUILT_PATCH_OPTS $patch_args \
		     	       --no-backup-if-mismatch -Ef \
			       >/dev/null 2>/dev/null
		then
			# Generating a relative diff for a subset of files in
			# the patch will fail. Also, if a patch was force
			# applied, we know that it won't apply cleanly. In
			# all other cases, print a warning.
			
			if [ ! -e $QUILT_PC/$last_patch~refresh -a \
			       ${#opt_files[@]} -eq 0 ]
			then
				printf $"Failed to patch temporary files\n" >&2
			fi
		fi
	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" ] \
			&& printf $"File %s is not being modified\n" "$file" >&2
			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" \
	| colorize

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

if [ -n "$files_were_shadowed" ]
then
	printf $"More recent patches modify files in patch %s\n" \
	       "$(print_patch $last_patch)" >&2
	die 1
fi
die 0
### Local Variables:
### mode: shell-script
### End:
# vim:filetype=sh
