← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~jelmer/launchpad/stacked-code-imports into lp:launchpad

 

Jelmer Vernooij has proposed merging lp:~jelmer/launchpad/stacked-code-imports into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #485932 in Launchpad itself: "should stack foreign branch imports"
  https://bugs.launchpad.net/launchpad/+bug/485932

For more details, see:
https://code.launchpad.net/~jelmer/launchpad/stacked-code-imports/+merge/81139

Stack code imports on the default development focus branch.

This should make imports of branches that are related to the development focus a lot faster, and use less disk space.

This will help significantly with e.g. imports of new gcc branches.

For now, this only stacks onto public branches.

This branch was landed earlier and then rolled back, because the newer bzr-git it shipped was incompatible with the bzr currently on Launchpad. That has now been fixed.
-- 
https://code.launchpad.net/~jelmer/launchpad/stacked-code-imports/+merge/81139
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jelmer/launchpad/stacked-code-imports into lp:launchpad.
=== modified file 'lib/lp/codehosting/codeimport/tests/test_worker.py'
--- lib/lp/codehosting/codeimport/tests/test_worker.py	2011-10-30 20:36:51 +0000
+++ lib/lp/codehosting/codeimport/tests/test_worker.py	2011-11-03 12:36:25 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
+
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for the code import worker."""
@@ -49,7 +49,14 @@
 import subvertpy.ra
 
 from canonical.config import config
-from canonical.testing.layers import BaseLayer
+from canonical.testing.layers import (
+    BaseLayer,
+    DatabaseFunctionalLayer,
+    )
+from lp.code.interfaces.codehosting import (
+    branch_id_alias,
+    compose_public_url,
+    )
 from lp.codehosting import load_optional_plugin
 from lp.codehosting.codeimport.tarball import (
     create_tarball,
@@ -67,6 +74,7 @@
     BzrImportWorker,
     BzrSvnImportWorker,
     CodeImportBranchOpenPolicy,
+    CodeImportSourceDetails,
     CodeImportWorkerExitCode,
     CSCVSImportWorker,
     ForeignTreeStore,
@@ -85,7 +93,11 @@
     )
 from lp.codehosting.tests.helpers import create_branch_with_one_revision
 from lp.services.log.logger import BufferLogger
-from lp.testing import TestCase
+from lp.testing import (
+    TestCase,
+    TestCaseWithFactory,
+    )
+from zope.security.proxy import removeSecurityProxy
 
 
 class ForeignBranchPluginLayer(BaseLayer):
@@ -807,7 +819,7 @@
         raise NotImplementedError(
             "Override this with a VCS-specific implementation.")
 
-    def makeSourceDetails(self, module_name, files):
+    def makeSourceDetails(self, module_name, files, stacked_on_url=None):
         """Make a `CodeImportSourceDetails` that points to a real repository.
 
         This should set `self.foreign_commit_count` to an appropriate value.
@@ -954,7 +966,7 @@
         self.foreign_commit_count += 1
         shutil.rmtree('working_dir')
 
-    def makeSourceDetails(self, module_name, files):
+    def makeSourceDetails(self, module_name, files, stacked_on_url=None):
         """Make a CVS `CodeImportSourceDetails` pointing at a real CVS repo.
         """
         cvs_server = CVSServer(self.makeTemporaryDirectory())
@@ -966,7 +978,8 @@
         self.foreign_commit_count = 2
 
         return self.factory.makeCodeImportSourceDetails(
-            rcstype='cvs', cvs_root=cvs_server.getRoot(), cvs_module='trunk')
+            rcstype='cvs', cvs_root=cvs_server.getRoot(), cvs_module='trunk',
+            stacked_on_url=stacked_on_url)
 
 
 class SubversionImportHelpers:
@@ -991,7 +1004,7 @@
         self.foreign_commit_count += 1
         shutil.rmtree('working_tree')
 
