#!/bin/bash

# patch management
#
# Copyright (C) 2003 Neil Brown <neilb@cse.unsw.edu.au>
#
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#    Author: Neil Brown
#    Email: <neilb@cse.unsw.edu.au>
#    Paper: Neil Brown
#           School of Computer Science and Engineering
#           The University of New South Wales
#           Sydney, 2052
#           Australia


# metadata is in .patches
# there is:
#   files:  list of all files checked out
#   name:   name of current patch
#   status: status of current patch
#   notes:  notes on current patch
#   applied/  patches applied nnn-name
#   removed/  patches removed nnn-name
#   included/ patches that have been included upstream
#   patch:  a recent copy of the 'current' patch
#   get-version: a script which will report the version number of the base dist
#   dest/   symlink to directory to publish snapshots to
#   mail/   composed mail messages ready for sending
#   maintainer  who to email patches to (Linus etc)
#   cc        who to CC patches to: prefix address
#
# the nnn in names in applied and removed are sequence numbers
# whenever we add a file we choose one more than the highest used number
# patch files contain then name implicitly and start with
#   Status: status
# then a blank line, normally a one line description, another blank, and more detail.
#

#
# Todo - auto bk pull:
#    bk pull
#    bk export -t patch -r DEVEL, > /tmp/apatch
#    bk tag DEVEL
#    while p open last && p discard ; do : ; done
#    p clean
#    patch -p1 -f < /tmp/apatch

