#! /bin/bash
#############################################################################
# Copyright (c) 2005 Novell, Inc.
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, contact Novell, Inc.
#
# To contact Novell about this file by physical or electronic mail,
# you may find current contact information at www.novell.com
#############################################################################
create_cache_index() {
perl -w -e '
use Digest::MD5 qw(md5_hex);
use FileHandle;
my $md5 = new Digest::MD5;
my @files = <STDIN>;
chomp @files;
undef $/;
foreach my $file (@files) {
my $fh = new IO::File($file, "r")
or die "$file: $!\n";
my $data=<$fh>;
$md5->add($data);
my $timestamp = (stat($fh))[9];
print md5_hex($data), " ", $md5->clone->hexdigest,
" $timestamp $file\n";
}'
}
read_cache() {
pos=0
while read md5sum running_md5sum timestamp patch; do
md5sums[pos]=$md5sum
running_md5sums[pos]=$running_md5sum
timestamps[pos]=$timestamp
patches[pos++]=$patch
done
}
write_cache() {
for ((pos = 0; pos < ${#patches[@]}; pos++)); do
echo ${md5sums[pos]} ${running_md5sums[pos]} \
${timestamps[pos]} ${patches[pos]}
done
}
usage() {
echo >&2 \
"Usage: ${0##*/} [--generate] [--clean] [--source-tree dir] {--cache dir} {--temp dir}
Read a list of patches from standard input, and either compute aggregate
patches in the --cache directory (--generate), or replace patches
with their precomputed aggregates in this list before writing the new
list to standard output.
--cache dir
Output directory in which to store the cache files.
--generate
Generate cache in the specified cache directory.
--source-tree dir
The source tree the patches apply to (required with --generate).
--clean
Don't use the cache when regenerating it.
--temp dir
Set the temp directory (defaults to /var/tmp)."
exit $1
}
options=`getopt -o hd: --long help,generate,clean,cache: \
--long source-tree:,temp: -- "$@"`
if [ $? -ne 0 ]; then
usage 1
fi
eval set -- "$options"
temp=/var/tmp
while :; do
case "$1" in
--source-tree)
source_tree=$2
shift
;;
--generate)
opt_generate=1
;;
--clean)
opt_clean=1
;;
--cache)
cachedir=$2
shift
;;
--temp)
temp=$2
shift
;;
-h|--help)
usage 0
;;
--)
shift
break
;;
esac
shift
done
if [ -z "$cachedir" -o $# -ne 0 -o \
\( -n "$opt_generate" -a -z "$source_tree" \) ]; then
usage 1
fi
pos=0
while read patch; do
new_patches[pos++]=$patch
done
if [ -n "$opt_generate" ]; then
mkdir -p $cachedir
tmpdir=$(mktemp -d $temp/${0##*/}.XXXXXX)
tmpcachedir=$(mktemp -d $cachedir/${0##*/}.XXXXXX)
trap "rm -rf $tmpdir $tmpcachedir" EXIT
if [ -n "$opt_clean" ]; then
rm -f $cachedir/*.gz
fi
( IFS=$'\n'; echo "${new_patches[*]}" ) \
| create_cache_index > $tmpcachedir/md5sums
read_cache < $tmpcachedir/md5sums
if [ "$(stat -c %d $source_tree/)" = "$(stat -c %d $tmpdir/)" ]; then
cp -rld $source_tree $tmpdir/a
else
cp -rd $source_tree $tmpdir/a
fi
# Use an exponentially declining number of patches in each
# successive combined patch.
for ((pos = 0, half = ${#new_patches[@]} >> 1, end = half;
half >= 16;
half >>= 1, end += half)); do
batch=()
while ((pos < end)); do
batch[${#batch[@]}]=${new_patches[pos]}
((pos++))
done
md5sum=${running_md5sums[pos-1]}
printf '%s (%d%%) .' "$md5sum.gz" $((100*$pos/${#new_patches[@]}))
if [ -e $cachedir/$md5sum.gz ]; then
ln $cachedir/$md5sum.gz $tmpcachedir/
ln $cachedir/$md5sum.series $tmpcachedir/ 2> /dev/null
zcat $tmpcachedir/$md5sum.gz \
| patch -s -p1 -E -d $tmpdir/a -f --no-backup-if-mismatch \
|| break
echo ..
continue
fi
cp -rld $tmpdir/a $tmpdir/b
for patch in "${batch[@]}"; do
patch -s -p1 -E -d $tmpdir/b -f --no-backup-if-mismatch < $patch \
|| break 2
done
echo -n .
(cd $tmpdir && diff -Nr -U0 a b) \
| sed -e '/^diff/d' -e '/^\(---\|+++\) /s/'$'\t''.*//' \
| gzip -9 > $tmpcachedir/$md5sum.gz
( IFS=$'\n'; echo "${batch[*]}" > $tmpcachedir/$md5sum.series )
echo -n .
rm -rf $tmpdir/a
mv $tmpdir/b $tmpdir/a
echo
done
rm -f $cachedir/* 2> /dev/null
mv -f $tmpcachedir/* $cachedir/ 2> /dev/null
else
if [ -e $cachedir/md5sums ]; then
read_cache < $cachedir/md5sums
fi
new_timestamps=( $(IFS=$'\n'; echo "${new_patches[*]}" \
| xargs stat -c "%Y"$'\n') )
for ((pos = 0; pos < ${#new_patches[@]}; pos++, cache_end++)); do
[ "${new_patches[pos]}" != "${patches[pos]}" ] && break
if [ "${new_timestamps[pos]}" != "${timestamps[pos]}" ]; then
set -- $(md5sum "${new_patches[pos]}")
if [ "$1" = "${md5sums[pos]}" ]; then
timestamps[pos]=${new_timestamps[pos]}
update_cache=1
else
break
fi
fi
done
if [ -n "$update_cache" ]; then
write_cache > $cachedir/md5sums
fi
batch=()
for ((pos = 0; pos < ${#new_patches[@]}; pos++)); do
if ((pos < cache_end)) &&
[ -e $cachedir/${running_md5sums[pos]}.gz ]; then
echo $cachedir/${running_md5sums[pos]}.gz
batch=()
continue
fi
batch[${#batch[@]}]=${new_patches[pos]}
done
( IFS=$'\n'; echo "${batch[*]}" )
fi
# vim:shiftwidth=4 softtabstop=4