5 # Copyright (C) 2003 Neil Brown <neilb@cse.unsw.edu.au>
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 # Email: <neilb@cse.unsw.edu.au>
25 # School of Computer Science and Engineering
26 # The University of New South Wales
31 # metadata is in .patches
33 # files: list of all files checked out
34 # name: name of current patch
35 # status: status of current patch
36 # notes: notes on current patch
37 # applied/ patches applied nnn-name
38 # removed/ patches removed nnn-name
39 # included/ patches that have been included upstream
40 # patch: a recent copy of the 'current' patch
41 # get-version: a script which will report the version number of the base dist
42 # dest/ symlink to directory to publish snapshots to
43 # mail/ composed mail messages ready for sending
44 # maintainer who to email patches to (Linus etc)
45 # cc who to CC patches to: prefix address
47 # the nnn in names in applied and removed are sequence numbers
48 # whenever we add a file we choose one more than the highest used number
49 # patch files contain then name implicitly and start with
51 # then a blank line, normally a one line description, another blank, and more detail.
55 # Todo - auto bk pull:
57 # bk export -t patch -r DEVEL, > /tmp/apatch
59 # while p open last && p discard ; do : ; done
61 # patch -p1 -f < /tmp/apatch
65 # walk up directory tree until a .patches directory
67 # set OrigDir to name of where we were .. not dots.
70 while [ ! -d .patches
-a " $dir" != " /" ]
78 OrigDir
=$base/$OrigDir
86 name
=`cat .patches/name 2> /dev/null`
87 status
=`cat .patches/status 2> /dev/null`
92 if true
# || cmp -s "$1" "$1~current~" && cmp -s "$1" "$1~orig~"
94 rm -f "$1~current~" "$1~orig~"
97 echo >&2 "ERROR $1 doesn't match original"
104 cp "$1" "$1~snapshot~"
109 diff -u "$1" "$1~snapshot~"
113 cp "$1~snapshot~" "$1"
120 [ -f $file ] ||
>> $file
123 if [ ! -f "$file~orig~" ] ; then
124 mv "$file" "$file~orig~"
125 cp "$file~orig~" "$file"
126 echo $file >> .patches
/files
127 sort -o .patches
/files .patches
/files
130 if [ ! -f "$file~current~" ] ; then
131 mv "$file" "$file~current~"
132 cp "$file~current~" "$file"
135 echo >&2 Cannot checkout
$file
144 done < .patches
/files
149 if cmp -s "$1~current~" "$1" ||
[ ! -f "$1" -a ! -f "$1~current~" ]
153 echo "diff ./$1~current~ ./$1"
156 diff -N --show-c-function -u .
/$1 .
/$1~current~
158 diff -N --show-c-function -u .
/$1~current~ .
/$1
165 if cmp -s "$1~orig~" "$1"
169 echo "diff ./$1~orig~ ./$1"
170 diff --show-c-function -u .
/$1~orig~ .
/$1
177 if [ -f "$1" ] ; then
178 mv "$1" "$1~current~"
179 cp -p "$1~current~" $1
186 cmp -s "$1~current~" $1 ||
{ rm -f "$1" ; cp "$1~current~" $1; }
193 mv "$1~current~" "$1"
194 mv "$1.tmp" "$1~current~"
197 CERT
='Signed-off-by: Neil Brown <neilb@suse.de>'
201 [ -s .patches
/status
] && echo "Status: `cat .patches/status`"
202 [ -s .patches
/notes
] && { echo; cat .patches
/notes
; }
203 if grep -F "$CERT" .patches
/notes
> /dev
/null
2>&1
208 all_files diff_one
$1 > .patches
/tmp
209 echo "### Diffstat output"
210 diffstat
-p0 2> /dev
/null
< .patches
/tmp
212 [ -s .patches
/tmp
] ||
rm .patches
/patch
221 # move .patches/patch to $dir/nnn$name
223 [ -d $dir ] || mkdir
$dir ||
exit 1
224 largest
=`ls $dir | sed -n -e 's/^\([0-9][0-9][0-9]\).*/\1/p' | sort -n | tail -1`
225 if [ "0$largest" -eq 999 ]
226 then echo >&2 'ARRG - too many patches!' ; exit 1
228 new
=`expr "0$largest" + 1001`
230 mv .patches
/patch $dir/$new$name
235 # set "prefix" to number for -pn by looking at first file in given patch.
237 file=`lsdiff $1 | head -$n | tail -1`
240 while [ \
( -n "$file" -a ! -f "$file" \
) -o " $file" != " ${file#/}" ]
242 file=`expr "$file" : '[^/]*/\(.*\)'`
243 prefix
=`expr $prefix + 1`
246 then echo "Cannot find $orig" >&2
249 else find_prefix
"$1" $
[n
+1]
252 if [ " $orig" != " $file" ]
254 echo "Found $orig as $file - prefix $prefix"
260 # remove first line, Status: line, leading blanks,
261 # everything from ' *---' and trailing blanks
263 BEGIN { head= 1; blanks=0 ; }
264 head == 1 && ( $1 == "Status:" || $0 == "" ) {
268 $0 == "" { blanks++; next; }
269 $0 ~ /^ *---/ { exit }
271 { while (blanks > 0) {
282 echo >&2 'Usage: p [help|co|make|discard|commit|status|name|...] args'
288 if [ " $cmd" = " help" ] || find_home
290 else echo >&2 "p $cmd: cannot find .patches directory"
296 if [ $# -ne 1 ] ; then
297 echo >&2 Usage
: p co
file; exit 1
300 if [ ! -f "$OrigDir$file" ]
302 echo >&2 "p co: file $file not found"; exit 1;
304 check_out
"$OrigDir$file"
311 if [ -s .patches
/patch ] ; then
314 echo >&2 "No current patch" ; exit 1;
319 * ) pfile
=`echo .patches/[ra][ep][mp]*/*$1*`
322 then echo >&2 "Cannot find unique patch '$1' - found: $pfile"; exit 1;
324 if grep -s '^+.*[ ]$' $pfile > /dev
/null
326 ${PAGER-less -p '^\+.*[ ]$'} $pfile
333 all_files diff_one_orig
339 if [ $cmd = name
] ; then
340 if [ -n "$name" ]; then
341 echo "changing name from '$name' to '$1'"
343 echo "Setting name to '$1'"
345 echo "$1" > .patches
/name
347 if [ $cmd = status
] ; then
348 if [ -n "$status" ]; then
349 echo "changing status from '$status' to '$1'"
351 echo "Setting status to '$1'"
353 echo "$1" > .patches
/status
358 echo -n "Name ($name)? " ; read name
359 echo -n "Status ($status)? " ; read status
360 [ -n "$name" ] && { echo $name > .patches
/name
; }
361 [ -n "$status" ] && { echo $status > .patches
/status
; }
364 echo "Usage: p $cmd [new-$cmd]"; exit 1;
369 ${EDITOR:-vi} .patches
/notes
373 if [ -s .patches
/patch ]
375 else echo >&2 No
patch to
$cmd ; exit 1
377 if grep -s '^+.*[ ]$' .patches
/patch > /dev
/null
379 echo >&2 remove trailing spaces
/tabs first
!!
382 if [ -s .patches
/to-resolv
]
383 then echo "Please resolve outstanding conflicts first with 'p resolve'"
387 if [ -z "$name" ] ; then
388 echo -n "Name? " ; read name
389 if [ -z "$name" ] ; then
390 echo >&2 "No current name, please set with 'p name'"
393 echo $name > .patches
/name
395 if [ -z "$status" ] ; then
396 echo -n "Status? " ; read status
397 if [ -z "$status" ] ; then
398 echo >&2 "No current status, please set with 'p status'"
401 echo $status > .patches
/status
403 if [ -s .patches
/notes
]
408 echo "Description..."
410 echo "====Do Not Remove===="
413 ${EDITOR-vi} .patches
/notes
414 mv .patches
/notes .patches
/tmp
415 sed '/^====Do Not Remove====/,$d' .patches
/tmp
> .patches
/notes
420 if [ $cmd = commit
] ; then
421 save_patch applied
"$name"
422 echo Saved as
$new$name
425 save_patch removed
"$name"
426 echo Saved as
$new$name
427 all_files discard_one
429 rm -f .patches
/name .patches
/status .patches
/notes
434 mv .patches
/patch .patches
/last-purge
435 all_files discard_one
436 rm -f .patches
/name .patches
/status .patches
/notes
441 if [ -s .patches
/patch ]
443 echo >&2 Patch
$name already open
- please commit
; exit 1;
447 echo "Available patches are:"
452 then echo >&2 "Usage: p open patchname" ; exit 1
454 if [ " $1" = " last" ]
456 pfile
=`ls -d .patches/applied/[0-9]* | tail -1`
458 pfile
=`echo .patches/applied/*$1*`
461 then echo >&2 "Cannot find unique patch '$1' - found: $pfile"; exit 1
463 # lets see if it applies cleanly
464 if patch -s --fuzz=0 --dry-run -R -f -p0 < "$pfile"
465 then echo Ok
, it seems to apply
466 else echo >&2 "Sorry, that patch doesn't apply" ; exit 1
469 patch --fuzz=0 -R -f -p0 < "$pfile"
471 sed -n -e '2q' -e 's/^Status: *//p' $pfile > .patches
/status
472 base
=${pfile##*/[0-9][0-9][0-9]}
473 [ -s .patches
/name
] ||
echo $base > .patches
/name
474 extract_notes
$pfile >> .patches
/notes
475 mv $pfile .patches
/patch
480 if [ " $1" = " -f" ] ; then
484 if [ -s .patches
/patch ]
486 echo >&2 Patch
$name already open
, please commit
; exit 1;
490 echo "Unapplied patches are:"
496 echo >&2 "Usage: p included patchname"; exit 1
499 last
) pfile
=`ls -d .patches/removed/[0-9]* | tail -1` ;;
500 */* ) echo >&2 "Only local patches can have been included"; exit 1 ;;
501 *) pfile
=`echo .patches/removed/*$1*`
504 then echo >&2 "Cannot find unique patch '$1' - found $pfile"; exit 1
506 echo "Using $pfile..."
508 # make sure patch applies in reverse
509 if patch -s --fuzz=2 -l --dry-run -f -p0 -R < "$pfile"
510 then echo "Yep, that seems to be included"
512 then echo "It doesn't apply reverse-out cleanly, but you asked for it..."
513 else echo >&2 "Sorry, patch cannot be removed"; exit 1
515 mv "$pfile" .patches
/patch
516 name
=${pfile##*/[0-9][0-9][0-9]}
517 save_patch included
$name
518 echo "Moved to $new$name"
521 # there are some patches in .removed that may be included in the current source
522 # we try to backout each one. If it backs out successfully, we move it to
523 # .reviewed and conitnue, else we abort
524 # Once this has been done often enough, 'reviewed' should be run to
525 # move stuff to 'included' and to revert those patches
527 if [ " $1" = " -f" ] ; then
531 if [ -s .patches
/path
]
533 echo >&2 Patch
$name already open
, please deal with it
; exit 1;
537 echo "Pending patches are:"
543 echo >&2 "Usage: p review patchname"; exit 1
546 */* ) echo >&2 "Only local patches can have been included"; exit 1 ;;
547 *) pfile
=`echo .patches/removed/*$1*`
550 then echo >&2 "Cannot find unique patch '$1' - found $pfile"; exit 1
552 echo "Starting from $pfile..."
554 for fl
in .patches
/removed
/*
556 if [ " $fl" = " $pfile" ]; then found
=yes ; fi
557 if [ -n "$found" ]; then
560 lsdiff
--strip=$prefix "$fl" |
grep -v 'file.*changed' |
while read a b
563 if patch -s --fuzz=0 --dry-run -f -p$prefix -R < "$fl"
564 then echo Looks good..
566 then echo "It doesn't backout cleanly, but you asked for it..."
567 cp $fl .patches
/last-backed
568 else echo "Patch won't back out, sorry"
571 patch --fuzz=0 -f -p$prefix -R < "$fl" |
tee .patches
/tmp
572 sed -n -e '2q' -e 's/^Status: *//p' $fl > .patches
/status
574 base
=${base##[0-9][0-9][0-9]}
575 base
=${base##patch-?-}
576 [ -s .patches
/name
] ||
echo $base > .patches
/name
577 extract_notes
$fl >> .patches
/notes
578 rm -f .patches
/wiggled
579 sed -n -e 's/.*saving rejects to file \(.*\).rej/\1/p' .patches
/tmp |
581 do echo Wiggling
$file.rej into place
584 wiggle
--replace --merge $file $file.rej ||
585 echo $file >> .patches
/to-resolve
588 mv $fl .patches
/patch
589 save_patch reviewed
$base
590 if [ -f .patches
/wiggled
]
591 then echo 'Some wiggling was needed. Please review and commit'
600 # all the currently applied patches are patches that have been
601 # reviewed as included.
602 # rip them out and stick them (reversed) into included.
607 save_patch included
"$name"
608 echo Saved as
"$new$name"
609 all_files discard_one
610 rm -f .patches
/name .patches
/status .patches
/notes
614 echo "Applied patches are:"
617 echo "Unapplied patches are:"
623 if [ " $1" = " -f" ]; then
626 if [ " $1" = " -a" ]; then
631 if [ -s .patches
/patch -a -z "$append" ]
633 echo >&2 Patch
$name already open
- please commit
; exit 1;
637 echo "Unapplied patches are:"
642 then echo >&2 "Usage: p apply patchname"; exit 1
645 last
) pfile
=`ls -d .patches/removed/[0-9]* | tail -1` ; echo last is
"$pfile";;
647 * ) pfile
=`echo .patches/removed/*$1*`
650 then echo >&2 "Cannot find unique patch '$1' - found: $pfile"; exit 1
653 lsdiff
--strip=$prefix "$pfile" |
grep -v 'file.*changed' |
while read a b
656 # lets see if it applies cleanly
657 if patch -s --fuzz=0 --dry-run -f -p$prefix < "$pfile"
658 then echo OK
, it seems to apply
660 then echo "It doesn't apply cleanly, but you asked for it...."
661 echo "Saving original at .patches/last-applied"
662 cp $pfile .patches
/last-applied
663 else echo >&2 "Sorry, patch doesn't apply"; exit 1
666 patch --fuzz=0 -f -p$prefix < "$pfile" |
tee .patches
/tmp
667 sed -n -e '2q' -e 's/^Status: *//p' $pfile > .patches
/status
669 base
=${base##[0-9][0-9][0-9]}
670 base
=${base##patch-?-}
671 [ -s .patches
/name
] ||
echo $base > .patches
/name
672 extract_notes
$pfile >> .patches
/notes
674 sed -n -e 's/.*saving rejects to file \(.*\).rej/\1/p' .patches
/tmp |
676 do echo Wiggling
$file.rej into place
678 wiggle
--replace --merge $file $file.rej ||
679 echo $file >> .patches
/to-resolve
684 mv $pfile .patches
/patch
689 name
=`date -u +%Y-%m-%d-%H`
690 if [ -d .patches
/dest
]
692 else echo >&2 No destination specified
at .patches
/dest
; exit 1;
694 if [ -d .patches
/dest
/$name ]
696 echo >&2 $name already exists
; exit 1
698 target
=.patches
/dest
/$name
700 if [ -f .patches
/get-version
] ;
701 then .
/.patches
/get-version
> $target/version
703 [ -f .config
] && cp .config
$target
704 cp .patches
/applied
/* $target
706 cp 2> /dev
/null .patches
/removed
/* $target/misc ||
rmdir $target/misc
707 chmod -R a
+rX
$target
708 all_files diff_one_orig
> $target/patch-all-
$name
710 echo Published
at `/bin/pwd`
717 while p open last
&& p discard
; do : ; done
722 if [ -s .patches
/patch ]
724 echo >&2 Patch
$name already open
- please commit
; exit 1;
728 echo "Unapplied patches are:"
733 then echo >&2 "Usage: p recommit patchname"; exit 1
736 last
) pfile
=`ls -d .patches/removed/[0-9]* | tail -1` ; echo last is
"$pfile";;
738 * ) pfile
=`echo .patches/removed/*$1*`
741 then echo >&2 "Cannot find unique patch '$1' - found: $pfile"; exit 1
743 while [ -s "$pfile" ] &&
744 p apply last
&& p commit
; do : ; done
749 if [ -s .patches
/patch ]
751 echo >&2 Patch
$name already open
- please commit
; exit 1;
755 echo "Applied patches are:"
760 then echo >&2 "Usage: p decommit patchname"; exit 1
763 last
) pfile
=`ls -d .patches/applied/[0-9]* | tail -1` ; echo last is
"$pfile";;
765 * ) pfile
=`echo .patches/applied/*$1*`
768 then echo >&2 "Cannot find unique patch '$1' - found: $pfile"; exit 1
770 while [ -s "$pfile" ] &&
771 p open last
&& p discard
; do : ; done
783 if [ ! -s .patches
/resolving
]
784 then sort -u .patches
/to-resolve
> .patches
/resolving
; > .patches
/to-resolve
786 if [ ! -s .patches
/resolving
]
787 then echo "Nothing to resolve" ; exit 0;
789 echo "Resolving: " ; cat .patches
/resolving
790 for file in `cat .patches/resolving`
794 wiggle
--replace --merge $file ||
795 echo $file >> .patches
/to-resolve
800 cd .patches
/SOURCE
&& bk pull
805 if [ -s .patches
/patch ]
807 echo >&2 Patch
$name already open
- please commit
; exit 1;
809 p openall
&& p clean
&&
810 (cd .patches
/SOURCE
; bk
export -tpatch -rLATEST, ) > .patches
/imported-patch
&&
811 patch --dry-run -f -p1 < .patches
/imported-patch
&&
812 patch -f -p1 < .patches
/imported-patch
&&
813 ( rm .patches
/imported-patch
; cd .patches
/SOURCE
; bk tag LATEST
)
817 # Convert some applied patches into email messages.
818 # Select patches that start with $1. Look in .patches/cc for who to Cc: to
819 rmdir .patches
/mail 2>/dev
/null
820 if [ -d .patches
/mail ] ; then
821 echo >&2 There is already some email
- run
"email" or
"nomail"
826 if [ ! -s .patches
/maintainer
] ; then
827 echo "No maintainer - please add one"
830 if [ ! -s .patches
/owner
] ; then
831 echo "Your address and other headers must be in .patches/owner"
834 messid
="<`date +'%Y%m%d%H%M%S'`.$$.patches@`uname -n`>"
836 for patch in .patches
/applied
/???
${1}*
840 if [ -n "$2" ] && [ $2 -gt $n ] ; then continue; fi
841 if [ -n "$3" ] && [ $3 -lt $n ] ; then continue; fi
842 cnt
=$
(expr $cnt + 1 )
848 if [ -s .patches
/owner.
$1 ] ; then
849 cat .patches
/owner.
$1
853 echo "To: `cat .patches/maintainer`"
854 if [ -s .patches
/cc
] ; then
855 while read word prefix addr
856 do if [ " $word" = " $1" ] ; then
864 echo "Subject: [PATCH] ${sprefix}Intro"
866 echo "Subject: [PATCH 000 of $cnt] ${sprefix}Introduction"
868 echo "Message-ID: $messid"
870 echo PUT COMMENTS HERE
871 } > .patches
/mail
/000Intro
874 for patch in .patches
/applied
/???
${1}*
878 if [ -n "$2" ] && [ $2 -gt $n ] ; then continue; fi
879 if [ -n "$3" ] && [ $3 -lt $n ] ; then continue; fi
882 if [ -s .patches
/owner.
$1 ] ; then
883 cat .patches
/owner.
$1
887 echo "To: `cat .patches/maintainer`"
888 if [ -s .patches
/cc
] ; then
889 while read word prefix addr
890 do if [ " $word" = " $1" ] ; then
896 head=`sed -e '/^Status/d' -e '/^$/d' -e q $patch`
897 zerothis
=$
(expr $this + 1000)
900 echo "Subject: [PATCH] $sprefix$head"
902 echo "Subject: [PATCH ${zerothis#1} of $cnt] $sprefix$head"
904 echo "References: $messid"
906 if [ $cnt = 1 ] ; then
907 echo "### Comments for Changeset"
910 } > .patches
/mail
/${patch#.patches/applied/}
911 this
=$
(expr $this + 1)
913 if [ -f .patches
/mail
/000Intro
]; then cat .patches
/mail
/* |
sed -n -e 's/^Subject://p' >> .patches
/mail
/000Intro
; fi
918 echo "Removing .patches/mail directory"
923 PATH
=$HOME/bin
:/usr
/lib
:/usr
/sbin
:$PATH
924 for i
in .patches
/mail
/*
929 sendmail
-t < $i && rm $i
935 if [ ! -f $helpfile ]
936 then echo >&2 $helpfile not found
: no
help available
; exit 2;
938 if [ -z "$1" ] ; then
940 sed -n -e '/^ /p' -e '/^[^ ]/q' $helpfile
942 echo "Available help topics are:"
943 sed -n '/^[^ ]/p' $helpfile |
sort |
column
946 awk '$0 ~ /^[^ ]/ && printed {doprint=0; printed=0}
947 doprint && $0 !~ /^[^ ]/ {print; printed=1;}
948 $0 == "'$1'" {doprint=1; found=1}
949 END { if (!found) print "No help available for '$1'"; }
955 echo >&2 "p $cmd - unknown command - try 'p help'"; exit 1;