diff --git a/debian/changelog b/debian/changelog index ecf8a1c..5bf2a71 100644 --- a/debian/changelog +++ b/debian/changelog @@ -22,7 +22,12 @@ ubuntu-dev-tools (0.93) UNRELEASED; urgency=low * mk-sbuild.1: update documentation to reflect alternative config file names for distro and schroot type overrides. - -- Kees Cook Tue, 09 Feb 2010 07:10:39 -0800 + [ Ryan Kavanagh ] + * Added the merge_changelog script from + https://lists.ubuntu.com/archives/ubuntu-x/2009-June/000586.html for those + who need to manually merge packages. + + -- Ryan Kavanagh Mon, 15 Feb 2010 09:19:55 -0500 ubuntu-dev-tools (0.92) lucid; urgency=low diff --git a/debian/control b/debian/control index fef7f1c..bf8c0b3 100644 --- a/debian/control +++ b/debian/control @@ -45,6 +45,8 @@ Description: useful tools for Ubuntu developers - lp-set-dup - sets the "duplicate of" bug of a bug and its dups. - manage-credentials - manage Launchpad token credentials. - massfile - fill multiple bugs using a template. + - merge_changelog - manually merges two Debian changelogs with the same base + version. - mk-sbuild-lv - script to create LVM snapshot chroots via schroot and sbuild. - pbuilder-dist, cowbuilder-dist - wrapper script for managing several build diff --git a/debian/copyright b/debian/copyright index a30bbd3..10881e4 100644 --- a/debian/copyright +++ b/debian/copyright @@ -5,6 +5,7 @@ Upstream Authors: Albert Damen Albin Tonnerre + Bryce Harrington Daniel Hahler Daniel Holbach Iain Lane @@ -43,7 +44,7 @@ Copyright: (C) 2008, 2009, Nathan Handler (C) Patrick Schoenfeld (C) 2006-2007, Pete Savage - (C) 2009 Ryan Kavanagh + (C) 2009-2010 Ryan Kavanagh (C) 2007-2009, Siegfried-A. Gevatter (C) 2008, Stephan Hermann (C) 2007 Steve Kowalik @@ -68,10 +69,11 @@ On Debian and Ubuntu systems, the complete text of the GNU General Public License v2 can be found in `/usr/share/common-licenses/GPL-2'. dch-repeat, get-branches, get-build-deps, grab-attachments, grab-merge, -hugdaylist, manage-credentials, massfile, mk-sbuild-lv, pbuilder-dist-simple, -ppaput, pull-debian-debdiff, pull-debian-source, pull-lp-source, pull-revu-source, -setup-packaging-environment, suspicious-source, ubuntu-build and what-patch are -licensed under the GNU General Public License, version 3: +hugdaylist, manage-credentials, massfile, merge_changelog, mk-sbuild-lv, +pbuilder-dist-simple, ppaput, pull-debian-debdiff, pull-debian-source, +pull-lp-source, pull-revu-source, setup-packaging-environment, +suspicious-source, ubuntu-build and what-patch are licensed under the GNU +General Public License, version 3: 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 diff --git a/doc/merge_changelog.1 b/doc/merge_changelog.1 new file mode 100644 index 0000000..e0ae332 --- /dev/null +++ b/doc/merge_changelog.1 @@ -0,0 +1,21 @@ +.TH merge_changelog 1 "February 15, 2010" "ubuntu-dev-tools" + +.SH NAME +merge_changelog \- merges two changelogs with a common base +grab\-merge \- grab's a merge's files from merges.ubuntu.com. + +.SH SYNOPSIS +\fBmerge_changelog\fP <\fIleft changelog\fP> <\fIright changelog\fP> + +.SH DESCRIPTION +\fBmerge_changelog\fP takes two changelogs that once shared a common source, +merges them back together, and prints the merged result to stdout. This +is useful if you need to manually merge a ubuntu package with a new +Debian release of the package + +.SH AUTHORS +\fBmerge_changelog\fP was written by Scott James Remnant +Bryce Harrington . This manpage was written by Ryan Kavanagh +. +.PP +Both are released under the GNU General Public License, version 3. diff --git a/merge_changelog b/merge_changelog new file mode 100755 index 0000000..6d76c6a --- /dev/null +++ b/merge_changelog @@ -0,0 +1,264 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright ? 2008 Canonical Ltd. +# Author: Scott James Remnant . +# Hacked up by: Bryce Harrington +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of version 3 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, see . + +import os, sys, re, time, logging + +from stat import * +from textwrap import fill + +def usage(): + print '''Usage: merge_changelog + +merge_changelog takes two changelogs that once shared a common source, +merges them back together, and prints the merged result to stdout. This +is useful if you need to manually merge a ubuntu package with a new +Debian release of the package. +''' + sys.exit(1) + +######################################################################## +# Changelog Management +######################################################################## + +# Regular expression for top of debian/changelog +CL_RE = re.compile(r'^(\w[-+0-9a-z.]*) \(([^\(\) \t]+)\)((\s+[-0-9a-z]+)+)\;', + re.IGNORECASE) + +def merge_changelog(left_changelog, right_changelog): + """Merge a changelog file.""" + + left_cl = read_changelog(left_changelog) + right_cl = read_changelog(right_changelog) + + for right_ver, right_text in right_cl: + while len(left_cl) and left_cl[0][0] > right_ver: + (left_ver, left_text) = left_cl.pop(0) + print left_text + + while len(left_cl) and left_cl[0][0] == right_ver: + (left_ver, left_text) = left_cl.pop(0) + + print right_text + + for left_ver, left_text in left_cl: + print left_text + + return False + +def read_changelog(filename): + """Return a parsed changelog file.""" + entries = [] + + cl = open(filename) + try: + (ver, text) = (None, "") + for line in cl: + match = CL_RE.search(line) + if match: + try: + ver = Version(match.group(2)) + except ValueError: + ver = None + + text += line + elif line.startswith(" -- "): + if ver is None: + ver = Version("0") + + text += line + entries.append((ver, text)) + (ver, text) = (None, "") + elif len(line.strip()) or ver is not None: + text += line + finally: + cl.close() + + if len(text): + entries.append((ver, text)) + + return entries + +######################################################################## +# Version parsing code +######################################################################## +# Regular expressions make validating things easy +valid_epoch = re.compile(r'^[0-9]+$') +valid_upstream = re.compile(r'^[A-Za-z0-9+:.~-]*$') +valid_revision = re.compile(r'^[A-Za-z0-9+.~]+$') + +# Character comparison table for upstream and revision components +cmp_table = "~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-.:" + + +class Version(object): + """Debian version number. + + This class is designed to be reasonably transparent and allow you + to write code like: + + | s.version >= '1.100-1' + + The comparison will be done according to Debian rules, so '1.2' will + compare lower. + + Properties: + epoch Epoch + upstream Upstream version + revision Debian/local revision + """ + + def __init__(self, ver): + """Parse a string or number into the three components.""" + self.epoch = 0 + self.upstream = None + self.revision = None + + ver = str(ver) + if not len(ver): + raise ValueError + + # Epoch is component before first colon + idx = ver.find(":") + if idx != -1: + self.epoch = ver[:idx] + if not len(self.epoch): + raise ValueError + if not valid_epoch.search(self.epoch): + raise ValueError + ver = ver[idx+1:] + + # Revision is component after last hyphen + idx = ver.rfind("-") + if idx != -1: + self.revision = ver[idx+1:] + if not len(self.revision): + raise ValueError + if not valid_revision.search(self.revision): + raise ValueError + ver = ver[:idx] + + # Remaining component is upstream + self.upstream = ver + if not len(self.upstream): + raise ValueError + if not valid_upstream.search(self.upstream): + raise ValueError + + self.epoch = int(self.epoch) + + def getWithoutEpoch(self): + """Return the version without the epoch.""" + str = self.upstream + if self.revision is not None: + str += "-%s" % (self.revision,) + return str + + without_epoch = property(getWithoutEpoch) + + def __str__(self): + """Return the class as a string for printing.""" + str = "" + if self.epoch > 0: + str += "%d:" % (self.epoch,) + str += self.upstream + if self.revision is not None: + str += "-%s" % (self.revision,) + return str + + def __repr__(self): + """Return a debugging representation of the object.""" + return "<%s epoch: %d, upstream: %r, revision: %r>" \ + % (self.__class__.__name__, self.epoch, + self.upstream, self.revision) + + def __cmp__(self, other): + """Compare two Version classes.""" + other = Version(other) + + result = cmp(self.epoch, other.epoch) + if result != 0: return result + + result = deb_cmp(self.upstream, other.upstream) + if result != 0: return result + + result = deb_cmp(self.revision or "", other.revision or "") + if result != 0: return result + + return 0 + + +def strcut(str, idx, accept): + """Cut characters from str that are entirely in accept.""" + ret = "" + while idx < len(str) and str[idx] in accept: + ret += str[idx] + idx += 1 + + return (ret, idx) + +def deb_order(str, idx): + """Return the comparison order of two characters.""" + if idx >= len(str): + return 0 + elif str[idx] == "~": + return -1 + else: + return cmp_table.index(str[idx]) + +def deb_cmp_str(x, y): + """Compare two strings in a deb version.""" + idx = 0 + while (idx < len(x)) or (idx < len(y)): + result = deb_order(x, idx) - deb_order(y, idx) + if result < 0: + return -1 + elif result > 0: + return 1 + + idx += 1 + + return 0 + +def deb_cmp(x, y): + """Implement the string comparison outlined by Debian policy.""" + x_idx = y_idx = 0 + while x_idx < len(x) or y_idx < len(y): + # Compare strings + (x_str, x_idx) = strcut(x, x_idx, cmp_table) + (y_str, y_idx) = strcut(y, y_idx, cmp_table) + result = deb_cmp_str(x_str, y_str) + if result != 0: return result + + # Compare numbers + (x_str, x_idx) = strcut(x, x_idx, "0123456789") + (y_str, y_idx) = strcut(y, y_idx, "0123456789") + result = cmp(int(x_str or "0"), int(y_str or "0")) + if result != 0: return result + + return 0 + + +if __name__ == '__main__': + if len(sys.argv) != 3: + usage() + + left_changelog = sys.argv[1] + right_changelog = sys.argv[2] + + merge_changelog(left_changelog, right_changelog) + sys.exit(0) diff --git a/setup.py b/setup.py index 25007a3..485a26e 100755 --- a/setup.py +++ b/setup.py @@ -29,6 +29,7 @@ setup(name='ubuntu-dev-tools', 'lp-shell', 'manage-credentials', 'massfile', + 'merge_changelog', 'mk-sbuild', 'pbuilder-dist', 'pbuilder-dist-simple',