-    def makeSourceDetails(self, branch_name, files):
+    def makeSourceDetails(self, branch_name, files, stacked_on_url=None):
         """Make a SVN `CodeImportSourceDetails` pointing at a real SVN repo.
         """
         svn_server = SubversionServer(self.makeTemporaryDirectory())
@@ -1002,7 +1015,8 @@
         svn_branch_url = svn_branch_url.replace('://localhost/', ':///')
         self.foreign_commit_count = 2
         return self.factory.makeCodeImportSourceDetails(
-            rcstype=self.rcstype, url=svn_branch_url)
+            rcstype=self.rcstype, url=svn_branch_url,
+            stacked_on_url=stacked_on_url)
 
 
 class TestSubversionImport(WorkerTest, SubversionImportHelpers,
@@ -1092,6 +1106,36 @@
         self.assertEqual(
             CodeImportWorkerExitCode.SUCCESS, worker.run())
 
+    def test_stacked(self):
+        stacked_on = self.make_branch('stacked-on')
+        source_details = self.makeSourceDetails(
+            'trunk', [('README', 'Original contents')],
+            stacked_on_url=stacked_on.base)
+        stacked_on.fetch(Branch.open(source_details.url))
+        base_rev_count = self.foreign_commit_count
+        # There should only be one revision there, the other
+        # one is in the stacked-on repository.
+        self.addCleanup(stacked_on.lock_read().unlock)
+        self.assertEquals(
+            base_rev_count,
+            len(stacked_on.repository.revisions.keys()))
+        worker = self.makeImportWorker(
+            source_details,
+            opener_policy=AcceptAnythingPolicy())
+        self.makeForeignCommit(source_details)
+        self.assertEqual(
+            CodeImportWorkerExitCode.SUCCESS, worker.run())
+        branch = self.getStoredBazaarBranch(worker)
+        self.assertEquals(
+            base_rev_count,
+            len(stacked_on.repository.revisions.keys()))
+        # There should only be one revision there, the other
+        # one is in the stacked-on repository.
+        self.addCleanup(branch.lock_read().unlock)
+        self.assertEquals(1,
+             len(branch.repository.revisions.without_fallbacks().keys()))
+        self.assertEquals(stacked_on.base, branch.get_stacked_on_url())
+
 
 class TestGitImport(WorkerTest, TestActualImportMixin,
                     PullingImportWorkerTests):
@@ -1131,7 +1175,7 @@
             committer="Joe Random Hacker <joe@xxxxxxxxxxx>", ref=ref)
         self.foreign_commit_count += 1
 
-    def makeSourceDetails(self, branch_name, files):
+    def makeSourceDetails(self, branch_name, files, stacked_on_url=None):
         """Make a Git `CodeImportSourceDetails` pointing at a real Git repo.
         """
         repository_path = self.makeTemporaryDirectory()
@@ -1143,7 +1187,8 @@
         self.foreign_commit_count = 1
 
         return self.factory.makeCodeImportSourceDetails(
-            rcstype='git', url=git_server.get_url())
+            rcstype='git', url=git_server.get_url(),
+            stacked_on_url=stacked_on_url)
 
     def test_non_master(self):
         # non-master branches can be specified in the import URL.
@@ -1212,7 +1257,7 @@
             text=message, user="Jane Random Hacker", force=1, extra=extra)
         self.foreign_commit_count += 1
 
-    def makeSourceDetails(self, branch_name, files):
+    def makeSourceDetails(self, branch_name, files, stacked_on_url=None):
         """Make a Mercurial `CodeImportSourceDetails` pointing at a real repo.
         """
         repository_path = self.makeTemporaryDirectory()
@@ -1224,7 +1269,8 @@
         self.foreign_commit_count = 1
 
         return self.factory.makeCodeImportSourceDetails(
-            rcstype='hg', url=hg_server.get_url())
+            rcstype='hg', url=hg_server.get_url(),
+            stacked_on_url=stacked_on_url)
 
     def test_non_default(self):
         # non-default branches can be specified in the import URL.
