[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#698057: britney: Move/refactor parts into a sep. module



Package: release.debian.org
Severity: wishlist
Tags: patch
User: release.debian.org@packages.debian.org
Usertags: britney

Hi,

I have written a series of patches to reduce the size of britney.py.
In total, these patches removes 274 lines from britney.py[1].  There
are some refactoring patches interleaved with this that I thought
would be useful to do, but I can take them out if preferred.

Reviews greatly appreciated,
~Niels

[1] From 2815 to 2541, so just a little shy of removing/relocating
10% of the britney.py code.

Niels Thykier (15):
  Remove unused write_bugs
  Move same_source to separate module
  Move "constants" to a new consts module
  Move "undo_changes" to britney_util
  Move old_libraries_format to britney_util
  Merge get_reverse_tree and get_full_tree
  Add container filters and use it to refactor get_reverse_tree
  Move register_reverses to britney_util
  Move the package loop into register_reverses
  register_reverses: factor out a[0]
  Move get_reverse_tree into britney_util
  Move {read,write}_nuninst to britney_util
  Move write_heidi to britney_util
  Move eval_uninst to britney_util
  Move newlyuninst to britney_util

 britney.py      |  410 +++++++++----------------------------------------------
 britney_util.py |  362 ++++++++++++++++++++++++++++++++++++++++++++++++
 consts.py       |   39 ++++++
 3 files changed, 469 insertions(+), 342 deletions(-)
 create mode 100644 britney_util.py
 create mode 100644 consts.py

-- 
1.7.10.4
>From 1e67fe3c817e063c40f65d98ae66ed132947bdd9 Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Sat, 12 Jan 2013 14:47:51 +0100
Subject: [PATCH 01/15] Remove unused write_bugs

The method was last invoked in 2008 (commit 3dc3be1c7).

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 britney.py |   15 ---------------
 1 file changed, 15 deletions(-)

diff --git a/britney.py b/britney.py
index 7f569762ca991af6008c139f204f1ba5cc17f98b..f1b25e1a2d957c4e9e357c773f5a97d035fd5c72 100755
--- a/britney.py
+++ b/britney.py
@@ -648,21 +648,6 @@ class Britney(object):
             bugs[pkg] += l[1].split(",")
         return bugs
 
-    def write_bugs(self, basedir, bugs):
-        """Write the release critical bug summary to the specified directory
-
-        For a more detailed explanation of the format, please check the method
-        read_bugs.
-        """
-        filename = os.path.join(basedir, "BugsV")
-        self.__log("Writing RC bugs data to %s" % filename)
-        f = open(filename, 'w')
-        for pkg in sorted(bugs.keys()):
-            if not bugs[pkg]:
-                continue
-            f.write("%s %s\n" % (pkg, ','.join(bugs[pkg])))
-        f.close()
-
     def __maxver(self, pkg, dist):
         """Return the maximum version for a given package name
         
-- 
1.7.10.4

>From 36608dc26db075612508a9b191a7a9f380960816 Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Fri, 28 Dec 2012 10:39:08 +0100
Subject: [PATCH 02/15] Move same_source to separate module

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 britney.py      |   67 ++++++++++++++++++++++---------------------------------
 britney_util.py |   43 +++++++++++++++++++++++++++++++++++
 2 files changed, 70 insertions(+), 40 deletions(-)
 create mode 100644 britney_util.py

diff --git a/britney.py b/britney.py
index f1b25e1a2d957c4e9e357c773f5a97d035fd5c72..f67215f56674fe0e0675a7f134b0bd99ee4bf722 100755
--- a/britney.py
+++ b/britney.py
@@ -181,7 +181,6 @@ does for the generation of the update excuses.
 """
 
 import os
-import re
 import sys
 import string
 import time
@@ -213,7 +212,7 @@ from excuse import Excuse
 from migrationitem import MigrationItem, HintItem
 from hints import HintCollection
 from britney import buildSystem
-
+from britney_util import same_source
 
 __author__ = 'Fabio Tranchitella and the Debian Release Team'
 __version__ = '2.0'
@@ -968,26 +967,6 @@ class Britney(object):
     # Utility methods for package analysis
     # ------------------------------------
 
-    def same_source(self, sv1, sv2):
-        """Check if two version numbers are built from the same source
-
-        This method returns a boolean value which is true if the two
-        version numbers specified as parameters are built from the same
-        source. The main use of this code is to detect binary-NMU.
-        """
-        if sv1 == sv2:
-            return 1
-
-        m = re.match(r'^(.*)\+b\d+$', sv1)
-        if m: sv1 = m.group(1)
-        m = re.match(r'^(.*)\+b\d+$', sv2)
-        if m: sv2 = m.group(1)
-
-        if sv1 == sv2:
-            return 1
-
-        return 0
-
     def get_dependency_solvers(self, block, arch, distribution):
         """Find the packages which satisfy a dependency block
 
@@ -1115,7 +1094,7 @@ class Britney(object):
         self.excuses.append(excuse)
         return True
 
-    def should_upgrade_srcarch(self, src, arch, suite):
+    def should_upgrade_srcarch(self, src, arch, suite, same_source=same_source):
         """Check if a binary package should be upgraded
 
         This method checks if a binary package should be upgraded; this can
@@ -1127,6 +1106,8 @@ class Britney(object):
         It returns False if the given package doesn't need to be upgraded,
         True otherwise. In the former case, a new excuse is appended to
         the the object attribute excuses.
+
+        same_source is an opt to avoid "load global".
         """
         # retrieve the source packages for testing and suite
         source_t = self.sources['testing'][src]
@@ -1141,7 +1122,7 @@ class Britney(object):
         
         # if there is a `remove' hint and the requested version is the same as the
         # version in testing, then stop here and return False
-        for hint in [ x for x in self.hints.search('remove', package=src) if self.same_source(source_t[VERSION], x.version) ]:
+        for hint in [ x for x in self.hints.search('remove', package=src) if same_source(source_t[VERSION], x.version) ]:
             excuse.addhtml("Removal request by %s" % (hint.user))
             excuse.addhtml("Trying to remove package, not update it")
             excuse.addhtml("Not considered")
@@ -1169,7 +1150,7 @@ class Britney(object):
                 continue
 
             # if the new binary package is not from the same source as the testing one, then skip it
-            if not self.same_source(source_t[VERSION], pkgsv):
+            if not same_source(source_t[VERSION], pkgsv):
                 anywrongver = True
                 excuse.addhtml("From wrong source: %s %s (%s not %s)" % (pkg_name, binary_u[VERSION], pkgsv, source_t[VERSION]))
                 break
@@ -1202,7 +1183,7 @@ class Britney(object):
         # package is not fake, then check what packages should be removed
         if not anywrongver and (anyworthdoing or not self.sources[suite][src][FAKESRC]):
             srcv = self.sources[suite][src][VERSION]
-            ssrc = self.same_source(source_t[VERSION], srcv)
+            ssrc = same_source(source_t[VERSION], srcv)
             # for every binary package produced by this source in testing for this architecture
             for pkg in sorted([x.split("/")[0] for x in self.sources['testing'][src][BINARIES] if x.endswith("/"+arch)]):
                 # if the package is architecture-independent, then ignore it
@@ -1228,7 +1209,7 @@ class Britney(object):
         # otherwise, return False
         return False
 
-    def should_upgrade_src(self, src, suite):
+    def should_upgrade_src(self, src, suite, same_source=same_source):
         """Check if source package should be upgraded
 
         This method checks if a source package should be upgraded. The analysis
@@ -1238,6 +1219,8 @@ class Britney(object):
         It returns False if the given package doesn't need to be upgraded,
         True otherwise. In the former case, a new excuse is appended to
         the object attribute excuses.
+
+        same_source is an opt to avoid "load global".
         """
 
         # retrieve the source packages for testing (if available) and suite
@@ -1280,8 +1263,8 @@ class Britney(object):
         # if there is a `remove' hint and the requested version is the same as the
         # version in testing, then stop here and return False
         for item in self.hints.search('remove', package=src):
-            if source_t and self.same_source(source_t[VERSION], item.version) or \
-               self.same_source(source_u[VERSION], item.version):
+            if source_t and same_source(source_t[VERSION], item.version) or \
+               same_source(source_u[VERSION], item.version):
                 excuse.addhtml("Removal request by %s" % (item.user))
                 excuse.addhtml("Trying to remove package, not update it")
                 update_candidate = False
@@ -1303,7 +1286,7 @@ class Britney(object):
             unblock_cmd = "un" + block_cmd
             unblocks = self.hints.search(unblock_cmd, package=src)
 
-            if unblocks and self.same_source(unblocks[0].version, source_u[VERSION]):
+            if unblocks and same_source(unblocks[0].version, source_u[VERSION]):
                 excuse.addhtml("Ignoring %s request by %s, due to %s request by %s" %
                                (block_cmd, blocked[block_cmd].user, unblock_cmd, unblocks[0].user))
             else:
@@ -1321,14 +1304,14 @@ class Britney(object):
         if suite == 'unstable':
             if src not in self.dates:
                 self.dates[src] = (source_u[VERSION], self.date_now)
-            elif not self.same_source(self.dates[src][0], source_u[VERSION]):
+            elif not same_source(self.dates[src][0], source_u[VERSION]):
                 self.dates[src] = (source_u[VERSION], self.date_now)
 
             days_old = self.date_now - self.dates[src][1]
             min_days = self.MINDAYS[urgency]
 
             for age_days_hint in [ x for x in self.hints.search('age-days', package=src) if \
-               self.same_source(source_u[VERSION], x.version) ]:
+               same_source(source_u[VERSION], x.version) ]:
                 excuse.addhtml("Overriding age needed from %d days to %d by %s" % (min_days,
                     int(age_days_hint.days), age_days_hint.user))
                 min_days = int(age_days_hint.days)
@@ -1336,7 +1319,7 @@ class Britney(object):
             excuse.setdaysold(days_old, min_days)
             if days_old < min_days:
                 urgent_hints = [ x for x in self.hints.search('urgent', package=src) if \
-                   self.same_source(source_u[VERSION], x.version) ]
+                   same_source(source_u[VERSION], x.version) ]
                 if urgent_hints:
                     excuse.addhtml("Too young, but urgency pushed by %s" % (urgent_hints[0].user))
                 else:
@@ -1384,7 +1367,7 @@ class Britney(object):
                 pkgsv = binary_u[SOURCEVER]
 
                 # if it wasn't built by the same source, it is out-of-date
-                if not self.same_source(source_u[VERSION], pkgsv):
+                if not same_source(source_u[VERSION], pkgsv):
                     if pkgsv not in oodbins:
                         oodbins[pkgsv] = []
                     oodbins[pkgsv].append(pkg)
@@ -1459,7 +1442,7 @@ class Britney(object):
                         "though it fixes more than it introduces, whine at debian-release)" % pkg)
 
         # check if there is a `force' hint for this package, which allows it to go in even if it is not updateable
-        forces = [ x for x in self.hints.search('force', package=src) if self.same_source(source_u[VERSION], x.version) ]
+        forces = [ x for x in self.hints.search('force', package=src) if same_source(source_u[VERSION], x.version) ]
         if forces:
             excuse.dontinvalidate = True
         if not update_candidate and forces:
@@ -1468,7 +1451,7 @@ class Britney(object):
 
         # if the suite is *-proposed-updates, the package needs an explicit approval in order to go in
         if suite in ['tpu', 'pu']:
-            approves = [ x for x in self.hints.search('approve', package=src) if self.same_source(source_u[VERSION], x.version) ]
+            approves = [ x for x in self.hints.search('approve', package=src) if same_source(source_u[VERSION], x.version) ]
             if approves:
                 excuse.addhtml("Approved by %s" % approves[0].user)
             else:
@@ -1541,12 +1524,14 @@ class Britney(object):
                     exclookup[x].is_valid = False
             i = i + 1
  
-    def write_excuses(self):
+    def write_excuses(self, same_source=same_source):
         """Produce and write the update excuses
 
         This method handles the update excuses generation: the packages are
         looked at to determine whether they are valid candidates. For the details
         of this procedure, please refer to the module docstring.
+
+        same_source is an opt to avoid "load global".
         """
 
         self.__log("Update Excuses generation started", type="I")
@@ -1604,7 +1589,7 @@ class Britney(object):
 
             # check if the version specified in the hint is the same as the considered package
             tsrcv = sources['testing'][src][VERSION]
-            if not self.same_source(tsrcv, item.version): continue
+            if not same_source(tsrcv, item.version): continue
 
             # add the removal of the package to upgrade_me and build a new excuse
             upgrade_me.append("-%s" % (src))
@@ -2682,13 +2667,15 @@ class Britney(object):
                 if i not in to_skip:
                     self.do_hint("easy", "autohinter", [ HintItem("%s/%s" % (x[0], x[1])) for x in l[i] ])
 
-    def old_libraries(self):
+    def old_libraries(self, same_source=same_source):
         """Detect old libraries left in testing for smooth transitions
 
         This method detects old libraries which are in testing but no longer
         built from the source package: they are still there because other
         packages still depend on them, but they should be removed as soon
         as possible.
+
+        same_source is an opt to avoid "load global".
         """
         sources = self.sources['testing']
         testing = self.binaries['testing']
@@ -2698,7 +2685,7 @@ class Britney(object):
             for pkg_name in testing[arch][0]:
                 pkg = testing[arch][0][pkg_name]
                 if pkg_name not in unstable[arch][0] and \
-                   not self.same_source(sources[pkg[SOURCE]][VERSION], pkg[SOURCEVER]):
+                   not same_source(sources[pkg[SOURCE]][VERSION], pkg[SOURCEVER]):
                     removals.append("-" + pkg_name + "/" + arch)
         return removals
 
diff --git a/britney_util.py b/britney_util.py
new file mode 100644
index 0000000000000000000000000000000000000000..326f83cbd584fcb9b161d554a5d5c8c37efbadd7
--- /dev/null
+++ b/britney_util.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+# Refactored parts from britney.py, which is/was:
+# Copyright (C) 2001-2008 Anthony Towns <ajt@debian.org>
+#                         Andreas Barth <aba@debian.org>
+#                         Fabio Tranchitella <kobold@debian.org>
+# Copyright (C) 2010-2012 Adam D. Barratt <adsb@debian.org>
+
+# 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.
+
+import re
+
+binnmu_re = re.compile(r'^(.*)\+b\d+$')
+
+def same_source(sv1, sv2, binnmu_re=binnmu_re):
+    """Check if two version numbers are built from the same source
+
+    This method returns a boolean value which is true if the two
+    version numbers specified as parameters are built from the same
+    source. The main use of this code is to detect binary-NMU.
+
+    binnmu_re is an opt to avoid "load global".
+    """
+    if sv1 == sv2:
+        return 1
+
+    m = binnmu_re.match(sv1)
+    if m: sv1 = m.group(1)
+    m = binnmu_re.match(sv2)
+    if m: sv2 = m.group(1)
+
+    if sv1 == sv2:
+        return 1
+
+    return 0
-- 
1.7.10.4

>From 4f1e53487fad8c5f3b20709faf94975b66a41bf0 Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Fri, 28 Dec 2012 10:58:34 +0100
Subject: [PATCH 03/15] Move "constants" to a new consts module

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 britney.py |   24 +++---------------------
 consts.py  |   39 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 42 insertions(+), 21 deletions(-)
 create mode 100644 consts.py

diff --git a/britney.py b/britney.py
index f67215f56674fe0e0675a7f134b0bd99ee4bf722..c7381c9ecf94a699ec0f7d4ea79778c64774ed91 100755
--- a/britney.py
+++ b/britney.py
@@ -213,31 +213,13 @@ from migrationitem import MigrationItem, HintItem
 from hints import HintCollection
 from britney import buildSystem
 from britney_util import same_source
+from consts import (VERSION, SECTION, BINARIES, MAINTAINER, FAKESRC,
+                   SOURCE, SOURCEVER, ARCHITECTURE, DEPENDS, CONFLICTS,
+                   PROVIDES, RDEPENDS, RCONFLICTS)
 
 __author__ = 'Fabio Tranchitella and the Debian Release Team'
 __version__ = '2.0'
 
-# source package
-VERSION = 0
-SECTION = 1
-BINARIES = 2
-MAINTAINER = 3
-FAKESRC = 4
-
-# binary package
-SOURCE = 2
-SOURCEVER = 3
-ARCHITECTURE = 4
-# PREDEPENDS = 5 - No longer used by the python code
-#  - The C-code needs it for alignment reasons and still check it
-#    but ignore it if it is None (so keep it None).
-DEPENDS = 6
-CONFLICTS = 7
-PROVIDES = 8
-RDEPENDS = 9
-RCONFLICTS = 10
-
-
 class Britney(object):
     """Britney, the Debian testing updater script
     
diff --git a/consts.py b/consts.py
new file mode 100644
index 0000000000000000000000000000000000000000..47670b25bbea19f89240fd65f210c6c895169808
--- /dev/null
+++ b/consts.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+# Constants from britney.py
+#
+# Assuming constants are copyrightable, then they are:
+# Copyright (C) 2001-2008 Anthony Towns <ajt@debian.org>
+#                         Andreas Barth <aba@debian.org>
+#                         Fabio Tranchitella <kobold@debian.org>
+# Copyright (C) 2010-2012 Adam D. Barratt <adsb@debian.org>
+
+# 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.
+
+# source package
+VERSION = 0
+SECTION = 1
+BINARIES = 2
+MAINTAINER = 3
+FAKESRC = 4
+
+# binary package
+SOURCE = 2
+SOURCEVER = 3
+ARCHITECTURE = 4
+# PREDEPENDS = 5 - No longer used by the python code
+#  - The C-code needs it for alignment reasons and still check it
+#    but ignore it if it is None (so keep it None).
+DEPENDS = 6
+CONFLICTS = 7
+PROVIDES = 8
+RDEPENDS = 9
+RCONFLICTS = 10
-- 
1.7.10.4

>From b712ff7f3ab5f07430ed9685a1b480ba1b6fb48f Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Fri, 28 Dec 2012 11:32:03 +0100
Subject: [PATCH 04/15] Move "undo_changes" to britney_util

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 britney.py      |   74 +++----------------------------------------------------
 britney_util.py |   72 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 75 insertions(+), 71 deletions(-)

diff --git a/britney.py b/britney.py
index c7381c9ecf94a699ec0f7d4ea79778c64774ed91..327aae0365b32f7b6adf6e42e1d9af723dddafdf 100755
--- a/britney.py
+++ b/britney.py
@@ -212,7 +212,7 @@ from excuse import Excuse
 from migrationitem import MigrationItem, HintItem
 from hints import HintCollection
 from britney import buildSystem
-from britney_util import same_source
+from britney_util import same_source, undo_changes
 from consts import (VERSION, SECTION, BINARIES, MAINTAINER, FAKESRC,
                    SOURCE, SOURCEVER, ARCHITECTURE, DEPENDS, CONFLICTS,
                    PROVIDES, RDEPENDS, RCONFLICTS)
@@ -2144,7 +2144,7 @@ class Britney(object):
                     skipped.append(pkg)
                 single_undo = [(undo, item)]
                 # (local-scope) binaries is actually self.binaries["testing"] so we cannot use it here.
-                self.undo_changes(single_undo, systems, sources, self.binaries)
+                undo_changes(single_undo, systems, sources, self.binaries)
 
         # if we are processing hints, return now
         if hint:
@@ -2244,75 +2244,7 @@ class Britney(object):
             self.output_write("FAILED\n")
             if not lundo: return
 
-            self.undo_changes(lundo, self.systems, self.sources, self.binaries)
-
-
-    def undo_changes(self, lundo, systems, sources, binaries):
-        """Undoes one or more changes to testing
-
-        * lundo is a list of (undo, item)-tuples
-        * systems is the britney-py.c system
-        * sources is the table of all source packages for all suites
-        * binaries is the table of all binary packages for all suites
-          and architectures
-        """
-
-        # We do the undo process in "4 steps" and each step must be
-        # fully completed for each undo-item before starting on the
-        # next.
-        #
-        # see commit:ef71f0e33a7c3d8ef223ec9ad5e9843777e68133 and
-        # #624716 for the issues we had when we did not do this.
-
-
-        # STEP 1
-        # undo all the changes for sources
-        for (undo, item) in lundo:
-            for k in undo['sources'].keys():
-                if k[0] == '-':
-                    del sources["testing"][k[1:]]
-                else:
-                    sources["testing"][k] = undo['sources'][k]
-
-        # STEP 2
-        # undo all new binaries (consequence of the above)
-        for (undo, item) in lundo:
-            if not item.is_removal and item.package in sources[item.suite]:
-                for p in sources[item.suite][item.package][BINARIES]:
-                    binary, arch = p.split("/")
-                    if item.architecture in ['source', arch]:
-                        del binaries["testing"][arch][0][binary]
-                        systems[arch].remove_binary(binary)
-
-
-        # STEP 3
-        # undo all other binary package changes (except virtual packages)
-        for (undo, item) in lundo:
-            for p in undo['binaries'].keys():
-                binary, arch = p.split("/")
-                if binary[0] == "-":
-                    del binaries['testing'][arch][0][binary[1:]]
-                    systems[arch].remove_binary(binary[1:])
-                else:
-                    binaries_t_a = binaries['testing'][arch][0]
-                    binaries_t_a[binary] = undo['binaries'][p]
-                    systems[arch].remove_binary(binary)
-                    systems[arch].add_binary(binary, binaries_t_a[binary][:PROVIDES] + \
-                         [", ".join(binaries_t_a[binary][PROVIDES]) or None])
-
-        # STEP 4
-        # undo all changes to virtual packages
-        for (undo, item) in lundo:
-            for p in undo['nvirtual']:
-                j, arch = p.split("/")
-                del binaries['testing'][arch][1][j]
-            for p in undo['virtual']:
-                j, arch = p.split("/")
-                if j[0] == '-':
-                    del binaries['testing'][arch][1][j[1:]]
-                else:
-                    binaries['testing'][arch][1][j] = undo['virtual'][p]
-
+            undo_changes(lundo, self.systems, self.sources, self.binaries)
 
 
 
diff --git a/britney_util.py b/britney_util.py
index 326f83cbd584fcb9b161d554a5d5c8c37efbadd7..8d3cc0f5840fec82b1190a1b270526d7b4b7845b 100644
--- a/britney_util.py
+++ b/britney_util.py
@@ -17,6 +17,7 @@
 # GNU General Public License for more details.
 
 import re
+from consts import BINARIES, PROVIDES
 
 binnmu_re = re.compile(r'^(.*)\+b\d+$')
 
@@ -41,3 +42,74 @@ def same_source(sv1, sv2, binnmu_re=binnmu_re):
         return 1
 
     return 0
+
+
+def undo_changes(lundo, systems, sources, binaries,
+                 BINARIES=BINARIES, PROVIDES=PROVIDES):
+    """Undoes one or more changes to testing
+
+    * lundo is a list of (undo, item)-tuples
+    * systems is the britney-py.c system
+    * sources is the table of all source packages for all suites
+    * binaries is the table of all binary packages for all suites
+      and architectures
+
+    The "X=X" parameters are optimizations to avoid "load global"
+    in loops.
+    """
+
+    # We do the undo process in "4 steps" and each step must be
+    # fully completed for each undo-item before starting on the
+    # next.
+    #
+    # see commit:ef71f0e33a7c3d8ef223ec9ad5e9843777e68133 and
+    # #624716 for the issues we had when we did not do this.
+
+
+    # STEP 1
+    # undo all the changes for sources
+    for (undo, item) in lundo:
+        for k in undo['sources']:
+            if k[0] == '-':
+                del sources["testing"][k[1:]]
+            else:
+                sources["testing"][k] = undo['sources'][k]
+
+    # STEP 2
+    # undo all new binaries (consequence of the above)
+    for (undo, item) in lundo:
+        if not item.is_removal and item.package in sources[item.suite]:
+            for p in sources[item.suite][item.package][BINARIES]:
+                binary, arch = p.split("/")
+                if item.architecture in ['source', arch]:
+                    del binaries["testing"][arch][0][binary]
+                    systems[arch].remove_binary(binary)
+
+
+    # STEP 3
+    # undo all other binary package changes (except virtual packages)
+    for (undo, item) in lundo:
+        for p in undo['binaries']:
+            binary, arch = p.split("/")
+            if binary[0] == "-":
+                del binaries['testing'][arch][0][binary[1:]]
+                systems[arch].remove_binary(binary[1:])
+            else:
+                binaries_t_a = binaries['testing'][arch][0]
+                binaries_t_a[binary] = undo['binaries'][p]
+                systems[arch].remove_binary(binary)
+                systems[arch].add_binary(binary, binaries_t_a[binary][:PROVIDES] + \
+                     [", ".join(binaries_t_a[binary][PROVIDES]) or None])
+
+    # STEP 4
+    # undo all changes to virtual packages
+    for (undo, item) in lundo:
+        for p in undo['nvirtual']:
+            j, arch = p.split("/")
+            del binaries['testing'][arch][1][j]
+        for p in undo['virtual']:
+            j, arch = p.split("/")
+            if j[0] == '-':
+                del binaries['testing'][arch][1][j[1:]]
+            else:
+                binaries['testing'][arch][1][j] = undo['virtual'][p]
-- 
1.7.10.4

>From 23eb43024b755d92a4ecabfb7cdebbaad86dc505 Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Fri, 28 Dec 2012 11:37:53 +0100
Subject: [PATCH 05/15] Move old_libraries_format to britney_util

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 britney.py      |   18 +++---------------
 britney_util.py |   14 ++++++++++++++
 2 files changed, 17 insertions(+), 15 deletions(-)

diff --git a/britney.py b/britney.py
index 327aae0365b32f7b6adf6e42e1d9af723dddafdf..71c4beb6041cd8f5148c7210b9e09cdd0da9d17e 100755
--- a/britney.py
+++ b/britney.py
@@ -212,7 +212,7 @@ from excuse import Excuse
 from migrationitem import MigrationItem, HintItem
 from hints import HintCollection
 from britney import buildSystem
-from britney_util import same_source, undo_changes
+from britney_util import old_libraries_format, same_source, undo_changes
 from consts import (VERSION, SECTION, BINARIES, MAINTAINER, FAKESRC,
                    SOURCE, SOURCEVER, ARCHITECTURE, DEPENDS, CONFLICTS,
                    PROVIDES, RDEPENDS, RCONFLICTS)
@@ -2337,14 +2337,14 @@ class Britney(object):
             removals = self.old_libraries()
             if len(removals) > 0:
                 self.output_write("Removing packages left in testing for smooth updates (%d):\n%s" % \
-                    (len(removals), self.old_libraries_format(removals)))
+                    (len(removals), old_libraries_format(removals)))
                 self.do_all(actions=[ MigrationItem(x) for x in removals ])
                 removals = self.old_libraries()
         else:
             removals = ()
 
         self.output_write("List of old libraries in testing (%d):\n%s" % \
-             (len(removals), self.old_libraries_format(removals)))
+             (len(removals), old_libraries_format(removals)))
 
         # output files
         if not self.options.dry_run:
