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 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"
153 echo "diff ./$1~current~ ./$1"
154 diff --show-c-function -u .
/$1~current~ .
/$1
160 if cmp -s "$1~orig~" "$1"
164 echo "diff ./$1~orig~ ./$1"
165 diff --show-c-function -u .
/$1~orig~ .
/$1
172 mv "$1" "$1~current~"
187 mv "$1~current~" "$1"
188 mv "$1.tmp" "$1~current~"
194 [ -s .patches
/status
] && echo "Status: `cat .patches/status`"
196 [ -s .patches
/notes
] && { cat .patches
/notes
; echo; }
197 all_files diff_one
> .patches
/tmp
198 echo " ----------- Diffstat output ------------"
199 diffstat
-p0 2> /dev
/null
< .patches
/tmp
201 [ -s .patches
/tmp
] ||
rm .patches
/patch
210 # move .patches/patch to $dir/nnn$name
212 [ -d $dir ] || mkdir
$dir ||
exit 1
213 largest
=`ls $dir | sed -n -e 's/^\([0-9][0-9][0-9]\).*/\1/p' | sort -n | tail -1`
214 if [ "0$largest" -eq 999 ]
215 then echo >&2 'ARRG - too many patches!' ; exit 1
217 new
=`expr "0$largest" + 1001`
219 mv .patches
/patch $dir/$new$name
224 # set "prefix" to number for -pn by looking at first file in given patch.
225 file=`lsdiff $1 | head -1`
228 while [ -n "$file" -a ! -f "$file" ]
230 file=`expr "$file" : '[^/]*/\(.*\)'`
231 prefix
=`expr $prefix + 1`
234 then echo "Cannot find $orig" >&2 ; exit 1;
236 if [ " $orig" != " $file" ]
238 echo "Found $orig as $file - prefix $prefix"
244 # remove first line, Status: line, leading blanks,
245 # everything from ' *---' and trailing blanks
247 BEGIN { head= 1; blanks=0 ; }
248 head == 1 && ( $1 == "Status:" || $0 == "" ) {
252 $0 == "" { blanks++; next; }
253 $0 ~ /^ *---/ { exit }
254 { while (blanks > 0) {
265 echo >&2 'Usage: p [help|co|make|discard|commit|status|name|...] args'
271 if [ " $cmd" = " help" ] || find_home
273 else echo >&2 "p $cmd: cannot find .patches directory"
279 if [ $# -ne 1 ] ; then
280 echo >&2 Usage
: p co
file; exit 1
283 if [ ! -f "$OrigDir$file" ]
285 echo >&2 "p co: file $file not found"; exit 1;
287 check_out
"$OrigDir$file"
294 if [ -s .patches
/patch ] ; then
297 echo >&2 "No current patch" ; exit 1;
302 * ) pfile
=`echo .patches/[ra][ep][mp]*/*$1*`
305 then echo >&2 "Cannot find unique patch '$1' - found: $pfile"; exit 1;
307 ${PAGER-less} $pfile;
311 all_files diff_one_orig
317 if [ $cmd = name
] ; then
318 if [ -n "$name" ]; then
319 echo "changing name from '$name' to '$1'"
321 echo "Setting name to '$1'"
323 echo "$1" > .patches
/name
325 if [ $cmd = status
] ; then
326 if [ -n "$status" ]; then
327 echo "changing status from '$status' to '$1'"
329 echo "Setting status to '$1'"
331 echo "$1" > .patches
/status
336 echo -n "Name ($name)? " ; read name
337 echo -n "Status ($status)? " ; read status
338 [ -n "$name" ] && { echo $name > .patches
/name
; }
339 [ -n "$status" ] && { echo $status > .patches
/status
; }
342 echo "Usage: p $cmd [new-$cmd]"; exit 1;
347 ${EDITOR:-vi} .patches
/notes
351 if [ -s .patches
/patch ]
353 else echo >&2 No
patch to
$cmd ; exit 1
355 if [ -s .patches
/to-resolv
]
356 then echo "Please resolve outstanding conflicts first with 'p resolve'"
360 if [ -z "$name" ] ; then
361 echo -n "Name? " ; read name
362 if [ -z "$name" ] ; then
363 echo >&2 "No current name, please set with 'p name'"
366 echo $name > .patches
/name
368 if [ -z "$status" ] ; then
369 echo -n "Status? " ; read status
370 if [ -z "$status" ] ; then
371 echo >&2 "No current status, please set with 'p status'"
374 echo $status > .patches
/status
376 if [ -s .patches
/notes
]
381 echo "Description..."
383 echo "====Do Not Remove===="
386 ${EDITOR-vi} .patches
/notes
387 mv .patches
/notes .patches
/tmp
388 sed '/^====Do Not Remove====/,$d' .patches
/tmp
> .patches
/notes
393 if [ $cmd = commit
] ; then
394 save_patch applied
"$name"
395 echo Saved as
$new$name
398 save_patch removed
"$name"
399 echo Saved as
$new$name
400 all_files discard_one
402 rm -f .patches
/name .patches
/status .patches
/notes
407 mv .patches
/patch .patches
/last-purge
408 all_files discard_one
409 rm -f .patches
/name .patches
/status .patches
/notes
414 if [ -s .patches
/patch ]
416 echo >&2 Patch
$name already open
- please commit
; exit 1;
420 echo "Available patches are:"
425 then echo >&2 "Usage: p open patchname" ; exit 1
427 if [ " $1" = " last" ]
429 pfile
=`ls -d .patches/applied/[0-9]* | tail -1`
431 pfile
=`echo .patches/applied/*$1*`
434 then echo >&2 "Cannot find unique patch '$1' - found: $pfile"; exit 1
436 # lets see if it applies cleanly
437 if patch -s --fuzz=0 --dry-run -R -f -p0 < "$pfile"
438 then echo Ok
, it seems to apply
439 else echo >&2 "Sorry, that patch doesn't apply" ; exit 1
442 patch --fuzz=0 -R -f -p0 < "$pfile"
444 sed -n -e '2q' -e 's/^Status: *//p' $pfile > .patches
/status
445 base
=${pfile##*/[0-9][0-9][0-9]}
446 [ -s .patches
/name
] ||
echo $base > .patches
/name
447 extract_notes
$pfile >> .patches
/notes
448 mv $pfile .patches
/patch
453 if [ " $1" = " -f" ] ; then
457 if [ -s .patches
/patch ]
459 echo >&2 Patch
$name already open
, please commit
; exit 1;
463 echo "Unapplied patches are:"
469 echo >&2 "Usage: p included patchname"; exit 1
472 last
) pfile
=`ls -d .patches/removed/[0-9]* | tail -1` ;;
473 */* ) echo >&2 "Only local patches can have been included"; exit 1 ;;
474 *) pfile
=`echo .patches/removed/*$1*`
477 then echo >&2 "Cannot find unique patch '$1' - found $pfile"; exit 1
479 echo "Using $pfile..."
481 # make sure patch applies in reverse
482 if patch -s --fuzz=0 --dry-run -f -p0 -R < "$pfile"
483 then echo "Yep, that seems to be included"
485 then echo "It doesn't apply reverse-out cleanly, but you asked for it..."
486 else echo >&2 "Sorry, patch cannot be removed"; exit 1
488 mv "$pfile" .patches
/patch
489 name
=${pfile##*/[0-9][0-9][0-9]}
490 save_patch included
$name
491 echo "Moved to $new$name"
494 echo "Applied patches are:"
497 echo "Unapplied patches are:"
503 if [ " $1" = " -f" ]; then
506 if [ " $1" = " -a" ]; then
511 if [ -s .patches
/patch -a -z "$append" ]
513 echo >&2 Patch
$name already open
- please commit
; exit 1;
517 echo "Unapplied patches are:"
522 then echo >&2 "Usage: p apply patchname"; exit 1
525 last
) pfile
=`ls -d .patches/removed/[0-9]* | tail -1` ; echo last is
"$pfile";;
527 * ) pfile
=`echo .patches/removed/*$1*`
530 then echo >&2 "Cannot find unique patch '$1' - found: $pfile"; exit 1
533 lsdiff
--strip=$prefix "$pfile" |
grep -v 'file.*changed' |
while read a b
536 # lets see if it applies cleanly
537 if patch -s --fuzz=0 --dry-run -f -p$prefix < "$pfile"
538 then echo OK
, it seems to apply
540 then echo "It doesn't apply cleanly, but you asked for it...."
541 echo "Saving original at .patches/last-applied"
542 cp $pfile .patches
/last-applied
543 else echo >&2 "Sorry, patch doesn't apply"; exit 1
546 patch --fuzz=0 -f -p$prefix < "$pfile" |
tee .patches
/tmp
547 sed -n -e '2q' -e 's/^Status: *//p' $pfile > .patches
/status
549 base
=${base##[0-9][0-9][0-9]}
550 base
=${base##patch-?-}
551 [ -s .patches
/name
] ||
echo $base > .patches
/name
552 extract_notes
$pfile >> .patches
/notes
554 sed -n -e 's/.*saving rejects to file \(.*\).rej/\1/p' .patches
/tmp |
556 do echo Wiggling
$file.rej into place
558 wiggle
--replace --merge $file $file.rej ||
559 echo $file >> .patches
/to-resolve
564 mv $pfile .patches
/patch
569 name
=`date -u +%Y-%m-%d:%H`
570 if [ -d .patches
/dest
]
572 else echo >&2 No destination specified
at .patches
/dest
; exit 1;
574 if [ -d .patches
/dest
/$name ]
576 echo >&2 $name already exists
; exit 1
578 target
=.patches
/dest
/$name
580 if [ -f .patches
/get-version
] ;
581 then .
/.patches
/get-version
> $target/version
583 [ -f .config
] && cp .config
$target
584 cp .patches
/applied
/* $target
586 cp 2> /dev
/null .patches
/removed
/* $target/misc ||
rmdir $target/misc
587 chmod -R a
+rX
$target
588 all_files diff_one_orig
> $target/patch-all-
$name
590 echo Published
at `/bin/pwd`
597 while p open last
&& p discard
; do : ; done
609 if [ ! -s .patches
/resolving
]
610 then sort -u .patches
/to-resolve
> .patches
/resolving
; > .patches
/to-resolve
612 if [ ! -s .patches
/resolving
]
613 then echo "Nothing to resolve" ; exit 0;
615 echo "Resolving: " ; cat .patches
/resolving
616 for file in `cat .patches/resolving`
620 wiggle
--replace --merge $file ||
621 echo $file >> .patches
/to-resolve
626 cd .patches
/SOURCE
&& bk pull
629 p openall
&& p clean
&&
630 (cd .patches
/SOURCE
; bk
export -tpatch -rLATEST, ) > .patches
/imported-patch
&&
631 patch --dry-run -f -p1 < .patches
/imported-patch
&&
632 patch -f -p1 < .patches
/imported-patch
&&
633 ( rm .patches
/imported-patch
; cd .patches
/SOURCE
; bk tag LATEST
)
637 # Convert some applied patches into email messages.
638 # Select patches that start with $1. Look in .patches/cc for who to Cc: to
639 rmdir .patches
/mail 2>/dev
/null
640 if [ -d .patches
/mail ] ; then
641 echo >&2 There is already some email
- run
"email" or
"nomail"
646 if [ ! -s .patches
/maintainer
] ; then
647 echo "No maintainer - please add one"
650 if [ ! -s .patches
/owner
] ; then
651 echo "Your address and other headers must be in .patches/owner"
654 cnt
=$
(ls .patches
/applied
/???
${1}* |
wc -l)
655 cnt
=$
(echo $cnt) # discard spaces
657 for patch in .patches
/applied
/???
${1}*
662 echo "To: `cat .patches/maintainer`"
663 if [ -s .patches
/cc
] ; then
664 while read word prefix addr
665 do if [ " $word" = " $1" ] ; then
671 head=`sed -e '/^Status/d' -e '/^$/d' -e q $patch`
674 echo "Subject: [PATCH] $sprefix $head"
676 echo "Subject: [PATCH] $sprefix$this of $cnt - $head"
679 echo '### Comments for ChangeSet'
680 sed -e '1,/^[^S]/d' $patch
681 } > .patches
/mail
/${patch#.patches/applied/}
682 this
=$
(expr $this + 1)
688 echo "Removing .patches/mail directory"
693 PATH
=/usr
/lib
:/usr
/sbin
:$PATH
694 for i
in .patches
/mail
/*
699 sendmail
-t < $i && rm $i
705 if [ ! -f $helpfile ]
706 then echo >&2 $helpfile not found
: no
help available
; exit 2;
708 if [ -z "$1" ] ; then
710 sed -n -e '/^ /p' -e '/^[^ ]/q' $helpfile
712 echo "Available help topics are:"
713 sed -n '/^[^ ]/p' $helpfile |
sort |
column
716 awk '$0 ~ /^[^ ]/ && printed {doprint=0; printed=0}
717 doprint && $0 !~ /^[^ ]/ {print; printed=1;}
718 $0 == "'$1'" {doprint=1; found=1}
719 END { if (!found) print "No help available for '$1'"; }
725 echo >&2 "p $cmd - unknown command - try 'p help'"; exit 1;