@@ -1291,7 +1337,7 @@
             committer="Joe Random Hacker <joe@xxxxxxxxxxx>")
         self.foreign_commit_count += 1
 
-    def makeSourceDetails(self, branch_name, files):
+    def makeSourceDetails(self, branch_name, files, stacked_on_url=None):
         """Make Bzr `CodeImportSourceDetails` pointing at a real Bzr repo.
         """
         repository_path = self.makeTemporaryDirectory()
@@ -1303,7 +1349,8 @@
         self.foreign_commit_count = 1
 
         return self.factory.makeCodeImportSourceDetails(
-            rcstype='bzr', url=bzr_server.get_url())
+            rcstype='bzr', url=bzr_server.get_url(),
+            stacked_on_url=stacked_on_url)
 
     def test_partial(self):
         self.skip(
@@ -1441,3 +1488,88 @@
             self.old_server.stop_server()
         self.assertEqual(
             CodeImportWorkerExitCode.FAILURE_INVALID, worker.run())
+
+
+class CodeImportSourceDetailsTests(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        # Use an admin user as we aren't checking edit permissions here.
+        TestCaseWithFactory.setUp(self, 'admin@xxxxxxxxxxxxx')
+
+    def test_bzr_arguments(self):
+        code_import = self.factory.makeCodeImport(
+            bzr_branch_url="http://example.com/foo";)
+        arguments = CodeImportSourceDetails.fromCodeImport(
+            code_import).asArguments()
+        self.assertEquals([
+            str(code_import.branch.id), 'bzr', 'http://example.com/foo'],
+            arguments)
+
+    def test_hg_arguments(self):
+        code_import = self.factory.makeCodeImport(
+            hg_repo_url="http://example.com/foo";)
+        arguments = CodeImportSourceDetails.fromCodeImport(
+            code_import).asArguments()
+        self.assertEquals([
+            str(code_import.branch.id), 'hg', 'http://example.com/foo'],
+            arguments)
+
+    def test_git_arguments(self):
+        code_import = self.factory.makeCodeImport(
+                git_repo_url="git://git.example.com/project.git")
+        arguments = CodeImportSourceDetails.fromCodeImport(
+            code_import).asArguments()
+        self.assertEquals([
+            str(code_import.branch.id), 'git',
+            'git://git.example.com/project.git'],
+            arguments)
+
+    def test_cvs_arguments(self):
+        code_import = self.factory.makeCodeImport(
+            cvs_root=':pserver:foo@xxxxxxxxxxx/bar', cvs_module='bar')
+        arguments = CodeImportSourceDetails.fromCodeImport(
+            code_import).asArguments()
+        self.assertEquals([
+            str(code_import.branch.id), 'cvs',
+            ':pserver:foo@xxxxxxxxxxx/bar', 'bar'],
+            arguments)
+
+    def test_svn_arguments(self):
+        code_import = self.factory.makeCodeImport(
+                svn_branch_url='svn://svn.example.com/trunk')
+        arguments = CodeImportSourceDetails.fromCodeImport(
+            code_import).asArguments()
+        self.assertEquals([
+            str(code_import.branch.id), 'svn',
+            'svn://svn.example.com/trunk'],
+            arguments)
+
+    def test_bzr_stacked(self):
+        devfocus = self.factory.makeAnyBranch(private=False)
+        code_import = self.factory.makeCodeImport(
+                bzr_branch_url='bzr://bzr.example.com/foo',
+                target=devfocus.target)
+        code_import.branch.stacked_on = devfocus
+        details = CodeImportSourceDetails.fromCodeImport(
+            code_import)
+        self.assertEquals([
+            str(code_import.branch.id), 'bzr',
+            'bzr://bzr.example.com/foo',
+            compose_public_url('http', branch_id_alias(devfocus))],
+            details.asArguments())
+
+    def test_bzr_stacked_private(self):
+        # Code imports can't be stacked on private branches.
+        devfocus = self.factory.makeAnyBranch(private=True)
+        code_import = self.factory.makeCodeImport(
+                target=devfocus.target,
+                bzr_branch_url='bzr://bzr.example.com/foo')
+        code_import.branch.stacked_on = devfocus
+        details = CodeImportSourceDetails.fromCodeImport(
+            code_import)
+        self.assertEquals([
+            str(code_import.branch.id), 'bzr',
+            'bzr://bzr.example.com/foo'],
+            details.asArguments())

=== modified file 'lib/lp/codehosting/codeimport/worker.py'
--- lib/lp/codehosting/codeimport/worker.py	2011-10-15 01:00:53 +0000
+++ lib/lp/codehosting/codeimport/worker.py	2011-11-03 12:36:25 +0000
@@ -41,7 +41,8 @@
     )
 from bzrlib.transport import (
     do_catching_redirections,
-    get_transport,
+    get_transport_from_path,
+    get_transport_from_url,
     )
 import bzrlib.ui
 from bzrlib.upgrade import upgrade
@@ -61,6 +62,10 @@
 from canonical.config import config
 from lp.code.enums import RevisionControlSystems
 from lp.code.interfaces.branch import get_blacklisted_hostnames
+from lp.code.interfaces.codehosting import (
+    branch_id_alias,
+    compose_public_url,
+    )
 from lp.codehosting.codeimport.foreigntree import (
     CVSWorkingTree,
     SubversionWorkingTree,
@@ -150,7 +155,7 @@
         return urljoin(self.transport.base, '%08x' % db_branch_id)
 
     def pull(self, db_branch_id, target_path, required_format,
-             needs_tree=False):
+             needs_tree=False, stacked_on_url=None):
         """Pull down the Bazaar branch of an import to `target_path`.
 
         :return: A Bazaar branch for the code import corresponding to the
@@ -164,6 +169,8 @@
                 target_path, format=required_format)
             if needs_tree:
                 local_branch.bzrdir.create_workingtree()
+            if stacked_on_url:
+                local_branch.set_stacked_on_url(stacked_on_url)
             return local_branch
         # The proper thing to do here would be to call
         # "remote_bzr_dir.sprout()".  But 2a fetch slowly checks which
@@ -172,7 +179,7 @@
         # at the vfs level.
         control_dir = remote_bzr_dir.root_transport.relpath(
             remote_bzr_dir.transport.abspath('.'))
-        target = get_transport(target_path)
+        target = get_transport_from_path(target_path)
         target_control = target.clone(control_dir)
         target_control.create_prefix()
         remote_bzr_dir.transport.copy_tree_to_transport(target_control)
@@ -187,7 +194,8 @@
             local_bzr_dir.create_workingtree()
         return local_bzr_dir.open_branch()
 
-    def push(self, db_branch_id, bzr_branch, required_format):
+    def push(self, db_branch_id, bzr_branch, required_format,
+             stacked_on_url=None):
         """Push up `bzr_branch` as the Bazaar branch for `code_import`.
 
         :return: A boolean that is true if the push was non-trivial
@@ -219,6 +227,10 @@
                     upgrade_url, format=required_format)
             else:
                 old_branch = None
