#!/usr/bin/env bash
#
# Generate a mail feed for a commits list.
#
# Author: David Woodhouse <dwmw2@infradead.org>

# Environment variables used by this script...
#
# $SENDMAIL and $MLIST must be set appropriately if you intend to
# actually send mail to a mailing list (or other recipient). Otherwise,
# an mbox file named 'git-commits-mail.out' will be created in the 
# current directory.
#
# $FROM specifies the From: header used in the mails. It'll default
# to GIT_COMMITTER_EMAIL if that exists, or to `whoami`@`hostname`
#
# $EXCLUDEHEAD is given as additional arguments to the git-rev-list
# invocation, and is intended to allow other branches to be excluded.
# For example, subsystem trees might avoid mailing changes which were
# merged in from Linus' tree by setting EXCLUDEHEAD=^linus
#
# $GIT_DIR has the obvious effect.
#
# $MAILTAG specifies the file in which the 'last commit mailed' information
# is stored. By default, this will be $GIT_DIR/refs/tags/MailDone, or
# just the relative path '.git/refs/tags/MailDone' if $GIT_DIR is not specified

if [ -z "SENDMAIL" -o -z "$MLIST" ]; then 
    SENDMAIL="tee --append"
    MLIST=git-commits-mail.out
fi

if [ -z "$FROM" ]; then
    if [ -n "$GIT_COMMITTER_EMAIL" ]; then 
	FROM="$GIT_COMMITTER_EMAIL"
    else
	FROM=`whoami`@`hostname`
    fi
fi

if [ -z "$MAILTAG" ]; then
    if [ "$GIT_DIR" = "" ]; then
	MAILTAG=.git/refs/tags/MailDone
    else
	MAILTAG=$GIT_DIR/refs/tags/MailDone
    fi
fi

# Command line arguments. The first specifies the commit or branch to treat
# as HEAD, and the second is the starting commit. Defaults to HEAD and 
# whatever's in $MAILTAG, resp.

if [ -z $2 ]; then
    lastmail=$(git-rev-parse `cat $MAILTAG`)
    if [ -z "$lastmail" ]; then
	echo "No last tag"
	exit 1
    fi
else
    lastmail=$(git-rev-parse $2)
fi

if [ -z $1 ]; then
    base=$(git-rev-parse HEAD) || exit 1
else
    base=$(git-rev-parse $1) || exit 1
fi


if [ "$base" != "$lastmail" ]; then
    git-rev-list --topo-order --no-merges $lastmail..$base $EXCLUDEHEAD | tac |
    while read commit ; do 
      ( git-cat-file commit $commit | while read key rest; do

	case "$key" in
	    "parent"|"tree")
		eval $key="$rest"
		;;

	    "author"|"committer")
		date=(${rest#*> })
		sec=${date[0]}; tz=${date[1]}
		dtz=${tz/+/+ }; dtz=${dtz/-/- }
		pdate="$(date -Rud "1970-01-01 UTC + $sec sec $dtz" 2>/dev/null)"
		if [ "$pdate" ]; then
		    eval $key=\"`echo $rest | sed "s/>.*/> ${pdate/+0000/$tz}/"`\"
		else
		    eval $key=\"$rest\"
		fi
		;;
	    
	    "")
		read SUBJECT
		SUBHEX="`echo -n "$SUBJECT" | od -t x1 -A none | tr a-f A-F`"
		if echo $SUBHEX | egrep -q ' [8-9A-F]' ; then
		    # Subject contains non-ASCII.
		    NEWSUB="=?UTF-8?Q?"
		    for CHR in $SUBHEX ; do
			case $CHR in
			    20|3D|3F|8*|9*|A*|B*|C*|D*|E*|F*)
				NEWSUB="$NEWSUB=$CHR"
				;;
			    *)
				NEWSUB="$NEWSUB`echo -en \\\\x$CHR`"
			esac
		    done
		    SUBJHDR="$NEWSUB?="
		else
		    SUBJHDR="$SUBJECT"
		fi
		cat <<EOF 
From: $FROM
To: $MLIST
Subject: $SUBJHDR
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Git-Commit: $commit
X-Git-Parent: $parent

commit $commit
parent $parent
tree $tree
author $author
committer $committer

$SUBJECT
EOF
		# Now the rest of the commit comments
		cat
		echo
		# ... and then the patch itself
		git-diff --patch-with-stat $parent $commit
		echo Mail: $SUBJECT >&2

		break;
		;;
	    
            *)
		# Ignore unknown headers.
		;;
	esac
      done # each header
    
      ) | $SENDMAIL $MLIST
    
    done # each commit

    echo $base > $MAILTAG
fi

