guilt: enhanced patch queue management

guilt prefers to track the status and series of patches
under .git/patches. But this location doesn't allow the
status of a quilt queue to be committed to a secondary
repository and later restored.

This change does three things:

  - allows GUILT_BASE to be changed (with a default to "wrs")
  - allows shadow tracking of the patches (for rebase)
  - enhances the header detection and creation of patches
    as they are pushed onto the tree.

Upstream-Status: Inappropriate [oe-specific]

Signed-off-by: Bruce Ashfield <bruce.ashfield@windriver.com>

---
 guilt |  183 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 159 insertions(+), 24 deletions(-)

--- a/guilt
+++ b/guilt
@@ -153,14 +153,16 @@ get_branch()
 
 verify_branch()
 {
-	[ ! -d "$GIT_DIR/patches" ] &&
+	[ ! -d "$GUILT_DIR" ] &&
 		die "Patches directory doesn't exist, try guilt-init"
-	[ ! -d "$GIT_DIR/patches/$branch" ] &&
+	[ ! -d "$GUILT_DIR/$branch" ] &&
 		die "Branch $branch is not initialized, try guilt-init"
-	[ ! -f "$GIT_DIR/patches/$branch/series" ] &&
+	[ ! -f "$GUILT_DIR/$branch/series" ] &&
 		die "Branch $branch does not have a series file"
-	[ ! -f "$GIT_DIR/patches/$branch/status" ] &&
+	[ ! -f "$GUILT_DIR/$branch/status" ] &&
 		die "Branch $branch does not have a status file"
+	[ -f "$GUILT_DIR/$branch/applied" ] &&
+		die "Warning: Branch $branch has 'applied' file - guilt is not compatible with stgit"
 	[ -f "$GIT_DIR/patches/$branch/applied" ] &&
 		die "Warning: Branch $branch has 'applied' file - guilt is not compatible with stgit"
 }
@@ -339,6 +341,17 @@ BEGIN{}
 '
 }
 
+# usage: do_get_only_patch patchfile
+# similar to do_get_patch except everything leading up to
+# the first diff line and after the last chunk are removed
+do_get_only_patch()
+{
+	cat "$1" | awk '
+BEGIN{}
+/^(diff )/,/^(-- |END{})/
+' | sed '/^-- *$/D'
+}
+
 # usage: do_get_header patchfile
 do_get_header()
 {
@@ -352,8 +365,13 @@ do_get_header()
 BEGIN{skip=0}
 /^Subject:/ && (NR==1){print substr($0, 10); next}
 /^From:/{skip=1; next}
+/^Author:/{skip=1; next}
+/^Date:/{skip=1; next}
+/^commit/{skip=1; next}
 /^[ \t\f\n\r\v]*$/ && (skip==1){skip=0; next}
 /^(diff |---$|--- )/{exit}
+/^diff --git/{exit}
+/^Index: /{exit}
 {print $0}
 END{}
 '
@@ -415,6 +433,15 @@ series_insert_patch()
 	mv "$series.tmp" "$series"
 }
 