+        # This can be done safely, since only modern formats are used to import
+        # to.
+        if stacked_on_url is not None:
+            remote_branch.set_stacked_on_url(stacked_on_url)
         pull_result = remote_branch.pull(bzr_branch, overwrite=True)
         # Because of the way we do incremental imports, there may be revisions
         # in the branch's repo that are not in the ancestry of the branch tip.
@@ -237,7 +249,7 @@
 def get_default_bazaar_branch_store():
     """Return the default `BazaarBranchStore`."""
     return BazaarBranchStore(
-        get_transport(config.codeimport.bazaar_branch_store))
+        get_transport_from_url(config.codeimport.bazaar_branch_store))
 
 
 class CodeImportSourceDetails:
@@ -260,12 +272,13 @@
     """
 
     def __init__(self, branch_id, rcstype, url=None, cvs_root=None,
-                 cvs_module=None):
+                 cvs_module=None, stacked_on_url=None):
         self.branch_id = branch_id
         self.rcstype = rcstype
         self.url = url
         self.cvs_root = cvs_root
         self.cvs_module = cvs_module
+        self.stacked_on_url = stacked_on_url
 
     @classmethod
     def fromArguments(cls, arguments):
@@ -273,34 +286,55 @@
         branch_id = int(arguments.pop(0))
         rcstype = arguments.pop(0)
         if rcstype in ['svn', 'bzr-svn', 'git', 'hg', 'bzr']:
-            [url] = arguments
+            url = arguments.pop(0)
+            try:
+                stacked_on_url = arguments.pop(0)
+            except IndexError:
+                stacked_on_url = None
             cvs_root = cvs_module = None
         elif rcstype == 'cvs':
             url = None
+            stacked_on_url = None
             [cvs_root, cvs_module] = arguments
         else:
             raise AssertionError("Unknown rcstype %r." % rcstype)
-        return cls(branch_id, rcstype, url, cvs_root, cvs_module)
+        return cls(
+            branch_id, rcstype, url, cvs_root, cvs_module, stacked_on_url)
 
     @classmethod
     def fromCodeImport(cls, code_import):
         """Convert a `CodeImport` to an instance."""
-        branch_id = code_import.branch.id
+        branch = code_import.branch
+        if branch.stacked_on is not None and not branch.stacked_on.private:
+            stacked_path = branch_id_alias(branch.stacked_on)
+            stacked_on_url = compose_public_url('http', stacked_path)
+        else:
+            stacked_on_url = None
         if code_import.rcs_type == RevisionControlSystems.SVN:
-            return cls(branch_id, 'svn', str(code_import.url))
+            return cls(
+                branch.id, 'svn', str(code_import.url),
+                stacked_on_url=stacked_on_url)
         elif code_import.rcs_type == RevisionControlSystems.BZR_SVN:
-            return cls(branch_id, 'bzr-svn', str(code_import.url))
+            return cls(
+                branch.id, 'bzr-svn', str(code_import.url),
+                stacked_on_url=stacked_on_url)
         elif code_import.rcs_type == RevisionControlSystems.CVS:
             return cls(
-                branch_id, 'cvs',
+                branch.id, 'cvs',
                 cvs_root=str(code_import.cvs_root),
                 cvs_module=str(code_import.cvs_module))
         elif code_import.rcs_type == RevisionControlSystems.GIT:
-            return cls(branch_id, 'git', str(code_import.url))
+            return cls(
+                branch.id, 'git', str(code_import.url),
+                stacked_on_url=stacked_on_url)
         elif code_import.rcs_type == RevisionControlSystems.HG:
-            return cls(branch_id, 'hg', str(code_import.url))
+            return cls(
+                branch.id, 'hg', str(code_import.url),
+                stacked_on_url=stacked_on_url)
         elif code_import.rcs_type == RevisionControlSystems.BZR:
-            return cls(branch_id, 'bzr', str(code_import.url))
+            return cls(
+                branch.id, 'bzr', str(code_import.url),
+                stacked_on_url=stacked_on_url)
         else:
             raise AssertionError("Unknown rcstype %r." % code_import.rcs_type)
 
@@ -310,6 +344,8 @@
         result = [str(self.branch_id), self.rcstype]
         if self.rcstype in ['svn', 'bzr-svn', 'git', 'hg', 'bzr']:
             result.append(self.url)
+            if self.stacked_on_url is not None:
+                result.append(self.stacked_on_url)
         elif self.rcstype == 'cvs':
             result.append(self.cvs_root)
             result.append(self.cvs_module)
@@ -367,12 +403,12 @@
         :param filename: The name of the file to retrieve (must be a filename,
             not a path).
         :param dest_transport: The transport to retrieve the file to,
-            defaulting to ``get_transport('.')``.
+            defaulting to ``get_transport_from_path('.')``.
         :return: A boolean, true if the file was found and retrieved, false
             otherwise.
         """
         if dest_transport is None:
-            dest_transport = get_transport('.')
+            dest_transport = get_transport_from_path('.')
         remote_name = self._getRemoteName(filename)
         if self._transport.has(remote_name):
             dest_transport.put_file(
@@ -390,7 +426,7 @@
             defaulting to ``get_transport('.')``.
         """
         if source_transport is None:
-            source_transport = get_transport('.')
+            source_transport = get_transport_from_path('.')
         remote_name = self._getRemoteName(filename)
         local_file = source_transport.get(filename)
         self._transport.create_prefix()
@@ -506,7 +542,8 @@
             shutil.rmtree(self.BZR_BRANCH_PATH)
         return self.bazaar_branch_store.pull(
             self.source_details.branch_id, self.BZR_BRANCH_PATH,
-            self.required_format, self.needs_bzr_tree)
+            self.required_format, self.needs_bzr_tree,
+            stacked_on_url=self.source_details.stacked_on_url)
 
     def pushBazaarBranch(self, bazaar_branch):
         """Push the updated Bazaar branch to the server.
@@ -515,7 +552,8 @@
         """
         return self.bazaar_branch_store.push(
             self.source_details.branch_id, bazaar_branch,
-            self.required_format)
+            self.required_format,
+            stacked_on_url=self.source_details.stacked_on_url)
 
     def getWorkingDirectory(self):
         """The directory we should change to and store all scratch files in.
@@ -699,7 +737,7 @@
                     last_error = e
             else:
                 raise last_error
-        transport = get_transport(url)
+        transport = get_transport_from_url(url)
         transport, format = do_catching_redirections(find_format, transport,
             redirected)
         return format.open(transport)
@@ -733,11 +771,7 @@
                 inter_branch = InterBranch.get(remote_branch, bazaar_branch)
                 self._logger.info("Importing branch.")
                 revision_limit = self.getRevisionLimit()
-                if revision_limit is None:
-                    # bzr < 2.4 does not support InterBranch.fetch()
-                    bazaar_branch.fetch(remote_branch)
-                else:
-                    inter_branch.fetch(limit=revision_limit)
+                inter_branch.fetch(limit=revision_limit)
                 if bazaar_branch.repository.has_revision(remote_branch_tip):
                     pull_result = inter_branch.pull(overwrite=True)
                     if pull_result.old_revid != pull_result.new_revid:

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2011-10-28 06:49:00 +0000
+++ lib/lp/testing/factory.py	2011-11-03 12:36:25 +0000
@@ -477,7 +477,8 @@
         return epoch + timedelta(minutes=self.getUniqueInteger())
 
     def makeCodeImportSourceDetails(self, branch_id=None, rcstype=None,
-                                    url=None, cvs_root=None, cvs_module=None):
+                                    url=None, cvs_root=None, cvs_module=None,
+                                    stacked_on_url=None):
         if branch_id is None:
             branch_id = self.getUniqueInteger()
         if rcstype is None:
@@ -499,7 +500,8 @@
         else:
             raise AssertionError("Unknown rcstype %r." % rcstype)
         return CodeImportSourceDetails(
-            branch_id, rcstype, url, cvs_root, cvs_module)
+            branch_id, rcstype, url, cvs_root, cvs_module,
+            stacked_on_url=stacked_on_url)
 
 
 class BareLaunchpadObjectFactory(ObjectFactory):

=== modified file 'utilities/sourcedeps.cache'
--- utilities/sourcedeps.cache	2011-10-20 19:14:27 +0000
+++ utilities/sourcedeps.cache	2011-11-03 12:36:25 +0000
@@ -4,20 +4,20 @@
         "launchpad@xxxxxxxxxxxxxxxxx-20101123183213-777lz46xgagn1deg"
     ], 
     "bzr-git": [
-        266, 
-        "launchpad@xxxxxxxxxxxxxxxxx-20110920132415-uxxt8vjsfxgu7hcs"
+        269, 
+        "launchpad@xxxxxxxxxxxxxxxxx-20111102164015-iq52sc9qew0f00gz"
     ], 
     "bzr-hg": [
-        291, 
-        "launchpad@xxxxxxxxxxxxxxxxx-20110822162405-m5xrb9p3ahzddmcm"
+        292, 
+        "launchpad@xxxxxxxxxxxxxxxxx-20111011005914-kzwlk4nk5u44bntc"
     ], 
     "bzr-loom": [
         51, 
         "launchpad@xxxxxxxxxxxxxxxxx-20110921142504-p5xovac1r571h5iq"
     ], 
     "bzr-svn": [
-        2718, 
-        "launchpad@xxxxxxxxxxxxxxxxx-20110926213320-7gt1ojtyqykvxpnb"
+        2720, 
+        "launchpad@xxxxxxxxxxxxxxxxx-20111102161617-u478fn7rhbkekhbe"
     ], 
     "cscvs": [
         432, 
@@ -28,8 +28,8 @@
         "aaron@xxxxxxxxxxxxxxxx-20100715135013-uoi3q430urx9gwb8"
     ], 
     "dulwich": [
-        431, 
-        "launchpad@xxxxxxxxxxxxxxxxx-20110920121315-wcv6kpxvpetjfaq8"
+        433, 
+        "launchpad@xxxxxxxxxxxxxxxxx-20111102161814-dm6dx8d808rrg6a1"
     ], 
     "loggerhead": [
         456, 
@@ -64,8 +64,8 @@
         "launchpad@xxxxxxxxxxxxxxxxx-20110329053617-irncjfr14k0m00zp"
     ], 
     "subvertpy": [
-        2047, 
-        "launchpad@xxxxxxxxxxxxxxxxx-20110926191617-4bpor0qt8apcxw22"
+        2049, 
+        "launchpad@xxxxxxxxxxxxxxxxx-20111102161913-94jrf4rhs0x9htah"
     ], 
     "testresources": [
         16, 

=== modified file 'utilities/sourcedeps.conf'
--- utilities/sourcedeps.conf	2011-10-20 19:14:27 +0000
+++ utilities/sourcedeps.conf	2011-11-03 12:36:25 +0000
@@ -1,10 +1,10 @@
 bzr-builder lp:~launchpad-pqm/bzr-builder/trunk;revno=68
-bzr-git lp:~launchpad-pqm/bzr-git/devel;revno=266
-bzr-hg lp:~launchpad-pqm/bzr-hg/devel;revno=291
+bzr-git lp:~launchpad-pqm/bzr-git/devel;revno=269
+bzr-hg lp:~launchpad-pqm/bzr-hg/devel;revno=292
 bzr-loom lp:~launchpad-pqm/bzr-loom/trunk;revno=51
-bzr-svn lp:~launchpad-pqm/bzr-svn/devel;revno=2718
+bzr-svn lp:~launchpad-pqm/bzr-svn/devel;revno=2720
 cscvs lp:~launchpad-pqm/launchpad-cscvs/devel;revno=432
-dulwich lp:~launchpad-pqm/dulwich/devel;revno=431
+dulwich lp:~launchpad-pqm/dulwich/devel;revno=433
 difftacular lp:difftacular;revno=6
 loggerhead lp:~loggerhead-team/loggerhead/trunk-rich;revno=456
 lpreview lp:~launchpad-pqm/bzr-lpreview/devel;revno=23
@@ -12,6 +12,6 @@
 mustache.js lp:mustache.js;revno=166
 old_xmlplus lp:~launchpad-pqm/dtdparser/trunk;revno=4
 pygettextpo lp:~launchpad-pqm/pygettextpo/trunk;revno=24
-subvertpy lp:~launchpad-pqm/subvertpy/trunk;revno=2047
+subvertpy lp:~launchpad-pqm/subvertpy/trunk;revno=2049
 python-debian lp:~launchpad-pqm/python-debian/devel;revno=186
 testresources lp:~launchpad-pqm/testresources/dev;revno=16