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 --- 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 series_remove_patch() { @@ -473,8 +500,7 @@ remove_patch_refs() # usage: pop_many_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)