+series_append_patch()
+{
+    # unlike series_insert_patch, which inserts the passed
+    # patch after the current top patch, this function always
+    # appends the patch to the series
+
+    echo $1 >> "$series"
+}
+
 # usage: series_remove_patch <patchname>
 series_remove_patch()
 {
@@ -473,8 +500,7 @@ remove_patch_refs()
 # usage: pop_many_patches <commitish> <number of patches>
 pop_many_patches()
 {
-	assert_head_check
-
+	head_check "`tail -1 < "$applied" | cut -d: -f 1`"
 	(
 		cd_to_toplevel
 
@@ -508,50 +534,149 @@ remove_ref()
 	)
 }
 
+prep_patch()
+{
+	patch=$1;
+	tgt=$2;
+
+	if test -f $patch; then
+	    case $patch in
+		*.gz)  gzip -dc $patch > $tgt ;;
+		*.bz2) bzip2 -dc $patch >  $tgt ;;
+		*) cp $patch $tgt ;;
+	    esac;
+	fi;
+}
+
 # usage: commit patchname parent
 commit()
 {
 	(
 		TMP_MSG=`get_tmp_file msg`
+		TMP_PATCH=`get_tmp_file patch`
+		TMP_PATCH_OUT=`get_tmp_file patch_out`
+		TMP_INFO=`get_tmp_file info`
 
 		p="$GUILT_DIR/$branch/$1"
 		pname="$1"
+		prep_patch "$p" "$TMP_PATCH"
+		p=$TMP_PATCH
+
 		cd_to_toplevel
 
 		git diff-files --name-only | (while read n; do git update-index "$n" ; done)
 
+		# borrowed from git-am
+		header_type=git
+                git mailinfo "$TMP_MSG" "$TMP_PATCH_OUT" \
+                        <"$p" >"$TMP_INFO";
+
+                # skip pine's internal folder data
+                grep '^Author: Mail System Internal Data$' \
+                        <"$TMP_INFO" >/dev/null
+
+                git stripspace < "$TMP_MSG" > "$TMP_MSG.clean"
+		mv "$TMP_MSG.clean" "$TMP_MSG"
+		git stripspace < "$TMP_INFO" > "$TMP_INFO.clean"
+		mv "$TMP_INFO.clean" "$TMP_INFO"
+
+		# If mailinfo couldn't get something , try another way
 		# grab a commit message out of the patch
-		do_get_header "$p" > "$TMP_MSG"
+		if [ ! -s "$TMP_MSG" ] || [ ! -s "$TMP_INFO" ]; then
+		    do_get_header "$p" > "$TMP_MSG"
+		    header_type=guilt
+		fi
 
-		# make a default commit message if patch doesn't contain one
-		[ ! -s "$TMP_MSG" ] && echo "patch $pname" > "$TMP_MSG"
+		# last try: make a default commit message if patch doesn't contain one
+		[ ! -s "$TMP_MSG" ] && echo "auto_msg: patch $pname" > "$TMP_MSG"
 
-		# extract a From line from the patch header, and set
-		# GIT_AUTHOR_{NAME,EMAIL}
-		author_str=`sed -n -e '/^From:/ { s/^From: //; p; q; }; /^(diff |---$|--- )/ q' "$p"`
-		if [ ! -z "$author_str" ]; then
+
+		if [ "$header_type" = "guilt" ]; then
+
+  		    # extract a From line from the patch header, and set
+	            # GIT_AUTHOR_{NAME,EMAIL}
+		    author_str=`sed -n -e '/^From:/ { s/^From: //; p; q }; /^(diff |---)/ q' "$p"`
+		    if [ ! -z "$author_str" ]; then
 			GIT_AUTHOR_NAME=`echo $author_str | sed -e 's/ *<.*$//'`
 			export GIT_AUTHOR_NAME="${GIT_AUTHOR_NAME:-" "}"
                         export GIT_AUTHOR_EMAIL="`echo $author_str | sed -e 's/[^<]*//'`"
-		fi
+		    fi
+
+
+		    # check in the patch for a subject
+		    SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$p")"
+		    if [ -z "$SUBJECT" ]; then
+			# if we can't find a subject in the patch, then let's construct
+			# one from the header of the patch itself
+			SUBJECT=`cat "$TMP_MSG" | head -n 1`
+			if [ ${#SUBJECT} -gt 60 ]; then
+			    SUBJECT=${SUBJECT: -60}
+			fi
+		    fi
+
+		    if [ -z "$SUBJECT" ]; then
+			# if we are *still* without a subject, then just use
+			# the patch name
+			SUBJECT=`echo $1 | sed 's%^patch *%%'`
+
+			if [ ${#SUBJECT} -gt 60 ]; then
+			    SUBJECT=${SUBJECT: -60}
+			fi
+		    fi
 
-		# must strip nano-second part otherwise git gets very
-		# confused, and makes up strange timestamps from the past
-		# (chances are it decides to interpret it as a unix
-		# timestamp).
-		export GIT_AUTHOR_DATE="`stat -c %y "$p" | sed -e '
+		    SUBJECT=`echo $SUBJECT | sed s'%^ *%%'`
+
+		    if [ ! -z "$SUBJECT" ]; then
+			echo "$SUBJECT" >> $TMP_MSG.subject
+			echo "" >> $TMP_MSG.subject
+			cat "$TMP_MSG" >> $TMP_MSG.subject
+			mv "$TMP_MSG.subject" "$TMP_MSG"
+		    fi
+
+  		    # must strip nano-second part otherwise git gets very
+		    # confused, and makes up strange timestamps from the past
+		    # (chances are it decides to interpret it as a unix
+		    # timestamp).
+		    export GIT_AUTHOR_DATE="`stat -c %y "$p" | sed -e '\
 s/^\([0-9]\{4\}\)-\([0-9]\{2\}\)-\([0-9]\{2\}\) \([0-9]\{2\}\):\([0-9]\{2\}\):\([0-9]\{2\}\)\.[0-9]* \(.*\)$/\1-\2-\3 \4:\5:\6 \7/'`"
-		export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
+		    export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
+		else
+		    # using git headers, closely related to git-am
+		    
+		    GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$TMP_INFO")"
+		    GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$TMP_INFO")"
+		    GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$TMP_INFO")"
+		    if test -z "$GIT_AUTHOR_EMAIL"
+		    then
+			echo "Warning: patch does not have a valid e-mail address."
+			GIT_AUTHOR_EMAIL=$GIT_AUTHOR_NAME
+		    fi
+
+		    SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$TMP_INFO")"
+		    if [ ! -z "$SUBJECT" ]; then
+			echo "$SUBJECT" >> $TMP_MSG.subject
+			echo "" >> $TMP_MSG.subject
+			cat "$TMP_MSG" >> $TMP_MSG.subject
+			mv "$TMP_MSG.subject" "$TMP_MSG"
+		    fi
+		    export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+		fi
 
 		# commit
 		treeish=`git write-tree`
 		commitish=`git commit-tree $treeish -p $2 < "$TMP_MSG"`
+		if [ ! $? -eq 0 ]; then
+		    echo "ERROR. Could not commit tree"
+		    git-reset --hard HEAD^
+		    exit 1
+		fi
 		git update-ref HEAD $commitish
 
 		# mark patch as applied
 		git update-ref "refs/patches/$branch/$pname" HEAD
 
-		rm -f "$TMP_MSG"
+		rm -f "$TMP_MSG" "$TMP_LOG" "$TMP_PATCH" "$TMP_INFO" "$TMP_PATCH_OUT"
+
 	)
 }
 
@@ -568,7 +693,7 @@ push_patch()
 		bail_action="$2"
 		reject="--reject"
 
-		assert_head_check
+		head_check "`tail -1 < "$applied" | cut -d: -f 1`"
 		cd_to_toplevel
 
 		# apply the patch if and only if there is something to apply
@@ -671,7 +796,7 @@ refresh_patch()
 #			 incldiffstat
 __refresh_patch()
 {
-	assert_head_check
+	head_check "`tail -1 < "$applied" | cut -d: -f 1`"
 
 	(
 		TMP_DIFF=`get_tmp_file diff`
@@ -711,6 +836,10 @@ __refresh_patch()
 
 		head -n "-$N" < "$applied" > "$applied.tmp"
 		mv "$applied.tmp" "$applied"
+
+		# update the shadow status.
+		ref=`cat $GIT_DIR/refs/tags/${branch}_top`
+		echo "$ref:$1" >> $applied_shadow
 	)
 }
 
@@ -791,7 +920,12 @@ diffstat=`git config --bool guilt.diffst
 # The following gets run every time this file is source'd
 #
 
-GUILT_DIR="$GIT_DIR/patches"
+
+# GUILT_DIR="$GIT_DIR/patches"
+if [ -z "$GUILT_BASE" ]; then
+	GUILT_BASE=wrs
+fi
+GUILT_DIR="$GUILT_BASE/patches"
 
 branch=`get_branch`
 
@@ -814,6 +948,7 @@ fi
 # very useful files
 series="$GUILT_DIR/$branch/series"
 applied="$GUILT_DIR/$branch/status"
+applied_shadow="$GUILT_DIR/$branch/shadow_status"
 guards_file="$GUILT_DIR/$branch/guards"
 
 # determine a pager to use for anything interactive (fall back to more)
