Blob Blame History Raw
#! /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