#!/bin/bash
#
# Review changes file and the diffstat output for the diff file for a debian
# package.
# 
# Copied and somewhat enhanced from Joey's version (git.kitenet.net/joey)

export LC_ALL=C

# Black       0;30     Dark Gray     1;30
# Blue        0;34     Light Blue    1;34
# Green       0;32     Light Green   1;32
# Cyan        0;36     Light Cyan    1;36
# Red         0;31     Light Red     1;31
# Purple      0;35     Light Purple  1;35
# Brown       0;33     Yellow        1;33
# Light Gray  0;37     White         1;37
ESC="["
vid_bold="${ESC}1m"
vid_cyan="${ESC}36m"
vid_green="${ESC}1;32m"
vid_red="${ESC}1;31m"
vid_norm="${ESC}0m"

header () {
	echo "${vid_bold}>>> ${vid_cyan}$@ ${vid_norm}${vid_bold}<<<${vid_norm}"
}

usage () {
	echo "Usage: $0 [-d] [-f] [*.changes|*.dsc] [...]"
	echo -e "\t-d[dist]: Download latest version from Debian"
	echo -e "\t    (if 'dist' is empty unstable is used)"
	echo -e "\t    and rebuild reference dsc and changes"
	echo -e "\t-f: Do full source review including all upstream changes"
	echo -e "\t    (default is to just see debian/ diff for source"
}

if hash rmadison curl dpkg-genchanges realpath dh_gencontrol diffstat lintian debdiff /usr/lib/perl5/Net/SSL.pm;then
	# we're got all we need
	true
else
	echo "Missing our binaries (got dpkg-dev, realpath, lintian, diffstat, devscripts, libcrypt-ssleay-perl and debhelper installed?)."
	exit 1
fi

while [ $# -gt 0 ];do case $1 in
	-h|--help)
		usage
		exit 0
		;;
	-d*)
		[ "$1" = "-d" ]&&DIST=unstable||DIST=${1#-d*}
		# Get latest Debian version if exists
		DOWNLOADDEBIAN=yes
		ARGS="$ARGS $1"
		shift
		;;
	-w)
		# do a full dource review
		WHOUPLOADS=yes
		ARGS="$ARGS $1"
		shift
		;;
	-f)
		# do a full dource review
		FULLSOURCEDIFF=yes
		ARGS="$ARGS $1"
		shift
		;;
	*.dsc|*.changes)
		FILES="$FILES $1"
		shift
		;;
	*)
		if file=`ls -1 $1*.changes 2>/dev/null|grep -v _source.changes|head -n 1`;then
			FILES="$FILES $file"
		else
			echo "Unknown argument $1 - ignoring"
		fi
		shift
		;;
esac;done