@@ -2603,18 +2603,6 @@ class Britney(object):
                     removals.append("-" + pkg_name + "/" + arch)
         return removals
 
-    def old_libraries_format(self, libs):
-        """Format old libraries in a smart table"""
-        libraries = {}
-        for i in libs:
-            pkg, arch = i.split("/")
-            pkg = pkg[1:]
-            if pkg in libraries:
-                libraries[pkg].append(arch)
-            else:
-                libraries[pkg] = [arch]
-        return "\n".join(["  " + k + ": " + " ".join(libraries[k]) for k in libraries]) + "\n"
-
     def nuninst_arch_report(self, nuninst, arch):
         """Print a report of uninstallable packages for one architecture."""
         all = {}
diff --git a/britney_util.py b/britney_util.py
index 8d3cc0f5840fec82b1190a1b270526d7b4b7845b..0a3dc38778af36e35fc8843c8f8f8bd87a054001 100644
--- a/britney_util.py
+++ b/britney_util.py
@@ -113,3 +113,17 @@ def undo_changes(lundo, systems, sources, binaries,
                 del binaries['testing'][arch][1][j[1:]]
             else:
                 binaries['testing'][arch][1][j] = undo['virtual'][p]
+
+
+def old_libraries_format(libs):
+    """Format old libraries in a smart table"""
+    libraries = {}
+    for i in libs:
+        pkg, arch = i.split("/")
+        pkg = pkg[1:]
+        if pkg in libraries:
+            libraries[pkg].append(arch)
+        else:
+            libraries[pkg] = [arch]
+    return "\n".join("  " + k + ": " + " ".join(libraries[k]) for k in libraries) + "\n"
+
-- 
1.7.10.4

>From 69a5aeec36a91158cccf07f49738e12cb411e373 Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Wed, 30 May 2012 09:24:34 +0200
Subject: [PATCH 06/15] Merge get_reverse_tree and get_full_tree

Beside some "minor differences" they were computing the same "tree"
(read: "graph"), so merge them into one (get_reverse_tree) and
properly document return value and special cases.

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 britney.py |   51 ++++++++++++++++++++++-----------------------------
 1 file changed, 22 insertions(+), 29 deletions(-)

diff --git a/britney.py b/britney.py
index 71c4beb6041cd8f5148c7210b9e09cdd0da9d17e..27d3e4053e72290aa4786d0c465b51b2b5ab548a 100755
--- a/britney.py
+++ b/britney.py
@@ -190,7 +190,7 @@ import urllib
 import apt_pkg
 
 from functools import reduce, partial
-from itertools import chain, repeat
+from itertools import chain, repeat, izip
 from operator import attrgetter
 
 if __name__ == '__main__':
@@ -1898,11 +1898,7 @@ class Britney(object):
                     affected.update(self.get_reverse_tree(binary, parch, 'testing'))
                     # all the reverse conflicts and their dependency tree are affected by the change
                     for j in binaries[parch][0][binary][RCONFLICTS]:
-                        key = (j, parch)
-                        if key not in affected: affected.add(key)
-                        for p in self.get_full_tree(j, parch, 'testing'):
-                            key = (p, parch)
-                            if key not in affected: affected.add(key)
+                        affected.update(self.get_reverse_tree(j, parch, 'testing'))
                     self.systems[parch].remove_binary(binary)
                 else:
                     # the binary isn't in testing, but it may have been at
@@ -1920,7 +1916,6 @@ class Britney(object):
                         if p in tundo['binaries']:
                             for rdep in tundo['binaries'][p][RDEPENDS]:
                                 if rdep in binaries[parch][0] and rdep not in source[BINARIES]:
-                                    affected.add( (rdep, parch) )
                                     affected.update(self.get_reverse_tree(rdep, parch, 'testing'))
                 # add/update the binary package
                 binaries[parch][0][binary] = self.binaries[item.suite][parch][0][binary]
@@ -1952,10 +1947,27 @@ class Britney(object):
         return (item, affected, undo)
 
     def get_reverse_tree(self, pkg, arch, suite):
-        binaries = self.binaries[suite][arch][0]
+        """Calculate the full dependency tree for the given package
+
+        This method returns the full dependency tree for the package
+        `pkg`, inside the `arch` architecture for the suite `suite`
+        flatterned as an iterable.
+
+        The tree is returned as an iterable of (package, arch) tuples
+        and the iterable will contain (`pkg`, `arch`) if it is
+        available on that architecture.
 
+        If `pkg` is not available on that architecture in that suite,
+        this returns an empty iterable.
+
+        The method does not promise any ordering of the returned
+        elements and the iterable is not reusable nor mutable.
+        """
+        binaries = self.binaries[suite][arch][0]
+        if pkg not in binaries:
+            return frozenset()
         rev_deps = set(binaries[pkg][RDEPENDS])
-        seen = set()
+        seen = set([pkg])
         while len(rev_deps) > 0:
             # mark all of the current iteration of packages as affected
             seen |= rev_deps
@@ -1967,26 +1979,7 @@ class Britney(object):
             # in the process
             rev_deps = set([package for package in chain.from_iterable(new_rev_deps) \
                                  if package not in seen ])
-        return zip(seen, repeat(arch))
-
-    def get_full_tree(self, pkg, arch, suite):
-        """Calculate the full dependency tree for the given package
-
-        This method returns the full dependency tree for the package `pkg`,
-        inside the `arch` architecture for the suite `suite`.
-        """
-        packages = [pkg]
-        binaries = self.binaries[suite][arch][0]
-        if pkg in binaries:
-            l = n = 0
-            while len(packages) > l:
-                l = len(packages)
-                for p in packages[n:]:
-                    packages.extend([x for x in binaries[p][RDEPENDS] if x not in packages and x in binaries])
-                n = l
-            return packages
-        else:
-            return []
+        return izip(seen, repeat(arch))
 
     def _check_packages(self, binaries, systems, arch, affected, skip_archall, nuninst, pkg):
         broken = nuninst[arch + "+all"]
-- 
1.7.10.4

>From 011ff6e722e6019e3525514987e43b93c19532ce Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Thu, 5 Jul 2012 12:07:34 +0200
Subject: [PATCH 07/15] Add container filters and use it to refactor
 get_reverse_tree

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 britney.py      |   16 ++++++++--------
 britney_util.py |   30 ++++++++++++++++++++++++++++++
 2 files changed, 38 insertions(+), 8 deletions(-)

diff --git a/britney.py b/britney.py
index 27d3e4053e72290aa4786d0c465b51b2b5ab548a..1585a31af7d08c807a037faf1f909d5c67bb2971 100755
--- a/britney.py
+++ b/britney.py
@@ -212,7 +212,8 @@ from excuse import Excuse
 from migrationitem import MigrationItem, HintItem
 from hints import HintCollection
 from britney import buildSystem
-from britney_util import old_libraries_format, same_source, undo_changes
+from britney_util import (old_libraries_format, same_source, undo_changes,
+                          ifilter_except, ifilter_only)
 from consts import (VERSION, SECTION, BINARIES, MAINTAINER, FAKESRC,
                    SOURCE, SOURCEVER, ARCHITECTURE, DEPENDS, CONFLICTS,
                    PROVIDES, RDEPENDS, RCONFLICTS)
@@ -1968,17 +1969,16 @@ class Britney(object):
             return frozenset()
         rev_deps = set(binaries[pkg][RDEPENDS])
         seen = set([pkg])
-        while len(rev_deps) > 0:
+
+        binfilt = ifilter_only(binaries)
+        revfilt = ifilter_except(seen)
+        flatten = chain.from_iterable
+        while rev_deps:
             # mark all of the current iteration of packages as affected
             seen |= rev_deps
             # generate the next iteration, which is the reverse-dependencies of
             # the current iteration
-            new_rev_deps = [ binaries[x][RDEPENDS] for x in rev_deps \
-                             if x in binaries ]
-            # flatten the list-of-lists, filtering out already handled packages
-            # in the process
-            rev_deps = set([package for package in chain.from_iterable(new_rev_deps) \
-                                 if package not in seen ])
+            rev_deps = set(revfilt(flatten( binaries[x][RDEPENDS] for x in binfilt(rev_deps) )))
         return izip(seen, repeat(arch))
 
     def _check_packages(self, binaries, systems, arch, affected, skip_archall, nuninst, pkg):
diff --git a/britney_util.py b/britney_util.py
index 0a3dc38778af36e35fc8843c8f8f8bd87a054001..549f0bc29c1d548fcdbc502b2406ce40dbb99563 100644
--- a/britney_util.py
+++ b/britney_util.py
@@ -5,6 +5,7 @@
 #                         Andreas Barth <aba@debian.org>
 #                         Fabio Tranchitella <kobold@debian.org>
 # Copyright (C) 2010-2012 Adam D. Barratt <adsb@debian.org>
+# Copyright (C) 2012 Niels Thykier <niels@thykier.net>
 
 # 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
@@ -16,6 +17,9 @@
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 
+
+from functools import partial
+from itertools import ifilter, ifilterfalse
 import re
 from consts import BINARIES, PROVIDES
 
@@ -44,6 +48,32 @@ def same_source(sv1, sv2, binnmu_re=binnmu_re):
     return 0
 
 
+def ifilter_except(container, iterable=None):
+    """Filter out elements in container
+
+    If given an iterable it returns a filtered iterator, otherwise it
+    returns a function to generate filtered iterators.  The latter is
+    useful if the same filter has to be (re-)used on multiple
+    iterators that are not known on before hand.
+    """
+    if iterable is not None:
+        return ifilterfalse(container.__contains__, iterable)
+    return partial(ifilterfalse, container.__contains__)
+
+
+def ifilter_only(container, iterable=None):
+    """Filter out elements in which are not in container
+
+    If given an iterable it returns a filtered iterator, otherwise it
+    returns a function to generate filtered iterators.  The latter is
+    useful if the same filter has to be (re-)used on multiple
+    iterators that are not known on before hand.
+    """
+    if iterable is not None:
+        return ifilter(container.__contains__, iterable)
+    return partial(ifilter, container.__contains__)
+
+
 def undo_changes(lundo, systems, sources, binaries,
                  BINARIES=BINARIES, PROVIDES=PROVIDES):
     """Undoes one or more changes to testing
-- 
1.7.10.4

>From 7b2a5edaf7a3a408d7501fc400728d264922bfc5 Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Fri, 28 Dec 2012 11:52:56 +0100
Subject: [PATCH 08/15] Move register_reverses to britney_util

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 britney.py      |   44 ++------------------------------------------
 britney_util.py |   49 ++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 50 insertions(+), 43 deletions(-)

diff --git a/britney.py b/britney.py
index 1585a31af7d08c807a037faf1f909d5c67bb2971..f31fc9b84ac6cf0938508f2a9c57e2ef517b29c5 100755
--- a/britney.py
+++ b/britney.py
@@ -213,7 +213,7 @@ from migrationitem import MigrationItem, HintItem
 from hints import HintCollection
 from britney import buildSystem
 from britney_util import (old_libraries_format, same_source, undo_changes,
-                          ifilter_except, ifilter_only)
+                          register_reverses)
 from consts import (VERSION, SECTION, BINARIES, MAINTAINER, FAKESRC,
                    SOURCE, SOURCEVER, ARCHITECTURE, DEPENDS, CONFLICTS,
                    PROVIDES, RDEPENDS, RCONFLICTS)
@@ -558,52 +558,12 @@ class Britney(object):
             packages[pkg] = dpkg
 
         # loop again on the list of packages to register reverse dependencies and conflicts
-        register_reverses = self.register_reverses
         for pkg in packages:
             register_reverses(pkg, packages, provides, check_doubles=False)
 
         # return a tuple with the list of real and virtual packages
         return (packages, provides)
 
-    def register_reverses(self, pkg, packages, provides, check_doubles=True, parse_depends=apt_pkg.parse_depends):
-        """Register reverse dependencies and conflicts for the specified package
-
-        This method registers the reverse dependencies and conflicts for
-        a given package using `packages` as the list of packages and `provides`
-        as the list of virtual packages.
-
-        The method has an optional parameter parse_depends which is there
-        just for performance reasons and is not meant to be overwritten.
-        """
-        # register the list of the dependencies for the depending packages
-        dependencies = []
-        if packages[pkg][DEPENDS]:
-            dependencies.extend(parse_depends(packages[pkg][DEPENDS], False))
-        # go through the list
-        for p in dependencies:
-            for a in p:
-                # register real packages
-                if a[0] in packages and (not check_doubles or pkg not in packages[a[0]][RDEPENDS]):
-                    packages[a[0]][RDEPENDS].append(pkg)
-                # also register packages which provide the package (if any)
-                if a[0] in provides:
-                    for i in provides.get(a[0]):
-                        if i not in packages: continue
-                        if not check_doubles or pkg not in packages[i][RDEPENDS]:
-                            packages[i][RDEPENDS].append(pkg)
-        # register the list of the conflicts for the conflicting packages
-        if packages[pkg][CONFLICTS]:
-            for p in parse_depends(packages[pkg][CONFLICTS], False):
-                for a in p:
-                    # register real packages
-                    if a[0] in packages and (not check_doubles or pkg not in packages[a[0]][RCONFLICTS]):
-                        packages[a[0]][RCONFLICTS].append(pkg)
-                    # also register packages which provide the package (if any)
-                    if a[0] in provides:
-                        for i in provides[a[0]]:
-                            if i not in packages: continue
-                            if not check_doubles or pkg not in packages[i][RCONFLICTS]:
-                                packages[i][RCONFLICTS].append(pkg)
      
     def read_bugs(self, basedir):
         """Read the release critial bug summary from the specified directory
@@ -1938,7 +1898,7 @@ class Britney(object):
             for p in source[BINARIES]:
                 binary, parch = p.split("/")
                 if item.architecture not in ['source', parch]: continue
-                self.register_reverses(binary, binaries[parch][0] , binaries[parch][1])
+                register_reverses(binary, binaries[parch][0] , binaries[parch][1])
 
             # add/update the source package
             if item.architecture == 'source':
diff --git a/britney_util.py b/britney_util.py
index 549f0bc29c1d548fcdbc502b2406ce40dbb99563..907fe275f256977cd452023a35aed3ee3867b269 100644
--- a/britney_util.py
+++ b/britney_util.py
@@ -18,10 +18,14 @@
 # GNU General Public License for more details.
 
 
+import apt_pkg
 from functools import partial
 from itertools import ifilter, ifilterfalse
 import re
-from consts import BINARIES, PROVIDES
+
+
+from consts import (BINARIES, PROVIDES, DEPENDS, CONFLICTS,
+                    RDEPENDS, RCONFLICTS)
 
 binnmu_re = re.compile(r'^(.*)\+b\d+$')
 
@@ -157,3 +161,46 @@ def old_libraries_format(libs):
             libraries[pkg] = [arch]
     return "\n".join("  " + k + ": " + " ".join(libraries[k]) for k in libraries) + "\n"
 
+
+
+def register_reverses(pkg, packages, provides, check_doubles=True,
+                      parse_depends=apt_pkg.parse_depends,
+                      RDEPENDS=RDEPENDS, RCONFLICTS=RCONFLICTS):
+    """Register reverse dependencies and conflicts for the specified package
+
+    This method registers the reverse dependencies and conflicts for
+    a given package using `packages` as the list of packages and `provides`
+    as the list of virtual packages.
+
+    The "X=X" parameters are optimizations to avoid "load global" in
+    the loops.
+    """
+    # register the list of the dependencies for the depending packages
+    dependencies = []
+    if packages[pkg][DEPENDS]:
+        dependencies.extend(parse_depends(packages[pkg][DEPENDS], False))
+    # go through the list
+    for p in dependencies:
+        for a in p:
+                # register real packages
+            if a[0] in packages and (not check_doubles or pkg not in packages[a[0]][RDEPENDS]):
+                packages[a[0]][RDEPENDS].append(pkg)
+            # also register packages which provide the package (if any)
+            if a[0] in provides:
+                for i in provides.get(a[0]):
+                    if i not in packages: continue
+                    if not check_doubles or pkg not in packages[i][RDEPENDS]:
+                        packages[i][RDEPENDS].append(pkg)
+    # register the list of the conflicts for the conflicting packages
+    if packages[pkg][CONFLICTS]:
+        for p in parse_depends(packages[pkg][CONFLICTS], False):
+            for a in p:
+                # register real packages
+                if a[0] in packages and (not check_doubles or pkg not in packages[a[0]][RCONFLICTS]):
+                    packages[a[0]][RCONFLICTS].append(pkg)
+                # also register packages which provide the package (if any)
+                if a[0] in provides:
+                    for i in provides[a[0]]:
+                        if i not in packages: continue
+                        if not check_doubles or pkg not in packages[i][RCONFLICTS]:
+                            packages[i][RCONFLICTS].append(pkg)
-- 
1.7.10.4

>From c90d8d6b982ef1d9234f8475a6f6655914528ede Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Fri, 28 Dec 2012 12:14:36 +0100
Subject: [PATCH 09/15] Move the package loop into register_reverses

By moving the package loop inside register_reverses, it will be
invoked a lot less (reducing the overhead of invoking functions).

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 britney.py      |   13 ++++++-----
 britney_util.py |   67 ++++++++++++++++++++++++++++++++-----------------------
 2 files changed, 46 insertions(+), 34 deletions(-)

diff --git a/britney.py b/britney.py
index f31fc9b84ac6cf0938508f2a9c57e2ef517b29c5..52df2ba8166cf9f1102069a3c5959d41e52d1e4e 100755
--- a/britney.py
+++ b/britney.py
@@ -558,8 +558,7 @@ class Britney(object):
             packages[pkg] = dpkg
 
         # loop again on the list of packages to register reverse dependencies and conflicts
-        for pkg in packages:
-            register_reverses(pkg, packages, provides, check_doubles=False)
+        register_reverses(packages, provides, check_doubles=False)
 
         # return a tuple with the list of real and virtual packages
         return (packages, provides)
@@ -1895,10 +1894,12 @@ class Britney(object):
                 affected.update(self.get_reverse_tree(binary, parch, 'testing'))
 
             # register reverse dependencies and conflicts for the new binary packages
-            for p in source[BINARIES]:
-                binary, parch = p.split("/")
-                if item.architecture not in ['source', parch]: continue
-                register_reverses(binary, binaries[parch][0] , binaries[parch][1])
+            if item.architecture == 'source':
+                pkg_iter = (p.split("/")[0] for p in source[BINARIES])
+            else:
+                ext = "/" + item.architecture
+                pkg_iter = (p.split("/")[0] for p in source[BINARIES] if p.endswith(ext))
+            register_reverses(binaries[parch][0], binaries[parch][1], iterator=pkg_iter)
 
             # add/update the source package
             if item.architecture == 'source':
diff --git a/britney_util.py b/britney_util.py
index 907fe275f256977cd452023a35aed3ee3867b269..fbe3de1ebcc02e09a677ac342e8fc04cc23e7e71 100644
--- a/britney_util.py
+++ b/britney_util.py
@@ -163,44 +163,55 @@ def old_libraries_format(libs):
 
 
 
-def register_reverses(pkg, packages, provides, check_doubles=True,
+def register_reverses(packages, provides, check_doubles=True, iterator=None,
                       parse_depends=apt_pkg.parse_depends,
+                      DEPENDS=DEPENDS, CONFLICTS=CONFLICTS,
                       RDEPENDS=RDEPENDS, RCONFLICTS=RCONFLICTS):
-    """Register reverse dependencies and conflicts for the specified package
+    """Register reverse dependencies and conflicts for a given
+    sequence of packages
 
-    This method registers the reverse dependencies and conflicts for
-    a given package using `packages` as the list of packages and `provides`
-    as the list of virtual packages.
+    This method registers the reverse dependencies and conflicts for a
+    given sequence of packages.  `packages` is a table of real
+    packages and `provides` is a table of virtual packages.
+
+    iterator is the sequence of packages for which the reverse
+    relations should be updated.
 
     The "X=X" parameters are optimizations to avoid "load global" in
     the loops.
     """
-    # register the list of the dependencies for the depending packages
-    dependencies = []
-    if packages[pkg][DEPENDS]:
-        dependencies.extend(parse_depends(packages[pkg][DEPENDS], False))
-    # go through the list
-    for p in dependencies:
-        for a in p:
-                # register real packages
-            if a[0] in packages and (not check_doubles or pkg not in packages[a[0]][RDEPENDS]):
-                packages[a[0]][RDEPENDS].append(pkg)
-            # also register packages which provide the package (if any)
-            if a[0] in provides:
-                for i in provides.get(a[0]):
-                    if i not in packages: continue
-                    if not check_doubles or pkg not in packages[i][RDEPENDS]:
-                        packages[i][RDEPENDS].append(pkg)
-    # register the list of the conflicts for the conflicting packages
-    if packages[pkg][CONFLICTS]:
-        for p in parse_depends(packages[pkg][CONFLICTS], False):
+    if iterator is None:
+        iterator = packages.iterkeys()
+    else:
+        iterator = ifilter_only(packages, iterator)
+
+    for pkg in iterator:
+        # register the list of the dependencies for the depending packages
+        dependencies = []
+        if packages[pkg][DEPENDS]:
+            dependencies.extend(parse_depends(packages[pkg][DEPENDS], False))
+        # go through the list
+        for p in dependencies:
             for a in p:
                 # register real packages
-                if a[0] in packages and (not check_doubles or pkg not in packages[a[0]][RCONFLICTS]):
-                    packages[a[0]][RCONFLICTS].append(pkg)
+                if a[0] in packages and (not check_doubles or pkg not in packages[a[0]][RDEPENDS]):
+                    packages[a[0]][RDEPENDS].append(pkg)
                 # also register packages which provide the package (if any)
                 if a[0] in provides:
                     for i in provides[a[0]]:
                         if i not in packages: continue
-                        if not check_doubles or pkg not in packages[i][RCONFLICTS]:
-                            packages[i][RCONFLICTS].append(pkg)
+                        if not check_doubles or pkg not in packages[i][RDEPENDS]:
+                            packages[i][RDEPENDS].append(pkg)
+        # register the list of the conflicts for the conflicting packages
+        if packages[pkg][CONFLICTS]:
+            for p in parse_depends(packages[pkg][CONFLICTS], False):
+                for a in p:
+                    # register real packages
+                    if a[0] in packages and (not check_doubles or pkg not in packages[a[0]][RCONFLICTS]):
+                        packages[a[0]][RCONFLICTS].append(pkg)
+                    # also register packages which provide the package (if any)
+                    if a[0] in provides:
+                        for i in provides[a[0]]:
+                            if i not in packages: continue
+                            if not check_doubles or pkg not in packages[i][RCONFLICTS]:
+                                packages[i][RCONFLICTS].append(pkg)
-- 
1.7.10.4

>From 52aeada77a3a36e06cfb1d7c6bed718778f868b1 Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Fri, 28 Dec 2012 13:32:04 +0100
Subject: [PATCH 10/15] register_reverses: factor out a[0]

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 britney_util.py |   27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)

diff --git a/britney_util.py b/britney_util.py
index fbe3de1ebcc02e09a677ac342e8fc04cc23e7e71..7d4117f99f9f907cbdfb6c0c0a13f8ef820d520c 100644
--- a/britney_util.py
+++ b/britney_util.py
@@ -188,30 +188,33 @@ def register_reverses(packages, provides, check_doubles=True, iterator=None,
     for pkg in iterator:
         # register the list of the dependencies for the depending packages
         dependencies = []
-        if packages[pkg][DEPENDS]:
-            dependencies.extend(parse_depends(packages[pkg][DEPENDS], False))
+        pkg_data = packages[pkg]
+        if pkg_data[DEPENDS]:
+            dependencies.extend(parse_depends(pkg_data[DEPENDS], False))
         # go through the list
         for p in dependencies:
             for a in p:
+                dep = a[0]
                 # register real packages
-                if a[0] in packages and (not check_doubles or pkg not in packages[a[0]][RDEPENDS]):
-                    packages[a[0]][RDEPENDS].append(pkg)
+                if dep in packages and (not check_doubles or pkg not in packages[dep][RDEPENDS]):
+                    packages[dep][RDEPENDS].append(pkg)
                 # also register packages which provide the package (if any)
-                if a[0] in provides:
-                    for i in provides[a[0]]:
+                if dep in provides:
+                    for i in provides[dep]:
                         if i not in packages: continue
                         if not check_doubles or pkg not in packages[i][RDEPENDS]:
                             packages[i][RDEPENDS].append(pkg)
         # register the list of the conflicts for the conflicting packages
-        if packages[pkg][CONFLICTS]:
-            for p in parse_depends(packages[pkg][CONFLICTS], False):
+        if pkg_data[CONFLICTS]:
+            for p in parse_depends(pkg_data[CONFLICTS], False):
                 for a in p:
+                    con = a[0]
                     # register real packages
-                    if a[0] in packages and (not check_doubles or pkg not in packages[a[0]][RCONFLICTS]):
-                        packages[a[0]][RCONFLICTS].append(pkg)
+                    if con in packages and (not check_doubles or pkg not in packages[con][RCONFLICTS]):
+                        packages[con][RCONFLICTS].append(pkg)
                     # also register packages which provide the package (if any)
-                    if a[0] in provides:
-                        for i in provides[a[0]]:
+                    if con in provides:
+                        for i in provides[con]:
                             if i not in packages: continue
                             if not check_doubles or pkg not in packages[i][RCONFLICTS]:
                                 packages[i][RCONFLICTS].append(pkg)
-- 
1.7.10.4

>From 7c04d8cf636362de2b5fbc44d3df0f5f9c57ddc8 Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Sat, 12 Jan 2013 16:34:02 +0100
Subject: [PATCH 11/15] Move get_reverse_tree into britney_util

Rename get_reverse_tree and move it to britney_util with slightly
different arguments.

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 britney.py      |   49 ++++++++-----------------------------------------
 britney_util.py |   43 ++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 50 insertions(+), 42 deletions(-)

diff --git a/britney.py b/britney.py
index 52df2ba8166cf9f1102069a3c5959d41e52d1e4e..f7c5b9b376c6c379286a9e4cd62f6da493be49b6 100755
--- a/britney.py
+++ b/britney.py
@@ -190,7 +190,6 @@ import urllib
 import apt_pkg
 
 from functools import reduce, partial
-from itertools import chain, repeat, izip
 from operator import attrgetter
 
 if __name__ == '__main__':
@@ -213,7 +212,7 @@ from migrationitem import MigrationItem, HintItem
 from hints import HintCollection
 from britney import buildSystem
 from britney_util import (old_libraries_format, same_source, undo_changes,
-                          register_reverses)
+                          register_reverses, compute_reverse_tree)
 from consts import (VERSION, SECTION, BINARIES, MAINTAINER, FAKESRC,
                    SOURCE, SOURCEVER, ARCHITECTURE, DEPENDS, CONFLICTS,
                    PROVIDES, RDEPENDS, RCONFLICTS)
@@ -1740,6 +1739,7 @@ class Britney(object):
         # local copies for better performances
         sources = self.sources
         binaries = self.binaries['testing']
+        get_reverse_tree = partial(compute_reverse_tree, self.binaries["testing"])
         # remove all binary packages (if the source already exists)
         if item.architecture == 'source' or not item.is_removal:
             if item.package in sources['testing']:
@@ -1810,7 +1810,7 @@ class Britney(object):
                     # save the old binary for undo
                     undo['binaries'][p] = binaries[parch][0][binary]
                     # all the reverse dependencies are affected by the change
-                    affected.update(self.get_reverse_tree(binary, parch, 'testing'))
+                    affected.update(get_reverse_tree(binary, parch))
                     # remove the provided virtual packages
                     for j in binaries[parch][0][binary][PROVIDES]:
                         key = j + "/" + parch
@@ -1834,7 +1834,7 @@ class Britney(object):
         # updates but not supported as a manual hint
         elif item.package in binaries[item.architecture][0]:
             undo['binaries'][item.package + "/" + item.architecture] = binaries[item.architecture][0][item.package]
-            affected.update(self.get_reverse_tree(item.package, item.architecture, 'testing'))
+            affected.update(get_reverse_tree(item.package, item.architecture))
             del binaries[item.architecture][0][item.package]
             self.systems[item.architecture].remove_binary(item.package)
 
@@ -1855,10 +1855,10 @@ class Britney(object):
                     # save the old binary package
                     undo['binaries'][p] = binaries[parch][0][binary]
                     # all the reverse dependencies are affected by the change
-                    affected.update(self.get_reverse_tree(binary, parch, 'testing'))
+                    affected.update(get_reverse_tree(binary, parch))
                     # all the reverse conflicts and their dependency tree are affected by the change
                     for j in binaries[parch][0][binary][RCONFLICTS]:
-                        affected.update(self.get_reverse_tree(j, parch, 'testing'))
+                        affected.update(get_reverse_tree(j, parch))
                     self.systems[parch].remove_binary(binary)
                 else:
                     # the binary isn't in testing, but it may have been at
@@ -1876,7 +1876,7 @@ class Britney(object):
                         if p in tundo['binaries']:
                             for rdep in tundo['binaries'][p][RDEPENDS]:
                                 if rdep in binaries[parch][0] and rdep not in source[BINARIES]:
-                                    affected.update(self.get_reverse_tree(rdep, parch, 'testing'))
+                                    affected.update(get_reverse_tree(rdep, parch))
                 # add/update the binary package
                 binaries[parch][0][binary] = self.binaries[item.suite][parch][0][binary]
                 self.systems[parch].add_binary(binary, binaries[parch][0][binary][:PROVIDES] + \
@@ -1891,7 +1891,7 @@ class Britney(object):
                         undo['virtual'][key] = binaries[parch][1][j][:]
                     binaries[parch][1][j].append(binary)
                 # all the reverse dependencies are affected by the change
-                affected.update(self.get_reverse_tree(binary, parch, 'testing'))
+                affected.update(get_reverse_tree(binary, parch))
 
             # register reverse dependencies and conflicts for the new binary packages
             if item.architecture == 'source':
@@ -1908,39 +1908,6 @@ class Britney(object):
         # return the package name, the suite, the list of affected packages and the undo dictionary
         return (item, affected, undo)
 
-    def get_reverse_tree(self, pkg, arch, suite):
-        """Calculate the full dependency tree for the given package
-
-        This method returns the full dependency tree for the package
-        `pkg`, inside the `arch` architecture for the suite `suite`
-        flatterned as an iterable.
-
-        The tree is returned as an iterable of (package, arch) tuples
-        and the iterable will contain (`pkg`, `arch`) if it is
-        available on that architecture.
-
-        If `pkg` is not available on that architecture in that suite,
-        this returns an empty iterable.
-
-        The method does not promise any ordering of the returned
-        elements and the iterable is not reusable nor mutable.
-        """
-        binaries = self.binaries[suite][arch][0]
-        if pkg not in binaries:
-            return frozenset()
-        rev_deps = set(binaries[pkg][RDEPENDS])
-        seen = set([pkg])
-
-        binfilt = ifilter_only(binaries)
-        revfilt = ifilter_except(seen)
-        flatten = chain.from_iterable
-        while rev_deps:
-            # mark all of the current iteration of packages as affected
-            seen |= rev_deps
-            # generate the next iteration, which is the reverse-dependencies of
-            # the current iteration
-            rev_deps = set(revfilt(flatten( binaries[x][RDEPENDS] for x in binfilt(rev_deps) )))
-        return izip(seen, repeat(arch))
 
     def _check_packages(self, binaries, systems, arch, affected, skip_archall, nuninst, pkg):
         broken = nuninst[arch + "+all"]
diff --git a/britney_util.py b/britney_util.py
index 7d4117f99f9f907cbdfb6c0c0a13f8ef820d520c..e530b4d0dedda73f93f7580a6996396377735572 100644
--- a/britney_util.py
+++ b/britney_util.py
@@ -20,7 +20,7 @@
 
 import apt_pkg
 from functools import partial
-from itertools import ifilter, ifilterfalse
+from itertools import chain, ifilter, ifilterfalse, izip, repeat
 import re
 
 
@@ -218,3 +218,44 @@ def register_reverses(packages, provides, check_doubles=True, iterator=None,
                             if i not in packages: continue
                             if not check_doubles or pkg not in packages[i][RCONFLICTS]:
                                 packages[i][RCONFLICTS].append(pkg)
+
+
+def compute_reverse_tree(packages_s, pkg, arch,
+                     set=set, flatten=chain.from_iterable,
+                     RDEPENDS=RDEPENDS):
+    """Calculate the full dependency tree for the given package
+
+    This method returns the full dependency tree for the package
+    `pkg`, inside the `arch` architecture for a given suite flattened
+    as an iterable.  The first argument `packages_s` is the binary
+    package table for that given suite (e.g. Britney().binaries["testing"]).
+
+    The tree (or grapH) is returned as an iterable of (package, arch)
+    tuples and the iterable will contain (`pkg`, `arch`) if it is
+    available on that architecture.
+
+    If `pkg` is not available on that architecture in that suite,
+    this returns an empty iterable.
+
+    The method does not promise any ordering of the returned
+    elements and the iterable is not reusable.
+
+    The flatten=... and the "X=X" parameters are optimizations to
+    avoid "load global" in the loops.
+    """
+    binaries = packages_s[arch][0]
+    if pkg not in binaries:
+        return frozenset()
+    rev_deps = set(binaries[pkg][RDEPENDS])
+    seen = set([pkg])
+
+    binfilt = ifilter_only(binaries)
+    revfilt = ifilter_except(seen)
+
+    while rev_deps:
+        # mark all of the current iteration of packages as affected
+        seen |= rev_deps
+        # generate the next iteration, which is the reverse-dependencies of
+        # the current iteration
+        rev_deps = set(revfilt(flatten( binaries[x][RDEPENDS] for x in binfilt(rev_deps) )))
+    return izip(seen, repeat(arch))
-- 
1.7.10.4

>From acc1fa9edb0f914123243364eef0d0fd0b90c606 Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Sat, 12 Jan 2013 17:17:41 +0100
Subject: [PATCH 12/15] Move {read,write}_nuninst to britney_util

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 britney.py      |   29 +++++++----------------------
 britney_util.py |   33 +++++++++++++++++++++++++++++++++
 2 files changed, 40 insertions(+), 22 deletions(-)

diff --git a/britney.py b/britney.py
index f7c5b9b376c6c379286a9e4cd62f6da493be49b6..65ed878add1c78d91a32c811ba3391198c4ce25f 100755
--- a/britney.py
+++ b/britney.py
@@ -212,7 +212,8 @@ from migrationitem import MigrationItem, HintItem
 from hints import HintCollection
 from britney import buildSystem
 from britney_util import (old_libraries_format, same_source, undo_changes,
-                          register_reverses, compute_reverse_tree)
+                          register_reverses, compute_reverse_tree,
+                          read_nuninst, write_nuninst)
 from consts import (VERSION, SECTION, BINARIES, MAINTAINER, FAKESRC,
                    SOURCE, SOURCEVER, ARCHITECTURE, DEPENDS, CONFLICTS,
                    PROVIDES, RDEPENDS, RCONFLICTS)
@@ -273,7 +274,7 @@ class Britney(object):
                 if self.options.print_uninst:
                     self.nuninst_arch_report(nuninst, arch)
             if not self.options.print_uninst:
-                self.write_nuninst(nuninst)
+                write_nuninst(self.options.noninst_status, nuninst)
         else:
             self.__log("Not building the list of non-installable packages, as requested", type="I")
 
@@ -885,25 +886,6 @@ class Britney(object):
             f.write(output + "\n")
         f.close()
 
-    def write_nuninst(self, nuninst):
-        """Write the non-installable report"""
-        f = open(self.options.noninst_status, 'w')
-        f.write("Built on: " + time.strftime("%Y.%m.%d %H:%M:%S %z", time.gmtime(time.time())) + "\n")
-        f.write("Last update: " + time.strftime("%Y.%m.%d %H:%M:%S %z", time.gmtime(time.time())) + "\n\n")
-        f.write("".join([k + ": " + " ".join(nuninst[k]) + "\n" for k in nuninst]))
-        f.close()
-
-    def read_nuninst(self):
-        """Read the non-installable report"""
-        f = open(self.options.noninst_status)
-        nuninst = {}
-        for r in f:
-            if ":" not in r: continue
-            arch, packages = r.strip().split(":", 1)
-            if arch.split("+", 1)[0] in self.options.architectures:
-                nuninst[arch] = set(packages.split())
-        return nuninst
-
 
     # Utility methods for package analysis
     # ------------------------------------
@@ -1628,10 +1610,13 @@ class Britney(object):
 
         It returns a dictionary with the architectures as keys and the list
         of uninstallable packages as values.
+
+        NB: If build is False, requested_arch is ignored.
         """
         # if we are not asked to build the nuninst, read it from the cache
         if not build:
-            return self.read_nuninst()
+            return read_nuninst(self.options.noninst_status,
+                                self.options.architectures)
 
         nuninst = {}
 
diff --git a/britney_util.py b/britney_util.py
index e530b4d0dedda73f93f7580a6996396377735572..26a47ecea37dd82d75b96eb89457fa4f5d0c94d7 100644
--- a/britney_util.py
+++ b/britney_util.py
@@ -22,6 +22,7 @@ import apt_pkg
 from functools import partial
 from itertools import chain, ifilter, ifilterfalse, izip, repeat
 import re
+import time
 
 
 from consts import (BINARIES, PROVIDES, DEPENDS, CONFLICTS,
@@ -259,3 +260,35 @@ def compute_reverse_tree(packages_s, pkg, arch,
         # the current iteration
         rev_deps = set(revfilt(flatten( binaries[x][RDEPENDS] for x in binfilt(rev_deps) )))
     return izip(seen, repeat(arch))
+
+
+def write_nuninst(filename, nuninst):
+    """Write the non-installable report
+
+    Write the non-installable report derived from `nuninst` to the
+    file denoted by `filename`.
+    """
+    with open(filename, 'w') as f:
+        # Having two fields with (almost) identical dates seems a bit
+        # redundant.
+        f.write("Built on: " + time.strftime("%Y.%m.%d %H:%M:%S %z", time.gmtime(time.time())) + "\n")
+        f.write("Last update: " + time.strftime("%Y.%m.%d %H:%M:%S %z", time.gmtime(time.time())) + "\n\n")
+        f.write("".join([k + ": " + " ".join(nuninst[k]) + "\n" for k in nuninst]))
+
+
+def read_nuninst(filename, architectures):
+    """Read the non-installable report
+
+    Read the non-installable report from the file denoted by
+    `filename` and return it.  Only architectures in `architectures`
+    will be included in the report.
+    """
+    nuninst = {}
+    with open(filename) as f:
+        for r in f:
+            if ":" not in r: continue
+            arch, packages = r.strip().split(":", 1)
+            if arch.split("+", 1)[0] in architectures:
+                nuninst[arch] = set(packages.split())
+    return nuninst
+
-- 
1.7.10.4

>From 5b40bd3bdd20c7616255fcd10f066b97c1abbc06 Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Sat, 12 Jan 2013 17:47:24 +0100
Subject: [PATCH 13/15] Move write_heidi to britney_util

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 britney.py      |   39 ++++-----------------------------------
 britney_util.py |   41 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 43 insertions(+), 37 deletions(-)

diff --git a/britney.py b/britney.py
index 65ed878add1c78d91a32c811ba3391198c4ce25f..46cf42e9517bd12b63e11330f7435aa083e5d14c 100755
--- a/britney.py
+++ b/britney.py
@@ -213,7 +213,7 @@ from hints import HintCollection
 from britney import buildSystem
 from britney_util import (old_libraries_format, same_source, undo_changes,
                           register_reverses, compute_reverse_tree,
-                          read_nuninst, write_nuninst)
+                          read_nuninst, write_nuninst, write_heidi)
 from consts import (VERSION, SECTION, BINARIES, MAINTAINER, FAKESRC,
                    SOURCE, SOURCEVER, ARCHITECTURE, DEPENDS, CONFLICTS,
                    PROVIDES, RDEPENDS, RCONFLICTS)
@@ -803,39 +803,6 @@ class Britney(object):
 
         return hints
 
-    def write_heidi(self, filename):
-        """Write the output HeidiResult
-
-        This method write the output for Heidi, which contains all the
-        binary packages and the source packages in the form:
-        
-        <pkg-name> <pkg-version> <pkg-architecture> <pkg-section>
-        <src-name> <src-version> source <src-section>
-        """
-        self.__log("Writing Heidi results to %s" % filename)
-        f = open(filename, 'w')
-
-        # local copies
-        sources = self.sources['testing']
-
-        # write binary packages
-        for arch in sorted(self.options.architectures):
-            binaries = self.binaries['testing'][arch][0]
-            for pkg_name in sorted(binaries):
-                pkg = binaries[pkg_name]
-                pkgv = pkg[VERSION]
-                pkgarch = pkg[ARCHITECTURE] or 'all'
-                pkgsec = pkg[SECTION] or 'faux'
-                f.write('%s %s %s %s\n' % (pkg_name, pkgv, pkgarch, pkgsec))
-
-        # write sources
-        for src_name in sorted(sources):
-            src = sources[src_name]
-            srcv = src[VERSION]
-            srcsec = src[SECTION] or 'unknown'
-            f.write('%s %s source %s\n' % (src_name, srcv, srcsec))
-
-        f.close()
 
     def write_controlfiles(self, basedir, suite):
         """Write the control files
@@ -2262,7 +2229,9 @@ class Britney(object):
             self.write_dates(self.options.testing, self.dates)
 
             # write HeidiResult
-            self.write_heidi(self.options.heidi_output)
+            self.__log("Writing Heidi results to %s" % self.options.heidi_output)
+            write_heidi(self.options.heidi_output, self.sources["testing"],
+                        self.binaries["testing"])
 
         self.printuninstchange()
         self.__log("Test completed!", type="I")
diff --git a/britney_util.py b/britney_util.py
index 26a47ecea37dd82d75b96eb89457fa4f5d0c94d7..3c11a661c9af9c8b14d81d6a14a41c538d070e0c 100644
--- a/britney_util.py
+++ b/britney_util.py
@@ -25,8 +25,8 @@ import re
 import time
 
 
-from consts import (BINARIES, PROVIDES, DEPENDS, CONFLICTS,
-                    RDEPENDS, RCONFLICTS)
+from consts import (VERSION, BINARIES, PROVIDES, DEPENDS, CONFLICTS,
+                    RDEPENDS, RCONFLICTS, ARCHITECTURE, SECTION)
 
 binnmu_re = re.compile(r'^(.*)\+b\d+$')
 
@@ -292,3 +292,40 @@ def read_nuninst(filename, architectures):
                 nuninst[arch] = set(packages.split())
     return nuninst
 
+
+def write_heidi(filename, sources_t, packages_t,
+                VERSION=VERSION, SECTION=SECTION,
+                ARCHITECTURE=ARCHITECTURE, sorted=sorted):
+    """Write the output HeidiResult
+
+    This method write the output for Heidi, which contains all the
+    binary packages and the source packages in the form:
+
+    <pkg-name> <pkg-version> <pkg-architecture> <pkg-section>
+    <src-name> <src-version> source <src-section>
+
+    The file is written as `filename`, it assumes all sources and
+    packages in `sources_t` and `packages_t` to be the packages in
+    "testing".
+
+    The "X=X" parameters are optimizations to avoid "load global" in
+    the loops.
+    """
+    with open(filename, 'w') as f:
+
+        # write binary packages
+        for arch in sorted(packages_t):
+            binaries = packages_t[arch][0]
+            for pkg_name in sorted(binaries):
+                pkg = binaries[pkg_name]
+                pkgv = pkg[VERSION]
+                pkgarch = pkg[ARCHITECTURE] or 'all'
+                pkgsec = pkg[SECTION] or 'faux'
+                f.write('%s %s %s %s\n' % (pkg_name, pkgv, pkgarch, pkgsec))
+
+        # write sources
+        for src_name in sorted(sources_t):
+            src = sources_t[src_name]
+            srcv = src[VERSION]
+            srcsec = src[SECTION] or 'unknown'
+            f.write('%s %s source %s\n' % (src_name, srcv, srcsec))
-- 
1.7.10.4

>From a16234c6bf19a2bb80428807f2cc3d7e531d0cbb Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Sat, 12 Jan 2013 19:58:15 +0100
Subject: [PATCH 14/15] Move eval_uninst to britney_util

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 britney.py      |   31 +++++++++++--------------------
 britney_util.py |   16 ++++++++++++++++
 2 files changed, 27 insertions(+), 20 deletions(-)

diff --git a/britney.py b/britney.py
index 46cf42e9517bd12b63e11330f7435aa083e5d14c..ba6de545a4bf0eb49c5c3a674ee0c97927f8fc9f 100755
--- a/britney.py
+++ b/britney.py
@@ -213,7 +213,8 @@ from hints import HintCollection
 from britney import buildSystem
 from britney_util import (old_libraries_format, same_source, undo_changes,
                           register_reverses, compute_reverse_tree,
-                          read_nuninst, write_nuninst, write_heidi)
+                          read_nuninst, write_nuninst, write_heidi,
+                          eval_uninst)
 from consts import (VERSION, SECTION, BINARIES, MAINTAINER, FAKESRC,
                    SOURCE, SOURCEVER, ARCHITECTURE, DEPENDS, CONFLICTS,
                    PROVIDES, RDEPENDS, RCONFLICTS)
@@ -1648,20 +1649,6 @@ class Britney(object):
             res.append("%s-%d" % (arch[0], n))
         return "%d+%d: %s" % (total, totalbreak, ":".join(res))
 
-    def eval_uninst(self, nuninst):
-        """Return a string which represents the uninstallable packages
-
-        This method returns a string which represents the uninstallable
-        packages reading the uninstallability statistics `nuninst`.
-
-        An example of the output string is:
-            * i386: broken-pkg1, broken-pkg2
-        """
-        parts = []
-        for arch in self.options.architectures:
-            if arch in nuninst and len(nuninst[arch]) > 0:
-                parts.append("    * %s: %s\n" % (arch,", ".join(sorted(nuninst[arch]))))
-        return "".join(parts)
 
     def is_nuninst_asgood_generous(self, old, new):
         diff = 0
@@ -2026,7 +2013,8 @@ class Britney(object):
         self.output_write(" finish: [%s]\n" % ",".join([ x.uvname for x in selected ]))
         self.output_write("endloop: %s\n" % (self.eval_nuninst(self.nuninst_orig)))
         self.output_write("    now: %s\n" % (self.eval_nuninst(nuninst_comp)))
-        self.output_write(self.eval_uninst(self.newlyuninst(self.nuninst_orig, nuninst_comp)))
+        self.output_write(eval_uninst(self.options.architectures,
+                                      self.newlyuninst(self.nuninst_orig, nuninst_comp)))
         self.output_write("\n")
 
         return (nuninst_comp, extra)
@@ -2089,7 +2077,8 @@ class Britney(object):
             self.output_write("easy: %s\n" %  nuninst_end_str)
 
             if not force:
-                self.output_write(self.eval_uninst(self.newlyuninst(nuninst_start, nuninst_end)) + "\n")
+                self.output_write(eval_uninst(self.options.architectures,
+                                              self.newlyuninst(nuninst_start, nuninst_end)) + "\n")
 
         if force or self.is_nuninst_asgood_generous(self.nuninst_orig, nuninst_end):
             # Result accepted either by force or by being better than the original result.
@@ -2104,7 +2093,8 @@ class Britney(object):
             self.output_write("  end: %s\n" % nuninst_end_str)
             if force:
                 self.output_write("force breaks:\n")
-                self.output_write(self.eval_uninst(self.newlyuninst(nuninst_start, nuninst_end)) + "\n")
+                self.output_write(eval_uninst(self.options.architectures,
+                                              self.newlyuninst(nuninst_start, nuninst_end)) + "\n")
             self.output_write("SUCCESS (%d/%d)\n" % (len(actions or self.upgrade_me), len(extra)))
             self.nuninst_orig = nuninst_end
             if not actions:
@@ -2238,8 +2228,9 @@ class Britney(object):
 
     def printuninstchange(self):
         self.__log("Checking for newly uninstallable packages", type="I")
-        text = self.eval_uninst(self.newlyuninst(
-            self.nuninst_orig_save, self.nuninst_orig))
+        text = eval_uninst(self.options.architectures, self.newlyuninst(
+                        self.nuninst_orig_save, self.nuninst_orig))
+
         if text != '':
             self.output_write("\nNewly uninstallable packages in testing:\n%s" % \
                 (text))
diff --git a/britney_util.py b/britney_util.py
index 3c11a661c9af9c8b14d81d6a14a41c538d070e0c..d19577ac81b4499596419a4c777c5e1bc13288fd 100644
--- a/britney_util.py
+++ b/britney_util.py
@@ -293,6 +293,22 @@ def read_nuninst(filename, architectures):
     return nuninst
 
 
+def eval_uninst(architectures, nuninst):
+    """Return a string which represents the uninstallable packages
+
+    This method returns a string which represents the uninstallable
+    packages reading the uninstallability statistics `nuninst`.
+
+    An example of the output string is:
+      * i386: broken-pkg1, broken-pkg2
+    """
+    parts = []
+    for arch in architectures:
+        if arch in nuninst and nuninst[arch]:
+            parts.append("    * %s: %s\n" % (arch,", ".join(sorted(nuninst[arch]))))
+    return "".join(parts)
+
+
 def write_heidi(filename, sources_t, packages_t,
                 VERSION=VERSION, SECTION=SECTION,
                 ARCHITECTURE=ARCHITECTURE, sorted=sorted):
-- 
1.7.10.4

>From f2ec41dd60ff07ce659e5dac66690fd31862a101 Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Sat, 12 Jan 2013 20:13:43 +0100
Subject: [PATCH 15/15] Move newlyuninst to britney_util

Also, renamed it for consistency with the other X_uninst functions.

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 britney.py      |   24 +++++-------------------
 britney_util.py |   15 +++++++++++++++
 2 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/britney.py b/britney.py
index ba6de545a4bf0eb49c5c3a674ee0c97927f8fc9f..b0668c0defe130273ffc26939f34de3ef8f40a28 100755
--- a/britney.py
+++ b/britney.py
@@ -214,7 +214,7 @@ from britney import buildSystem
 from britney_util import (old_libraries_format, same_source, undo_changes,
                           register_reverses, compute_reverse_tree,
                           read_nuninst, write_nuninst, write_heidi,
-                          eval_uninst)
+                          eval_uninst, newly_uninst)
 from consts import (VERSION, SECTION, BINARIES, MAINTAINER, FAKESRC,
                    SOURCE, SOURCEVER, ARCHITECTURE, DEPENDS, CONFLICTS,
                    PROVIDES, RDEPENDS, RCONFLICTS)
@@ -1552,20 +1552,6 @@ class Britney(object):
     # Upgrade run
     # -----------
 
-    def newlyuninst(self, nuold, nunew):
-        """Return a nuninst statstic with only new uninstallable packages
-
-        This method subtracts the uninstallable packages of the statistic
-        `nunew` from the statistic `nuold`.
-
-        It returns a dictionary with the architectures as keys and the list
-        of uninstallable packages as values.
-        """
-        res = {}
-        for arch in nuold:
-            if arch not in nunew: continue
-            res[arch] = [x for x in nunew[arch] if x not in nuold[arch]]
-        return res
 
     def get_nuninst(self, requested_arch=None, build=False):
         """Return the uninstallability statistic for all the architectures
@@ -2014,7 +2000,7 @@ class Britney(object):
         self.output_write("endloop: %s\n" % (self.eval_nuninst(self.nuninst_orig)))
         self.output_write("    now: %s\n" % (self.eval_nuninst(nuninst_comp)))
         self.output_write(eval_uninst(self.options.architectures,
-                                      self.newlyuninst(self.nuninst_orig, nuninst_comp)))
+                                      newly_uninst(self.nuninst_orig, nuninst_comp)))
         self.output_write("\n")
 
         return (nuninst_comp, extra)
@@ -2078,7 +2064,7 @@ class Britney(object):
 
             if not force:
                 self.output_write(eval_uninst(self.options.architectures,
-                                              self.newlyuninst(nuninst_start, nuninst_end)) + "\n")
+                                              newly_uninst(nuninst_start, nuninst_end)) + "\n")
 
         if force or self.is_nuninst_asgood_generous(self.nuninst_orig, nuninst_end):
             # Result accepted either by force or by being better than the original result.
