summaryrefslogtreecommitdiffstats
path: root/scripts/mergechanges.sh
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/mergechanges.sh')
-rwxr-xr-xscripts/mergechanges.sh402
1 files changed, 402 insertions, 0 deletions
diff --git a/scripts/mergechanges.sh b/scripts/mergechanges.sh
new file mode 100755
index 0000000..ffaf992
--- /dev/null
+++ b/scripts/mergechanges.sh
@@ -0,0 +1,402 @@
+#!/bin/bash
+##
+## mergechanges -- merge Architecture: and Files: fields of a set of .changes
+## Copyright 2002 Gergely Nagy <algernon@debian.org>
+## Changes copyright 2002,2003 by Julian Gilbey <jdg@debian.org>
+##
+## $MadHouse: home/bin/mergechanges,v 1.1 2002/01/25 12:37:27 algernon Exp $
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## 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, see <https://www.gnu.org/licenses/>.
+
+set -e
+
+PROGNAME=${0##*/}
+
+synopsis() {
+ echo "Usage: $PROGNAME [-h|--help|--version] [-d] [-S|--source] [-i|--indep] [-f] <file1> <file2> [<file> ...]"
+}
+
+usage() {
+ synopsis
+ cat <<EOT
+ Merge the changes files <file1>, <file2>, .... Output on stdout
+ unless -f option given, in which case, output to
+ <package>_<version>_multi.changes in the same directory as <file1>.
+ If -i is given, only source and architecture-independent packages
+ are included in the output.
+ If -S is given, only the source package is included in the output.
+EOT
+}
+
+version() {
+ echo \
+"This is $PROGNAME, from the Debian devscripts package, version ###VERSION###
+This code is copyright (C) 2002 Gergely Nagy <algernon@debian.org>
+Changes copyright 2002 by Julian Gilbey <jdg@debian.org>
+This program comes with ABSOLUTELY NO WARRANTY.
+You are free to redistribute this code under the terms of the
+GNU General Public License, version 2 or later."
+}
+
+# Commandline parsing
+FILE=0
+DELETE=0
+REMOVE_ARCHDEP=0
+REMOVE_INDEP=0
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ --version)
+ version
+ exit 0
+ ;;
+ -f)
+ FILE=1
+ shift
+ ;;
+ -d)
+ DELETE=1
+ shift
+ ;;
+ -i|--indep)
+ REMOVE_ARCHDEP=1
+ shift
+ ;;
+ -S|--source)
+ REMOVE_ARCHDEP=1
+ REMOVE_INDEP=1
+ shift
+ ;;
+ -*)
+ echo "Unrecognised option $1. Use $PROGNAME --help for help" >&2
+ exit 1
+ ;;
+ *)
+ break
+ ;;
+ esac
+done
+
+# Sanity check #0: Do we have enough parameters?
+if [ $# -lt 2 ]; then
+ echo "Not enough parameters." >&2
+ synopsis >&2
+ exit 1
+fi
+
+# Sanity check #1: Do the requested files exist?
+for f in "$@"; do
+ if ! test -r $f; then
+ echo "ERROR: Cannot read $f!" >&2
+ exit 1
+ fi
+done
+
+# Get a (possibly multi-line) field.
+get_field() {
+ perl -e '
+ use warnings;
+ use strict;
+ use autodie;
+
+ use Dpkg::Control;
+
+ my $field = shift;
+ foreach my $file (@ARGV) {
+ my $changes = Dpkg::Control->new(type => CTRL_FILE_CHANGES);
+ $changes->load($file);
+ next unless defined $changes->{$field};
+ print $changes->{$field};
+ print "\n";
+ }
+ ' "$@"
+}
+
+# Extract the Architecture: field from all .changes files,
+# and merge them, sorting out duplicates. Skip architectures
+# other than all and source if desired.
+ARCHS=$(get_field Architecture "$@" | tr ' ' '\n' | sort -u)
+if test ${REMOVE_ARCHDEP} = 1; then
+ ARCHS=$(echo "$ARCHS" | grep -E '^(all|source)$')
+fi
+if test ${REMOVE_INDEP} = 1; then
+ ARCHS=$(echo "$ARCHS" | grep -vxF all)
+fi
+ARCHS=$(echo "$ARCHS" | tr '\n' ' ' | sed 's/ $//')
+
+checksum_uniq() {
+ local line
+ local IFS=
+ if test ${REMOVE_ARCHDEP} = 1 -o ${REMOVE_INDEP} = 1; then
+ while read line; do
+ case "$line" in
+ ("")
+ # empty first line
+ echo "$line"
+ ;;
+ (*.dsc|*.diff.gz|*.tar.*|*_source.buildinfo)
+ # source
+ echo "$line"
+ ;;
+ (*_all.deb|*_all.udeb|*_all.buildinfo)
+ # architecture-indep
+ if test ${REMOVE_INDEP} = 0; then
+ echo "$line"
+ fi
+ ;;
+ (*.deb|*.udeb|*.buildinfo)
+ # architecture-specific
+ if test ${REMOVE_ARCHDEP} = 0; then
+ echo "$line"
+ fi
+ ;;
+ (*)
+ echo "Unrecognised file, is it architecture-dependent?" >&2
+ echo "$line" >&2
+ exit 1
+ ;;
+ esac
+ done | awk '{if(arr[$NF] != 1){arr[$NF] = 1; print;}}'
+ else
+ awk '{if(arr[$NF] != 1){arr[$NF] = 1; print;}}'
+ fi
+}
+
+# Extract & merge the Version: field from all files..
+# Don't catch Version: GnuPG lines, though!
+VERSION=$(get_field Version "$@" | sort -u)
+SVERSION=$(echo "$VERSION" | perl -pe 's/^\d+://')
+# Extract & merge the sources from all files
+SOURCE=$(get_field Source "$@" | sort -u)
+# Extract & merge the files from all files
+FILES=$(get_field Files "$@" | checksum_uniq)
+# Extract & merge the sha1 checksums from all files
+SHA1S=$(get_field Checksums-Sha1 "$@" | checksum_uniq)
+# Extract & merge the sha256 checksums from all files
+SHA256S=$(get_field Checksums-Sha256 "$@" | checksum_uniq)
+# Extract & merge the description from all files
+DESCRIPTIONS=$(get_field Description "$@" | sort -u)
+# Extract & merge the Formats from all files
+FORMATS=$(get_field Format "$@" | sort -u)
+# Extract & merge the Checksums-* field names from all files
+CHECKSUMS=$(grep -h "^Checksums-.*:" "$@" | sort -u)
+UNSUPCHECKSUMS="$(echo "${CHECKSUMS}" | grep -v "^Checksums-Sha\(1\|256\):" || true)"
+
+# Sanity check #2: Versions must match
+if test $(echo "${VERSION}" | wc -l) -ne 1; then
+ echo "ERROR: Version numbers do not match:" >&2
+ grep "^Version: [0-9]" "$@" >&2
+ exit 1
+fi
+
+# Sanity check #3: Sources must match
+if test $(echo "${SOURCE}" | wc -l) -ne 1; then
+ echo "Error: Source packages do not match:" >&2
+ grep "^Source: " "$@" >&2
+ exit 1
+fi
+
+# Sanity check #4: Description for same binary must match
+if test $(echo "${DESCRIPTIONS}" | sed -e 's/ \+- .*$//' | uniq -d | wc -l) -ne 0; then
+ echo "Error: Descriptions do not match:" >&2
+ echo "${DESCRIPTIONS}" >&2
+ exit 1
+fi
+
+# Sanity check #5: Formats must match
+if test $(echo "${FORMATS}" | wc -l) -ne 1; then
+ if test "${FORMATS}" = "$(printf "1.7\n1.8\n")"; then
+ FORMATS="1.7"
+ CHECKSUMS=""
+ UNSUPCHECKSUMS=""
+ SHA1S=""
+ SHA256S=""
+ else
+ echo "Error: Changes files have different Format fields:" >&2
+ grep "^Format: " "$@" >&2
+ exit 1
+ fi
+fi
+
+# Sanity check #6: The Format must be one we understand
+case "$FORMATS" in
+ 1.7|1.8) # Supported
+ ;;
+ *)
+ echo "Error: Changes files use unknown Format:" >&2
+ echo "${FORMATS}" >&2
+ exit 1
+ ;;
+esac
+
+# Sanity check #7: Unknown checksum fields
+if test -n "${UNSUPCHECKSUMS}"; then
+ echo "Error: Unsupported checksum fields:" >&2
+ echo "${UNSUPCHECKSUMS}" >&2
+ exit 1
+fi
+
+if test ${FILE} = 1; then
+ DIR=$(dirname "$1")
+ REDIR1="> '${DIR}/${SOURCE}_${SVERSION}_multi.changes'"
+ REDIR2=">$REDIR1"
+fi
+
+# Temporary output
+OUTPUT=$(mktemp --tmpdir mergechanges.tmp.XXXXXXXXXX)
+DESCFILE=$(mktemp --tmpdir mergechanges.tmp.XXXXXXXXXX)
+trap 'rm -f "${OUTPUT}" "${DESCFILE}"' EXIT
+
+# Copy one of the files to ${OUTPUT}, nuking any PGP signature
+if $(grep -q "BEGIN PGP SIGNED MESSAGE" "$1"); then
+ perl -ne 'next if 1../^$/; next if /^$/..1; print' "$1" > ${OUTPUT}
+else
+ cp "$1" ${OUTPUT}
+fi
+
+# Combine the Binary: and Description: fields. This is straightforward,
+# unless we want to exclude some binary packages, in which case we need
+# more thought.
+BINARY=$(get_field Binary "$@" | tr ' ' '\n' | sort -u)
+if test ${REMOVE_ARCHDEP} = 1 && test ${REMOVE_INDEP} = 1; then
+ BINARY=
+ DESCRIPTIONS=
+elif test ${REMOVE_ARCHDEP} = 1 || test ${REMOVE_INDEP} = 1; then
+ keep_binaries=$(
+ get_field Files "$@" | while read -r line; do
+ file="${line##* }"
+ case "$line" in
+ ("")
+ # empty first line
+ echo "$line"
+ ;;
+ (*.dsc|*.diff.gz|*.tar.*|*.buildinfo)
+ # source or buildinfo
+ ;;
+ (*_all.deb|*_all.udeb)
+ # architecture-indep
+ package="${file%%_*}"
+
+ if ! echo "$BINARY" | grep -q -x -F "$package"; then
+ echo "Warning: $package not found in Binary field" >&2
+ echo "$line" >&2
+ fi
+
+ if test ${REMOVE_INDEP} != 1; then
+ echo "$package"
+ fi
+ ;;
+ (*.deb|*.udeb)
+ # architecture-specific
+ package="${file%%_*}"
+
+ if ! echo "$BINARY" | grep -q -x -F "$package"; then
+ echo "Warning: $package not found in Binary field" >&2
+ echo "$line" >&2
+ fi
+
+ if test ${REMOVE_ARCHDEP} != 1; then
+ echo "$package"
+ fi
+ ;;
+ (*)
+ echo "Unrecognised file, is it architecture-dependent?" >&2
+ echo "$line" >&2
+ exit 1
+ ;;
+ esac
+ done \
+ | tr '\n' ' ')
+
+ BINARY=$(
+ echo "$BINARY" |
+ while read -r line; do
+ if echo " $keep_binaries" | grep -q -F " $line "; then
+ echo "$line";
+ fi
+ done
+ )
+ DESCRIPTIONS=$(
+ echo "$DESCRIPTIONS" |
+ while read -r line; do
+ package="${line%% *}"
+ if echo " $keep_binaries" | grep -q -F " $package "; then
+ echo "$line";
+ fi
+ done
+ )
+fi
+BINARY=$(echo "$BINARY" | tr '\n' ' ' | sed 's/ $//')
+
+if test -n "${DESCRIPTIONS}"; then
+ printf "Description:" > "${DESCFILE}"
+ echo "${DESCRIPTIONS}" | sed -e 's/^/ /' >> "${DESCFILE}"
+fi
+
+if [ -n "$BINARY" ]; then
+ BINARY="Binary: $BINARY\\n"
+fi
+
+# Modify the output to be the merged version:
+# * Replace the Architecture: and Binary: fields
+# * Nuke the value of Checksums-*: and Files:
+# * Insert the Description: field before the Changes: field
+#
+# We print Binary directly before Source instead of directly replacing
+# Binary, because with dpkg 1.19.3, if the first .changes file is
+# source-only, it won't have a Binary field at all.
+eval "awk -- '/^[^ ]/{ deleting=0 }
+ /^ /{
+ if (!deleting) {
+ print
+ }
+ next
+ }
+ /^Architecture: /{printf \"%s ${ARCHS}\\n\", \$1; deleting=1; next}
+ /^Source: /{printf \"${BINARY}\"; print; next}
+ /^Binary: /{deleting=1; next}
+ /^Changes:/{
+ field=\$0
+ while ((getline < \"${DESCFILE}\") > 0) {
+ print
+ }
+ printf \"%s\\n\", field
+ next
+ }
+ /^Format: /{ printf \"%s ${FORMATS}\\n\", \$1; deleting=1; next}
+ /^(Checksums-.*|Files|Description):/{ deleting=1; next }
+ { print }' \
+ ${OUTPUT} ${REDIR1}"
+
+# Voodoo magic to get the merged file and checksum lists into the output
+if test -n "${SHA1S}"; then
+ eval "printf 'Checksums-Sha1:' ${REDIR2}"
+ eval "echo '${SHA1S}' | sed -e 's/^/ /' ${REDIR2}"
+fi
+if test -n "${SHA256S}"; then
+ eval "printf 'Checksums-Sha256:' ${REDIR2}"
+ eval "echo '${SHA256S}' | sed -e 's/^/ /' ${REDIR2}"
+fi
+eval "printf 'Files:' ${REDIR2}"
+eval "echo '${FILES}' | sed -e 's/^/ /' ${REDIR2}"
+
+if test ${DELETE} = 1; then
+ rm "$@"
+fi
+
+exit 0