set -- $FILES
if [ $# -gt 1 ];then
	#we're supposed to review more than one package
	for file in $@;do
		$0 $ARGS $file
	done
	exit 0
	# stop here - we've done our job by telling ourselves one at a time
fi

if [ -d debian -a -r debian/changelog ];then
	echo "We're inside a Debian source structure. Searching for our package files..."
	# Get the package name.
	package=`head -n 1 debian/changelog | cut -f 1 -d ' '`

	# Get the package version.
	version=`head -n 1 debian/changelog | cut -f 2 -d ' ' | perl -pe 's/\((.*?)\)/$1/; s/^\d+://;'`
	versionNE=${version#*:}
	for path in .. ../build-area;do
		changes=`ls -1 $path/${package}_${versionNE}*.changes 2>/dev/null|grep -v _source.changes|head -n 1`
		dsc=`ls -1 ${path}/${package}_${versionNE}.dsc 2>/dev/null`
		[ -z "$changes" -o -z "$dsc" ]||break
	done
	if [ -z "$changes" -o -z "$dsc" ];then
		echo "Didn't find ${package}_${versionNE}.dsc and ${package}_${versionNE}_`dpkg-architecture -qDEB_HOST_ARCH`.changes. Exiting."
		exit 1
	else
		echo "Using package files in `realpath $path`"
	fi
else
	if [ -r "$1" ];then
		# try to get a *.dsc or *.changes from $1
		package=`grep ^Source: "$1"|awk '{print $2}'`
		version=`grep ^Version: "$1"|awk '{print $2}'|head -n 1`
		versionNE=${version#*:}
		# just in case it's already GnuPG signed
		path=`dirname "$1"`
	else
		echo "Invalid file given: $1 - Ignoring."
	fi
	if [ -z ${package} -o -z ${version} ];then
		echo "Not inside a Debian package directory and no dsc or changes given. Exiting."
		usage
		exit 1
	fi
fi

# convert to absolute path
path=$(realpath "$path")
upath=$path
# last version (preferably in unstable) is in same dir by default
[ -z "$changes" ]&&changes=`ls -1 "${path}"/${package}_${versionNE}*.changes|head -n 1`
[ -z "$dsc" ]&&dsc="${path}/${package}_${versionNE}.dsc"
dsc=$(realpath "$dsc")
changes=$(realpath "$changes")
binaries=`grep ^Binary: "${changes}"`
binaries=${binaries#*: }

(
	if [ ! -z $DOWNLOADDEBIAN ];then
		header "Downloading latest $DIST version of ${package}"
		# make sure we can return here
		pushd . >/dev/null
		if [ -d debian ];then
			cd ..
		fi
		tmpdir=$(mktemp -d)
		cd ${tmpdir}
		if apt-get -qq source "${package}/$DIST";then
			lastdsc=`ls -1 *.dsc`
			uversion=`grep ^Version: ${lastdsc}|awk '{print $2}'|head -n 1`
			uversionNE=${uversion#*:}
			lastchanges="${package}_${uversionNE}_debreview.changes"
			sourcedir=`find . -maxdepth 1 -mindepth 1 -type d -name "${package}-*"`
			cd ${sourcedir}
			fakeroot dh_gencontrol >/dev/null 2>&1
			debs=`sed -e s/_${uversionNE}.*// debian/files|xargs`
			cd ..
			echo "fetching ${debs} ..."
			[ -x /usr/bin/aptitude ]&&aptitude download -t$DIST ${debs}||apt-get download -t$DIST ${debs}
			arch=`dpkg-architecture -qDEB_HOST_ARCH`
			for deb in $debs;do
				# check for binNMUs
				#gnugk_2%3a2.3.2-3-1+b1_amd64.deb
				file=`ls -1 ${deb}_${uversionNE}*_${arch}.deb ${deb}_${uversion/:/%3a}*_${arch}.deb 2>/dev/null|head -n 1`
				sed -i -e "s,${deb}_${uversion/:/%3a}_${arch}\.deb,${file}," ${sourcedir}/debian/files
				# make sure we get both versions either way...
				sed -i -e "s,${deb}_${uversionNE}_${arch}\.deb,${file}," ${sourcedir}/debian/files
			done
			cd ${sourcedir}
			dpkg-genchanges -sa >../$lastchanges
			cd ..
			rm -rf ${sourcedir}
			if [ "${package}_${versionNE}.dsc" = "${lastdsc}" ];then
				echo "Same version as in $DIST! Rebuild?"
				echo "Anyhow, not moving files into ${path} ..."
				upath=${tmpdir}
			else
				echo "Moving $DIST version back to ${path} for comparison."
				for file in *;do
					if [ -f "${path}/${file}" ];then
						cmp -s "${file}" "${path}/${file}"||fileexists=1
					fi
				done
				if [ -z "${fileexists}" ];then
					mv * "${path}"/
				else
					echo "Cannot move files back. Exist already."
					upath=${tmpdir}
				fi
			fi
			lastdsc="${upath}/${lastdsc}"
			lastchanges="${upath}/${lastchanges}"
		else
			echo "FAILED to download $DIST version. Are you sure one exists?"
		fi
		popd >/dev/null
	fi

	[ -z "$lastchanges" ]&&lastchanges=`ls -1 "${upath}"/${package}_*.changes|sort -r|grep -v _source.|grep -v ${package}_${versionNE}|head -n 1`
	[ -z "$lastdsc" ]&&lastdsc=`ls -1 "${upath}"/${package}_*.dsc|sort -r|grep -v _source.|grep -v ${package}_${versionNE}|head -n 1`
	[ -z "$uversion" -a ! -z "$lastdsc" ]&&uversion=`grep ^Version: "${lastdsc}"|awk '{print $2}'|head -n 1`
	debv3=`ls -1 "${path}"/${package}_${versionNE}.debian.tar.{gz,bz2,lzma} 2>/dev/null`

	header "Reviewing ${package}_${version}${uversion+ (against $uversion)}"
	if [ ! -z "$uversion" ];then
		debianversion=$(dcontrol ${package}/$DIST/@source|sed -n -e "/^Version/s,^.*: ,,p"|head -n 1)
		if [ -z "$debianversion" ];then
			debianversion=$(dcontrol ${package}/sid/@source|sed -n -e "/^Version/s,^.*: ,,p"|head -n 1)
			[ "$uversion" = "$debianversion" ]||echo "W: EEEEK! Your Debian mirror is outdated! unstable has: $debianversion"
		else
			[ "$uversion" = "$debianversion" ]||echo "W: EEEEK! Your Debian mirror is outdated! $DIST has: $debianversion"
		fi
	fi
	if [ -z "${binaries}" ];then
		header "Currently NOT in Debian (initial upload)"
	else
		header "Currently in Debian"
		#apt-cache policy ${binaries}
		rmadison -u qa ${package} ${binaries}
		rmadison -u bpo ${package} ${binaries}
	fi

	header Ubuntu packages
	rmadison -u ubuntu ${package} ${binaries}


	header WNPP check
	wnpp-check ${package}
	wnpp-alert ${package}
	
	
	header RC bugs check
	rc-alert src:${package} ${binaries}
	
	
	if [ "$WHOUPLOADS" = "yes" ];then
		header Who usually uploads
		who-uploads ${package}
	fi

	if [ -d .svn -a -d debian ]; then
		header svn
		svn status
	fi
	if [ -d .git -a -d debian ]; then
		header git
		git status || true
	fi
	pkgsrc=$(mktemp -d)
	pushd $pkgsrc >/dev/null
	dget -uxq "file:${dsc}" >/dev/null
	sourcedir=`find . -maxdepth 1 -mindepth 1 -type d -name "${package}-*"`
	cd $sourcedir
	if [ -r debian/watch ];then
		header debian/watch
		uscan --no-conf --report --no-verbose
		header "Checking source tarball"
		tmpdir=$(mktemp -d)
		uscan --no-conf --download-current-version --no-verbose --download --force-download --destdir=$tmpdir --rename
		ourorig=`ls -1 ../*.orig.tar.*`
		origorig=`ls -1 $tmpdir/*`
		if cmp -s "$ourorig" "$origorig";then
			echo "Upstream sources are ${vid_green}identical${vid_norm}."
		else
			echo "Upstream sources are ${vid_red}different${vid_norm}!"
		fi
		rm -rf $tmpdir
	fi


	header Checking URLs
	urls=`sed -n -e 's#^\(.*[[:space:]]\)\?\(https\?://.*\)\([[:space:]].*\)*$#\2#p' debian/control debian/copyright|sort|uniq`
	echo "URLs found in debian/control and debian/copyright:"
	headers=$(mktemp)
	for url in $urls;do
		prefix=""
		count=0
		while [ ! -z "$url" -a $count -le 5 ];do
			count=$((count + 1))
			if curl -s -f -I "$url" >$headers;then
				sed -i -e 's#$##' $headers
				httpresult=`sed -n -e 's#^HTTP.* \([0-9]\{3\}\).*$#\1#p' $headers`
				newurl=`sed -n -e 's#^Location:[[:space:]]\+\(.*\)$#\1#p' $headers`
				echo "${prefix}${url} - ${vid_green}OK${vid_norm} - $httpresult${newurl:+ - moved to $newurl}"
				url=${newurl}
				[ -z "$url" ]||prefix=" => "
			else
				echo "$url - ${vid_red}FAILED${vid_norm}"
			fi
		done
		[ $count -gt 5 ]&&echo "$url - ${vid_red}REDIRECTION LOOP? ABORTED!${vid_norm}"
		echo
	done
	egrep -i '^VCS.*:' debian/control
	cd ..
	popd >/dev/null
	rm -rf $pkgsrc
	rm -f $headers


	header "Checking debs for all/any"
	tmpdir=$(mktemp -d)
	pushd $tmpdir >/dev/null
	debs=`for deb in $binaries;do sed -n -e "/${deb}_\(\d\+\(:|%3a\)\)\?${versionNE}_.*\.deb/s,^.*[[:space:]]\([^[:space:]]\+\)$,\1,p" ${changes}|sort -u;done`
	for deb in $debs;do
		dpkg -x ${path}/${deb} x
		if [ `echo ${deb}|sed -e 's#^.*_\([^_\.]\+\)\.deb#\1#'` = "all" ];then
			if find x -type f -exec file {} \; 2>/dev/null|egrep -q '(ELF|lib.*\.a:[[:space:]])';then
				echo "W: $deb - should be arch all - yet found ELF binaries?!"
			else
				echo "$deb - appears to be ok"
			fi
		else
			if find x -type f -exec file {} \; 2>/dev/null|egrep -q '(ELF|lib.*\.a:[[:space:]])';then
				echo "$deb - appears to be ok"
			else
				echo "W: $deb - should be arch any - yet not a single ELF binaries found?!"
			fi
		fi
		rm -rf x
	done
	popd >/dev/null
	rm -rf ${tmpdir}
	
	
	header "lintian (ftpmaster rejects)"
	lintian -F "${changes}" &&echo 'Nothing to complain about.'
	
	
	header lintian -I --pedantic
	lintian -IE --pedantic "${changes}"&&echo 'Nothing to complain about.'
	
	if [ ! -z "$lastdsc" -a -r ${dsc} -a "${lastdsc}" != "${dsc}" ];then
		if [ ! -z "$FULLSOURCEDIFF" ];then
			header debdiff `basename ${lastdsc}` "->" `basename ${dsc}`
			difftmp=$(mktemp -d)
			pushd $difftmp >/dev/null
			mkdir cmp
			if [ "${package}-${uversion}" = "${package}-${version}" ];then
				olddir="${package}-${uversion}.old"
				newdir="${package}-${uversion}.new"
			else
				olddir="${package}-${uversion}"
				newdir="${package}-${version}"
			fi
			mkdir "${olddir}";cd "${olddir}"
			dget -u -q "file:${lastdsc}" >/dev/null
			sourceold=`find . -maxdepth 1 -mindepth 1 -type d -name "${package}-*"`
			mv ${sourceold} ../cmp/${olddir}
			cd ..
			mkdir "${newdir}";cd "${newdir}"
			dget -u -q "file:${dsc}" >/dev/null
			sourcenew=`find . -maxdepth 1 -mindepth 1 -type d -name "${package}-*"`
			mv ${sourcenew} ../cmp/${newdir}
			cd ../cmp
			diff -qr ${olddir} ${newdir}|sed -e "s,${olddir},${vid_green}${olddir}${vid_norm}," \
				-e "s,${newdir},${vid_red}${newdir}${vid_norm},"
			popd >/dev/null
			rm -rf $difftmp
			debdiff --noconf --show-moved -w "${lastdsc}" "${dsc}" 2>/dev/null
		else
			difftmp=$(mktemp -d)
			header debdiff `basename ${lastdsc}` "->" `basename ${dsc}` "(debian only)"
			pushd $difftmp >/dev/null
			uversion=`grep ^Version: ${lastdsc}|awk '{print $2}'|head -n 1`
			uversionNE=${uversion#*:}
			if [ -r "${upath}"/${package}_*${uversionNE}.diff.gz ];then
				# we still got old format
				echo "Converting ${uversion} to quilt 3.0 style for comparison"
				dget -u -q "file:${lastdsc}" >/dev/null
				sourcedir=`find . -maxdepth 1 -mindepth 1 -type d -name "${package}-*"`
				cd ${sourcedir}
				mkdir debian/source ; echo '3.0 (quilt)' > debian/source/format
				dpkg-buildpackage -S -sa --single-debian-patch -nc -uc -us >/dev/null 2>&1
				cd ..
				rm -rf ${sourcedir}
				upath=$difftmp
			fi
			if [ -r "${path}"/${package}_${versionNE}.diff.gz ];then
				# we still got old format
				echo "Converting ${version} to quilt 3.0 style for comparison"
				dget -u -q "file:${dsc}" >/dev/null
				sourcedir=`find . -maxdepth 1 -mindepth 1 -type d -name "${package}-*"`
				cd ${sourcedir}
				mkdir debian/source ; echo '3.0 (quilt)' > debian/source/format
				dpkg-buildpackage -S -sa --single-debian-patch -nc -uc -us >/dev/null 2>&1
				cd ..
				rm -rf ${sourcedir}
				debv3=`ls -1 "${difftmp}"/${package}_${version}.debian.tar.{gz,bz2,lzma} 2>/dev/null`
				tmpdebv3=true
			fi
			if [ "${package}-${uversion}" = "${package}-${version}" ];then
				olddir="${package}-${uversion}.old"
				newdir="${package}-${uversion}.new"
			else
				olddir="${package}-${uversion}"
				newdir="${package}-${version}"
			fi
			mkdir ${olddir};cd ${olddir}
			udebv3=`ls -1 "${upath}"/${package}_${uversionNE}.debian.tar.{gz,bz2,lzma} 2>/dev/null`
			case "${udebv3}" in
				*.gz)
					tar xfz "${udebv3}"
					;;
				*.bz2)
					tar xfj "${udebv3}"
					;;
				*.lzma)
					tar xfJ "${udebv3}"
					;;
			esac
			cd ..
			mkdir ${newdir};cd ${newdir}
			case "${debv3}" in
				*.gz)
					tar xfz "${debv3}"
					;;
				*.bz2)
					tar xfj "${debv3}"
					;;
				*.lzma)
					tar xfJ "${debv3}"
					;;
			esac
			cd ..
			header debdiff - short output only
			diff -qr ${olddir} ${newdir}|sed -e "s, ${package}-${version}/debian, ${vid_green}${package}-${version}${vid_norm}/debian," \
				-e "s, ${package}-${uversion}/debian, ${vid_red}${package}-${uversion}${vid_norm}/debian,"
			header debdiff - detailled diff
			diff -urNEbwB ${olddir} ${newdir}
			popd >/dev/null
			rm -rf $difftmp
			[ -z "$tmpdebv3" ]||unset debv3
		fi
	fi
	if [ ! -z "$FULLSOURCEDIFF" -a -z "$lastdsc" ];then
		tmpdir=$(mktemp -d)
		header list entire source of `basename ${dsc}`
		pushd $difftmp >/dev/null
		dget -u -q "file:${dsc}" >/dev/null
		sourcedir=`find . -maxdepth 1 -mindepth 1 -type d -name "${package}-*"`
		diff -urN /dev/null ${sourcedir}
		popd >/dev/null
		rm -rf $tmpdir
	fi
	if [ ! -z "$lastchanges" -a -r ${changes} -a "${lastchanges}" != "${changes}" ];then
		header debdiff `basename ${lastchanges}` "->" `basename ${changes}`
		debdiff --noconf --show-moved "${lastchanges}" "${changes}" 2>/dev/null | \
			sed	-e "s,{+,${vid_green}{+${vid_norm},g" \
				-e "s,+},${vid_green}+}${vid_norm},g" \
				-e "s,\[-,${vid_red}\[-${vid_norm},g" \
				-e "s,-\],${vid_red}-\]${vid_norm},g"

	fi
	if [ -r "${path}"/${package}_${versionNE}.diff.gz ];then
		header diffstat
		zcat "${path}"/${package}_${versionNE}.diff.gz | diffstat
	elif [ ! -z "$debv3" ];then
		header 'debian/ (source 3.0)'
		case "$debv3" in
			*.gz)
				tar tfvz "${debv3}"
				;;
			*.bz2)
				tar tfvj "${debv3}"
				;;
			*.lzma)
				tar tfvJ "${debv3}"
				;;
		esac
	fi
	header changes file
	cat "${changes}"
	header package contents
	debc "${changes}"

	header Final remarks
	cat <<EOF
Done reviewing. Did you run:
ispell -d american -g debian/control
aspell -d en -D -c debian/control
licensecheck -r .

and check
http://bugs.debian.org/src:${package}
http://packages.qa.debian.org/${package}
EOF
) 2>&1 | less -rX

if [ ! -z ${tmpdir} ];then
	echo Cleaning up...
	rm -rf ${tmpdir}
fi


# vim: set ai: #