@@ -2094,7 +2080,7 @@ class Britney(object):
             if force:
                 self.output_write("force breaks:\n")
                 self.output_write(eval_uninst(self.options.architectures,
-                                              self.newlyuninst(nuninst_start, nuninst_end)) + "\n")
+                                              newly_uninst(nuninst_start, nuninst_end)) + "\n")
             self.output_write("SUCCESS (%d/%d)\n" % (len(actions or self.upgrade_me), len(extra)))
             self.nuninst_orig = nuninst_end
             if not actions:
@@ -2228,7 +2214,7 @@ class Britney(object):
 
     def printuninstchange(self):
         self.__log("Checking for newly uninstallable packages", type="I")
-        text = eval_uninst(self.options.architectures, self.newlyuninst(
+        text = eval_uninst(self.options.architectures, newly_uninst(
                         self.nuninst_orig_save, self.nuninst_orig))
 
         if text != '':
diff --git a/britney_util.py b/britney_util.py
index d19577ac81b4499596419a4c777c5e1bc13288fd..c11f5650f29895a830c97bc5b77bf74f46d4c45a 100644
--- a/britney_util.py
+++ b/britney_util.py
@@ -293,6 +293,21 @@ def read_nuninst(filename, architectures):
     return nuninst
 
 
+def newly_uninst(nuold, nunew):
+    """Return a nuninst statstic with only new uninstallable packages
+
+    This method subtracts the uninstallable packages of the statistic
+    `nunew` from the statistic `nuold`.
+
+    It returns a dictionary with the architectures as keys and the list
+    of uninstallable packages as values.
+    """
+    res = {}
+    for arch in ifilter_only(nunew, nuold):
+        res[arch] = [x for x in nunew[arch] if x not in nuold[arch]]
+    return res
+
+
 def eval_uninst(architectures, nuninst):
     """Return a string which represents the uninstallable packages
 
-- 
1.7.10.4


Reply to: