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

usage()
{
	printf $"Usage: quilt diff [-p n|-p ab] [-u|-U num|-c|-C num] [--combine patch|-z] [-R] [-P patch] [--snapshot] [--diff=utility] [--no-timestamps] [--no-index] [--sort] [--color[=always|auto|never]] [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).

-p ab	Create a -p1 style patch, but use a/file and b/file as the
	original and new filenames instead of the default
	dir.orig/file and dir/file names.

-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 (auto activates it only if the output is a tty).

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

colorize()
{
	if [ -n "$opt_color" ]
	then
		sed -e '
		    s/^\(Index:\|---\|+++\|\*\*\*\) .*/'$color_diff_hdr'&'$color_clear'/
		t ; s/^+.*/'$color_diff_add'&'$color_clear'/
		t ; s/^-.*/'$color_diff_rem'&'$color_clear'/
		t ; s/^!.*/'$color_diff_mod'&'$color_clear'/
		t ; s/^\(@@ \-[0-9]\+\(,[0-9]\+\)\? +[0-9]\+\(,[0-9]\+\)\? @@\)\([ \t]*\)\(.*\)/'$color_diff_hunk'\1'$color_clear'\4'$color_diff_ctx'\5'$color_clear'/
		t ; s/^\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*.*/'$color_diff_cctx'&'$color_clear'/
		'
	else
		cat
	fi
}

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

	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 ! diff -q "$old_file" "$new_file" >/dev/null
		then
			LANG=$ORIGINAL_LANG $opt_diff "$old_file" "$new_file"
			true
		fi
	else
		diff_file "$file" "$old_file" "$new_file" | colorize

		# Test the return value of diff, and propagate the error if any
		status=${PIPESTATUS[0]}
		if [ $status != 0 ]
		then
			die $status
		fi
	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)
		last_patch=$2
		shift 2 ;;
	--combine)
		opt_combine=1
		first_patch=$2
		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

# Read in library functions
if ! [ -r $QUILT_DIR/scripts/patchfns ]
then
	echo "Cannot read library $QUILT_DIR/scripts/patchfns" >&2
	exit 1
fi
. $QUILT_DIR/scripts/patchfns

setup_colors

if [ -n "$first_patch" -a "$first_patch" != - ]
then
	first_patch=$(find_applied_patch "$first_patch") || exit 1
fi

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 \`--combine', \`--snapshot', and \`-z' cannot be combined.\n" >&2
	die 1
fi

last_patch=$(find_applied_patch "$last_patch") || exit 1

if [ -z "$opt_strip_level" ]
then
	opt_strip_level=$(patch_strip_level "$last_patch")
fi
case "$opt_strip_level" in
0 | 1 | ab)
	;;
*)
	printf $"Cannot diff patches with -p%s, please specify -p0, -p1, or -pab instead\n" \
	       "$opt_strip_level" >&2
	die 1
	;;
esac

# If diffing against snapshot, ensure that snapshot is actually present
if [ -n "$opt_snapshot" ] && [ ! -d "$QUILT_PC/$snap_subdir" ]
then
	printf $"No snapshot to diff against\n" >&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).
	files=( $(find $QUILT_PC/$snap_subdir -type f \
		  | sed -e "s/^$(quote_sed_re $QUILT_PC/$snap_subdir/)//" \
		  | sort) )
	printf "%s\n" "${files[@]}" >&4
	unset files
	# 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

# Properly handle spaces in file names
saved_IFS=$IFS
IFS=$'\n'

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

IFS=$saved_IFS

if [ -n "$opt_relative" ]
then
	workdir=$(gen_tempfile -d "$PWD/quilt")
	apply_patch_temporarily "$workdir" "$last_patch" "${files[@]}" \
	|| die 1
fi

setup_pager

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"

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

if [ -n "$files_were_shadowed" ]
then
	printf $"Warning: more recent patches modify files in patch %s\n" \
	       "$(print_patch "$last_patch")" >&2
fi
die 0
