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

Bug#698057: marked as done (britney: Move/refactor parts into a sep. module)



Your message dated Wed, 10 Jul 2013 20:42:15 +0200
with message-id <51DDAB07.4010406@thykier.net>
and subject line Re: Bug#698057: britney: Move/refactor parts into a sep. module
has caused the Debian Bug report #698057,
regarding britney: Move/refactor parts into a sep. module
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
698057: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=698057
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
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


--- End Message ---
--- Begin Message ---
On 2013-07-06 13:33, Niels Thykier wrote:
> [...]
> 
> I have attached the patch series rebased against the current master.
> All the comments should be fixed (these changes are visible in the
> attached interdiffs[1]).
> 
> The patches series produces the same results as the current master
> branch in the test suite.
> 
> ~Niels
> 
> [1] Note: to reduce the noise, the interdiff was done after rebasing.
> I.e. it is not an interdiff between the last patch, so changes caused by
> rebasing is not included.
> 
> 

Patches rebased and merged.

~Niels

--- End Message ---

Reply to: