Subject: [PATCH 5/6] Make cg-diff use optparse, and add features

From: Dan Holmsand <[email-masked]>
Date: Thu, 09 Jun 2005 13:24:07 +0200

This adds some new features to cg-diff:

  - diffstat (or rather git-apply --stat) support. The
    "-d" option outputs (optionally colorized) diff stats
    before the diff.

  - support for more git-diff-[tree/cache] options:
    -B, -R, -M, -C are now all passed on.

  - The COGITO_AUTO_COLOR environment variable makes output
    automatically colorized, if set and if we're on a color
    capable terminal.

We also use the new optparse function from cg-Xlib, to allow
for e.g. "cg-diff -drorigin" and stuff.

Reuse colorization logic from cg-Xlib.

And use LESS to make "less" search for chunks and diff --git
markers. This allows you to "n" your way through a series
of diffs, and gives a nice visual separation of patches.

Signed-off-by: Dan Holmsand <[email-masked]>
---

 cg-diff |  222 +++++++++++++++++++++++++++++----------------------------------
 1 files changed, 102 insertions(+), 120 deletions(-)

diff --git a/cg-diff b/cg-diff
--- a/cg-diff
+++ b/cg-diff
@@ -5,13 +5,16 @@
 #
 # Outputs a diff for converting the first tree to the second one.
 # By default compares the current working tree to the state at the
-# last commit. The output will automatically be displayed in a pager
-# unless it is piped to a program.
+# last commit.
 #
 # OPTIONS
 # -------
-# -c::
-#	Colorize the diff output
+# -c, --color::
+#	Colorize the diff output and use a pager for output (less by 
+#	default).
+#
+# -d, --diffstat::
+#	Show `diffstat' before diff.
 #
 # -p::
 #	Instead of one ID denotes a parent commit to the specified ID
@@ -24,153 +27,132 @@
 #	empty revision which means '-r rev:' compares between 'rev' and
 #	'HEAD', while '-r rev' compares between 'rev' and working tree.
 #
+# -R::
+#	Output diff in reverse.
+#
+# -M::
+#	Detect renames.
+#
+# -C::
+#	Detect copies (as well as renames).
+#
+# -B::
+#	Detect rewrites.
+#
 # -m::
 #	Base the diff at the merge base of the -r arguments (defaulting
-#	to master and origin).
+#	to HEAD and origin).
 #
 # ENVIRONMENT VARIABLES
 # ---------------------
 # PAGER::
 #	The pager to display log information in, defaults to `less`.
 #
-# PAGER_FLAGS::
-#	Flags to pass to the pager. By default `R` is added to the `LESS`
-#	environment variable to allow displaying of colorized output.
+# COGITO_AUTO_COLOR::
+#	If set, colorized output is used automatically on color-capable
+#	terminals.
 
 USAGE="cg-diff [-c] [-m] [-p] [-r FROM_ID[:TO_ID]] [FILE]..."
 
 . ${COGITO_LIB}cg-Xlib