find_home()
{
	# walk up directory tree until a .patches directory
	# is found.
	# set OrigDir to name of where we were .. not dots.
	OrigDir=
	dir=`pwd`
	while [ ! -d .patches -a " $dir"  != " /" ]
	do
		base=${dir##*/}
		base=${base#/}
		dir=${dir%/*}
		case $dir in
			"" ) dir=/
		esac
		OrigDir=$base/$OrigDir
		cd ..
	done
	test -d .patches
}

get_meta()
{
	name=`cat .patches/name 2> /dev/null`
	status=`cat .patches/status 2> /dev/null`
}

nl='
'
get_conf()
{
	_name=$1
	_context=$2
	_result=
	_active=yes
	_sep=
	[ -f .patches/config ] || >> .patches/config
	while read a b c
	do
	    case $a in
		    '[global]' ) _active=yes ;;
		    "[$_context]") _active=yes ;;
		    "["*"]" ) _active= ;;
		    * ) if [ " $b" == " =" -a " $a" = " $_name" -a -n "$_active" ];
			    then
			    if [ -z "$c" ]; then
				_result= _sep=
			    else
			        _result="$_result$_sep$c"
			        _sep=$nl
                            fi
			    fi
			    ;;
	    esac
	done < .patches/config
	_result=$(echo "$_result" | sed 's/^"//' )
	eval $_name=\"\$_result\"
}

upgrade_one()
{
	# move $1~current~ to .patches/current/$1 and same for orig
	fl=/$1
	for f in current orig
	do
	    if [ -f "$1~$f~" ]
		then
		    mkdir -p ".patches/$f${fl%/*}"
		    mv "$1~$f~" ".patches/$f/$1"
	    fi
	done
}


forget_one()
{
	if true # || cmp -s "$1" ".patches/curent/$1~" && cmp -s "$1" ".patches/orgi/$1"
	then
            rm -f ".patches/current/$1" ".patches/orig/$1"
	    chmod -w "$1"
	else
	    echo >&2 "ERROR $1 doesn't match original"
	fi
}

rebase_one()
{
    f="/$1"
    mkdir -p .patches/orig${f%/*}
    mkdir -p .patches/current${f%/*}
    rm -f .patches/orig$f .patches/current$f
    cp -p $1 .patches/orig$f
    cp -p $1 .patches/current$f
}

snap_one()
{
    cp "$1" "$1~snapshot~"
}

snap_diff()
{
    diff -u "$1" "$1~snapshot~"
}
snap_back()
{
    cp "$1~snapshot~" "$1"
}

check_out()
{
	file=$1
	file=${file#./}
	f=/$file; f=${f%/*}
	[ -f $file ] || >> $file
	if [ -f $file ]
	    then
	    if [ ! -f ".patches/orig/$file" ] ; then
		mkdir -p .patches/orig/$f
		mv "$file" ".patches/orig/$file"
		cp ".patches/orig/$file" "$file"
		echo $file >> .patches/files
		sort -o .patches/files .patches/files
		chmod u+w "$file"
	    fi
	    if [ ! -f ".patches/current/$file" ] ; then
		mkdir -p .patches/current/$f
		mv "$file" ".patches/current/$file"
		cp ".patches/current/$file" "$file"
	    fi
	else
	    echo >&2 Cannot checkout $file
	fi
}

all_files()
{
        >> .patches/files
	while read file
	do eval $1 $file $2
	done < .patches/files
}

diff_one()
{
	if cmp -s ".patches/current/$1" "$1" || [ ! -f "$1" -a ! -f ".patches/current/$1" ]
	then :
	else
		echo
		echo "diff .prev/$1 ./$1"
		if [ " $2" = " -R" ]
		then
		  diff -N --show-c-function -u "./$1" "./.patches/current/$1"
		else
		  diff -N --show-c-function -u "./.patches/current/$1" "./$1"
		fi
	fi
}

diff_one_orig()
{
	if cmp -s ".patches/orig/$1" "$1"
	then :
	else
		echo
		echo "diff ./.patches/orig/$1 ./$1"
		diff --show-c-function -u "./.patches/orig/$1" "./$1"
	fi
}

commit_one()
{
    rm -f ".patches/current/$1"
    if [  -f "$1" ] ; then
	mv "$1" ".patches/current/$1"
	cp -p ".patches/current/$1" $1
	chmod u+w $1
    fi
}

discard_one()
{
	cmp -s ".patches/current/$1" $1 || { rm -f "$1" ; cp ".patches/current/$1" $1; }
	chmod u+w $1
}

swap_one()
{
	mv "$1" "$1.tmp"
	mv ".patches/current/$1" "$1"
	mv "$1.tmp" ".patches/current/$1"
}

make_diff()
{
	get_conf tagline
	upgrade_one "$1"
   { {
	[ -s .patches/status ] && echo "Status: `cat .patches/status`"
	[ -s .patches/notes ] && { echo; cat .patches/notes ; }
	if [ -z "$tagline" ] || grep -F "$tagline" .patches/notes > /dev/null 2>&1
	then :
        else echo "$tagline"
	fi
	echo
	all_files diff_one $1 > .patches/tmp
	echo "### Diffstat output"
	diffstat -p0 2> /dev/null < .patches/tmp
	cat .patches/tmp
	[ -s .patches/tmp ] || rm .patches/patch
	rm .patches/tmp
   } | sed 's,^--- ./.patches/current/,--- .prev/,' ; } > .patches/patch
}

save_patch()
{
	dir=.patches/$1
	name=$2
	# move .patches/patch to $dir/nnn$name
	#for some new nnn
	[ -d $dir ] || mkdir $dir || exit 1
	largest=`ls $dir | sed -n -e 's/^\([0-9][0-9][0-9]\).*/\1/p' | sort -n | tail -1`
	if [ "0$largest" -eq 999 ]
	then echo >&2 'ARRG - too many patches!' ; exit 1
	fi
	new=`expr "0$largest" + 1001`
	new=${new#1}
	mv .patches/patch $dir/$new$name
}

find_prefix()
{
	# set "prefix" to number for -pn by looking at first file in given patch.
	n=${2-1}
	file=`lsdiff $1 | head -$n | tail -1`
	orig=$file
	prefix=0
	case $file in
	    b/* ) prefix=1; return
	esac
	while [ \( -n "$file" -a ! -f "$file" \) -o " $file" != " ${file#/}" ]
	do
	    file=`expr "$file" : '[^/]*/\(.*\)'`
	    prefix=`expr $prefix + 1`
	done
	if [ -z "$file" ]
	then echo "Cannot find $orig" >&2
	   if [ $n -gt 4 ]
	   then exit 2;
	   else find_prefix "$1" $[n+1]
	   fi
	fi
	if [ " $orig" != " $file" ]
	then
	    echo "Found $orig as $file - prefix $prefix"
	fi
}

extract_notes()
{
 # remove first line, Status: line, leading blanks,
 # everything from ' *---' and trailing blanks
 awk '
    BEGIN { head= 1; blanks=0 ; }
    head == 1 && ( $1 == "Status:" || $0 == "" ) {
	    next;
    }
    { head = 0; }
    $0 == "" { blanks++; next; }
    $0 ~ /^ *---/ { exit }
    $0 ~ /^###/ { exit }
    {   while (blanks > 0) {
	   blanks--; print "";
	}
	print $0;
    }
  ' $1
}


if [ $# -eq 0 ]
then
  echo >&2 'Usage: p [help|co|make|discard|commit|status|name|...] args'
  exit 1
fi
cmd=$1
shift

if [ " $cmd" = " help" ] || find_home
then :
else echo >&2 "p $cmd: cannot find .patches directory"
     exit 1
fi

case $cmd in
   co )
	if [ $# -ne 1 ] ; then
		echo >&2 Usage: p co file; exit 1
	fi
	file=$1
	if [ ! -f "$OrigDir$file" ]
	then
		echo >&2 "p co: file $file not found"; exit 1;
	fi
	check_out "$OrigDir$file"

	;;
  make | view )
	case $1 in
	    "" )
		make_diff
		if [ -s .patches/patch ] ; then
		    pfile=.patches/patch
		else
		    echo >&2 "No current patch" ; exit 1;
		fi
		;;

	    */* ) pfile=$1;;
	    * ) pfile=`echo .patches/[ra][ep][mp]*/*$1*`
	esac
	if [ ! -f "$pfile" ]
	then echo >&2 "Cannot find unique patch '$1' - found: $pfile"; exit 1;
	fi
	ptn='^\+.*((	|[^	].{7}){10}.|[ 	]$)'
	if grep -E -s "$ptn" $pfile > /dev/null
	then
	    ${PAGER-less -p "$ptn"} $pfile
	else
	    ${PAGER-less} $pfile
	fi
	;;

  all )
	all_files diff_one_orig
	;;
  status | name )
	case $# in
	 1 )
		get_meta
		if [ $cmd = name ] ; then
		  if [ -n "$name" ]; then
			echo "changing name from '$name' to '$1'"
		  else
			echo "Setting name to '$1'"
		  fi
		  echo "$1" > .patches/name
		fi
		if [ $cmd = status ] ; then
		  if [ -n "$status" ]; then
			echo "changing status from '$status' to '$1'"
		  else
			echo "Setting status to '$1'"
		  fi
		  echo "$1" > .patches/status
		fi
		;;
	  0 )
		get_meta
		echo -n "Name ($name)? " ; read name
		echo -n "Status ($status)?  " ; read status
		[ -n "$name" ] && { echo $name > .patches/name ; }
		[ -n "$status" ] && { echo $status > .patches/status ; }
		;;
	  * )
		echo "Usage: p $cmd [new-$cmd]"; exit 1;
	esac
	;;
  note* )
	>> .patches/notes
	${EDITOR:-vi} .patches/notes
	;;
  discard|commit )
	make_diff
	if [ -s .patches/patch ]
	then :
	else echo >&2 No patch to $cmd ; exit 1
	fi
	if grep -s '^+.*[ 	]$' .patches/patch > /dev/null
	then
	    echo >&2 remove trailing spaces/tabs first !!
#	    exit 1
	fi
	if [ $cmd == "commit" -a -f scripts/checkpatch.pl ] ; then
	    perl scripts/checkpatch.pl .patches/patch
	fi
	if [ -s .patches/to-resolve ]
	then echo "Please resolve outstanding conflicts first with 'p resolve'"
	    exit 1
	fi
	get_meta
	if [ -z "$name" ] ; then
		echo -n "Name? " ; read name
		if [ -z "$name" ] ; then
		   echo >&2 "No current name, please set with 'p name'"
		   exit 1;
		fi
		echo $name > .patches/name
	fi
	if [ -z "$status" ] ; then
		echo -n "Status? " ; read status
		if [ -z "$status" ] ; then
		    echo >&2 "No current status, please set with 'p status'"
		    exit 1;
		fi
		echo $status > .patches/status
	fi
	if [ -s .patches/notes ]
	then :
	else
	    { echo "Title...."
	      echo
	      echo "Description..."
	      echo
	      echo "====Do Not Remove===="
	      cat .patches/patch
	    } > .patches/notes
	    ${EDITOR-vi} .patches/notes
	    mv .patches/notes .patches/tmp
	    sed '/^====Do Not Remove====/,$d' .patches/tmp > .patches/notes
	    rm .patches/tmp
	fi
	make_diff
	
	if [ $cmd = commit ] ; then
	   save_patch applied "$name"
	   echo Saved as $new$name
	   all_files commit_one
	else
	   save_patch removed "$name"
	   echo Saved as $new$name
	   all_files discard_one
	fi
	rm -f .patches/name .patches/status .patches/notes
	;;

  purge )
        make_diff
	mv .patches/patch .patches/last-purge
	all_files discard_one
	rm -f .patches/name .patches/status .patches/notes
	;;
  open )
	make_diff
	get_meta
	if [ -s .patches/patch ]
	then
		echo >&2 Patch $name already open - please commit; exit 1;
	fi
	if [ $# -eq 0 ]
	then
		echo "Available patches are:"
		ls .patches/applied
		exit 0
	fi
	if [ $# -ne 1 ]
	then echo >&2 "Usage: p open patchname" ; exit 1
	fi
	if [ " $1" = " last" ]
	then
	    pfile=`ls -d .patches/applied/[0-9]* | tail -1`
        else
	    pfile=`echo .patches/applied/*$1*`
	fi
	if [ ! -f "$pfile" ]
	then echo >&2 "Cannot find unique patch '$1' - found: $pfile"; exit 1
	fi
	# lets see if it applies cleanly
	if patch -s --fuzz=0 --dry-run -R -f -p0 < "$pfile"
	then echo Ok, it seems to apply
	else echo >&2 "Sorry, that patch doesn't apply" ; exit 1
	fi
	# lets go for it ...
	patch --fuzz=0 -R -f -p0 < "$pfile"
	all_files swap_one
	sed -n -e '2q' -e 's/^Status: *//p' $pfile > .patches/status
	base=${pfile##*/[0-9][0-9][0-9]}
	[ -s .patches/name ] || echo $base > .patches/name
	extract_notes $pfile >> .patches/notes
	mv $pfile .patches/patch

	;;
  included )
	force=
	if [ " $1" = " -f" ] ; then
	    force=yes; shift
	fi
	make_diff; get_meta
	if [ -s .patches/patch ]
	then
	    echo >&2 Patch $name already open, please commit; exit 1;
	fi
	if [ $# -eq 0 ]
	then
	    echo "Unapplied patches are:"
	    ls .patches/removed
	    exit 0;
	fi
	if [ $# -ne 1 ]
	then
	     echo >&2 "Usage: p included patchname"; exit 1
	fi
	case $1 in
	    last ) pfile=`ls -d .patches/removed/[0-9]* | tail -1` ;;
	    */* ) echo >&2 "Only local patches can have been included"; exit 1 ;;
	    *) pfile=`echo .patches/removed/*$1*`
	esac
	if [ ! -f "$pfile" ]
	then echo >&2 "Cannot find unique patch '$1' - found $pfile"; exit 1
	fi
	echo "Using $pfile..."

	# make sure patch applies in reverse
	if patch -s --fuzz=2  -l --dry-run -f -p0 -R < "$pfile"
	then echo "Yep, that seems to be included"
	elif [ -n "$force" ]
	then echo "It doesn't apply reverse-out cleanly, but you asked for it..."
	else echo >&2 "Sorry, patch cannot be removed"; exit 1
	fi
	mv "$pfile" .patches/patch
	name=${pfile##*/[0-9][0-9][0-9]}
	save_patch included $name
	echo "Moved to $new$name"
	;;
  review )
	# there are some patches in .removed that may be included in the current source
	# we try to backout each one. If it backs out successfully, we move it to
	# .reviewed and continue, else  we abort
	# Once this has been done often enough, 'reviewed' should be run to
	# move stuff to 'included' and to revert those patches
	force=
	if [ " $1" = " -f" ] ; then
	    force=yes; shift
	fi
	make_diff; get_meta
	if [ -s .patches/patch ]
        then
	    echo >&2 Patch $name already open, please deal with it; exit 1;
	fi
	if [ -f .patches/in-review ]
	then :
	else
		applied=`ls .patches/applied`
		if [ -n "$applied" ]
		then
			echo >&2 Cannot review patches while any are applied.
			exit 1;
		fi
		> .patches/in-review
	fi
	if [ $# -eq 0 ]
	then
	    echo "Pending patches are:"
	    ls .patches/removed
	    exit 0;
	fi
	if [ $# -ne 1 ]
	then
	     echo >&2 "Usage: p review patchname"; exit 1
	fi
	case $1 in
	    */* ) echo >&2 "Only local patches can have been included"; exit 1 ;;
	    *) pfile=`echo .patches/removed/*$1*`
	esac
	if [ ! -f "$pfile" ]
	then echo >&2 "Cannot find unique patch '$1' - found $pfile"; exit 1
	fi
	echo "Starting from $pfile..."
	found=
	for fl in .patches/removed/*
	do
	  if [ " $fl" = " $pfile" ]; then found=yes ; fi
          if [ -n "$found" ]; then
	      echo Checking $fl
	      find_prefix "$fl"
	      lsdiff --strip=$prefix "$fl" | grep -v 'file.*changed' | while read a b
	      do check_out $a
	      done
	      if patch -s --fuzz=0 --dry-run -f -p$prefix -R < "$fl"
	      then echo Looks good..
	      elif [ -n "$force" ]
	      then echo "It doesn't backout cleanly, but you asked for it..."
		  cp $fl .patches/last-backed
	      else echo "Patch won't back out, sorry"
		  exit 1
	      fi
	      patch --fuzz=0 -f -p$prefix -R < "$fl" | tee .patches/tmp
	      sed -n -e '2q' -e 's/^Status: *//p' $fl > .patches/status
	      base=${fl##*/}
	      base=${base##[0-9][0-9][0-9]}
	      base=${base##patch-?-}
	      [ -s .patches/name ] || echo $base > .patches/name
	      extract_notes $fl >> .patches/notes
	      rm -f .patches/wiggled
	sed -n -e 's/.*saving rejects to file \(.*\).rej/\1/p' .patches/tmp |
	while read file
	do  echo Wiggling $file.rej into place
	    rm -f $file.porig
	    > .patches/wiggled
	    wiggle --replace --merge $file $file.rej ||
	    echo $file >> .patches/to-resolve
	done

	      mv $fl .patches/patch
	      save_patch reviewed $base
	      if [ -f .patches/wiggled ]
	      then echo 'Some wiggling was needed. Please review and commit'
		  exit 0
	      fi
	      p commit || exit 1
	  fi
        done
	;;

  reviewed )
	      # all the currently applied patches are patches that have been
	      # reviewed as included.
	      # rip them out and stick them (reversed) into included.
	      if [ ! -f .patches/in-review ]
		  then
		      echo >&2 Not currently reviewing patches!
		      exit 1;
	      fi
	      while p open last
	      do
		make_diff -R
		get_meta
		save_patch included "$name"
		echo Saved as "$new$name"
		all_files discard_one
		rm -f .patches/name .patches/status .patches/notes
	      done
	      rm .patches/in-review
	      ;;
  list )
	    echo "Applied patches are:"
	    ls .patches/applied

	    echo "Unapplied patches are:"
	    ls .patches/removed
	    exit 0
	    ;;
  lista )
	    echo "Applied patches are:"
	    ls .patches/applied
	    exit 0
	    ;;
 apply )
	if [ -f .patches/in-review ]
	then
		echo >&2 Cannot apply patches while reviewing other - use p reviewed
		exit 1
	fi
	force= append=
	if [ " $1" = " -f" ]; then
	    force=yes; shift
	fi
	if [ " $1" = " -a" ]; then
	    append=yes; shift
	fi
	make_diff
	get_meta
	if [ -s .patches/patch -a -z "$append" ]
	then
	    echo >&2 Patch $name already open - please commit ; exit 1;
	fi
	if [ $# -eq 0 ]
	then
	    echo "Unapplied patches are:"
	    ls .patches/removed
	    exit 0
	fi
	if [ $# -ne 1 ]
	then echo >&2 "Usage: p apply patchname"; exit 1
	fi
	case $1 in
	    last ) pfile=`ls -d .patches/removed/[0-9]* | tail -1` ; echo last is "$pfile";;
	    */* ) pfile=$1 ;;
	    * ) pfile=`echo .patches/removed/*$1*`
	esac
	if [ ! -f "$pfile" ]
	then echo >&2 "Cannot find unique patch '$1' - found: $pfile"; exit 1
	fi
	find_prefix "$pfile"
	lsdiff --strip=$prefix "$pfile" | grep -v 'file.*changed' | while read a b
	do check_out $a
	done
	# lets see if it applies cleanly
	if patch -s --fuzz=0 --dry-run -f -p$prefix < "$pfile"
	then echo OK, it seems to apply
	elif [ -n "$force" ]
	then echo "It doesn't apply cleanly, but you asked for it...."
	    echo "Saving original at .patches/last-conflict"
	    cp $pfile .patches/last-conflict
	else echo >&2 "Sorry, patch doesn't apply"; exit 1
	fi
	# lets go for it ...
	cp $pfile .patches/last-applied
	patch --fuzz=0 -f -p$prefix < "$pfile" | tee .patches/tmp
	sed -n -e '2q' -e 's/^Status: *//p' $pfile > .patches/status
	base=${pfile##*/}
	base=${base##[0-9][0-9][0-9]}
	base=${base##patch-?-}
	[ -s .patches/name ] || echo $base > .patches/name
	extract_notes $pfile >> .patches/notes

	sed -n -e 's/.*saving rejects to file \(.*\).rej/\1/p' .patches/tmp |
	while read file
	do  echo Wiggling $file.rej into place
	    rm -f $file.porig
	    wiggle --replace --merge $file $file.rej ||
	    echo $file >> .patches/to-resolve
	done

	case $pfile in
	    .patches/removed/* )
		mv $pfile .patches/patch
        esac
	;;

  unapply )
	get_meta
	mv .patches/last-applied .patches/patch
	save_patch removed $name
	echo Restored to $new$name
	make_diff
	mv .patches/patch .patches/last-purge
	all_files discard_one
	rm -f .patches/name .patches/status .patches/notes
	;;
  publish )
	name=`date -u +%Y-%m-%d-%H`
	if [ -d .patches/dest ]
	then : good
	else echo >&2 No destination specified at .patches/dest ; exit 1;
	fi
	if [ -d .patches/dest/$name ]
	then
	    echo >&2 $name already exists ; exit 1
	fi
	target=.patches/dest/$name
	mkdir $target
	if [ -f .patches/get-version ] ;
	then ./.patches/get-version > $target/version
	fi
	[ -f .config ] && cp .config $target
	cp .patches/applied/* $target
	mkdir $target/misc
	cp 2> /dev/null .patches/removed/* $target/misc || rmdir $target/misc
	chmod -R a+rX $target
	all_files diff_one_orig > $target/patch-all-$name
	cd $target
	echo Published at `/bin/pwd`
	;;
  clean )
	all_files forget_one
	> .patches/files
	;;
  openall )
        while $0 open last && $0 discard ; do : ; done
	;;
  recommit )
	make_diff
	get_meta
	if [ -s .patches/patch ]
	then
	    echo >&2 Patch $name already open - please commit ; exit 1;
	fi
	if [ $# -eq 0 ]
	then
	    echo "Unapplied patches are:"
	    ls .patches/removed
	    exit 0
	fi
	if [ $# -ne 1 ]
	then echo >&2 "Usage: p recommit patchname"; exit 1
	fi
	case $1 in
	    last ) pfile=`ls -d .patches/removed/[0-9]* | tail -1` ; echo last is "$pfile";;
	    */* ) pfile=$1 ;;
	    * ) pfile=`echo .patches/removed/*$1*`
	esac
	if [ ! -f "$pfile" ]
	then echo >&2 "Cannot find unique patch '$1' - found: $pfile"; exit 1
	fi
	while [ -s "$pfile" ]  &&
		$0 apply last && $0 commit ; do : ; done
	;;
  decommit )
	make_diff
	get_meta
	if [ -s .patches/patch ]
	then
	    echo >&2 Patch $name already open - please commit ; exit 1;
	fi
	if [ $# -eq 0 ]
	then
	    echo "Applied patches are:"
	    ls .patches/applied
	    exit 0
	fi
	if [ $# -ne 1 ]
	then echo >&2 "Usage: p decommit patchname"; exit 1
	fi
	case $1 in
	    last ) pfile=`ls -d .patches/applied/[0-9]* | tail -1` ; echo last is "$pfile";;
	    */* ) pfile=$1 ;;
	    * ) pfile=`echo .patches/applied/*$1*`
	esac
	if [ ! -f "$pfile" ]
	then echo >&2 "Cannot find unique patch '$1' - found: $pfile"; exit 1
	fi
	while [ -s "$pfile" ]  &&
	     $0 open last && $0 discard ; do : ; done
	;;

  rebase )
	# move all applied patches to included, and
	# copy current to orig and current
	make_diff
	if [ -s .patches/patch ]
	then
	    echo >&2 Patch already open - please commit; exit 1;
	fi
	for p in `ls .patches/applied`
	do
	  name=${p##[0-9][0-9][0-9]}
	  mv .patches/applied/$p .patches/patch
	  save_patch included $name
	done
	all_files rebase_one
	;;
  snapshot )
	all_files snap_one
	;;
  snapdiff )
	all_files snap_diff
	;;
  snapback )
	all_files snap_back
	;;
  upgrade )
	all_files upgrade_one
	;;
  resolve )
        if [ ! -s .patches/resolving ]
	then sort -u .patches/to-resolve > .patches/resolving ; > .patches/to-resolve
        fi
        if [ ! -s .patches/resolving ]
	then echo "Nothing to resolve" ; exit 0;
	fi
	echo "Resolving: " ; cat .patches/resolving
	for file in `cat .patches/resolving`
	do
	  ${EDITOR:-vi} $file
	  rm -f $file.porig
	  wiggle --replace --merge $file ||
	    echo $file >> .patches/to-resolve
	done
	> .patches/resolving
	;;

  export )
	# there must be only one patch.  We
	# git commit, p commit, p rebase
	if [ -n "`ls .patches/applied`" ]
	then
	    echo 'Cannot export when there are applied patches'
	    exit 1;
	fi
	make_diff
	if [ -s .patches/patch ]
	then
	    # Ok, go for it.
	    git add `cat .patches/files`
	    author=`grep '^From:' .patches/notes | head -n 1 | sed 's/From: *//'`
	    if [ -n "$author" ]
		then git commit --author="$author" -a -F .patches/notes
		else git commit -a -F .patches/notes
	    fi
	    $0 commit
	    $0 rebase
	fi
	;;
  pull )
        cd .patches/SOURCE && bk pull
	;;
  update )
	make_diff
	get_meta
	if [ -s .patches/patch ]
	then
		echo >&2 Patch $name already open - please commit; exit 1;
	fi
        p openall && p clean &&
	  (cd .patches/SOURCE ; bk export -tpatch -rLATEST, ) > .patches/imported-patch &&
	  patch --dry-run -f -p1 < .patches/imported-patch &&
	  patch -f -p1 < .patches/imported-patch &&
	  ( rm .patches/imported-patch ; cd .patches/SOURCE ; bk tag LATEST )
	;;

  premail )
  	# Convert some applied patches into email messages.
  	# Select patches that start with $1. Look in .patches/cc for who to Cc: to
  	rmdir .patches/mail 2>/dev/null
	if [ -d .patches/mail ] ; then
	    echo >&2 There is already some email - run "email" or "nomail"
	    ls .patches/mail
	    exit 1;
	fi
	mkdir .patches/mail

	get_conf author $1
	get_conf header $1
	if [ -n "$author" ]
	then
		headers="From: $author"
		if [ -n "$header" ] ; then
		    headers="$headers$nl$header"
		fi
	elif [ -s .patches/owner ]; then
		headers=`cat .patches/owner`;
        else
		echo Please add author information to .patches/config
		exit 1
	fi
	get_conf maintainer $1
	if [ -z "$maintainer" -a -s .patches/maintainer ]
	    then
		maintainer=`cat .patches/maintainer`
	fi

	if [ -z "$maintainer" ] ; then
	    echo "No maintainer - please add one"
	    exit 1;
	fi

	messid="<`date +'%Y%m%d%H%M%S'`.$$.patches@`uname -n`>"
	cnt=0
	> .patches/.tmp.cc
	for patch in .patches/applied/???${1}*
	do
          n=${patch##*/}
	  n=${n:0:3}
	  if [ -n "$2" ] && [ $2 -gt $n ] ; then continue; fi
	  if [ -n "$3" ] && [ $3 -lt $n ] ; then continue; fi
	  if [ -n "$4" ]; then
	      case ,$4,  in *,$n,* ) ;; *) continue; esac
	  fi
	  cnt=$(expr $cnt + 1 )
	  sed -n -e 's/^\(Signed-[Oo]ff-[Bb]y\|Acked-[Bb]y\|Cc\|From\): */Cc: /p' $patch | grep -v neilb >> .patches/.tmp.cc
	done
	get_conf cc $1
	get_conf tag $1
	this=1
	if [ $cnt -gt 1 ]
	then
	{
	    echo "$headers"
	    echo "To: $maintainer"

	    if [ -n "$cc" ]; then
		    echo "Cc: $cc"
	    fi
	    if [ -n "$tag" ]; then
		    sprefix="$tag: "
	    fi
	    if [ -s .patches/.tmp.cc ]
	    then sort -u .patches/.tmp.cc
	    fi
	    if [ -s .patches/cc ] ; then
		while read word prefix addr
		  do if [ " $word" = " $1" ] ; then
			echo "Cc: $addr"
			sprefix="$prefix: "
		    fi
		  done < .patches/cc
	    fi
	    if [ $cnt = 1 ]
		  then
		  echo "Subject: [PATCH] ${sprefix}Intro"
		  else
		  echo "Subject: [PATCH 000 of $cnt] ${sprefix}Introduction EXPLAIN PATCH SET HERE"
	    fi
	    echo "Message-ID: $messid"
	    echo
	    echo PUT COMMENTS HERE
	} > .patches/mail/000Intro
	fi

	for patch in .patches/applied/???${1}*
	do
          n=${patch##*/}
	  n=${n:0:3}
	  if [ -n "$2" ] && [ $2 -gt $n ] ; then continue; fi
	  if [ -n "$3" ] && [ $3 -lt $n ] ; then continue; fi
	  if [ -n "$4" ]; then
	      case ,$4,  in *,$n,* ) ;; *) continue; esac
	  fi
	  if [ -f ./scripts/checkpatch.pl ] 
	  then perl ./scripts/checkpatch.pl $patch
	  fi
	  {
	      sprefix=
	      echo "$headers"
	      echo "To: $maintainer"
	      if [ -n "$cc" ]; then
		    echo "Cc: $cc"
	      fi
	      sed -n -e 's/^\(Signed-[Oo]ff-[Bb]y\|Acked-[Bb]y\|Cc\|From\): */Cc: /p' $patch | grep -v neilb | sort -u
	      if [ -n "$tag" ]; then
		    sprefix="$tag: "
	      fi
	      if [ -s .patches/cc ] ; then
		  while read word prefix addr
		    do if [ " $word" = " $1" ] ; then
			echo "Cc: $addr"
			sprefix="$prefix: "
		    fi
		  done < .patches/cc
	      fi
	      head=`sed -e '/^Status/d' -e '/^$/d' -e q $patch`
	      zerothis=$(expr $this + 1000)
	      if [ $cnt = 1 ]
		  then
		  echo "Subject: [PATCH] $sprefix$head"
		  else
		  echo "Subject: [PATCH ${zerothis#1} of $cnt] $sprefix$head"
	      fi
	      echo "References: $messid"
	      echo
	      if [ $cnt = 1 ] ; then
		  echo "### Comments for Changeset"
	      fi
	      sed -e '1,3d' $patch
	  } > .patches/mail/${patch#.patches/applied/}
	  this=$(expr $this + 1)
	done
	if [ -f .patches/mail/000Intro ]; then cat .patches/mail/* | sed -n -e 's/^Subject://p'  >> .patches/mail/000Intro ; fi
	ls .patches/mail
	;;

    nomail )
	  echo "Removing .patches/mail directory"
	  rm -rf .patches/mail
	  ;;

     email )
     	PATH=$HOME/bin:/usr/lib:/usr/sbin:$PATH
          for i in .patches/mail/*
	  do
	    if [ -f "$i" ]
		then
		echo Sending $i.
		sendmail -t < $i && rm $i
	    fi
	  done
	  ;;

   test )
     # test all removed patches to see which ones are clearly included
     for p in .patches/removed/*
     do
        if patch -R --dry-run -p0 --fuzz=0 -s -f < "$p" > /dev/null 2>&1
        then echo $p
        fi
     done
     ;;
   help )
	helpfile=$0.help
	if [ ! -f $helpfile ]
	then echo >&2 $helpfile not found: no help available ; exit 2;
	fi
        if [ -z "$1" ] ; then
	   echo
	   sed -n -e '/^ /p' -e '/^[^ ]/q' $helpfile
	   echo
	   echo "Available help topics are:"
	   sed -n '/^[^ ]/p' $helpfile | sort | column
	else
	    echo
	    awk '$0 ~ /^[^ ]/ && printed {doprint=0; printed=0}
      		doprint && $0 !~ /^[^ ]/ {print; printed=1;}
		$0 == "'$1'" {doprint=1; found=1}
		END { if (!found) print "No help available for '$1'"; }
		' $helpfile
	    echo
        fi
	;;
  * )
	echo >&2 "p $cmd - unknown command - try 'p help'"; exit 1;
esac
exit 0;
