diff options
39 files changed, 4388 insertions, 0 deletions
@@ -0,0 +1,9 @@ +* Patch is less sensitive to the file names in the diff headers than + the scripts are. It tries both filenames specified and apparently + checks if the file exists. We only look at the `+++ file ...' header, + and ignore the other. Other than patch we cannot simply look at the + file system to determine which one is the imput file. + +* Patch destroys the backup files it generates if a file appears more + than once in a patch. This is the reason why we use the backup-files + utility instead. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fcd8df2 --- /dev/null +++ b/Makefile @@ -0,0 +1,67 @@ +SCRIPTS_IN := inpatch newpatch patchadd poppatch pushpatch refpatch toppatch \ + importpatch +LIB_SCRIPTS_IN := apatch rpatch patchfns backup-files +LIB_SCRIPTS := touched-by-patch parse-patch +#LIB_LIB := patchfns + +VERSION := 0.11 + +prefix := /usr/local +bindir := $(prefix)/bin +datadir := $(prefix)/share +LIB := $(datadir)/patch-scripts +docdir := $(datadir)/doc/packages + +CFLAGS = -Wall + +% : %.in + @echo "$< -> $@" + @sed -e "s:@LIB@:$(LIB):g" $< > $@ + @chmod --reference=$< $@ + +all : scripts README + +scripts : $(SCRIPTS_IN) $(LIB_SCRIPTS_IN:%=lib/%) + +README : README.in + @echo "$< -> $@" + @awk '/@REFERENCE@/ { system("$(MAKE) -s reference") ; next }'$$'\n'' \ + { print }' $< > $@ + +reference : $(SCRIPTS_IN) + @for i in $(SCRIPTS_IN); \ + do \ + echo; \ + ./$$i -h; \ + done | \ + awk '/Usage:/ { sub(/Usage: ?/, "") ; print ; next } '$$'\n'' \ + { printf " %s\n", $$0 }' + +dist : distclean + rm -f patch-scripts-$(VERSION) + ln -s . patch-scripts-$(VERSION) + tar cvfz patch-scripts-$(VERSION).tar.gz \ + --exclude=patch-scripts-$(VERSION)/patch-scripts-* \ + --exclude=patch-scripts-$(VERSION)/CVS \ + patch-scripts-$(VERSION)/* + rm -f patch-scripts-$(VERSION) + +install : all + install -d $(BUILD_ROOT)$(LIB) + install -m 755 $(LIB_SCRIPTS_IN:%=lib/%) \ + $(BUILD_ROOT)$(LIB) + install -m 755 $(LIB_SCRIPTS:%=lib/%) \ + $(BUILD_ROOT)$(LIB) + #install -m 644 $(LIB_LIB:%=lib/%) \ + # $(BUILD_ROOT)$(LIB) + + install -d $(BUILD_ROOT)$(bindir) + install -m 755 $(SCRIPTS_IN) \ + $(BUILD_ROOT)$(bindir) + + install -d $(BUILD_ROOT)$(docdir)/patch-scripts + install -m 644 README needs-checking/docco.txt \ + $(BUILD_ROOT)$(docdir)/patch-scripts + +clean distclean : + rm -f $(SCRIPTS_IN) $(LIB_SCRIPTS_IN:%=lib/%) README diff --git a/README.in b/README.in new file mode 100644 index 0000000..872d9b0 --- /dev/null +++ b/README.in @@ -0,0 +1,108 @@ +The scripts in this package simplify working with a series of patches. +The usual tasks like applying, refreshing and reversing are supported. + +docco.txt contains Adrew Morton's description of his suite of patch +scripts. These scripts are based on Andrew's scripts, but include many +changes. Please keep this in mind when reading Andrew's documentation. + + +Concepts +======== + +(Please see docco.txt.) + +The scripts maintain a stack of patches, on top of which additional +patches can be applied, and from with patches can be removed. The list of +patches is kept in a file (searched for in this order in +.pc/series, series, patches/series). + +The scripts keep meta information in the .pc/ directory. The .pc/ +directory contains: + + .pc/applied-patches + The list of currently applied patches. + + .pc/patch/.pc (for each applied patch) + A list of files modified by the patch. + + .pc/patch/ (for each applied patch) + Copies of the files that the patch modifies, + before modifying. The original versions of + the files are needed for refreshing patches, + and for ensuring that removing a patch does + recreate the original file contents. + + +Operation +========= + +Patches to be applied must be listed in the series file. + +Patches are applied with pushpatch, and are removed with poppatch. + +Refpatch refreshes a patch. + +New patches can be added at the current position in the patch sequence +with newpatch. + +Additional files to be modified can be added to a patch with patchadd. + + +Installation +============ + +To start using the scripts the working directory must contain: + + patches/ + Patches to work with. + + series (or patches/series) + List of patches in order of applying. + +The scripts will create the .pc/ directory that contains the meta +information needed for managing the patches automatically. + + +Series file +=========== + +The patches that are relevant for a project must be listed in the file +series, which is searched for in .pc/, ./ and patches/ in this order. +The patches will be applied in the order given. The series file has +the following format: + + # Comment + subdir/patch-file-name.patch [-pN] + +The location of patches is specified relative to the patches/ directory. +Optionally a strip level (-p0, -p1, etc.) can be specified. When +refreshing a patch, only levels 0 and 1 are supported. + + +Command reference +================= +@REFERENCE@ + +Helper files/scripts +==================== + +patchfns + A collection of functions. + +apatch + Add a patch. Used by pushpatch. + +rpatch + Remove a patch. Used by poppatch. + +parse-patch {-s|-u} section file [< replacement] + Select a %section from a patch (-s) or replace a + %section with the text from standard input (-u). + +touched-by-patch filename + Print a list of files modified by a patch file. + +backup-files + A simple utility that creates / restores / removes + backup files; this works around a patch bug (see BUGS). + @@ -0,0 +1,13 @@ +Keep -p0 and -p1 patches, and convert -pN (N>1) to -p1 in refpatch + +Test Suite + +Support compressed files in .pc/? + +Use proper temporary files in parse-patch, so that parse-patch can be used +multiple times in pipes! + +check if things like reading the patch file list should be done globally +in some scripts. + +Clean up temp file handling diff --git a/importpatch.in b/importpatch.in new file mode 100755 index 0000000..6d0cb0b --- /dev/null +++ b/importpatch.in @@ -0,0 +1,188 @@ +#!/bin/sh + +# Read in library functions +if ! [ -r @LIB@/patchfns ] +then + echo "Cannot read library @LIB@/patchfns" >&2 + exit 1 +fi +. @LIB@/patchfns + +usage() +{ + echo "Usage: importpatch [-f] [-p num] [-n patchname] [patchfile]" + if [ x$1 = x-h ] + then + cat <<EOF + +Import an external patch. If a patch file name is specified, +the patch will be stored in this relative path in the patches/ +directory. Else, if an input file name is given this name is +used as the patch name. + +-p num + Number of directory levels to strip when aplying (default=1) + +-n patchfile + File name relative to patches/ to use. + +-f Overwite/update existing patches. + +EOF + exit 0 + else + exit 1 + fi +} + +options=`getopt -o fn:p:h -- "$@"` + +if [ $? -ne 0 ] +then + usage +fi + +eval set -- "$options" + +while true +do + case "$1" in + -n) + opt_patch=$(echo "$2" | + sed -e 's:^'"$P"'patches/::' -e 's:^\.pc/::') + shift 2 ;; + -p) + opt_strip=$2 + shift 2 ;; + -f) + opt_force=1 + shift ;; + -h) + usage -h ;; + --) + shift + break ;; + esac +done + +if [ $# -eq 1 ] +then + input_file=$1 +elif [ $# -gt 1 ] +then + usage +fi + +[ -n "$opt_strip" ] && patch_args="-p$opt_strip" + +if [ -n "$opt_patch" ] +then + patch=$(stripit "$opt_patch") + patch_file="${P}patches/$opt_patch" +else + patch="$(stripit "$input_file")" + if [ -n "$patch" ] + then + opt_patch="$patch.patch" + else + echo "Please use -n to specify a patch file name." + exit 1 + fi + patch_file="${P}patches/$opt_patch" +fi + +if echo "$patch_file" | grep -q -e $'[ \t]' +then + echo "Patch file name \"$patch_file\" contains whitespace." + exit 1 +fi + +if is_applied $patch +then + echo "Patch $patch is applied." + exit 1 +fi + +case "$input_file" in +'') + tmp=$(mktemp /tmp/patch-scripts.XXXXXX) + if ! cat > $tmp + then + echo "Cannot read from standard input." + exit 1 + fi + input_file=$tmp ;; +*.gz) + tmp=$(mktemp /tmp/patch-scripts.XXXXXX) + if ! gzip -cd "$input_file" > $tmp + then + echo "Cannot decompress file $input_file" + exit 1 + fi + input_file=$tmp ;; +*.bz2) + tmp=$(mktemp /tmp/patch-scripts.XXXXXX) + if ! bzip2 -cd "$input_file" > $tmp + then + echo "Cannot decompress file $input_file" + exit + fi + input_file=$tmp ;; +*) + if ! [ -r "$input_file" ] + then + echo "Cannot read from file $input_file" + exit 1 + fi +esac + +if [ -e "$patch_file" ] +then + if [ -z "$opt_force" ] + then + echo "Patch $patch exists. Replace with -f." + exit 1 + fi + + if grep -q '^%patch$' "$patch_file" && + ! grep -q '^%patch$' "$input_file" + then + echo "Updating %patch section of patch $patch" + if ! @LIB@/parse-patch -u patch $patch_file< "$input_file" + then + echo "Failed to update %patch section of patch $patch" + exit 1 + fi + else + echo "Replacing patch $patch with new version" + if ! cat "$input_file" > "$patch_file" + then + echo "Failed to replace patch $patch" + exit 1 + fi + fi +else + echo "Importing patch $patch (stored as $patch_file)" + if ! grep -q '^%patch$' "$input_file" + then + echo "%patch" >> "$patch_file" + fi + if ! cat "$input_file" >> "$patch_file" + then + echo "Failed to import patch $patch" + exit 1 + fi +fi + +rm -rf .pc/$patch + +if ! patch_in_series $patch && + ! insert_in_series $opt_patch "$patch_args" +then + echo "Failed to insert $patch in file series." +fi + +if [ -n "$tmp" ] +then + rm -f $tmp +fi diff --git a/lib/apatch.in b/lib/apatch.in new file mode 100755 index 0000000..a14d219 --- /dev/null +++ b/lib/apatch.in @@ -0,0 +1,135 @@ +#!/bin/sh + +if ! [ -r @LIB@/patchfns ] +then + echo "Cannot read library @LIB@/patchfns" >&2 + exit 1 +fi +. @LIB@/patchfns + +usage() +{ + echo "Usage: apatch [-fq] patchname" + exit 1 +} + +interrupt() +{ + local pc_file=$(pc_file_name $1) + @LIB@/backup-files -s -f $pc_file -B .pc/$patch/ -r + echo "apatch interrupted by user" + exit 1 +} + +apply_patch() +{ + local patch=$(stripit $1) + local pc_file=$(pc_file_name $patch) + local patch_file=$(patch_file_name $patch) + local file status + + if ! [ -e "$patch_file" ] + then + echo "No patch named $patch found." + return 1 + fi + + #if is_applied "$patch" + #then + # echo "$patch" is already applied + # return 1 + #fi + + trap "" SIGINT + refresh_file_list $patch + status=$? + if [ $status -eq 2 ] + then + [ -z "$opt_quiet" ] && echo "Recreated file list for $patch" + elif [ $status -ne 0 ] + then + return 1 + fi + + if ! @LIB@/backup-files -s -f $pc_file -B .pc/$patch/ + then + exit 1 + fi + + trap "interrupt $patch" SIGINT + # (patch -b fails if a file appears more than once in a patch!) + #patch $(patch_args $patch) --no-backup-if-mismatch \ + # -E $silent -i $patch_file + patch $(patch_args $patch) --no-backup-if-mismatch \ + -E $silent -i $patch_file + status=$? + trap "" SIGINT + + # Remember date/time of applying so that poppatch can + # avoid reverse applying the patch in the usual cases. + touch $pc_file + + if [ $status -eq 0 -o -n "$opt_force" ] + then + add_to_db $patch + if [ $status -eq 0 ] + then + echo "Applied $patch" + rm -f $(pc_file_name $patch)~forced + else + touch $(pc_file_name $patch)~forced + echo "Applied $patch (forced; needs refpatch)" + fi + else + @LIB@/backup-files -s -f $pc_file -B .pc/$patch/ -r + echo "Patch $patch does not apply" + fi + trap - SIGINT + return $status +} + +options=`getopt -o fqh -- "$@"` + +if [ $? -ne 0 ] +then + usage +fi + +eval set -- "$options" + +while true +do + case "$1" in + -f) + opt_force=1 + shift ;; + -q) + opt_quiet=1 + shift ;; + -h) + usage -h ;; + --) + shift + break ;; + esac +done + +if [ $# -ne 1 ] +then + usage +fi + +[ -n "$opt_quiet" ] && silent=-s + +patch=$(stripit $1) + +top=$(top_patch) +if [ -n "$top" -a -e $(pc_file_name $top)~forced ] +then + echo "The topmost patch $top was force applied. Please run" \ + "refpatch before applying other patches." + exit 1 +fi + +apply_patch $patch + diff --git a/lib/backup-files.c b/lib/backup-files.c new file mode 100644 index 0000000..b9f1b21 --- /dev/null +++ b/lib/backup-files.c @@ -0,0 +1,212 @@ +/* backup-files.c + Andreas Gruenbacher, 18 January 2003 + + `patch -b' fails to back up files correctly if a file occurs + more than once in a patch, so we use this utility instead. +*/ + +/* + - catch SIGINT + - remove backup files and directories +*/ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> + +const char *progname; + +void +usage(void) +{ + printf("Usage: %s [-B prefix] [-z suffix] [-f filelist] [-s] [-r|-x]\n" + "\n" + "\tCreate hard linked backup copies of a list of files\n" + "\tread from standard input.\n" + "\n" + "\t-r\tRestore the backup\n" + "\t-x\tRemove backup files and empty parent directories\n" + "\t-B\tPath name prefix for backup files\n" + "\t-z\tPath name suffix for backup files\n" + "\t-s\tSilent operation; only print error messages\n\n", + progname); +} + +void +create_parents(char *filename) +{ + struct stat st; + char *f = strchr(filename, '/'); + + if (f == NULL) + return; + *f = '\0'; + if (stat(f, &st) != 0) { + while (f != NULL) { + *f = '\0'; + mkdir(filename, 0777); + *f = '/'; + f = strchr(f+1, '/'); + } + } else { + *f = '/'; + } +} + +void +remove_parents(char *filename) +{ + char *f; + + while ((f = strrchr(filename, '/')) != NULL) { + *f = '\0'; + rmdir(filename); + } +} + +enum { what_backup, what_restore, what_remove }; + +#define LINE_LENGTH 1024 + +int +main(int argc, char *argv[]) +{ + char orig[LINE_LENGTH], *o, backup[LINE_LENGTH]; + FILE *file; + + const char *opt_prefix="", *opt_suffix="", *opt_file=NULL; + int opt_silent=0, opt_what=what_backup; + int opt, status=0; + + progname = argv[0]; + + while ((opt = getopt(argc, argv, "rxB:z:f:sh")) != -1) { + switch(opt) { + case 'r': + opt_what = what_restore; + break; + + case 'x': + opt_what = what_remove; + break; + + case 'B': + opt_prefix = optarg; + break; + + case 'f': + opt_file = optarg; + break; + + case 'z': + opt_suffix = optarg; + break; + + case 's': + opt_silent = 1; + break; + + case 'h': + default: + usage(); + return 0; + } + } + + if ((*opt_prefix == '\0' && *opt_suffix == '\0') || optind != argc) { + usage(); + return 1; + } + + if (opt_file != NULL) { + if ((file = fopen(opt_file, "r")) == NULL) { + perror(opt_file); + return 1; + } + } else { + file = stdin; + } + + while (fgets(orig, sizeof(orig), file)) { + if (strlen(opt_prefix) + strlen(orig) + strlen(opt_suffix) >= + sizeof(backup)) { + perror("Line buffer too small\n"); + return 1; + } + + o = strchr(orig, '\0'); + if (o > orig && *(o-1) == '\n') + *(o-1) = '\0'; + if (*orig == '\0') + continue; + + snprintf(backup, sizeof(backup), "%s%s%s", + opt_prefix, orig, opt_suffix); + + if (opt_what == what_backup) { + int fd; + create_parents(backup); + + if (link(orig, backup) == 0) { + if (!opt_silent) + printf("Copying %s\n", orig); + } else if ((fd = creat(backup, 0666)) != -1) { + close(fd); + if (!opt_silent) + printf("New file %s\n", orig); + } else { + perror(backup); + return 1; + } + } else if (opt_what == what_restore) { + struct stat st; + + create_parents(orig); + + if (stat(backup, &st) != 0) { + perror(backup); + status=1; + continue; + } + if (st.st_size == 0) { + if (unlink(orig) == 0 || errno == ENOENT) { + if (!opt_silent) + printf("Removing %s\n", orig); + unlink(backup); + remove_parents(backup); + } else { + if (errno != ENOENT) { + perror(orig); + status=1; + } + } + } else { + unlink(orig); + if (link(backup, orig) == 0) { + if (!opt_silent) + printf("Restoring %s\n", orig); + unlink(backup); + remove_parents(backup); + } else { + fprintf(stderr, "Could not restore " + "file `%s' to `%s': %s\n", + backup, orig, strerror(errno)); + status=1; + } + } + } else { + unlink(backup); + remove_parents(backup); + } + } + + if (file != stdin) { + fclose(file); + } + + return status; +} diff --git a/lib/parse-patch b/lib/parse-patch new file mode 100755 index 0000000..b4dded2 --- /dev/null +++ b/lib/parse-patch @@ -0,0 +1,105 @@ +#!/usr/bin/perl -w + +# Extract or update a section from a combined patch + documentation + +# meta information file. + +use FileHandle; +use Getopt::Long; +use strict; + +my $select; +my $update; + +if (!GetOptions("s|select=s" => \$select, + "u|update=s" => \$update) || + (!defined $select && !defined $update)) { + print STDERR "USAGE: $0 {-s|-u} section file [< replacement]\n"; + exit 1; +} + +foreach my $arg (@ARGV) { + my $fh; + + if ($arg =~ /\.gz$/) { + $fh = new FileHandle("gzip -cd $arg |"); + } elsif ($arg =~ /\.bz2$/) { + $fh = new FileHandle("bzip2 -cd $arg |"); + } else { + $fh = new FileHandle("< $arg"); + } + + unless ($fh) { + print STDERR "$arg: $!\n"; + next; + } + + if (defined $select) { + my $section = "head"; + my $newline = ""; + while (<$fh>) { + if (/^%(.*)/) { + last if $section eq $select; + $section = $1; + next; + } + if ($section eq $select) { + print $newline; + if ($_ eq "\n") { + $newline = $_; + } else { + $newline=""; + print; + } + } + } + } elsif (defined $update) { + my $fh2; + if ($arg =~ /\.gz$/) { + $fh2 = new FileHandle("| gzip -c > $arg.parse"); + } elsif ($arg =~ /\.bz2$/) { + $fh2 = new FileHandle("| bzip2 -c > $arg.parse"); + } else { + $fh2 = new FileHandle("$arg.parse", O_CREAT|O_WRONLY); + } + unless ($fh2) { + die "$arg.parse: $!\n"; + } + + # Copy things before updated section + while (<$fh>) { + if (/^%(.*)/ && $1 eq $update) { + last; + } + print $fh2 $_; + } + # Create/replace updated section + print $fh2 "%$update\n"; + while (<STDIN>) { + print $fh2 $_; + } + print $fh2 "\n"; + # Skip obsolete section + while (<$fh>) { + if (/^%(.*)/) { + print $fh2 $_; + last; + } + } + # Copy things after updated section + while (<$fh>) { + print $fh2 $_; + } + unless (close $fh2) { + die "$arg.patch: $!\n"; + } + + unlink "$arg~"; + unless (rename $arg, "$arg~") { + die "Failed to rename $arg to $arg~: $!\n"; + } + unless (rename "$arg.parse", $arg) { + die "Failed to rename $arg.parse to $arg: $!\n"; + } + } + close $fh; +} diff --git a/lib/patchfns.in b/lib/patchfns.in new file mode 100644 index 0000000..63fae45 --- /dev/null +++ b/lib/patchfns.in @@ -0,0 +1,393 @@ +debug() +{ + #echo "$@" >&2 + true +} + +if [ -n "$PATCHSCRIPTS" ] +then + P=$PATCHSCRIPTS/ +else + unset P +fi + +if [ -e .pc/series ] +then + SERIES=.pc/series +elif [ -e series ] +then + SERIES=series +else + SERIES=${P}patches/series +fi + +DB=".pc/applied-patches" + +quote_re() +{ + echo "$1" | sed -e 's:\([/.+*\[\\]\):\\\1:g' +} + +patch_file_name() +{ + local patch=$1 + + if [ -e $SERIES ] + then + awk '/^'"$(quote_re $patch)"'(|\.patch|\.diff?)([ \t]|$)/ \ + { printf "'"$P"'patches/%s\n", $1; exit }' \ + $SERIES + fi +} + +# The -pN option and possibly others that should be passed to patch. +patch_args() +{ + local patch=$1 + + if [ -e $SERIES ] + then + awk '/^'"$(quote_re $patch)"'(|\.patch|\.diff?)([ \t]|$)/ \ + { if (NF >= 2) + for (i=2; i <= NF; i++) + print $i ; + else print "-p1" ; + exit }' \ + $SERIES + fi +} + +patch_strip_level() +{ + local patch=$1 i + for i in $(patch_args $patch) + do + case $i in + -p) + echo $2 + return ;; + -p*) + echo ${i:2} + return ;; + esac + done + echo "1" +} + +change_db_strip_level() +{ + local level=$1 patch=$2 + + if [ x$level != x-p1 ] + then + level="$level" + else + level="" + fi + + if [ -e $SERIES ] + then + local tmp=$(mktemp /tmp/patch-scripts.XXXXXX) + awk '/^'"$(quote_re $patch)"'(\.patch|\.diff?)([ \t]|$)/ \ + {for(i=2; i<=NF; i++) + if ($i ~ /^-p/) + {$i="'"$level"'" ; break} + if (i > NF) + $i="'"$level"'"} + {print}' \ + $SERIES > $tmp + if cmp $SERIES $tmp >/dev/null 2>/dev/null + then + rm -f $tmp + else + mv -f $tmp $SERIES + fi + else + return 1 + fi +} + +patch_in_series() +{ + local patch=$1 + + if ! [ -e $SERIES ] + then + return 1 + else + grep -q '^'"$(quote_re $patch)"'\(\|\.patch\|.diff\?\)\([ \t]\|$\)' $SERIES + fi +} + +# Insert new patch after topmost patch +insert_in_series() +{ + local patch=$1 patch_args=$2 + local top=$(top_patch) tmp + + if [ -n "$patch_args" ] + then + patch_args=" $patch_args" + fi + + tmp=$(mktemp /tmp/patch-scripts.XXXXXX) || return 1 + if [ -n "$top" ] + then + awk ' { print } + /^'"$(quote_re $top)"'(|\.patch|\.diff?)([ \t]|$)/ \ + { print "'"$patch$patch_args"'" }' \ + < $SERIES \ + > $tmp \ + || return 1 + else + echo $patch$patch_args > $tmp + if [ -e $SERIES ] + then + cat $SERIES >> $tmp + fi + fi + mkdir -p $(dirname $SERIES) + mv -f $tmp $SERIES +} + +pc_file_name() +{ + while [ $# -gt 0 ] + do + echo ".pc/$1/.pc" + shift + done + +} + +backup_file_name() +{ + local patch=$1 + while [ $# -gt 1 ] + do + echo ".pc/$patch/$2" + shift + done +} + +__cat_series() +{ + local series=$1 + if [ -e $series ] + then + sed -e '/^#/d' -e 's/^[ \t]*//' -e 's/[ \t].*//' \ + -e 's/\.patch$//' -e 's/\.diff\?//' $series + else + return 1 + fi +} + +cat_series() +{ + __cat_series $SERIES +} + +top_patch() +{ + [ -e $DB ] && tail -1 $DB +} + +die() +{ + echo "error: $*" + exit 1 +} + +is_numeric() +{ + echo $1 | grep -q -e '^[0-9]*$' +} + +is_applied_last() +{ + local patch=$1 + [ "$(top_patch)" == $patch ] +} + +is_applied() +{ + local patch=$1 + [ -e $DB ] || return 1 + grep -q "^$patch\$" $DB +} + +patches_before() +{ + local patch=$1 + cat_series \ + | awk '$0 == "'"$patch"'" { exit } + { print }' +} + +patches_after() +{ + local patch=$1 + cat_series \ + | awk 'seen { print } + $0 == "'"$patch"'" { seen=1 }' +} + +# List all patches that have been applied on top of patch $1 +patches_on_top_of() +{ + local patch=$1 + [ -e $DB ] || return + awk '$0 == "'"$patch"'" { seen=1 ; next } + seen { print }' $DB +} + +# Print the name of the patch that modified the file $2 next after +# patch $1, or print nothing if patch $1 is on top. +next_patch_for_file() +{ + local patch=$1 file=$2 + local patches_on_top=$(patches_on_top_of $patch) + + if [ -n "$patches_on_top" ] + then + grep -l -e "^$(quote_re $file)\$" \ + $(pc_file_name $patches_on_top) \ + | head -1 \ + | sed -e 's:^\.pc/::' -e 's:/\.pc$::' + fi +} + +can_apply() +{ + local silent + if [ "x$1" == "x-s" ] + then + silent=-s + shift + fi + local patch=$1 + patch -p1 --dry-run -f $silent -i $(patch_file_name $patch) < /dev/null +} + +can_remove() +{ + local silent + if [ "x$1" == "x-s" ] + then + silent=-s + shift + fi + local patch=$1 + patch -R -p1 --dry-run -f $silent -i $(patch_file_name $patch) < /dev/null +} + +add_to_db() +{ + echo $1 >> $DB +} + +remove_from_db() +{ + local patch=$1 + local tmpfile + tmpfile=$(mktemp /tmp/patch-scripts.XXXXXX) + grep -v "^$patch\$" $DB > $tmpfile + mv -f $tmpfile $DB + [ -s $DB ] || rm -f $DB +} + +stripit() +{ + if [ -n "$1" ] + then + echo $1 | + sed -e 's/^\(\.\/\)*//' \ + -e 's/^'"$P"'patches\///' -e 's/^\.pc\///' \ + -e 's/\.patch$//' -e 's/\.diff\?$//' + fi +} + +file_in_patch() +{ + local file=$1 patch=$2 + files_in_patch $patch | grep -q "^$file\$" +} + +files_in_patch() +{ + local pc_file=$(pc_file_name $1) + if [ -e "$pc_file" ] + then + cat $pc_file + fi +} + +install_file_in_patch() +{ + local file=$1 patch=$2 bup_file=$(backup_file_name $patch $file) + + if ! echo $file @LIB@/backup-file -s -B .pc/$patch/ || \ + ! echo $file >> $(pc_file_name $patch) + then + return 1 + fi +} + +refresh_file_list() +{ + local patch=$1 + local pc_file=$(pc_file_name $patch) + local patch_file=$(patch_file_name $patch) + + if ! [ -e "$patch_file" ] + then + echo "Patch file $patch_file does not exist" + return 1 + fi + if [ ! -e $pc_file -o $pc_file -ot $patch_file ] + then + mkdir -p $(dirname $pc_file) + echo "touched-by-patch $(patch_strip_level $patch) $patch_file > $pc_file" >&2 + @LIB@/touched-by-patch -p$(patch_strip_level $patch) \ + $patch_file > $pc_file + if [ $? -ne 0 ] + then + return 1 + fi + return 2 + fi +} + +directory_names() +{ + echo "$@" \ + | awk 'BEGIN {RS=" "} + {sub(/\/?[^\/]*$/, "") ; printf "[%s]\n", $0} + {if (last != $0) + print last + last = $0} + END {print last}' +} + +need_file_there() +{ + if [ ! -e $1 ] + then + echo "File $1 does not exist" + exit 1 + fi +} + +patch_description() +{ + local patch_file=$1 + + if [ -e "$patch_file" ] + then + awk '/^--- / {exit} + diff {print diff ; diff=""} + /^diff / {diff=$0 ; next} + {print}' \ + $patch_file + fi +} diff --git a/lib/rpatch.in b/lib/rpatch.in new file mode 100755 index 0000000..d25695b --- /dev/null +++ b/lib/rpatch.in @@ -0,0 +1,202 @@ +#!/bin/sh + +if ! [ -r @LIB@/patchfns ] +then + echo "Cannot read library @LIB@/patchfns" >&2 + exit 1 +fi +. @LIB@/patchfns + +usage() +{ + echo "Usage: rpatch [-fRq] patchname" + exit 1 +} + +verify_removal() +{ + local patch=$1 + local bup file status=0 + local dir=$(basename $PWD) suffix=${patch//\//_} + + # Check if all changes of the patch are undone + for file in $(files_in_patch $patch) + do + bup=$(backup_file_name $patch $file) + if ! [ -s $file -o -s $bup ] || \ + cmp $file $bup > /dev/null 2> /dev/null + then + continue + fi + + if [ $status -eq 0 ] + then + echo "Patch does not remove changes:" + fi + + diff -Nu $DIFF_OPTS $bup $file \ + | sed -e 's:^--- [^ \t]*:--- '"$dir/$file.orig"':' \ + -e 's:^+++ [^ \t]*:+++ '"$dir/$file"':' + + status=1 + done + return $status +} + +files_may_have_changed() +{ + local patch=$1 file + local pc_file=$(pc_file_name $patch) + local apply_ts=$(date -r $pc_file '+%s') ts + + if [ $pc_file -ot $(patch_file_name $patch) ] + then + return 0 + fi + + for file in $(files_in_patch $patch) + do + if ! [ -e $file ] + then + return 0 # file is missing + fi + + ts=$(date -r $file '+%s') + if [ $? -ne 0 -o $ts -gt $apply_ts ] + then + return 0 # file has changed + fi + done + return 1 +} + +abort_patch() +{ + local pc_file=$(pc_file_name $1) + @LIB@/backup-files -s -f $pc_file -z ~rpatch -r + exit 1 +} + +interrupt() +{ + local patch=$1 + abort_patch $patch + echo "rpatch interrupted by user" + exit 1 +} + +do_remove() +{ + local patch=$1 pc_file=$(pc_file_name $patch) + + trap "" SIGINT + if [ -n "$opt_force" ] || \ + ( [ -z "$opt_remove" ] && ! files_may_have_changed $patch ) + then + # Optimize: Force remove if the patch has not been + # modified since it was applied. (Forced remove is + # faster!) + + @LIB@/backup-files $silent -f $pc_file -B .pc/$patch/ -r + status=$? + remove_from_db $patch + rm -f $(pc_file_name $patch)~forced + return $status + else + local patch_file=$(patch_file_name $patch) + if ! [ -e "$patch_file" ] + then + echo "No patch named $patch found." + return 1 + fi + + trap "interrupt $patch" SIGINT + #if [ ! -e $patch_file ] || grep -q '^%patch$' $patch_file + #then + # @LIB@/parse-patch -s patch $patch_file \ + # | patch $(patch_args $patch) --no-backup-if-mismatch \ + # -R -E $silent + #else + patch $(patch_args $patch) --no-backup-if-mismatch \ + -R -E $silent -i $patch_file + #fi + status=$? + trap "" SIGINT + if [ $status -eq 0 ] && verify_removal $patch + then + @LIB@/backup-files -s -f $pc_file -z ~rpatch -x + @LIB@/backup-files -s -f $pc_file -B .pc/$patch/ -x + + remove_from_db $patch + else + @LIB@/backup-files -s -f $pc_file -z ~rpatch -r + echo "Patch $patch does not remove cleanly" + return 1 + fi + fi + trap - SIGINT +} + +options=`getopt -o fRqh -- "$@"` + +if [ $? -ne 0 ] +then + usage +fi + +eval set -- "$options" + +while true +do + case "$1" in + -f) + opt_force=1 + shift ;; + -R) + opt_remove=1 # remove properly with patch -R; no tricks + unset opt_force + shift ;; + -q) + opt_quiet=1 + shift ;; + -h) + usage -h ;; + --) + shift + break ;; + esac +done + +if [ $# -ne 1 ] +then + usage +fi + +patch=$(stripit $1) +[ -n "$opt_quiet" ] && silent=-s + +top=$(top_patch) +if [ -n "$top" -a -e $(pc_file_name $top)~forced -a -z "$opt_force" ] +then + echo "The topmost patch $top was force applied. Please run" \ + "refpatch before removing it." + exit 1 +fi + +#if is_applied "$patch" +#then + do_remove "$patch" || exit 1 +#else +# echo "Patch $patch is not applied" +# exit 1 +#fi + +top=$(top_patch) +if [ -z "$top" ] +then + where="no patches applied" +else + where="now at $top" +fi +echo "Removed $patch, $where" + diff --git a/lib/touched-by-patch b/lib/touched-by-patch new file mode 100755 index 0000000..f26469e --- /dev/null +++ b/lib/touched-by-patch @@ -0,0 +1,63 @@ +#!/bin/sh +# Extract names of new files from a patch, print them out + +usage() +{ + echo "Usage: touched-by-patch [-p num] patchname" + exit 1 +} + +options=`getopt -o p:h -- "$@"` + +if [ $? -ne 0 ] +then + usage +fi + +eval set -- "$options" + +while true +do + case "$1" in + -p) + opt_p=$2 + shift 2 ;; + -h) + usage ;; + --) + shift + break ;; + esac +done + +if [ $# -ne 1 ] +then + usage +fi + +patch_file=$1 +[ -z "$opt_p" ] && opt_p=1 + +case "$patch_file" in +*.bz2) + command="bzip2 -cd $patch_file";; +*.gz) + command="gzip -cd $patch_file";; +*) + command="cat $patch_file";; +esac + +# Neither `+++' nor `---' works for all patches; patch looks at the +# file system to determine which file name to use. + +eval $command \ +| awk '/^\+\+\+[ \t]/ { + sub(/^\+\+\+[ \t]/, "") + sub(/[ \t].*/, "") + sub(/^\/dev\/null/, "") + for (i=0; i<'$opt_p'; i++) + sub(/^[^\/]*\//, "") + print + }' \ +| sort \ +| uniq diff --git a/needs-checking/combine-applied b/needs-checking/combine-applied new file mode 100755 index 0000000..487b553 --- /dev/null +++ b/needs-checking/combine-applied @@ -0,0 +1,46 @@ +#!/bin/sh + +# +# Make superpatch from currently applied patches using combinediff. +# + +. patchfns 2>/dev/null || +. /usr/lib/patch-scripts/patchfns 2>/dev/null || +. $PATCHSCRIPTS_LIBDIR/patchfns 2>/dev/null || +{ + echo "Impossible to find my library 'patchfns'." + echo "Check your install, or go to the right directory" + exit 1 +} + +usage() +{ + echo "Usage: combine-applied output-file" + exit 1 +} + +if [ $# -ne 1 ] +then + usage +fi + +need_file_there applied-patches +CURRENT=$(mktemp /tmp/cmbd-XXXXXXXX) +for FILE in `cat applied-patches` +do + NEXT=$(mktemp /tmp/cmbd-XXXXXXXX) + if [ -f $P/patches/$FILE ] + then + combinediff $CURRENT $P/patches/$FILE > $NEXT + elif [ -f $P/patches/$FILE.patch ] + then + combinediff $CURRENT $P/patches/$FILE.patch > $NEXT + elif [ -f $FILE ] + then + combinediff $CURRENT $FILE > $NEXT + fi + rm $CURRENT + CURRENT=$NEXT +done + +mv $NEXT "$1" diff --git a/needs-checking/combine-series b/needs-checking/combine-series new file mode 100755 index 0000000..914acec --- /dev/null +++ b/needs-checking/combine-series @@ -0,0 +1,46 @@ +#!/bin/sh + +# +# Make superpatch from current series using combinediff. +# + +. patchfns 2>/dev/null || +. /usr/lib/patch-scripts/patchfns 2>/dev/null || +. $PATCHSCRIPTS_LIBDIR/patchfns 2>/dev/null || +{ + echo "Impossible to find my library 'patchfns'." + echo "Check your install, or go to the right directory" + exit 1 +} + +usage() +{ + echo "Usage: combine-series output-file" + exit 1 +} + +if [ $# -ne 1 ] +then + usage +fi + +need_file_there series +CURRENT=$(mktemp /tmp/cmbd-XXXXXXXX) +for FILE in $(cat_series) +do + NEXT=$(mktemp /tmp/cmbd-XXXXXXXX) + if [ -f $P/patches/$FILE ] + then + combinediff $CURRENT $P/patches/$FILE > $NEXT + elif [ -f $P/patches/$FILE.patch ] + then + combinediff $CURRENT $P/patches/$FILE.patch > $NEXT + elif [ -f $FILE ] + then + combinediff $CURRENT $FILE > $NEXT + fi + rm $CURRENT + CURRENT=$NEXT +done + +mv $NEXT "$1" diff --git a/needs-checking/cvs-take-patch b/needs-checking/cvs-take-patch new file mode 100755 index 0000000..c6a6a2a --- /dev/null +++ b/needs-checking/cvs-take-patch @@ -0,0 +1,78 @@ +#!/bin/sh + +doit() +{ + echo $* + $* +} + +usage() +{ + echo "Usage: cvs-take-patch patch_file_name" + exit 1 +} + +# +# Find the highest level directory in $1 which does not +# contain the directory $2. Return it in $MISSING +# +highest_missing() +{ + START_DIR="$1" + NAME="$2" + MISSING="" + WHERE=$(dirname "$START_DIR") + PREV_WHERE=$START_DIR + while [ x"$WHERE" != x"$PREV_WHERE" ] + do + WHERE="$PREV_WHERE" + if [ ! -d "$WHERE"/"$NAME" ] + then + MISSING="$WHERE" + fi + PREV_WHERE=$(dirname "$WHERE") + done + echo highest_missing returns $MISSING +} + +# +# Add all new directries to CVS, top-down +# $1: name of a directory +# $2: name of the CVS directory +# +add_cvs_dirs() +{ + MISSING=foo + while [ "$MISSING" != "" ] + do + highest_missing $1 $2 + if [ x"$MISSING" != "x" ] + then + if [ ! -d "$MISSING"/"$2" ] + then + doit cvs add $MISSING + fi + fi + done +} + +PATCHFILE=$1 + +REMOVEDFILES=$(removed-by-patch $PATCHFILE) +if [ "$REMOVEDFILES" != "" ] +then + doit cvs remove $REMOVEDFILES +fi + +NEWFILES=$(added-by-patch $PATCHFILE) +for i in $NEWFILES +do + DIRNAME=$(dirname $i) + echo "Looking at $DIRNAME" + add_cvs_dirs $DIRNAME CVS +done + +if [ "$NEWFILES" != "" ] +then + doit cvs add $NEWFILES +fi diff --git a/needs-checking/docco.txt b/needs-checking/docco.txt new file mode 100644 index 0000000..1860f13 --- /dev/null +++ b/needs-checking/docco.txt @@ -0,0 +1,717 @@ +Patch management scripts +Andrew Morton <akpm@digeo.com> +18 October 2002 + +This is a description of a bunch of shell scripts which I use for +managing kernel patches. They are quite powerful. They can be used on +projects other than the linux kernel. They are easy to use, and fast. + +You end up doing a ton of recompiling with these scripts, because +you're pushing and popping all the time. ccache takes away the pain of +all that. http://ccache.samba.org/ - be sure to put the cache +directory on the same fs as where you're working so that ccache can use +hardlinks. + +The key philosophical concept is that your primary output is patches. +Not ".c" files, not ".h" files. But patches. So patches are the +first-class object here. + +Installation +============ + +You place all the scripts somewhere in your path, or in +/usr/lib/patch-scripts. + +Terminology +=========== + +The patch scripts require three special directories called "pc", +"patches" and "txt". + +If the environment variable PATCHSCRIPTS is set, it is taken to to be +the directory in which those three directories reside. Typically, it +would be a relative pathname. So + + setenv PATCHSCRIPTS ./i-put-them-here + +would tell the patch scripts to look in ./i-put-them-here/pc, etc. + +If PATCHSCRIPTS is not set, and the directory ./patch-scripts is +present then the patch scripts will us ./patch-scripts/pc/, +./patch-scripts/patches/ and ./patch-scripts/txt/. + +Otherwise, the patch scripts use ./pc, ./patches and ./txt. + +In this document, the symbol $P is used to describe the directory which +holds the pc/, patches/ and txt/ directories, as determined by the +above search. + +It is expected that $P will always expand to a relative path. + +Concepts +======== + +All work occurs with a single directory tree. All commands are invoked +within the root of that tree. The scripts manage a "stack" of patches. + +Each patch is a changeset against the base tree plus the preceding patches. + +All patches are listed, in order, in the file ./series. You manage the +series file. Lines in the series file which start with `#' are ignored. + +Any currently-applied patches are described in the file +./applied-patches. The patch scripts manage this file. + +Each patch affects a number of files in the tree. These files are +listed in a "patch control" file. These .pc files live in the +directory $P/pc/ + +Patches are placed in the directory $P/patches/ + +Documentation for the patches is placed in $P/txt/ + +So for a particular patch "my-first-patch" the following will exist: + +- An entry "my-first-patch.patch" in ./series + +- An entry "my-first-patch" in ./applied-patches (if it's currently applied) + +- A file $P/pc/my-first-patch.pc which contains the names of the + files which my-first-patch modifies, adds or removes + +- A file $P/txt/my-first-patch.txt which contains the patch's + changelog. + +- A file $P/patches/my-first-patch.patch, which is the output of the + patch scripts. + +Operation +========= + +When a patch "my-patch" is applied with apatch, or with pushpatch +(which calls apatch), all the affected files (from $P/pc/my-patch.pc) +are copied to files with ~my-patch appended. So if $P/pc/my-patch.pc +contained + + kernel/sched.c + fs/inode.c + +then apatch will copy those files into kernel/sched.c~my-patch and +fs/inode.c~my-patch. It will then apply the patch to kernel/sched.c +and fs/inode.c + +When a diff is regenerated by refpatch (which calls mpatch), the diff +is made between kernel/sched.c and kernel/sched.c~my-patch. How do the +scripts know to use "~my-patch"? Because my-patch is the current +topmost patch. It's the last line in ./applied-patches. + +In this way, the whole thing is stackable. If you have four patches +applied, say "patch-1", "patch-2", "patch-3" and "patch-4", and if +patch-2 and patch-4 both touch kernel/sched.c then you will have: + + kernel/sched.c~patch-2 Original copy, before patch-2 + kernel/sched.c~patch-4 Copy before patch-4. Contains changes + from patch-2 + kernel/sched.c Current working copy. Contains changes + from patch-4. + +This means that your diff headers contain "~patch-name" in them, which +is convenient documentation. + +Walkthrough +=========== + +Let's start. + +Go into /usr/src/linux (or wherever) + + mkdir pc patches txt + +Now let's generate a patch + + fpatch my-patch kernel/sched.c + +OK, we've copied kernel/sched.c to kernel/sched.c~my-patch. We've +appended "my-patch" to ./applied-patches and we've put "kernel/sched.c" +into the patch control file, pc/my-patch.pc. + + Now edit kernel/sched.c a bit. + +Now we're ready to document the patch + + Now write txt/my-patch.txt + +Now generate the patch + + refpatch + +This will generate patches/my-patch.patch. Take a look. + +Now remove the patch + + poppatch + +applied-patches is now empty, and the patch is removed. + +Now let's add a file to my-patch and then generate my-second-patch: + + Add "my-patch.patch" to ./series (no blank lines in that file please) + + pushpatch + +OK, the patch is applied again. Let's add another file + + fpatch kernel/printk.c + +Note that here we gave fpatch a single argument. So rather than +opening a new patch, it adds kernel/printk.c to the existing topmost +patch. That's my-patch. + + Edit kernel/printk.c + +Refresh my-patch (you end up running refpatch a lot) + + refpatch + +Now start a second patch: + + fpatch my-second-patch kernel/sched.c + +Now take a look at applied-patches. Also do an `ls kernel/sched*'. + + Edit kernel/sched.c, to make some changes for my-second-patch + +Generate my-second-patch: + + refpatch + +Take a look in patches/my-second-patch.patch + +Don't forget to add "my-second-patch.patch" to the series file. + +And remove both patches: + + poppatch + poppatch + + +That's pretty much it, really. + + +Command reference +================= + +Generally, where any of these commands take a "patch-name", that can be +of the form txt/patch-name.txt, patch-name.pc, just patch-name or +whatever. The scripts will strip off a leading "txt/", "patches/" or +"pc/" and any trailing extension. This is so you can do + + apatch patches/a<tab> + +to conveniently use shell tabbing to select patch names. + + + +added-by-patch + + Some internal thing. + +apatch [-f] patch-name + + This is the low-level function which adds patches. It does the + copying into ~-files and updates the applied-patches file. It + applies the actual patch. + + apatch will do a patch --dry-run first and will refuse to apply the + patch if the dryrun fails. + + So when you are getting rejects you do this: + + pushpatch # This fails, due to rejects. Drat. + apatch -f patch-name # Force the patch + (or) pushpatch -f # Force the patch + + OK, you've now applied patch-name, but you have rejects. Go fix + those up and do + + refpatch + + And you're ready to move on. + +combine-series output-file + + It incrementally combinediffs all the patches in series to make a + complete patch for the series. Requires combinediff frmo patchutils. + + See http://cyberelk.net/tim/patchutils/ (Don't download the + "experimental" patchutils - it seems to only have half of the + commands in it. Go for "stable") + +cvs-take-patch + + I forget. + +export_patch + + export the patches listed in ./series to a set of files which + are named in such a way that the sort order is the same as the + order of the series file. + + Usage: export_patch directory [prefix] + + Example: + + Suppose ./series contains + + mango.patch + orange.patch + banana.patch + apple.patch + pear.patch + + export_patch ../mypatches fruit + + The patches would be copied to + + ../mypatches/p00001_fruit_mango.patch + ../mypatches/p00002_fruit_orange.patch + ../mypatches/p00003_fruit_banana.patch + ../mypatches/p00003_fruit_banana.patch + ../mypatches/p00003_fruit_banana.patch + + Named in this way, someone may easily apply them: + + cat mypatches/p*fruit* | patch -p1 + + If prefix is omitted, the patchnames will be transformed + such that "original.patch" becomes "pXXXXX_original.patch". + +fpatch [patch-name] foo.c + + If patch-name is given, fpatch will start a new patch which + modifies (or adds, or removes) the single file foo.c. It updates + ./applied-patches and creates pc/patch-name.pc. fpatch will copy + foo.c to foo.c~patch-name in preparation for edits of foo.c. + + If patch-name is not given then fpatch will add foo.c to the + current topmost patch. It will add "foo.c" to $P/pc/$(toppatch).pc. + It will copy foo.c to foo.c~$(toppatch). + +import_patch + + Imports a set of patch files, creating $P/pc, $P/txt, $P/patches and + ./series as necessary. It also creates $P/txt/*.txt by stripping + off the top of the patches (and removes any diffstat output it finds, + so that it can eat refpatch output and export_patch output.) The + imported patch names are appended to the series file. + + In creating the $P/txt/*.txt files, mail headers are stripped with + formail, preserving the "From:" and "Subject:" lines. "DESC" and + "EDESC" markers are added if they are not already present, using the + "From:" and "Subject:" lines for the DESC portion, if they are present. + (See "patchdesc" command, below, for more on these markers.) + + Also, it can rename the patch file as it is imported by stripping out + a pattern. This is useful if, as often is the case, you have patch + sets with filenames designed to help sort the patches into the correct + order, such as "p001_xxx_funky_stuff.patch" you can have it automatically + renamed to funky_stuff.patch on import, and let the series file manage + the ordering. + + Import_patch will uncompress patches (*.Z, *.bz2, *.gz) as necessary. + + Usage: + + import_patch [-p pattern] patchfile ... + + Example: + + % ls ../fruit/p*patch + ../fruit/p00001_northern_apple.patch + ../fruit/p00001_tropical_mango.patch + ../fruit/p00002_northern_pear.patch + ../fruit/p00002_tropical_orange.patch + ../fruit/p00003_tropical_banana.patch + % import_patch -p 'p[0-9]*_tropical_' ../fruit/p*tropical* + Recreated pc/mango.pc + Recreated pc/orange.pc + Recreated pc/banana.pc + % import_patch -p 'p[0-9]*_northern_' ../fruit/p*northern* + Recreated pc/apple.pc + Recreated pc/pear.pc + + Then you can "pushpatch; refpatch" 5 times. + +inpatch + + List the names of ths files which are affected by the current + topmost patch. + + This is basically + + cat pc/$(toppatch).pc + +join-patch patchname + + "joins" the named patch to the current topmost patch. + + Use this when you want to merge two patches into one. All the + files which `patchname' affects are added to pc/$(toppatch).pc (if + they are not already there) and patch `patchname' is applied. The + top patch remains unchanged. You'll need to run refpatch afterwards. + +mpatch + + A low-level thing to generate patches + +new-kernel + + Some thing I use for importing a new kernel from kernel.org + +p0-2-p1 + + Internal thing to convert patch -p0 form into patch -p1 + +patchdesc + + Generates a single-line description of a patch. + + The txt/my-patch.txt files have the following format: + + <start of file> + DESC + some short description + EDESC + + The long description + <end of file> + + I use + + patchdesc $(cat series) + + to generate short-form summaries of the patch series. + +patchfns + + Internal utilities + +pcpatch + + Standalone tool to generate a .pc file from a patch. + + Say someone sends you "his-patch.diff". What you do is: + + cp ~/his-patch.diff patches/his-patch.patch + pcpatch his-patch + + This generates $P/pc/his-patch.pc and you're all set. Add + "his-patch.patch" to ./series in the right place and start pushing. + +p_diff + + I forget + +poppatch + + Remove one or more patches from the current stack. This command + does *not* use the series file. It works purely against + applied-patches. + + Usage: + + poppatch + Remove the topmost patch + poppatch 10 + Remove ten patches + poppatch some-patch-name[.patch] + Remove patches until "some-patch-name" is top patch + +pstatus + + Shows status of patches + + Usage: + pstatus [patchfile ...] + + One line per patch is output showing: + 1: Patch number in the series file + 2: Whether the patch is currently applied + 3: Name of patch + 4: Status of the patch (needs pcpatch, changelog, refpatch) + + If no patchfiles are specified, $P/patches/*.patch + are assumed. + + Caveats: + A patch set which contains separate patches to add a file + and modify that same file may give spurious "Needs refpatch" + status for the patch which adds the file or the topmost patch. + +ptkdiff + + Two modes: + + ptkdiff - + + Run tkdiff against all the file affected + by $(toppatch). The diff is only for the changes made + by the top patch! ie: it's between "filename" and + "filename~toppatch-name". + + ptkdiff filename + + Just run tkdiff against that file, + showing the changes which are due to toppatch. + +pushpatch [-f] + + Apply the next patch, from the series file. + + This consults ./applied-patches to find out the top patch, then + consults ./series to find the next patch. And pushes it. + + pushpatch + + Apply the next patch + + pushpatch 10 + + Apply the next ten patches + + pushpatch some-patch-name + + Keep pushing patches until "some-patch-name" is toppatch + + pushpatch -f + + Push the next patch, ignoring rejects. + +refpatch + + regnerates the topmost patch. Reads all the affected files + from pc/$(toppatch).pc and diffs them against their tilde-files. + + Also pastes into the patch your patch documentation and + generates a diffstat summary. + +removed-by-patch + + Some thing. + +rename-patch + + CVS rename for patches. + +rolled-up-patch + + Bit of a hack. Is designed to generate a rolled-up diff of all + currently-applied patches. But it requires a ../linux-2.x.y tree to + diff against. Needs to be redone. + +rpatch + + Internal command + +split-patch + + Some thing someone write to split patches up. I don't use it. + +tag-series + + Assuming you keep pc/*, patches/* and txt/* under CVS revision + control, tag-series allows you to tag a patchset's individual + components. I use + + tag-series s2_5_44-mm3 pc/2.5.44-mm3-series + + which will attach the cvs tag "s2_5_44-mm3" to every .pc, .patch + and .txt file which is mentioned in the series file + "pc/2.5.44-mm3-series". + + It will also tag pc/2.5.44-mm3-series, which is a bit redundant + given that I use a different series file for each patchset release.. + + +toppatch + + Print the name of the topmost patch. From ./applied-patches + +touched-by-patch patch-filename + + List the names of files which are affected by a diff. + +unitdiff.py + + Rasmus Andersen's script to convert a diff into minimum-context + form. This form has a better chance of applying if you're getting + nasty rejects. But patch can and will make mistakes when fed + small-context input. + + +Work Practices +============== + +I keep the kernel tree, the $P/pc/, $P/patches/ and $P/txt/ contents under +CVS control. This is important... + +I have several "series" files. I keep these in $P/pc/foo-series and use + + ln -s pc/foo-series series + +when I'm working on foo. + +If someone sends me a patch I'll do: + + cp ~/whatever patches/his-patch.patch + pcpatch his-patch + apatch his-patch + + If apatch fails then run `apatch -f his-patch' and fix the rejects. + + refpatch + + to clean up any fuzz. + + poppatch + cvs add pc/his-patch.pc patches/his-patch.patch + cvs commit pc patches + + Now edit ./series and place "his-patch.patch" in the appropriate place. + + +If you're working on a particular patch (say, "dud-patch") and you +balls something up, just run: + + refpatch # Generate the crap patch + poppatch # Remove it all + rm patches/dud-patch.patch + cvs up patches/dud-patch.patch + +and all is well. + + +Getting updates from Linus +========================== + +What I do is to grab the latest -bk diff from +http://www.kernel.org/pub/linux/kernel/people/dwmw2/bk-2.5/ +and do: + + gzip -d < cs<tab> > patches/linus.patch + pcpatch linus + apatch linus | grep diff + + Now fix up all the files which got deleted, + because there's something wrong with bitkeeper diffs: + + cvs up -ko <missing files from the above diff> + + apatch linus + $EDITOR linus/linus.txt + + Add the changeset number to txt/linus.txt + + refpatch + poppatch + + Now add "linus.patch" as the first entry in your ./series file and + start pushing your other patches on top of that. + +BUGS +==== + +Tons and tons. The scripts are fragile, the error handling is ungraceful and +if you do something silly you can end up in a pickle. + +Generally the scripts are very careful to not wreck your files or your +patches. But they can get the ./applied-patches and ~-files into an +awkward state. + +Usually you can sort it out by copying the ~-files back onto the originals +and removing the last line from ./applied-patches. Or do a "refpatch ; +poppatch ; rm patches/troublesome-patch.patch ; cvs up patches". + +If it's really bad, just blow away the entire tree and do a new CVS checkout. + + +Working on non-kernel projects +============================== + +Well it's the same thing. Say you've downloaded a copy of util-linux +and you want to make a change: + + cd /usr/src + tar xvfz ~/util-linux.tar.gz + cd util-linux + mkdir pc patches txt + fpatch my-patch sys-utils/rdev.c + fpatch sys-utils/ipcs.8 + <edit, edit> + refpatch + <ship patches/my-patch.patch> + +How to balls things up +====================== + +Well here's one way. Suppose you have 20 patches applied, and three of +them (say, "p1", "p6" and "p11") all modify "foo.c". + +Now you go and change foo.c. + +Well, to which patch does that change belong? You need to decide. +Let's say you decide "p6". + +If you run `refpatch' when "p11" is toppatch then you lose. The diff +went into p11. + +What you can do is: + +1: + poppatch p6 + <edit> + refpatch + pushpatch p11 + <test> + + (See why ccache is looking good?) + +or + +2: + <edit> + <test> + poppatch p6 <hope like hell that the other patches remove cleanly> + refpatch + + +Another good way of ballsing up is to cheat. Say "oh I just want to make +this one-line change". And "oh, and this one". + +Now you're getting in a mess. It's much, much better to just use the system: + + fpatch junk file1 + fpatch file2 + <edit> + <play> + refpatch + poppatch + rm pc/junk.pc patches/junk.patch + +Merging with -mm kernels +======================== + +Haven't tried this, but it should work: + +- Grab all the patches from broken-out/, place them in your $P/patches/ + +- Copy my series file into ./series (or $P/pc/akpm-series and symlink it) + +- pushpatch 99 + +And you're off and running. The nice thing about this is that you can +send me incremental diffs to diffs which I already have. + +Or whatever. I'm fairly handy with diffs nowadays. Rejects are +expected. I just prefer to have "one concept per diff". + diff --git a/needs-checking/export_patch b/needs-checking/export_patch new file mode 100755 index 0000000..8e7116a --- /dev/null +++ b/needs-checking/export_patch @@ -0,0 +1,58 @@ +#!/bin/sh + +. patchfns 2>/dev/null || +. /usr/lib/patch-scripts/patchfns 2>/dev/null || +. $PATCHSCRIPTS_LIBDIR/patchfns 2>/dev/null || +{ + echo "Impossible to find my library 'patchfns'." + echo "Check your install, or go to the right directory" + exit 1 +} + +usage() +{ + echo "export_patch: export the patches listed in ./series" 1>&2 + echo "usage: export_patch destination-directory [prefix] " 1>&2 + exit 1 +} + +DIR="$1" +PREFIX="$2""_" + +if [ "$DIR" = "" ] +then + usage +fi + +if [ -e "$DIR" -a ! -d "$DIR" ] +then + echo "$DIR exists already, but is not a directory." 1>&2 + exit 1 +fi + +if [ ! -r ./series ] +then + echo "./series is not readable." 1>&2 + exit 1 +fi + +mkdir -p "$DIR" || exit 1 + +count=1 +for x in $(cat_series)` +do + fname=`echo "$count" "$PREFIX" "$x" |\ + awk '{ if ( $2 != "_" ) + printf("p%05d_%s%s\n", $1, $2, $3); + else + printf("p%05d_%s\n", $1, $3); + }'` + if [ ! -r $P/patches/"$x" ] + then + echo "$P/patches/"$x" is not readable. skipping." 1>&2 + continue; + fi + cp -f $P/patches/"$x" "$DIR"/"$fname" || continue; + count=`expr $count + 1` +done + diff --git a/needs-checking/extract_description b/needs-checking/extract_description new file mode 100755 index 0000000..6fa0e68 --- /dev/null +++ b/needs-checking/extract_description @@ -0,0 +1,87 @@ +#!/bin/sh + +insert_line() +{ + PATTERN="$1" + LINE="$2" + FILE="$3" + awk ' BEGIN { found=0; } + /'"$PATTERN"'/ { + print; + if (!found) + printf("%s\n", "'$LINE'"); + found=1; + next; + } + { print; } + ' < "$FILE" +} + +# extract the description from the top of a patch +# filter stdin +# collapse adjacent blank lines to a single blank line +# remove any lines that look like diffstat output +# stop output on encountering a line beginning with '---' (beginning of patch) + + TMPFILE=`mktemp /tmp/xdtmp.XXXXXX` || exit 1 + formail -kfcb -X 'From:' -X 'Subject:' |\ + awk ' + BEGIN { found_end=0; lastone="x"; } + /^ .* [|] +[0-9]+ [+-]+$/ { + #/* we found something like diffstat output... */ + if (found_end == 1) { + /* we are past end of diffstat, let it pass */ + print; + } + next; + } + /^ [1-9][0-9]* files changed/ { + #/* end of diffstat output, stop filtering diffstat */ + found_end=1; + next; + } + /^--- / { exit; } + { + #/* collapse adjacent blank lines to 1 blank line */ + if ( $0 == "" && lastone == "" ) + next; + else + print; + lastone=$0; + } + ' | awk '{ if ($0 == "" && FNR == 1) next; print; }' > "$TMPFILE" + + descs=`head -10 $TMPFILE | grep -c '^[ ]*DESC[ ]*$'` + if [ "$descs" = "0" ] + then + # DESC is not 1st non blank line in the file + echo "DESC" + descs=0 + fi + edescs=`grep -c '^EDESC$' "$TMPFILE"` + subjects=`grep -c '^[ ]*Subject[:]' "$TMPFILE"` + froms=`grep -c '^[ ]*From[:]' "$TMPFILE"` + if [ "$edescs" = "0" ] + then + if [ "$subjects" != "0" ] + then + insert_line '^Subject[:]' 'EDESC' "$TMPFILE" + else + if [ "$froms" != "0" ] + then + insert_line '^From[:]' 'EDESC' "$TMPFILE" + else + if [ "$descs" = "0" ] + then + # blank DESC line... + echo '(undescribed patch)' + echo EDESC + cat "$TMPFILE" + else + insert_line '^DESC$' "EDESC" "$TMPFILE" + fi + fi + fi + else + cat $TMPFILE + fi diff --git a/needs-checking/join-patch b/needs-checking/join-patch new file mode 100755 index 0000000..493d434 --- /dev/null +++ b/needs-checking/join-patch @@ -0,0 +1,28 @@ +#!/bin/sh + +usage() +{ + echo "Usage: join-patch patchname" + exit 1 +} + +if [ $# -ne 1 ] +then + usage +fi + +PATCHNAME=$(stripit $1) + +if ! can_apply $PATCHNAME +then + echo Patch $PATCHNAME does not apply + exit 1 +fi + +pcpatch $PATCHNAME +for i in $(files_in_patch $PATCHNAME) +do + fpatch $i +done + +patch -p1 -i $(patch_file_name $PATCHNAME) -f diff --git a/needs-checking/linus-patch b/needs-checking/linus-patch new file mode 100755 index 0000000..24312b7 --- /dev/null +++ b/needs-checking/linus-patch @@ -0,0 +1,29 @@ +#!/bin/sh +# +# Grab a patch frmo kernel.org, install it. +# +# Usage: linus-patch http://www.kernel.org/pub/linux/kernel/people/dwmw2/bk-2.5/cset-1.786.152.7-to-1.798.txt.gz +# + +. patchfns 2>/dev/null || +. /usr/lib/patch-scripts/patchfns 2>/dev/null || +. $PATCHSCRIPTS_LIBDIR/patchfns 2>/dev/null || +{ + echo "Impossible to find my library 'patchfns'." + echo "Check your install, or go to the right directory" + exit 1 +} + +poppatch 999 || die poppatch +wget $1 || die wget +FILE=$(basename $1) +gzip -d < $FILE > $P/patches/linus.patch +pcpatch linus || die pcpatch +( + echo DESC + echo $FILE + echo EDESC + echo + echo $FILE +) > $P/txt/linus.txt +rm $FILE diff --git a/needs-checking/new-kernel b/needs-checking/new-kernel new file mode 100755 index 0000000..2b065a6 --- /dev/null +++ b/needs-checking/new-kernel @@ -0,0 +1,82 @@ +#!/bin/sh + +usage() +{ + echo "Usage: new-kernel linux-2.4.2-pre2 linux-2.4.3-pre3 linux-2.4.3 patch.gz cvs-dir" + exit 1 +} + +wantdir() +{ + if [ x$1 = x ] + then + usage + fi + if [ ! -d $1 ] + then + echo "directory $1 does not exist" + usage + fi +} + +wantfile() +{ + if [ x$1 = x ] + then + usage + fi + if [ ! -f $1 ] + then + echo "file $1 does not exist" + usage + fi +} + +doit() +{ + echo $* 1>&2 + $* || { + echo oops + exit 1 + } +} + + +CURRENT_KERNEL=$1 +NEXT_KERNEL=$2 +BASE_KERNEL=$3 +PATCH_FILE=$4 +CVS_DIR=$5 + +TEMP_PATCH=$(mktemp /tmp/patch-XXXXXX) +MY_DIFF="$CURRENT_KERNEL"--"$NEXT_KERNEL" + +wantdir $CURRENT_KERNEL +wantdir $BASE_KERNEL +wantdir $CVS_DIR +wantfile $PATCH_FILE + +doit rm -rf $NEXT_KERNEL +doit cp -a $BASE_KERNEL $NEXT_KERNEL +doit rm -f $TEMP_PATCH +doit gunzip < $PATCH_FILE > $TEMP_PATCH +cd $NEXT_KERNEL +doit patch -p1 --dry-run -i $TEMP_PATCH +doit patch -p1 -s -i $TEMP_PATCH +echo cd .. +cd .. + +echo diff -uNrp $CURRENT_KERNEL $NEXT_KERNEL +diff -uNrp $CURRENT_KERNEL $NEXT_KERNEL > $MY_DIFF + +echo cd $CVS_DIR +cd $CVS_DIR +doit patch -p1 --dry-run -s -i ../$MY_DIFF +doit patch -p1 -s -i ../$MY_DIFF +cvs-take-patch ../$MY_DIFF +cvs commit -m "'doing $NEXT_KERNEL'" +cvs update -ko -d -P + +TAG=$(echo $NEXT_KERNEL | sed -e 's@\.@_@g') +cvs tag $TAG +rm -f $TEMP_PATCH diff --git a/needs-checking/p_diff b/needs-checking/p_diff new file mode 100755 index 0000000..1497b05 --- /dev/null +++ b/needs-checking/p_diff @@ -0,0 +1,63 @@ +#!/bin/sh + +# +# Bring up a patched file in diff. We show the diffs +# in the topmost patch, unless it was specified +# + +. patchfns 2>/dev/null || +. /usr/lib/patch-scripts/patchfns 2>/dev/null || +. $PATCHSCRIPTS_LIBDIR/patchfns 2>/dev/null || +{ + echo "Impossible to find my library 'patchfns'." + echo "Check your install, or go to the right directory" + exit 1 +} + +usage() +{ + echo "Usage: pdiff [patchname] filename" + echo " pdiff [patchname] -" + exit 1 +} + +if [ $# == 1 ] +then + PATCH_NAME=$(top_patch) + FILENAME=$1 +elif [ $# == 2 ] +then + PATCH_NAME=$(stripit $1) + FILENAME=$2 +else + usage +fi + +if ! is_applied $PATCH_NAME +then + echo $PATCH_NAME is not applied + exit 1 +fi + +doit() +{ + filename=$1 + unpatched_file=$filename"~"$PATCH_NAME + need_file_there $filename + if [ -e $unpatched_file ] + then + diff -u $unpatched_file $filename + else + echo pdiff: $filename appears to not be in $PATCH_NAME + fi +} + +if [ x"$FILENAME" = "x-" ] +then + FILENAME=$(cat $P/pc/$PATCH_NAME.pc) +fi + +for i in $FILENAME +do + doit $i +done diff --git a/needs-checking/patchdesc b/needs-checking/patchdesc new file mode 100755 index 0000000..18d3e94 --- /dev/null +++ b/needs-checking/patchdesc @@ -0,0 +1,24 @@ +#!/bin/sh + +. patchfns 2>/dev/null || +. /usr/lib/patch-scripts/patchfns 2>/dev/null || +. $PATCHSCRIPTS_LIBDIR/patchfns 2>/dev/null || +{ + echo "Impossible to find my library 'patchfns'." + echo "Check your install, or go to the right directory" + exit 1 +} + +desc1() +{ + PATCH=$(stripit $1) + TXT=$(txt_file_name $PATCH) + echo $PATCH.patch + desc < $TXT + echo +} + +for i in $* +do + desc1 $i +done diff --git a/needs-checking/prep-patch b/needs-checking/prep-patch new file mode 100755 index 0000000..6fc4198 --- /dev/null +++ b/needs-checking/prep-patch @@ -0,0 +1,21 @@ +#!/bin/sh + +. patchfns 2>/dev/null || +. /usr/lib/patch-scripts/patchfns 2>/dev/null || +. $PATCHSCRIPTS_LIBDIR/patchfns 2>/dev/null || +{ + echo "Impossible to find my library 'patchfns'." + echo "Check your install, or go to the right directory" + exit 1 +} + +if [ $# -ne 1 ] +then + echo "Usage prep-patch patchname" + exit 1 +fi + +PATCHNAME=$(stripit $1) + +xcb -s 2 < $(patch_file_name $PATCHNAME) +head -2 $(txt_file_name $PATCHNAME) | tail -1 | tr -d '\n' | xcb -s 1 diff --git a/needs-checking/pstatus b/needs-checking/pstatus new file mode 100755 index 0000000..0c32102 --- /dev/null +++ b/needs-checking/pstatus @@ -0,0 +1,158 @@ +#!/bin/sh + +# print out patch status. Usage: pstatus [ patchfile ... ] +# +# Stephen Cameron <steve.cameron@hp.com> +# + +. patchfns 2>/dev/null || +. /usr/lib/patch-scripts/patchfns 2>/dev/null || +. $PATCHSCRIPTS_LIBDIR/patchfns 2>/dev/null || +{ + echo "Impossible to find my library 'patchfns'." + echo "Check your install, or go to the right directory" + exit 1 +} + +if [ ! -f $P/series ] +then + echo "./series does not exist." 1>&2 + exit 1 +fi + +if [ ! -d $P/patches ] +then + echo "Directory ./patches does not exist." 1>&2 + exit 1 +fi + + +PATCHLIST="$*" +if [ "$PATCHLIST" = "" ] +then + series_optimize=yes + PATCHLIST=$(cat_series) + SORTSERIES=`mktemp /tmp/ser.XXXXXX` || exit 1 + SORTPATCHES=`mktemp /tmp/pat.XXXXXX` || exit 1 + cat_series | sort > $SORTSERIES + exists="`echo $P/patches/*.patch 2>/dev/null`" + if [ "$exists" != "$P/patches/*.patch" ] + then + ls -1 $P/patches/*.patch | sed -e 's/^.*\/patches\///' \ + -e 's/[.]patch[ ]*$//' | sort > $SORTPATCHES + PATCHLIST="$PATCHLIST"" `comm -1 -3 $SORTSERIES $SORTPATCHES`" + fi + rm -f $SORTPATCHES $SORTSERIES +else + series_optimize=no +fi + +NSERIES=$(cat_series | wc -l | awk '{ print $1; }') +series=1 +for PATCH_NAME in $PATCHLIST +do + PATCH_NAME=$(stripit $PATCH_NAME) + # see if this patch even exists + if [ ! -f $P/patches/"$PATCH_NAME".patch ] + then + echo "$PATCH_NAME does not exist." + continue + fi + # see if this patch is applied + applied="-" + if [ -f $P/applied-patches ] + then + grep '^'"$PATCH_NAME"'$' $P/applied-patches > /dev/null + if [ "$?" = "0" ] + then + applied="a" + fi + fi + + # figure the status of this patch, that is, + # if it needs changelog, pcpatch, refpatch + + stat="" + if [ ! -f $P/txt/"$PATCH_NAME".txt ] + then + stat="changelog " + fi + if [ ! -f $P/pc/"$PATCH_NAME".pc ] + then + stat="$stat""pcpatch " + elif [ "$applied" != '-' ] + then + rpatch=n + + # for each file this patch touches + for y in `cat $P/pc/"$PATCH_NAME".pc` + do + # is the patch adding the file? + if [ ! -e "$y"'~'"$PATCH_NAME" -a -f "$y" ] + then + # file is newer than the patch? + if [ "$y" -nt $P/patches/"$PATCH_NAME".patch ] + then + rpatch=y + stat="$stat""refpatch " + break + fi + else + # modified file is newer than the patch? + if [ "$y"'~'"$PATCH_NAME" -nt \ + $P/patches/"$PATCH_NAME".patch ] + then + rpatch=y + stat="$stat""refpatch " + break + fi + if [ "`$PATCHSCRIPTS_LIBDIR/toppatch`" = "$PATCH_NAME" -a \ + "$y" -nt $P/patches/"$PATCH_NAME".patch ] + then + # toppatch, so check if the file + # is newer than the patch? + rpatch=y + stat="$stat""refpatch " + break + fi + fi + done + fi + # check if they changed the changelog recently + if [ "$rpatch" = "n" -a -f $P/txt/"$PATCH_NAME".txt \ + -a $P/txt/"$PATCH_NAME".txt -nt \ + $P/patches/"$PATCH_NAME".patch ] + then + rpatch=y + stat="$stat""refpatch " + fi + if [ "$stat" != "" ] + then + stat="Needs ""$stat" + fi + + if [ "$series_optimize" != "yes" ] + then + # have to find the series number the hard way. + series=$(cat_series | grep -n '^'"$PATCH_NAME"'$' |\ + awk -F: '{ printf "%d", $1}' ) + if [ "$series" = "" ] + then + series="?" + fi + fi + + echo "$series":"$applied":"$PATCH_NAME $stat" + + if [ "$series_optimize" = "yes" ] + then + if [ "$series" != "?" ] + then + series=`expr $series + 1` + if [ $series -gt $NSERIES ] + then + series="?" + fi + fi + fi +done diff --git a/needs-checking/ptkdiff b/needs-checking/ptkdiff new file mode 100755 index 0000000..a965bdd --- /dev/null +++ b/needs-checking/ptkdiff @@ -0,0 +1,49 @@ +#!/bin/sh + +# +# Bring up a patched file in tkdiff. We show the diffs +# in the topmost patch, unless it was specified +# + +. patchfns 2>/dev/null || +. /usr/lib/patch-scripts/patchfns 2>/dev/null || +. $PATCHSCRIPTS_LIBDIR/patchfns 2>/dev/null || +{ + echo "Impossible to find my library 'patchfns'." + echo "Check your install, or go to the right directory" + exit 1 +} + +usage() +{ + echo "Usage: ptkdiff filename ..." + echo " ptkdiff -" + exit 1 +} + +PATCH_NAME=$(top_patch) + +doit() +{ + filename=$1 + unpatched_file=$(backup_file_name $filename $PATCH_NAME) + need_file_there $filename + if [ -e $unpatched_file ] + then + tkdiff $unpatched_file $filename + else + echo ptkdiff: $filename appears to not be in $PATCH_NAME + fi +} + +if [ x"$1" = "x-" ] +then + FILENAME=$(files_in_patch $PATCH_NAME) +else + FILENAME="$*" +fi + +for i in $FILENAME +do + doit $i & +done diff --git a/needs-checking/removed-by-patch b/needs-checking/removed-by-patch new file mode 100755 index 0000000..ff12970 --- /dev/null +++ b/needs-checking/removed-by-patch @@ -0,0 +1,14 @@ +#!/bin/sh +# Extract names of new files from a patch, print them out + +PATCHFILE=$1 +case "$PATCHFILE" in +*.gz) CMD="gzip -d < $PATCHFILE";; +*) CMD="cat $PATCHFILE";; +esac + +TMP=$(mktemp /tmp/rbp-XXXXXX) + +eval $CMD | egrep '^\+\+\+.*1970|\+\+\+.*1969' > $TMP +sed -e 's@[^/]*/\([^ ]*\).*@\1@' < $TMP | sed -e 's@^linux/@@' | sort +rm -f $TMP diff --git a/needs-checking/rename-patch b/needs-checking/rename-patch new file mode 100755 index 0000000..e6e1af0 --- /dev/null +++ b/needs-checking/rename-patch @@ -0,0 +1,20 @@ +#!/bin/sh + +. patchfns 2>/dev/null || +. /usr/lib/patch-scripts/patchfns 2>/dev/null || +. $PATCHSCRIPTS_LIBDIR/patchfns 2>/dev/null || +{ + echo "Impossible to find my library 'patchfns'." + echo "Check your install, or go to the right directory" + exit 1 +} + +OLD=$(stripit $1) +NEW=$(stripit $2) + +mv $(pc_file_name $OLD) $(pc_file_name $NEW) +mv $(patch_file_name $OLD) $(patch_file_name $NEW) +mv $(txt_file_name $OLD) $(txt_file_name $NEW) + +cvs remove $(pc_file_name $OLD) $(patch_file_name $OLD) $(txt_file_name $OLD) +cvs add $(pc_file_name $NEW) $(patch_file_name $NEW) $(txt_file_name $NEW) diff --git a/needs-checking/rolled-up-patch b/needs-checking/rolled-up-patch new file mode 100755 index 0000000..4136609 --- /dev/null +++ b/needs-checking/rolled-up-patch @@ -0,0 +1,33 @@ +#!/bin/sh + +. patchfns 2>/dev/null || +. /usr/lib/patch-scripts/patchfns 2>/dev/null || +. $PATCHSCRIPTS_LIBDIR/patchfns 2>/dev/null || +{ + echo "Impossible to find my library 'patchfns'." + echo "Check your install, or go to the right directory" + exit 1 +} + +usage() +{ + echo "Usage: rolled-up-patch" + exit 1 +} + +if [ $# != 0 ] +then + usage +fi + +RUP=$(mktemp /tmp/rup-XXXXXX) +rm -f $RUP + +for i in $(cat applied-patches) +do + patch_name=$(stripit $i) + files_in_patch $patch_name +done | sort | uniq > $RUP + +kdiff $(cat $RUP) +rm -f $RUP diff --git a/needs-checking/split-patch b/needs-checking/split-patch new file mode 100755 index 0000000..08ce431 --- /dev/null +++ b/needs-checking/split-patch @@ -0,0 +1,29 @@ +#!/usr/bin/perl -w +$out = ""; +while (<>) { + next if (/^Only/); + next if (/^Binary/); + if (/^diff/ || /^Index/) { + if ($out) { + close OUT; + } + (@out) = split(' ', $_); + shift(@out) if (/^diff/); + $out = pop(@out); + $out =~ s:/*usr/:/:; + $out =~ s:/*src/:/:; + $out =~ s:^/*linux[^/]*::; + $out =~ s:\(w\)::; + next if ($out eq ""); + $out = "/var/tmp/patches/$out"; + $dir = $out; + $dir =~ s:/[^/]*$::; + print STDERR "$out\n"; + system("mkdir -p $dir"); + open(OUT, ">$out") || die("cannot open $out"); + } + if ($out) { + print OUT $_; + } +} + diff --git a/needs-checking/tag-series b/needs-checking/tag-series new file mode 100755 index 0000000..93a3cf9 --- /dev/null +++ b/needs-checking/tag-series @@ -0,0 +1,44 @@ +#!/bin/sh + +# tag-series tagname series-file-name +# +# Does a `cvs tag tagname' of all the .pc, .txt and .patch files mentioned +# in series-file-name. Also tags series-file-name. +# + +. patchfns 2>/dev/null || +. /usr/lib/patch-scripts/patchfns 2>/dev/null || +. $PATCHSCRIPTS_LIBDIR/patchfns 2>/dev/null || +{ + echo "Impossible to find my library 'patchfns'." + echo "Check your install, or go to the right directory" + exit 1 +} + +# tag_one tag patchname +# +tag_one() +{ + PN=$(stripit $2) + if [ -r $(txt_file_name $PN) ] + then + cvs tag $1 $(pc_file_name $PN) $(patch_file_name $PN) $(txt_file_name $PN) + else + cvs tag $1 $(pc_file_name $PN) $(patch_file_name $PN) + fi +} + +if [ $# -ne 2 ] +then + echo Usage: tag-series tagname series-file-name + exit 1 +fi + +TAG=$1 +SERIES=$2 + +for p in $(__cat_series $SERIES) +do + tag_one $TAG $p +done +cvs tag $TAG $SERIES diff --git a/needs-checking/unitdiff.py b/needs-checking/unitdiff.py new file mode 100755 index 0000000..d19d5e7 --- /dev/null +++ b/needs-checking/unitdiff.py @@ -0,0 +1,223 @@ +#!/usr/bin/python + +import sys +import re +import string + +#TODO +# clean up rest/file +# clean up +6 and like (assumptions). should be turned into 'find' +# make regession tests for all cases (Only in, etc) + +try: + filename = sys.argv[1] +except: + print 'requires a file name' + sys.exit(1) + +filefd = open(filename) +file = filefd.read() +filefd.close() + +rest = file +pat = "(^(?:diff .*\n)?--- .*\n\+\+\+ .*)?\n@@ -(\d+),?(\d+)? \+(\d+),?(\d+)? @@|^(Only in .*)" +startpat = re.compile(pat, re.M) + +pos = 0 +oldpos = 0 +filelen = len(rest) +oldrest = "" +while(1): + rexp = startpat.search(rest) + if not rexp: + break + + if rexp.group(6): + print rexp.group(6) + rest = rest[rexp.end(6)+1:] + continue + + header = rexp.group(1) + orgfile_start = string.atoi(rexp.group(2)) + if rexp.group(3): + orgfile_len = string.atoi(rexp.group(3)) + else: + orgfile_len = -1 + newfile_start = string.atoi(rexp.group(4)) + if rexp.group(5): + newfile_len = string.atoi(rexp.group(5)) + else: + newfile_len = -1 + rest = rest[rexp.start(2):] + rest = rest[string.find(rest, "\n")+1:] + + rexp2 = startpat.search(rest) + if rexp2: + if rexp2.start(6) != -1: + oldrest = rest[rexp2.start(6)-1:] + rest = rest[:rexp2.start(6)] + elif rexp2.start(1) == -1: + oldrest = rest[rexp2.start(2)-5:] + rest = rest[:rexp2.start(2)-4] + else: + oldrest = rest[rexp2.start(1)-1:] + rest = rest[:rexp2.start(1)] + else: + oldrest = rest + +# pos = filelen - len(oldrest) +# if pos - oldpos > 100: +# sys.stderr.write(`pos`+'/'+`filelen`+'\n') +# oldpos = pos + + first = 1 + oldminuses = 0 + oldplusses = 0 + oldoffset = 0 + while(1): + #erstat early line stuff med lookbehind paa {1,2}-dims + #nedenfor RAA + linepat = "^([^-+\n]*)\n?(((^[-+].*\n)|^(.*\n){1,2}(?=^[-+].*\n))+)(.*)\n?" + compat = re.compile(linepat, re.M) + rexp = compat.search(rest) + if not rexp: + break + + prematch = rexp.group(1) + match = rexp.group(2) + muddle = len(match) + +# print rest +# print 'prematch ', rexp.start(1), rexp.end(1), prematch +# print 'match ---------' +# print match +# print 'match --------' + + # dump unwanted early lines... + if match[0] != "+" and match[0] != "-": + while(1): + next = string.find(match, '\n') + if next == -1: + break + if match[next+1] == "+" or match[next+1] == "-": + prematch = match[:next] + match = match[next+1:] + break + match = match[next+1:] + + +# print 'prematch ', rexp.start(1), rexp.end(1), len(prematch) +# print '('+prematch+')' +# if prematch == ' ': +# print 'space' + muddle = muddle - len(match) + + lines = string.count(match, "\n") + compat = re.compile("^-", re.M) + minuses = len(compat.findall(match)) + compat = re.compile("^\+", re.M) + plusses = len(compat.findall(match)) + orgsize = minuses + 2 + (lines - minuses - plusses) + newsize = plusses + 2 + (lines - minuses - plusses) + + noeol = "^(\\\ No newline at end of file)$" + compnoeol = re.compile(noeol, re.M) + if compnoeol.search(match) or compnoeol.search(rexp.group(6)): + orgsize = orgsize - 1 + newsize = newsize - 1 + + coherent = 0 + if lines - plusses == 0: + coherent = 1 + elif lines - minuses == 0: + coherent = 1 + + # RAA FIXME + if not len(prematch):#or len(prematch) == 1 and prematch == ' ': + orgsize = orgsize -1 + newsize = newsize -1 + if rexp.start(6) == rexp.end(6): + orgsize = orgsize -1 + newsize = newsize -1 + +# print "lines in match: ", lines +# print "number of minuses: ", minuses +# print "number of plusses: ", plusses + + matchpos = rexp.start(2) + muddle + offset = string.count(rest[:matchpos], "\n") + +# print 'offset/oldoffset: ', offset,oldoffset +# print 'oldplusses/oldminuses: ', oldplusses, oldminuses +# print 'orgfile_start/newfile_start: ', orgfile_start, newfile_start + + orgstart = orgfile_start + offset + oldoffset - oldplusses + newstart = newfile_start + offset - oldminuses + oldoffset + + # RAA: Bwadr. Fix antagelse om prematch paa en anden + # maade + orgstartmod = 0 + newstartmod = 0 + if orgfile_start == 1 and not len(prematch): + orgstartmod = 1 + if newfile_start == 1 and not len(prematch): + newstartmod = 1 + if orgfile_start == 0 and orgfile_len == 0: + orgstartmod = 1 + # RAA Hack! + plusses = plusses + 1 + minuses = minuses +1 + if newfile_start == 0 and newfile_len == 0: + newstartmod = 1 + # RAA Hack! + plusses = plusses + 1 + minuses = minuses +1 + + if header and first: + print header + first = 0 + + # should the start(1) == 0 be orgstart == 1? RAA + if orgstart == 1 and newstart == 1 and plusses == 0 and coherent: + print "@@ -"+`orgstart`+","+`orgsize`+" +"+`newstart`+" @@" + print match[:string.rfind(match, "\n")] + print rexp.group(6) + elif rexp.start(6) == rexp.end(6) and plusses == 0 and coherent: + if orgstartmod: + orgstart = orgstart + 1 + if newstartmod: + newstart = newstart + 1 + print "@@ -"+`orgstart-1`+","+`orgsize`+" +"+`newstart-1`+" @@" + print prematch + print match[:string.rfind(match, "\n")] + elif orgstart == 1 and orgstart == 1 and minuses == 0 and coherent: + print "@@ -"+`orgstart`+" +"+`newstart`+","+`newsize`+" @@" + print match[:string.rfind(match, "\n")] + print rexp.group(6) + elif rexp.start(6) == rexp.end(6) and minuses == 0 and coherent: + if orgstartmod: + orgstart = orgstart + 1 + if newstartmod: + newstart = newstart + 1 + print "@@ -"+`orgstart-1`+" +"+`newstart-1`+","+`newsize`+" @@" + print prematch + print match[:string.rfind(match, "\n")] + else: + if orgstartmod: + orgstart = orgstart + 1 + if newstartmod: + newstart = newstart + 1 + print "@@ -"+`orgstart-1`+","+`orgsize`+" +"+`newstart-1`+","+`newsize`+" @@" + if len(prematch): + print prematch + print match[:string.rfind(match, "\n")] + if rexp.start(6) != rexp.end(6): + print rexp.group(6) + + rest = rest[rexp.end(6):] + oldminuses = minuses + oldminuses + oldplusses = plusses + oldplusses + oldoffset = oldoffset + offset + lines #include match()-lines + + + rest = oldrest diff --git a/newpatch.in b/newpatch.in new file mode 100755 index 0000000..5d14d95 --- /dev/null +++ b/newpatch.in @@ -0,0 +1,73 @@ +#!/bin/sh + +# Read in library functions +if ! [ -r @LIB@/patchfns ] +then + echo "Cannot read library @LIB@/patchfns" >&2 + exit 1 +fi +. @LIB@/patchfns + +usage() +{ + echo "Usage: newpatch patchname" + if [ x$1 = x-h ] + then + cat <<EOF + + Create a new patch, and insert it after the topmost patch + in the patch series file. + +EOF + exit 0 + else + exit 1 + fi +} + +options=`getopt -o h -- "$@"` + +if [ $? -ne 0 ] +then + usage +fi + +eval set -- "$options" + +while true +do + case "$1" in + -h) + usage -h ;; + --) + shift + break ;; + esac +done + +if [ $# -ne 1 ] +then + usage +fi + +patch_file=$(echo $1 | sed -e 's:^'"$P"'patches/::' -e 's:^\.pc/::') +patch=$(stripit $patch_file) +if [ -z "$patch" ] +then + usage +fi + +if patch_in_series $patch +then + echo "Patch $patch exists already" + exit 1 +fi + +pc_file=$(pc_file_name $patch) +mkdir -p $(dirname $pc_file) +touch $pc_file + +insert_in_series $patch_file || exit 1 +add_to_db $patch || exit 1 + +echo "Patch $patch is now on top" diff --git a/patch-scripts.changes b/patch-scripts.changes new file mode 100644 index 0000000..f78a580 --- /dev/null +++ b/patch-scripts.changes @@ -0,0 +1,11 @@ +------------------------------------------------------------------- +Tue Jan 14 10:02:50 CET 2003 - agruen@suse.de + +- Move library files to /usr/share/lib/patch-scripts +- Try to recover from keyboard interrupt (^C) + +------------------------------------------------------------------- +Mon Jan 13 13:04:27 CET 2003 - agruen@suse.de + +- Initial package + diff --git a/patch-scripts.spec b/patch-scripts.spec new file mode 100644 index 0000000..a5eda6c --- /dev/null +++ b/patch-scripts.spec @@ -0,0 +1,71 @@ +# +# spec file for patch scripts +# +# Copyright (c) 2002 SuSE Linux AG, Nuernberg, Germany. +# This file and all modifications and additions to the pristine +# package are under the same license as the package itself. +# +# Please submit bugfixes or comments via http://www.suse.de/feedback/ +# + +# neededforbuild fileutils make sed +# usedforbuild + +Name: patch-scripts +Summary: Scripts for working with series of patches +License: GPL +Group: Productivity/Text/Utilities +Version: 0.11 +Release: 1 +Requires: textutils diffutils patch gzip bzip2 perl mktemp +Autoreqprov: off +Source: patch-scripts-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-build +BuildArchitectures: noarch + +%description +The scripts allow to manage a series of patches by keeping +track of the changes each patch makes. Patches can be +applied, un-applied, refreshed, etc. + +The scripts are heavily based on Andrew Morton's patch scripts +found at http://www.zip.com.au/~akpm/linux/patches/. + +Authors: +-------- + Andrew Morton <akpm@digeo.com> + Andreas Gruenbacher <agruen@suse.de> + +%prep +%setup + +%build +make prefix=/usr BUILD_ROOT=$RPM_BUILD_ROOT + +%install +make install prefix=/usr BUILD_ROOT=$RPM_BUILD_ROOT + +%files +%defattr(-, root, root) +/usr/bin/newpatch +/usr/bin/patchadd +/usr/bin/pushpatch +/usr/bin/poppatch +/usr/bin/refpatch +/usr/bin/importpatch +/usr/bin/toppatch +/usr/bin/inpatch + +/usr/share/patch-scripts/patchfns +/usr/share/patch-scripts/apatch +/usr/share/patch-scripts/rpatch +/usr/share/patch-scripts/touched-by-patch +/usr/share/patch-scripts/parse-patch +/usr/share/patch-scripts/backup-files + +%doc /usr/share/doc/packages/patch-scripts/README +%doc /usr/share/doc/packages/patch-scripts/docco.txt + +%changelog -n star +* Mon Jan 13 2002 - agruen +- Initial package diff --git a/patchadd.in b/patchadd.in new file mode 100755 index 0000000..95f32c6 --- /dev/null +++ b/patchadd.in @@ -0,0 +1,95 @@ +#!/bin/sh + +# Read in library functions +if ! [ -r @LIB@/patchfns ] +then + echo "Cannot read library @LIB@/patchfns" >&2 + exit 1 +fi +. @LIB@/patchfns + +usage() +{ + echo "Usage: patchadd [-p patchname] filename ..." + if [ x$1 = x-h ] + then + cat <<EOF + +Add one or more files to the topmost or named patch. +Files must be added to the patch before being modified. +Files that are modified by patches on top of the specified +patch cannot be added. + +-p patchname + Patch to add files to. + +EOF + exit 0 + else + exit 1 + fi +} + +options=`getopt -o p:h -- "$@"` + +if [ $? -ne 0 ] +then + usage +fi + +eval set -- "$options" + +while true +do + case "$1" in + -p) + opt_patch=$2 + shift 2 ;; + -h) + usage -h ;; + --) + shift + break ;; + esac +done + +if [ $# -lt 1 ] +then + usage +fi + +patch=$(stripit $opt_patch) +if [ -z "$patch" ] +then + patch=$(top_patch) +fi +if [ -z "$patch" ] +then + echo "No patches seem to be applied" +fi + +if ! is_applied $patch +then + echo "Patch $patch is not applied" + exit 1 +fi + +for file in $* +do + if file_in_patch $file $patch + then + echo "File $file is already in patch $patch" + else + next_patch=$(next_patch_for_file $patch $file) + if [ -n "$next_patch" ] + then + echo "File $file shadowed by patch $next_patch" + exit 1 + fi + + if install_file_in_patch $file $patch + then + echo "File $file added to patch $patch" + fi + fi +done diff --git a/poppatch.in b/poppatch.in new file mode 100755 index 0000000..19b27f9 --- /dev/null +++ b/poppatch.in @@ -0,0 +1,170 @@ +#!/bin/sh + +# Read in library functions +if ! [ -r @LIB@/patchfns ] +then + echo "Cannot read library @LIB@/patchfns" >&2 + exit 1 +fi +. @LIB@/patchfns + +usage() +{ + echo "Usage: poppatch [-afRq] [npatches|patchname]" + if [ x$1 = x-h ] + then + cat <<EOF + +Remove patch(es) from the current stack. A number of +patches to remove, or a patch name can be specified. If a +patch name is given, remove all patches up to and +including the named patch. If neither a number nor a patch +name is specified, remove the next patch. + +-a Remove all patches in the series file. + +-f Force remove. The state before the patch(es) were + applied will be restored from backup files. + +-R Remove the patch with \`patch -R' and check if the + patch reverts all changes properly. + +-q Quiet operation. + +EOF + exit 0 + else + exit 1 + fi +} + +list_patches() +{ + local top=$(top_patch) n=0 patch + if [ -z "$top" ] + then + return 0 + fi + ( patches_before $top + echo $top ) \ + | tac \ + | if [ -n "$opt_all" ] + then + cat + else + while read patch + do + if [ -n "$number" ] + then + if [ $n -eq $number ] + then + break + fi + n=$[$n+1] + fi + echo $patch + if [ $patch = "$stop_at_patch" ] + then + break + fi + done + if [ -n "$stop_at_patch" -a "$patch" != "$stop_at_patch" ] + then + echo "Patch $stop_at_patch not found in file series" >&2 + return 1 + fi + fi +} + +options=`getopt -o fRqah -- "$@"` + +if [ $? -ne 0 ] +then + usage +fi + +eval set -- "$options" + +while true +do + case "$1" in + -f) + opt_force=1 + unset opt_remove + shift ;; + -R) + opt_remove=1 # remove with patch -R; no tricks + unset opt_force + shift ;; + -q) + opt_quiet=1 + shift ;; + -a) + opt_all=1 + shift ;; + -h) + usage -h ;; + --) + shift + break ;; + esac +done + +if [ $# -gt 1 ] +then + usage +fi + +if [ $# -eq 1 ] +then + if is_numeric $1 + then + number=$1 + else + stop_at_patch=$(stripit $1) + fi +else + [ -n "$opt_all" ] || number=1 +fi + +[ -n "$opt_force" ] && + rpatch_options="$rpatch_options -f" +[ -n "$opt_remove" ] && + rpatch_options="$rpatch_options -R" +[ -n "$opt_quiet" ] && + rpatch_options="$rpatch_options -q" + +if [ -n "$stop_at_patch" ] +then + if ! is_applied $stop_at_patch + then + echo "Patch $stop_at_patch is not applied." + exit 1 + fi +fi + +if ! patches=$(list_patches) 2>&1 +then + exit 1 +elif [ -z "$patches" ] +then + echo "No patches removed" + exit 0 +fi + +trap "interrupted=1" SIGINT + +for patch in $patches +do + if ! @LIB@/rpatch $rpatch_options $patch + then + echo Still at patch $patch + exit 1 + fi + if [ -n "$interrupted" ] + then + echo "poppatch interrupted by user" + exit 1 + fi + [ -z "$opt_quiet" ] && echo +done diff --git a/pushpatch.in b/pushpatch.in new file mode 100755 index 0000000..10b5ba8 --- /dev/null +++ b/pushpatch.in @@ -0,0 +1,163 @@ +#!/bin/sh + +# Read in library functions +if ! [ -r @LIB@/patchfns ] +then + echo "Cannot read library @LIB@/patchfns" >&2 + exit 1 +fi +. @LIB@/patchfns + +usage() +{ + echo "Usage: pushpatch [-afq] [npatches|patchname]" + if [ x$1 = x-h ] + then + cat <<EOF + +Apply patch(es) from the series file. A number of patches +to apply, or a patch name can be specified. If a patch +name is given, apply all patches up to and including the +named patch. If neither a number nor a patch name is +specified, apply the next patch. + +-a Apply all patches in the series file. + +-f Force apply, even if the patch has rejects. + +-q Quiet operation. + +EOF + exit 0 + else + exit 1 + fi +} + +list_patches() +{ + local top=$(top_patch) n=0 patch + if [ -n "$top" ] + then + patches_after $top + else + cat_series + fi \ + | if [ -n "$opt_all" ] + then + cat + else + while read patch + do + if [ -n "$number" ] + then + if [ $n -eq $number ] + then + break + fi + n=$[$n+1] + fi + echo $patch + if [ $patch = "$stop_at_patch" ] + then + break + fi + done + if [ -n "$stop_at_patch" -a "$patch" != "$stop_at_patch" ] + then + echo "Patch $stop_at_patch not found in file series" >&2 + return 1 + fi + fi +} + +options=`getopt -o fqah -- "$@"` + +if [ $? -ne 0 ] +then + usage +fi + +eval set -- "$options" + +while true +do + case "$1" in + -f) + opt_force=1 + shift ;; + -q) + opt_quiet=1 + shift ;; + -a) + opt_all=1 + shift ;; + -h) + usage -h ;; + --) + shift + break ;; + esac +done + +if [ $# -gt 1 ] +then + usage +fi + +if [ $# -eq 1 ] +then + if is_numeric $1 + then + number=$1 + else + stop_at_patch=$(stripit $1) + fi +else + [ -z "$opt_all" ] && number=1 +fi + +[ -n "$opt_force" ] && + apatch_options="$apatch_options -f" +[ -n "$opt_quiet" ] && + apatch_options="$apatch_options -q" + +if [ -n "$stop_at_patch" ] +then + if is_applied $stop_at_patch + then + echo "Patch $stop_at_patch is already applied." + exit 1 + fi +fi + +if ! patches=$(list_patches) 2>&1 +then + exit 1 +elif [ -z "$patches" ] +then + top=$(top_patch) + if [ -z "$top" ] + then + echo "No patches applied" + else + echo "File series fully applied, ends at patch $top" + fi + exit 0 +fi + +trap "interrupted=1" SIGINT + +for patch in $patches +do + if ! @LIB@/apatch $apatch_options $patch + then + exit 1 + fi + if [ -n "$interrupted" ] + then + echo "pushpatch interrupted by user" + exit 1 + fi + [ -z "$opt_quiet" ] && echo +done diff --git a/refpatch.in b/refpatch.in new file mode 100755 index 0000000..b421f68 --- /dev/null +++ b/refpatch.in @@ -0,0 +1,329 @@ +#!/bin/sh + +# Read in library functions +if ! [ -r @LIB@/patchfns ] +then + echo "Cannot read library @LIB@/patchfns" >&2 + exit 1 +fi +. @LIB@/patchfns + +usage() +{ + local redirect + if [ x$1 != x-h ] + then + redirect='>&2' + fi + echo "Usage: refpatch [-cfz] [patchname]" $redirect + + if [ x$1 = x-h ] + then + cat <<EOF + +Refresh an applied patch. Refreshes the specified patch, or +the topmost patch by default. Documentation on top of the +actual patch is retained. + +It is possible to refresh patches that are not on top. +If any patches on top of the patch to refresh modify the +same files, refpatch prints out the file and patch names. +If there are any such conflicts, patches can still be +refreshed with -f. In that case refpatch will print a +warning for each thus shadowed file, changes by more +recent patches will be ignored, and only changes in files +that have not been modified by any more recent patches +will end up in the specified patch. + +-c Write to standard output only; don't update the patch. + +-f Force refresh, even if more recent patches modify + some of the same files. + +-z Write to standard output the changes that have been + made relative to the topmost or specified patch. + +EOF + exit 0 + else + exit 1 + fi +} + +fix_diff_header() +{ + local from=$1 to=$2 + sed -e 's:^--- [^ \t]*:--- '"$from"':' \ + -e 's:^+++ [^ \t]*:+++ '"$to"':' +} + +diff_file() +{ + local file=$1 patch=$2 status=0 + local next_patch=$(next_patch_for_file $patch $file) + local old_file=$(backup_file_name $patch $file) + local new_file file_was_shadowed + if [ -n "$next_patch" ] + then + new_file=$(backup_file_name $next_patch $file) + file_was_shadowed=1 + else + new_file=$file + fi + + if [ ! -e $old_file -a ! -e $new_file ] + then + echo "File $new_file does not exist" >&2 + return 0 + fi + local dir=$(basename $PWD) suffix=${patch//\//_} old_hdr new_hdr + if [ $opt_strip -eq 0 ] + then + old_hdr=$file~$suffix + new_hdr=$file + else + old_hdr=$dir~$suffix/$file + new_hdr=$dir/$file + fi + + if [ -z "$opt_post" ] + then + echo diff -Nu $DIFF_OPTS $old_file $new_file >&2 + diff -Nu $DIFF_OPTS $old_file $new_file | + fix_diff_header $old_hdr $new_hdr + status=$? + + if [ $status -eq 0 -a -n "$file_was_shadowed" ] + then + status=2 + fi + elif [ -n "$file_was_shadowed" ] + then + return 0 + else + local tmp=$(mktemp /tmp/patch-scripts.XXXXXX) + if ! filterdiff $(patch_args $patch) -i $file \ + $(patch_file_name $patch) \ + | patch -s -o $tmp $old_file --no-backup-if-mismatch + then + echo "Failed to patch temporary file" >&2 + return 1 + fi + + echo diff -Nu $DIFF_OPTS $new_file.orig $new_file >&2 + diff -Nu $DIFF_OPTS $tmp $new_file | + fix_diff_header $new_file.orig $new_hdr + status=$? + rm -f $tmp + fi + return $status +} + +die () +{ + local status=$1 + [ -n "$tmp" ] && rm -f $tmp + [ -n "$tmp2" ] && rm -f $tmp2 + [ -n "$workdir" ] && rm -rf $workdir + exit $status +} + +options=`getopt -o p:fczh -- "$@"` + +if [ $? -ne 0 ] +then + usage +fi + +eval set -- "$options" + +while true +do + case "$1" in + -p) + opt_strip=$2 + shift 2 ;; + -c) + opt_stdout=1 + shift ;; + -f) + opt_force=1 + shift ;; + -z) + opt_post=1 + opt_stdout=1 + shift ;; + -h) + usage -h ;; + --) + shift + break ;; + esac +done + +if [ $# -eq 1 ] +then + opt_patch=$1 +elif [ $# -gt 1 ] +then + usage +fi + +if [ -n "$opt_patch" ] +then + patch=$(stripit $opt_patch) +else + patch=$(top_patch) + if [ -z "$patch" ] + then + echo "No patches seem to be applied" >&2 + exit 1 + fi +fi + +if ! is_applied "$patch" +then + echo "Patch $patch is not applied" >&2 + exit 1 +fi + +if [ -z "$opt_strip" ] +then + opt_strip=$(patch_strip_level $patch) +fi +if [ $opt_strip -gt 1 ] +then + echo "Cannot refresh patches with -p$opt_strip," \ + "please specify -p0 or -p1 instead" >&2 + exit 1 +fi + +trap "die 1" SIGTERM + +if [ -z "$opt_stdout" ] +then + tmp=$(mktemp /tmp/patch-scripts.XXXXXX) +fi + +if [ -n "$opt_post" ] +then + patch_file=$(patch_file_name $patch) + files=$(files_in_patch $patch) + workdir=$(mktemp -d patch-scripts.XXXXXX) + pwd=$PWD + + if ! cd .pc/$patch + then + echo "Cannot change into .pc/$patch" + die 1 + fi + if ! cp -l --parents $files $pwd/$workdir/ + then + echo "Failed to copy files to temporary directory" + die 1 + fi + if ! cd $pwd/$workdir + then + echo "Cannot change to temporary directory" + die 1 + fi + if ! patch $(patch_args $patch) --no-backup-if-mismatch \ + -E -i $pwd/$patch_file >/dev/null 2>/dev/null + then + echo "Failed to patch temporary files" + die 1 + fi + if ! cd $pwd + then + echo "Cannot change to source directory" + die 1 + fi + + for file in $(files_in_patch $patch) + do + suffix=${patch//\//_} + if [ $opt_strip -eq 0 ] + then + old_hdr=$file.orig + new_hdr=$file + else + dir=$(basename $PWD) + old_hdr=$dir.orig/$file + new_hdr=$dir/$file + fi + echo diff -Nu $DIFF_OPTS $new_hdr.orig $new_hdr >&2 + diff -Nu $DIFF_OPTS $workdir/$file $file \ + | fix_diff_header $old_hdr $new_hdr + if [ $? -ne 0 ] + then + status=1 + fi + done + die $status +else + for file in $(files_in_patch $patch) + do + diff_file $file $patch + status=$? + if [ $status -eq 2 ] + then + files_were_shadowed=1 + elif [ $status -ne 0 ] + then + echo "Diff failed, aborting." >&2 + die 1 + fi + + if [ -n "$files_were_shadowed" -a -z "$opt_force" ] + then + echo "More recent patches modify the same files." \ + "Enforce refresh with -f." >&2 + die 1 + fi + done +fi \ +| if [ -z "$opt_stdout" ] +then + cat > $tmp +else + cat +fi + +if [ -z "$opt_stdout" ] +then + if ! [ -s $tmp ] + then + echo "Nothing in patch $patch" >&2 + die 1 + fi + + patch_file=$(patch_file_name $patch) + + trap "" SIGINT + + if [ ! -e $patch_file ] || grep -q '^%patch$' $patch_file + then + touch $patch_file + cat $tmp \ + | @LIB@/parse-patch -u patch $patch_file + else + tmp2=$(mktemp /tmp/patch-scripts.XXXXXX) && + patch_description $patch_file > $tmp2 && + cat $tmp >> $tmp2 && + mv $tmp2 $patch_file + fi + status=$? + + rm -f $tmp $tmp2 + if [ $status -ne 0 ] + then + die 1 + fi + + rm -f $(pc_file_name $patch)~forced + echo "Refreshed patch $patch" + if ! change_db_strip_level -p$opt_strip $patch + then + die 1 + fi +fi diff --git a/toppatch.in b/toppatch.in new file mode 100755 index 0000000..0d2201d --- /dev/null +++ b/toppatch.in @@ -0,0 +1,132 @@ +#!/bin/sh + +# Read in library functions +if ! [ -r @LIB@/patchfns ] +then + echo "Cannot read library @LIB@/patchfns" >&2 + exit 1 +fi +. @LIB@/patchfns + +usage() +{ + echo "Usage: toppatch [-bpnfa] [patchname]" + if [ x$1 = x-h ] + then + cat <<EOF + +Print the name of the topmost patch on the current stack +of applied patches, or the names of one or more patches +relative to this patch. + +-b Print the names of all patches before the + topmost or specified patch. + +-p Print the name of the previous patch in the + series. + +-n Print the name of the next patch in the series. + +-f Print the names of all patches following the + topmost or specified patch. + +-a Print the names of all patches in the file series. + +EOF + exit 0 + else + exit 1 + fi +} + +options=`getopt -o bpnfah -- "$@"` + +if [ $? -ne 0 ] +then + usage +fi + +eval set -- "$options" + +opt_what=here + +while true +do + case "$1" in + -b) + opt_what=before + shift ;; + -p) + opt_what=previous + shift ;; + -n) + opt_what=next + shift ;; + -f) + opt_what=follow + shift ;; + -a) + opt_what=all + shift ;; + -h) + usage -h ;; + --) + shift + break ;; + esac +done + +if [ $# -gt 1 ] +then + usage +elif [ $# -eq 1 ] +then + opt_patch=$1 +fi + +if [ -n "$opt_patch" ] +then + patch=$(stripit $opt_patch) +else + patch=$(top_patch) +fi + +case $opt_what in +before) + if [ -n "$patch" ] + then + patches_before $patch + fi + ;; +previous) + if [ -n "$patch" ] + then + patches_before $patch | tail -1 + fi + ;; +here) + if [ -n "$patch" ] + then + echo $patch + fi + ;; +next) + if [ -z "$patch" ] + then + cat_series | head -1 + else + patches_after $patch | head -1 + fi + ;; +follow) + if [ -z "$patch" ] + then + cat_series + else + patches_after $patch + fi + ;; +all) + cat_series + ;; +esac |