-id1=" "
-id2=" "
-parent=
-opt_color=
-mergebase=
-
-# TODO: Make cg-log use this too.
-setup_colors()
-{
-	local C="diffhdr=1;36:diffhdradd=1;32:diffadd=32:diffhdrmod=1;35:diffmod=35:diffhdrrem=1;31:diffrem=31:diffhunk=36:diffctx=34:diffcctx=33:default=0"
-	[ -n "$COGITO_COLORS" ] && C="$C:$COGITO_COLORS"
-
-	C=${C//=/=\'$'\e'[}
-	C=col${C//:/m\'; col}m\'
-	#coldefault=$(tput op)
-	eval $C
+unset id1 id2 parent diffprog sedprog diffstat difftmp opt_color renames
+dtargs=()
+
+show_diffstat() {
+	[ -s "$difftmp" ] || return
+	git-apply --stat "$difftmp"
+	echo
+	cat "$difftmp"
 }
 
-while [ "$1" ]; do
-	case "$1" in
-	-c)
-		opt_color=1
-		setup_colors
-		;;
-	-p)
+while optparse; do
+	if optparse -p; then
 		parent=1
-		;;
-	-r)
-		shift
-		if echo "$1" | grep -q ':'; then
-			id2=$(echo "$1" | cut -d : -f 2)
-			[ "$id2" ] || log_end="HEAD"
-			id1=$(echo "$1" | cut -d : -f 1)
-		elif [ "$id1" = " " ]; then
-			id1="$1"
+	elif optparse -m; then
+		incoming=1
+	elif optparse -r=; then
+		if [ -z "${id1+set}" ]; then
+			id1=$OPTARG
+			if [[ "$id1" == *:* ]]; then
+				id2=${id1#*:}
+				id1=${id1%:*}
+			fi
 		else
-			id2="$1"
+			[ -z "${id2+set}" ] || die "too many revisions"
+			id2=$OPTARG
 		fi
-		;;
-	-m)
-		mergebase=1
-		;;
-	*)
-		break
-		;;
-	esac
-	shift
-done
-
-colorize() {
-	if [ "$opt_color" ]; then
-		gawk '
-		{ if (/^(Index:|diff --git) /)
-		    print "'$coldiffhdr'" $0 "'$coldefault'"
-		  else if (/^======*$/)
-		    print "'$coldiffhdr'" $0 "'$coldefault'"
-		  else if (/^\+\+\+/)
-		    print "'$coldiffhdradd'" $0 "'$coldefault'"
-		  else if (/^\*\*\*/)
-		    print "'$coldiffhdrmod'" $0 "'$coldefault'"
-		  else if (/^---/)
-		    print "'$coldiffhdrrem'" $0 "'$coldefault'"
-		  else if (/^(\+|new( file)? mode )/)
-		    print "'$coldiffadd'" $0 "'$coldefault'"
-		  else if (/^(-|(deleted file|old) mode )/)
-		    print "'$coldiffrem'" $0 "'$coldefault'"
-		  else if (/^!/)
-		    print "'$coldiffmod'" $0 "'$coldefault'"
-		  else if (/^@@ \-[0-9]+(,[0-9]+)? \+[0-9]+(,[0-9]+)? @@/)
-		    print gensub(/^(@@[^@]*@@)([ \t]*)(.*)/,
-		         "'$coldiffhunk'" "\\1" "'$coldefault'" \
-			 "\\2" \
-			 "'$coldiffctx'" "\\3" "'$coldefault'", "")
-		  else if (/^\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/)
-		    print "'$coldiffcctx'" $0 "'$coldefault'"
-		  else
-		    print
-		}'
+	elif optparse -c; then
+		opt_color=1
+	elif optparse -d || optparse --diffstat; then
+		diffstat=1
+	elif optparse -R; then
+		dtargs[${#dtargs[@]}]="-R"
+	elif optparse -M; then
+		[ "$renames" ] && optconflict
+		renames=1
+		dtargs[${#dtargs[@]}]="-M"
+	elif optparse -C; then
+		[ "$renames" ] && optconflict
+		renames=1
+		dtargs[${#dtargs[@]}]="-C"
+	elif optparse -B; then
+		dtargs[${#dtargs[@]}]="-B"
 	else
-		cat
+		optfail
 	fi
-}
+done
 
-if [ "$parent" ]; then
-	id2="$id1"
-	id="$id2"; [ "$id" = " " ] && id=""
-	id1=$(parent-id "$id" | head -n 1) || exit 1
-fi
+[ -n "$COGITO_AUTO_COLOR" -a -t 1 ] && [ "$(tput setaf 1 2>/dev/null)" ] && 
+opt_color=1
 
-if [ "$mergebase" ]; then
-	[ "$id1" != " " ] || id1="master"
-	[ "$id2" != " " ] || id2="origin"
-	id1=$(git-merge-base $(commit-id "$id1") $(commit-id "$id2"))
-fi
+LESS=$'+/\013^@@.*@@|^diff.--git..*$'" $LESS"
 
+diffprog=git-diff-tree
 
-filter=$(mktemp -t gitdiff.XXXXXX)
-for file in "$@"; do
-	echo "$file" >>$filter
-done
+if [ "$parent" ]; then
+	[ -z "${id2+set}" ] || die "too many revisions"
+	id2="$id1"
+	id1=$(parent-id "$id2" | head -n 1) || exit 1
+elif [ "$incoming" ]; then
+	tmp=$id1
+	id1="$(commit-id "${id2:-HEAD}")" || exit 1
+	id2="$(commit-id "${tmp:-origin}")" || exit 1
+	id1="$(git-merge-base "$id1" "$id2")" || exit 1
+fi
 
-if [ "$id2" = " " ]; then
-	if [ "$id1" != " " ]; then
-		tree=$(tree-id "$id1") || exit 1
-	else
-		tree=$(tree-id) || exit 1
-	fi
+id1=$(tree-id "$id1") || exit 1
 
+if [ -z "${id2+set}" ]; then
 	# Ensure to only diff modified files
 	git-update-cache --refresh >/dev/null
-
-	# FIXME: Update ret based on what did we match. And take "$@"
-	# to account after all.
-	ret=
-	cat $filter | xargs git-diff-cache -r -p $tree | colorize | pager
-
-	rm $filter
-
-	[ "$ret" ] && die "no files matched"
-	exit $ret
+	diffprog=git-diff-cache
+else
+	id2=$(tree-id "$id2") || exit 1
 fi
 
-
-id1=$(tree-id "$id1") || exit 1
-id2=$(tree-id "$id2") || exit 1
-
 [ "$id1" = "$id2" ] && die "trying to diff $id1 against itself"
+diffopts=(-r -p "${dtargs[@]}" $id1 $id2 "${ARGS[@]}")
+
+if [ "$diffstat" ]; then
+	difftmp=$(mktemp -t cgdiff.XXXXXX) || exit 1
+	trap "rm '$difftmp'" SIGTERM EXIT
+	$diffprog "${diffopts[@]}" > $difftmp
 
-cat $filter | xargs git-diff-tree -r -p $id1 $id2 | colorize | pager
+	diffprog=show_diffstat
+	diffopts=
+fi
 
-rm $filter
-exit 0
+if [ "$opt_color" ]; then
+	setup_colors
+	sedprog="$color_rules"
+
+	[ "$diffstat" ] && sedprog="$sedprog
+s,^\\( [^ ].*\\)\\( |  *[0-9][0-9]* \\),$colfiles\\1$coldefault\\2,"
+
+	$diffprog "${diffopts[@]}" | sed -e "$sedprog" | pager
+	exit $PIPESTATUS
+else
+	$diffprog "${diffopts[@]}"
+fi