Rev 3590: (jam) Add BranchBuilder.build_snapshot to help with building history in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Tue Jul 29 18:04:00 BST 2008


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 3590
revision-id:pqm at pqm.ubuntu.com-20080729170343-hy20k9g6euzsz04s
parent: pqm at pqm.ubuntu.com-20080729162519-0m9oz0vbyhv14ypl
parent: john at arbash-meinel.com-20080729162837-4bcjhmvi405qp25h
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Tue 2008-07-29 18:03:43 +0100
message:
  (jam) Add BranchBuilder.build_snapshot to help with building history
  	efficiently for testing.
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/branchbuilder.py        branchbuilder.py-20070427022007-zlxpqz2lannhk6y8-1
  bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
  bzrlib/tests/test_branch.py    test_branch.py-20060116013032-97819aa07b8ab3b5
  bzrlib/tests/test_branchbuilder.py test_branchbuilder.p-20070427022007-zlxpqz2lannhk6y8-2
  bzrlib/tests/test_memorytree.py test_memorytree.py-20060906023413-4wlkalbdpsxi2r4y-3
  bzrlib/tests/test_selftest.py  test_selftest.py-20051202044319-c110a115d8c0456a
  doc/developers/HACKING.txt     HACKING-20050805200004-2a5dc975d870f78c
    ------------------------------------------------------------
    revno: 3567.4.19
    revision-id:john at arbash-meinel.com-20080729162837-4bcjhmvi405qp25h
    parent: john at arbash-meinel.com-20080729161636-aekx4mzqqf4733wl
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-29 11:28:37 -0500
    message:
      Add directions to use BranchBuilder for tests.
    modified:
      doc/developers/HACKING.txt     HACKING-20050805200004-2a5dc975d870f78c
    ------------------------------------------------------------
    revno: 3567.4.18
    revision-id:john at arbash-meinel.com-20080729161636-aekx4mzqqf4733wl
    parent: john at arbash-meinel.com-20080722204034-x54day968ipfmr1y
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-29 11:16:36 -0500
    message:
      Apply the review changes from Martin to the exact patch he approved.
    modified:
      bzrlib/branchbuilder.py        branchbuilder.py-20070427022007-zlxpqz2lannhk6y8-1
      bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
      bzrlib/tests/test_branchbuilder.py test_branchbuilder.p-20070427022007-zlxpqz2lannhk6y8-2
      bzrlib/tests/test_errors.py    test_errors.py-20060210110251-41aba2deddf936a8
      bzrlib/tests/test_selftest.py  test_selftest.py-20051202044319-c110a115d8c0456a
    ------------------------------------------------------------
    revno: 3567.4.17
    revision-id:john at arbash-meinel.com-20080722204034-x54day968ipfmr1y
    parent: john at arbash-meinel.com-20080722194840-dqp3t6mc12f2s36n
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: use_branch_builder
    timestamp: Tue 2008-07-22 15:40:34 -0500
    message:
      Add the ability to define a series of commits, which allows us to hold open the locks.
    modified:
      bzrlib/branchbuilder.py        branchbuilder.py-20070427022007-zlxpqz2lannhk6y8-1
      bzrlib/tests/test_branchbuilder.py test_branchbuilder.p-20070427022007-zlxpqz2lannhk6y8-2
    ------------------------------------------------------------
    revno: 3567.4.16
    revision-id:john at arbash-meinel.com-20080722194840-dqp3t6mc12f2s36n
    parent: john at arbash-meinel.com-20080722194501-mmxs3jkr6wqm7lj6
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: use_branch_builder
    timestamp: Tue 2008-07-22 14:48:40 -0500
    message:
      Use the new BranchBuilder api in a Branch test
    modified:
      bzrlib/tests/test_branch.py    test_branch.py-20060116013032-97819aa07b8ab3b5
    ------------------------------------------------------------
    revno: 3567.4.15
    revision-id:john at arbash-meinel.com-20080722194501-mmxs3jkr6wqm7lj6
    parent: john at arbash-meinel.com-20080722192731-6ibtoplxbb52wz43
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-22 14:45:01 -0500
    message:
      Allow setting the commit message.
    modified:
      bzrlib/branchbuilder.py        branchbuilder.py-20070427022007-zlxpqz2lannhk6y8-1
      bzrlib/tests/test_branchbuilder.py test_branchbuilder.p-20070427022007-zlxpqz2lannhk6y8-2
    ------------------------------------------------------------
    revno: 3567.4.14
    revision-id:john at arbash-meinel.com-20080722192731-6ibtoplxbb52wz43
    parent: john at arbash-meinel.com-20080722192636-rv13r9es7hh3di4r
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-22 14:27:31 -0500
    message:
      Update NEWS about the make_branch_builder test helper
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
    ------------------------------------------------------------
    revno: 3567.4.13
    revision-id:john at arbash-meinel.com-20080722192636-rv13r9es7hh3di4r
    parent: john at arbash-meinel.com-20080722192052-vc2cqzcih8j0lbct
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-22 14:26:36 -0500
    message:
      Test that make_branch_builder works on a real filesystem.
      
      Also test that we *don't* create a working tree on disk. Because we are going
      to be building things in memory anyway.
    modified:
      bzrlib/branchbuilder.py        branchbuilder.py-20070427022007-zlxpqz2lannhk6y8-1
      bzrlib/tests/test_selftest.py  test_selftest.py-20051202044319-c110a115d8c0456a
    ------------------------------------------------------------
    revno: 3567.4.12
    revision-id:john at arbash-meinel.com-20080722192052-vc2cqzcih8j0lbct
    parent: john at arbash-meinel.com-20080722190601-03b3otlcafrwpty5
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-22 14:20:52 -0500
    message:
      Expose the branch building framework to the test suite.
      
      This way you can do 'self.make_branch_builder()' in lieu of all the other
      ways we used to do so.
    modified:
      bzrlib/branchbuilder.py        branchbuilder.py-20070427022007-zlxpqz2lannhk6y8-1
      bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
      bzrlib/tests/test_selftest.py  test_selftest.py-20051202044319-c110a115d8c0456a
    ------------------------------------------------------------
    revno: 3567.4.11
    revision-id:john at arbash-meinel.com-20080722190601-03b3otlcafrwpty5
    parent: john at arbash-meinel.com-20080722185757-98rezzd05dgcfr3e
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-22 14:06:01 -0500
    message:
      Add NEWS information so people can start using the new helper
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
    ------------------------------------------------------------
    revno: 3567.4.10
    revision-id:john at arbash-meinel.com-20080722185757-98rezzd05dgcfr3e
    parent: john at arbash-meinel.com-20080722184833-b06pe6ihw4imz7rc
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-22 13:57:57 -0500
    message:
      Clean up the build_snapshot api a bit.
      
      Move the parent_ids to the second parameter, because it makes more sense there.
      Document the function parameters and return value.
      Factor out moving the branch tip, to help clean up the function.
    modified:
      bzrlib/branchbuilder.py        branchbuilder.py-20070427022007-zlxpqz2lannhk6y8-1
      bzrlib/tests/test_branchbuilder.py test_branchbuilder.p-20070427022007-zlxpqz2lannhk6y8-2
    ------------------------------------------------------------
    revno: 3567.4.9
    revision-id:john at arbash-meinel.com-20080722184833-b06pe6ihw4imz7rc
    parent: john at arbash-meinel.com-20080722184724-as3agicle15r1szy
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-22 13:48:33 -0500
    message:
      comments
    modified:
      bzrlib/branchbuilder.py        branchbuilder.py-20070427022007-zlxpqz2lannhk6y8-1
    ------------------------------------------------------------
    revno: 3567.4.8
    revision-id:john at arbash-meinel.com-20080722184724-as3agicle15r1szy
    parent: john at arbash-meinel.com-20080722181225-nx1dwfmb4wky600x
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-22 13:47:24 -0500
    message:
      Add the ability to force a basis for a revision.
      
      This includes the ability to merge in another tree, though it won't
      actually *do* the merge under the covers.
      You have to manually set what you want the texts to be like.
    modified:
      bzrlib/branchbuilder.py        branchbuilder.py-20070427022007-zlxpqz2lannhk6y8-1
      bzrlib/tests/test_branchbuilder.py test_branchbuilder.p-20070427022007-zlxpqz2lannhk6y8-2
    ------------------------------------------------------------
    revno: 3567.4.7
    revision-id:john at arbash-meinel.com-20080722181225-nx1dwfmb4wky600x
    parent: john at arbash-meinel.com-20080722180140-oesgtrbjpyd8dzik
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-22 13:12:25 -0500
    message:
      Revert back to using MemoryTree.mkdir() rather than creating the directory during add().
      
      This complicates the api a bit, because we have to use multiple passes,
      but at least we don't change any tested behavior.
    modified:
      bzrlib/branchbuilder.py        branchbuilder.py-20070427022007-zlxpqz2lannhk6y8-1
      bzrlib/memorytree.py           memorytree.py-20060906023413-4wlkalbdpsxi2r4y-1
      bzrlib/tests/test_branchbuilder.py test_branchbuilder.p-20070427022007-zlxpqz2lannhk6y8-2
      bzrlib/tests/test_memorytree.py test_memorytree.py-20060906023413-4wlkalbdpsxi2r4y-3
    ------------------------------------------------------------
    revno: 3567.4.6
    revision-id:john at arbash-meinel.com-20080722180140-oesgtrbjpyd8dzik
    parent: john at arbash-meinel.com-20080722175957-9gz0my3cvanggos8
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-22 13:01:40 -0500
    message:
      unversioning a directory is recursive.
    modified:
      bzrlib/tests/test_branchbuilder.py test_branchbuilder.p-20070427022007-zlxpqz2lannhk6y8-2
    ------------------------------------------------------------
    revno: 3567.4.5
    revision-id:john at arbash-meinel.com-20080722175957-9gz0my3cvanggos8
    parent: john at arbash-meinel.com-20080722173559-3o5pf3v21qlghppy
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-22 12:59:57 -0500
    message:
      MemoryTree.add(directory) will now create a directory node in the Transport
    modified:
      bzrlib/memorytree.py           memorytree.py-20060906023413-4wlkalbdpsxi2r4y-1
      bzrlib/tests/test_branchbuilder.py test_branchbuilder.p-20070427022007-zlxpqz2lannhk6y8-2
      bzrlib/tests/test_memorytree.py test_memorytree.py-20060906023413-4wlkalbdpsxi2r4y-3
    ------------------------------------------------------------
    revno: 3567.4.4
    revision-id:john at arbash-meinel.com-20080722173559-3o5pf3v21qlghppy
    parent: john at arbash-meinel.com-20080722171545-51t59bf7uij8aftx
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-22 12:35:59 -0500
    message:
      Add the ability to 'unversion' files, and handle unknown actions.
      
      We use 'unversion' because memory_tree doesn't have 'remove', and I don't feel like
      implementing it.
    modified:
      bzrlib/branchbuilder.py        branchbuilder.py-20070427022007-zlxpqz2lannhk6y8-1
      bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
      bzrlib/tests/test_branchbuilder.py test_branchbuilder.p-20070427022007-zlxpqz2lannhk6y8-2
      bzrlib/tests/test_errors.py    test_errors.py-20060210110251-41aba2deddf936a8
    ------------------------------------------------------------
    revno: 3567.4.3
    revision-id:john at arbash-meinel.com-20080722171545-51t59bf7uij8aftx
    parent: john at arbash-meinel.com-20080722170741-yy0w48dac09xp1lz
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-22 12:15:45 -0500
    message:
      Add an action for modifying an existing file
    modified:
      bzrlib/branchbuilder.py        branchbuilder.py-20070427022007-zlxpqz2lannhk6y8-1
      bzrlib/tests/test_branchbuilder.py test_branchbuilder.p-20070427022007-zlxpqz2lannhk6y8-2
    ------------------------------------------------------------
    revno: 3567.4.2
    revision-id:john at arbash-meinel.com-20080722170741-yy0w48dac09xp1lz
    parent: john at arbash-meinel.com-20080722170355-00mcj4j1a8pdsout
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-22 12:07:41 -0500
    message:
      test that we can add more files into an existing build
    modified:
      bzrlib/tests/test_branchbuilder.py test_branchbuilder.p-20070427022007-zlxpqz2lannhk6y8-2
    ------------------------------------------------------------
    revno: 3567.4.1
    revision-id:john at arbash-meinel.com-20080722170355-00mcj4j1a8pdsout
    parent: pqm at pqm.ubuntu.com-20080721151553-11iasd1407hkznk1
    committer: John Arbash Meinel <john at arbash-meinel.com>
    branch nick: branch_builder
    timestamp: Tue 2008-07-22 12:03:55 -0500
    message:
      Initial work to have BranchBuilder allow us to do tree-shape work.
    modified:
      bzrlib/branchbuilder.py        branchbuilder.py-20070427022007-zlxpqz2lannhk6y8-1
      bzrlib/tests/test_branchbuilder.py test_branchbuilder.p-20070427022007-zlxpqz2lannhk6y8-2
=== modified file 'NEWS'
--- a/NEWS	2008-07-29 15:51:45 +0000
+++ b/NEWS	2008-07-29 17:03:43 +0000
@@ -87,6 +87,13 @@
     * RuleSearchers return () instead of [] now when there are no matches.
       (Ian Clatworthy)
 
+    * ``bzrlib.branchbuilder.BranchBuilder`` is now much more capable of
+      putting together a real history without having to create a full
+      WorkingTree. It is recommended that tests that are not directly
+      testing the WorkingTree use BranchBuilder instead.  See
+      ``BranchBuilder.build_snapshot`` or
+      ``TestCaseWithMemoryTree.make_branch_builder``.  (John Arbash Meinel)
+
 
 bzr 1.6beta3 2008-07-17
 -----------------------

=== modified file 'bzrlib/branchbuilder.py'
--- a/bzrlib/branchbuilder.py	2007-04-27 06:16:29 +0000
+++ b/bzrlib/branchbuilder.py	2008-07-29 16:16:36 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2007 Canonical Ltd
+# Copyright (C) 2007, 2008 Canonical Ltd
 #
 # 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
@@ -26,12 +26,24 @@
     BranchBuilder on the transport you want your branch on, and then call
     appropriate build_ methods on it to get the shape of history you want.
 
+    This is meant as a helper for the test suite, not as a general class for
+    real data.
+
     For instance:
       builder = BranchBuilder(self.get_transport().clone('relpath'))
-      builder.build_commit()
-      builder.build_commit()
-      builder.build_commit()
+      builder.start_series()
+      builder.build_snapshot('rev-id', [],
+        [('add', ('filename', 'f-id', 'file', 'content\n'))])
+      builder.build_snapshot('rev2-id', ['rev-id'],
+        [('modify', ('f-id', 'new-content\n'))])
+      builder.finish_series()
       branch = builder.get_branch()
+
+    :ivar _tree: This is a private member which is not meant to be modified by
+        users of this class. While a 'series' is in progress, it should hold a
+        MemoryTree with the contents of the last commit (ready to be modified
+        by the next build_snapshot command) with a held write lock. Outside of
+        a series in progress, it should be None.
     """
 
     def __init__(self, transport, format=None):
@@ -40,15 +52,18 @@
         :param transport: The transport the branch should be created on.
             If the path of the transport does not exist but its parent does
             it will be created.
-        :param format: The name of a format in the bzrdir format registry
-            for the branch to be built.
+        :param format: Either a BzrDirFormat, or the name of a format in the
+            bzrdir format registry for the branch to be built.
         """
         if not transport.has('.'):
             transport.mkdir('.')
         if format is None:
             format = 'default'
+        if isinstance(format, str):
+            format = bzrdir.format_registry.make_bzrdir(format)
         self._branch = bzrdir.BzrDir.create_branch_convenience(transport.base,
-            format=bzrdir.format_registry.make_bzrdir(format))
+            format=format, force_new_tree=False)
+        self._tree = None
 
     def build_commit(self):
         """Build a commit on the branch."""
@@ -60,6 +75,128 @@
         finally:
             tree.unlock()
 
+    def _move_branch_pointer(self, new_revision_id):
+        """Point self._branch to a different revision id."""
+        self._branch.lock_write()
+        try:
+            # We don't seem to have a simple set_last_revision(), so we
+            # implement it here.
+            cur_revno, cur_revision_id = self._branch.last_revision_info()
+            g = self._branch.repository.get_graph()
+            new_revno = g.find_distance_to_null(new_revision_id,
+                                                [(cur_revision_id, cur_revno)])
+            self._branch.set_last_revision_info(new_revno, new_revision_id)
+        finally:
+            self._branch.unlock()
+        if self._tree is not None:
+            # We are currently processing a series, but when switching branch
+            # pointers, it is easiest to just create a new memory tree.
+            # That way we are sure to have the right files-on-disk
+            # We are cheating a little bit here, and locking the new tree
+            # before the old tree is unlocked. But that way the branch stays
+            # locked throughout.
+            new_tree = memorytree.MemoryTree.create_on_branch(self._branch)
+            new_tree.lock_write()
+            self._tree.unlock()
+            self._tree = new_tree
+
+    def start_series(self):
+        """We will be creating a series of commits.
+
+        This allows us to hold open the locks while we are processing.
+
+        Make sure to call 'finish_series' when you are done.
+        """
+        if self._tree is not None:
+            raise AssertionError('You cannot start a new series while a'
+                                 ' series is already going.')
+        self._tree = memorytree.MemoryTree.create_on_branch(self._branch)
+        self._tree.lock_write()
+
+    def finish_series(self):
+        """Call this after start_series to unlock the various objects."""
+        self._tree.unlock()
+        self._tree = None
+
+    def build_snapshot(self, revision_id, parent_ids, actions,
+                       message=None):
+        """Build a commit, shaped in a specific way.
+
+        :param revision_id: The handle for the new commit, can be None
+        :param parent_ids: A list of parent_ids to use for the commit.
+            It can be None, which indicates to use the last commit.
+        :param actions: A list of actions to perform. Supported actions are:
+            ('add', ('path', 'file-id', 'kind', 'content' or None))
+            ('modify', ('file-id', 'new-content'))
+            ('unversion', 'file-id')
+            # not supported yet: ('rename', ('orig-path', 'new-path'))
+        :param message: An optional commit message, if not supplied, a default
+            commit message will be written.
+        :return: The revision_id of the new commit
+        """
+        if parent_ids is not None:
+            base_id = parent_ids[0]
+            if base_id != self._branch.last_revision():
+                self._move_branch_pointer(base_id)
+
+        if self._tree is not None:
+            tree = self._tree
+        else:
+            tree = memorytree.MemoryTree.create_on_branch(self._branch)
+        tree.lock_write()
+        try:
+            if parent_ids is not None:
+                tree.set_parent_ids(parent_ids)
+            # Unfortunately, MemoryTree.add(directory) just creates an
+            # inventory entry. And the only public function to create a
+            # directory is MemoryTree.mkdir() which creates the directory, but
+            # also always adds it. So we have to use a multi-pass setup.
+            to_add_directories = []
+            to_add_files = []
+            to_add_file_ids = []
+            to_add_kinds = []
+            new_contents = {}
+            to_unversion_ids = []
+            # TODO: MemoryTree doesn't support rename() or
+            #       apply_inventory_delta, so we'll postpone allowing renames
+            #       for now
+            # to_rename = []
+            for action, info in actions:
+                if action == 'add':
+                    path, file_id, kind, content = info
+                    if kind == 'directory':
+                        to_add_directories.append((path, file_id))
+                    else:
+                        to_add_files.append(path)
+                        to_add_file_ids.append(file_id)
+                        to_add_kinds.append(kind)
+                        if content is not None:
+                            new_contents[file_id] = content
+                elif action == 'modify':
+                    file_id, content = info
+                    new_contents[file_id] = content
+                elif action == 'unversion':
+                    to_unversion_ids.append(info)
+                else:
+                    raise ValueError('Unknown build action: "%s"' % (action,))
+            if to_unversion_ids:
+                tree.unversion(to_unversion_ids)
+            for path, file_id in to_add_directories:
+                if path == '':
+                    # Special case, because the path already exists
+                    tree.add([path], [file_id], ['directory'])
+                else:
+                    tree.mkdir(path, file_id)
+            tree.add(to_add_files, to_add_file_ids, to_add_kinds)
+            for file_id, content in new_contents.iteritems():
+                tree.put_file_bytes_non_atomic(file_id, content)
+
+            if message is None:
+                message = u'commit %d' % (self._branch.revno() + 1,)
+            return tree.commit(message, rev_id=revision_id)
+        finally:
+            tree.unlock()
+
     def get_branch(self):
         """Return the branch created by the builder."""
         return self._branch

=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py	2008-07-21 08:58:33 +0000
+++ b/bzrlib/tests/__init__.py	2008-07-22 19:20:52 +0000
@@ -49,6 +49,7 @@
 
 
 from bzrlib import (
+    branchbuilder,
     bzrdir,
     debug,
     errors,
@@ -1993,6 +1994,11 @@
         b = self.make_branch(relpath, format=format)
         return memorytree.MemoryTree.create_on_branch(b)
 
+    def make_branch_builder(self, relpath, format=None):
+        url = self.get_url(relpath)
+        tran = get_transport(url)
+        return branchbuilder.BranchBuilder(get_transport(url), format=format)
+
     def overrideEnvironmentForTesting(self):
         os.environ['HOME'] = self.test_home_dir
         os.environ['BZR_HOME'] = self.test_home_dir

=== modified file 'bzrlib/tests/test_branch.py'
--- a/bzrlib/tests/test_branch.py	2008-07-16 07:30:18 +0000
+++ b/bzrlib/tests/test_branch.py	2008-07-22 19:48:40 +0000
@@ -243,19 +243,18 @@
         self.assertEqual('ftp://bazaar-vcs.org', branch.get_bound_location())
 
     def test_set_revision_history(self):
-        tree = self.make_branch_and_memory_tree('.',
-            format=self.get_format_name())
-        tree.lock_write()
-        try:
-            tree.add('.')
-            tree.commit('foo', rev_id='foo')
-            tree.commit('bar', rev_id='bar')
-            tree.branch.set_revision_history(['foo', 'bar'])
-            tree.branch.set_revision_history(['foo'])
-            self.assertRaises(errors.NotLefthandHistory,
-                              tree.branch.set_revision_history, ['bar'])
-        finally:
-            tree.unlock()
+        builder = self.make_branch_builder('.', format=self.get_format_name())
+        builder.build_snapshot('foo', None,
+            [('add', ('', None, 'directory', None))],
+            message='foo')
+        builder.build_snapshot('bar', None, [], message='bar')
+        branch = builder.get_branch()
+        branch.lock_write()
+        self.addCleanup(branch.unlock)
+        branch.set_revision_history(['foo', 'bar'])
+        branch.set_revision_history(['foo'])
+        self.assertRaises(errors.NotLefthandHistory,
+                          branch.set_revision_history, ['bar'])
 
     def do_checkout_test(self, lightweight=False):
         tree = self.make_branch_and_tree('source',

=== modified file 'bzrlib/tests/test_branchbuilder.py'
--- a/bzrlib/tests/test_branchbuilder.py	2007-04-27 06:16:29 +0000
+++ b/bzrlib/tests/test_branchbuilder.py	2008-07-29 16:16:36 +0000
@@ -18,6 +18,7 @@
 
 from bzrlib import (
     branch as _mod_branch,
+    errors,
     revision as _mod_revision,
     tests,
     )
@@ -71,3 +72,218 @@
         self.assertEqual(
             [rev_id1],
             branch.repository.get_revision(branch.last_revision()).parent_ids)
+
+
+class TestBranchBuilderBuildSnapshot(tests.TestCaseWithMemoryTransport):
+
+    def assertTreeShape(self, expected_shape, tree):
+        """Check that the tree shape matches expectations."""
+        tree.lock_read()
+        try:
+            entries = [(path, ie.file_id, ie.kind)
+                       for path, ie in tree.iter_entries_by_dir()]
+        finally:
+            tree.unlock()
+        self.assertEqual(expected_shape, entries)
+
+    def build_a_rev(self):
+        builder = BranchBuilder(self.get_transport().clone('foo'))
+        rev_id1 = builder.build_snapshot('A-id', None,
+            [('add', ('', 'a-root-id', 'directory', None)),
+             ('add', ('a', 'a-id', 'file', 'contents'))])
+        self.assertEqual('A-id', rev_id1)
+        return builder
+
+    def test_add_one_file(self):
+        builder = self.build_a_rev()
+        branch = builder.get_branch()
+        self.assertEqual((1, 'A-id'), branch.last_revision_info())
+        rev_tree = branch.repository.revision_tree('A-id')
+        rev_tree.lock_read()
+        self.addCleanup(rev_tree.unlock)
+        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
+                              (u'a', 'a-id', 'file')], rev_tree)
+        self.assertEqual('contents', rev_tree.get_file_text('a-id'))
+
+    def test_add_second_file(self):
+        builder = self.build_a_rev()
+        rev_id2 = builder.build_snapshot('B-id', None,
+            [('add', ('b', 'b-id', 'file', 'content_b'))])
+        self.assertEqual('B-id', rev_id2)
+        branch = builder.get_branch()
+        self.assertEqual((2, rev_id2), branch.last_revision_info())
+        rev_tree = branch.repository.revision_tree(rev_id2)
+        rev_tree.lock_read()
+        self.addCleanup(rev_tree.unlock)
+        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
+                              (u'a', 'a-id', 'file'),
+                              (u'b', 'b-id', 'file')], rev_tree)
+        self.assertEqual('content_b', rev_tree.get_file_text('b-id'))
+
+    def test_add_empty_dir(self):
+        builder = self.build_a_rev()
+        rev_id2 = builder.build_snapshot('B-id', None,
+            [('add', ('b', 'b-id', 'directory', None))])
+        rev_tree = builder.get_branch().repository.revision_tree('B-id')
+        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
+                              (u'a', 'a-id', 'file'),
+                              (u'b', 'b-id', 'directory'),
+                             ], rev_tree)
+
+    def test_commit_message_default(self):
+        builder = BranchBuilder(self.get_transport().clone('foo'))
+        rev_id = builder.build_snapshot(None, None,
+            [('add', (u'', None, 'directory', None))])
+        branch = builder.get_branch()
+        rev = branch.repository.get_revision(rev_id)
+        self.assertEqual(u'commit 1', rev.message)
+
+    def test_commit_message_supplied(self):
+        builder = BranchBuilder(self.get_transport().clone('foo'))
+        rev_id = builder.build_snapshot(None, None,
+            [('add', (u'', None, 'directory', None))],
+            message=u'Foo')
+        branch = builder.get_branch()
+        rev = branch.repository.get_revision(rev_id)
+        self.assertEqual(u'Foo', rev.message)
+
+    def test_modify_file(self):
+        builder = self.build_a_rev()
+        rev_id2 = builder.build_snapshot('B-id', None,
+            [('modify', ('a-id', 'new\ncontent\n'))])
+        self.assertEqual('B-id', rev_id2)
+        branch = builder.get_branch()
+        rev_tree = branch.repository.revision_tree(rev_id2)
+        rev_tree.lock_read()
+        self.addCleanup(rev_tree.unlock)
+        self.assertEqual('new\ncontent\n', rev_tree.get_file_text('a-id'))
+
+    def test_delete_file(self):
+        builder = self.build_a_rev()
+        rev_id2 = builder.build_snapshot('B-id', None,
+            [('unversion', 'a-id')])
+        self.assertEqual('B-id', rev_id2)
+        branch = builder.get_branch()
+        rev_tree = branch.repository.revision_tree(rev_id2)
+        rev_tree.lock_read()
+        self.addCleanup(rev_tree.unlock)
+        self.assertTreeShape([(u'', 'a-root-id', 'directory')], rev_tree)
+
+    def test_delete_directory(self):
+        builder = self.build_a_rev()
+        rev_id2 = builder.build_snapshot('B-id', None,
+            [('add', ('b', 'b-id', 'directory', None)),
+             ('add', ('b/c', 'c-id', 'file', 'foo\n')),
+             ('add', ('b/d', 'd-id', 'directory', None)),
+             ('add', ('b/d/e', 'e-id', 'file', 'eff\n')),
+            ])
+        rev_tree = builder.get_branch().repository.revision_tree('B-id')
+        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
+                              (u'a', 'a-id', 'file'),
+                              (u'b', 'b-id', 'directory'),
+                              (u'b/c', 'c-id', 'file'),
+                              (u'b/d', 'd-id', 'directory'),
+                              (u'b/d/e', 'e-id', 'file')], rev_tree)
+        # Removing a directory removes all child dirs
+        builder.build_snapshot('C-id', None, [('unversion', 'b-id')])
+        rev_tree = builder.get_branch().repository.revision_tree('C-id')
+        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
+                              (u'a', 'a-id', 'file'),
+                             ], rev_tree)
+
+    def test_unknown_action(self):
+        builder = self.build_a_rev()
+        e = self.assertRaises(ValueError,
+            builder.build_snapshot, 'B-id', None, [('weirdo', ('foo',))])
+        self.assertEqual('Unknown build action: "weirdo"', str(e))
+
+    # TODO: rename a file/directory, but rename isn't supported by the
+    #       MemoryTree api yet, so for now we wait until it is used
+
+    def test_set_parent(self):
+        builder = self.build_a_rev()
+        builder.start_series()
+        self.addCleanup(builder.finish_series)
+        builder.build_snapshot('B-id', ['A-id'],
+            [('modify', ('a-id', 'new\ncontent\n'))])
+        builder.build_snapshot('C-id', ['A-id'], 
+            [('add', ('c', 'c-id', 'file', 'alt\ncontent\n'))])
+        # We should now have a graph:
+        #   A
+        #   |\
+        #   C B
+        # And not A => B => C
+        repo = builder.get_branch().repository
+        self.assertEqual({'B-id': ('A-id',), 'C-id': ('A-id',)},
+                         repo.get_parent_map(['B-id', 'C-id']))
+        b_tree = repo.revision_tree('B-id')
+        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
+                              (u'a', 'a-id', 'file'),
+                             ], b_tree)
+        self.assertEqual('new\ncontent\n', b_tree.get_file_text('a-id'))
+
+        # We should still be using the content from A in C, not from B
+        c_tree = repo.revision_tree('C-id')
+        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
+                              (u'a', 'a-id', 'file'),
+                              (u'c', 'c-id', 'file'),
+                             ], c_tree)
+        self.assertEqual('contents', c_tree.get_file_text('a-id'))
+        self.assertEqual('alt\ncontent\n', c_tree.get_file_text('c-id'))
+
+    def test_set_merge_parent(self):
+        builder = self.build_a_rev()
+        builder.start_series()
+        self.addCleanup(builder.finish_series)
+        builder.build_snapshot('B-id', ['A-id'],
+            [('add', ('b', 'b-id', 'file', 'b\ncontent\n'))])
+        builder.build_snapshot('C-id', ['A-id'],
+            [('add', ('c', 'c-id', 'file', 'alt\ncontent\n'))])
+        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
+        repo = builder.get_branch().repository
+        self.assertEqual({'B-id': ('A-id',), 'C-id': ('A-id',),
+                          'D-id': ('B-id', 'C-id')},
+                         repo.get_parent_map(['B-id', 'C-id', 'D-id']))
+        d_tree = repo.revision_tree('D-id')
+        # Note: by default a merge node does *not* pull in the changes from the
+        #       merged tree, you have to supply it yourself.
+        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
+                              (u'a', 'a-id', 'file'),
+                              (u'b', 'b-id', 'file'),
+                             ], d_tree)
+
+    def test_set_merge_parent_and_contents(self):
+        builder = self.build_a_rev()
+        builder.start_series()
+        self.addCleanup(builder.finish_series)
+        builder.build_snapshot('B-id', ['A-id'],
+            [('add', ('b', 'b-id', 'file', 'b\ncontent\n'))])
+        builder.build_snapshot('C-id', ['A-id'],
+            [('add', ('c', 'c-id', 'file', 'alt\ncontent\n'))])
+        builder.build_snapshot('D-id', ['B-id', 'C-id'],
+            [('add', ('c', 'c-id', 'file', 'alt\ncontent\n'))])
+        repo = builder.get_branch().repository
+        self.assertEqual({'B-id': ('A-id',), 'C-id': ('A-id',),
+                          'D-id': ('B-id', 'C-id')},
+                         repo.get_parent_map(['B-id', 'C-id', 'D-id']))
+        d_tree = repo.revision_tree('D-id')
+        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
+                              (u'a', 'a-id', 'file'),
+                              (u'b', 'b-id', 'file'),
+                              (u'c', 'c-id', 'file'),
+                             ], d_tree)
+        # Because we copied the exact text into *this* tree, the 'c' file
+        # should look like it was not modified in the merge
+        self.assertEqual('C-id', d_tree.inventory['c-id'].revision)
+
+    def test_start_finish_series(self):
+        builder = BranchBuilder(self.get_transport().clone('foo'))
+        builder.start_series()
+        try:
+            self.assertIsNot(None, builder._tree)
+            self.assertEqual('w', builder._tree._lock_mode)
+            self.assertTrue(builder._branch.is_locked())
+        finally:
+            builder.finish_series()
+        self.assertIs(None, builder._tree)
+        self.assertFalse(builder._branch.is_locked())

=== modified file 'bzrlib/tests/test_memorytree.py'
--- a/bzrlib/tests/test_memorytree.py	2007-11-21 23:42:50 +0000
+++ b/bzrlib/tests/test_memorytree.py	2008-07-22 18:12:25 +0000
@@ -125,6 +125,22 @@
         self.assertEqual('barshoom', tree.get_file('foo-id').read())
         tree.unlock()
 
+    def test_add_in_subdir(self):
+        branch = self.make_branch('branch')
+        tree = MemoryTree.create_on_branch(branch)
+        tree.lock_write()
+        self.addCleanup(tree.unlock)
+        tree.add([''], ['root-id'], ['directory'])
+        # Unfortunately, the only way to 'mkdir' is to call 'tree.mkdir', but
+        # that *always* adds the directory as well. So if you want to create a
+        # file in a subdirectory, you have to split out the 'mkdir()' calls
+        # from the add and put_file_bytes_non_atomic calls. :(
+        tree.mkdir('adir', 'dir-id')
+        tree.add(['adir/afile'], ['file-id'], ['file'])
+        self.assertEqual('adir/afile', tree.id2path('file-id'))
+        self.assertEqual('adir', tree.id2path('dir-id'))
+        tree.put_file_bytes_non_atomic('file-id', 'barshoom')
+
     def test_commit_trivial(self):
         """Smoke test for commit on a MemoryTree.
 

=== modified file 'bzrlib/tests/test_selftest.py'
--- a/bzrlib/tests/test_selftest.py	2008-07-17 03:06:11 +0000
+++ b/bzrlib/tests/test_selftest.py	2008-07-29 16:16:36 +0000
@@ -26,6 +26,7 @@
 
 import bzrlib
 from bzrlib import (
+    branchbuilder,
     bzrdir,
     errors,
     memorytree,
@@ -547,6 +548,43 @@
         self.assertEqual(format.repository_format.__class__,
             tree.branch.repository._format.__class__)
 
+    def test_make_branch_builder(self):
+        builder = self.make_branch_builder('dir')
+        self.assertIsInstance(builder, branchbuilder.BranchBuilder)
+        # Guard against regression into MemoryTransport leaking
+        # files to disk instead of keeping them in memory.
+        self.failIf(osutils.lexists('dir'))
+
+    def test_make_branch_builder_with_format(self):
+        # Use a repo layout that doesn't conform to a 'named' layout, to ensure
+        # that the format objects are used.
+        format = bzrdir.BzrDirMetaFormat1()
+        repo_format = weaverepo.RepositoryFormat7()
+        format.repository_format = repo_format
+        builder = self.make_branch_builder('dir', format=format)
+        the_branch = builder.get_branch()
+        # Guard against regression into MemoryTransport leaking
+        # files to disk instead of keeping them in memory.
+        self.failIf(osutils.lexists('dir'))
+        self.assertEqual(format.repository_format.__class__,
+                         the_branch.repository._format.__class__)
+        self.assertEqual(repo_format.get_format_string(),
+                         self.get_transport().get_bytes(
+                            'dir/.bzr/repository/format'))
+
+    def test_make_branch_builder_with_format_name(self):
+        builder = self.make_branch_builder('dir', format='knit')
+        the_branch = builder.get_branch()
+        # Guard against regression into MemoryTransport leaking
+        # files to disk instead of keeping them in memory.
+        self.failIf(osutils.lexists('dir'))
+        dir_format = bzrdir.format_registry.make_bzrdir('knit')
+        self.assertEqual(dir_format.repository_format.__class__,
+                         the_branch.repository._format.__class__)
+        self.assertEqual('Bazaar-NG Knit Repository Format 1',
+                         self.get_transport().get_bytes(
+                            'dir/.bzr/repository/format'))
+
     def test_safety_net(self):
         """No test should modify the safety .bzr directory.
 
@@ -605,6 +643,18 @@
         self.assertRaises(AssertionError, self.assertIsDirectory, 'a_file', t)
         self.assertRaises(AssertionError, self.assertIsDirectory, 'not_here', t)
 
+    def test_make_branch_builder(self):
+        builder = self.make_branch_builder('dir')
+        rev_id = builder.build_commit()
+        self.failUnlessExists('dir')
+        a_dir = bzrdir.BzrDir.open('dir')
+        self.assertRaises(errors.NoWorkingTree, a_dir.open_workingtree)
+        a_branch = a_dir.open_branch()
+        builder_branch = builder.get_branch()
+        self.assertEqual(a_branch.base, builder_branch.base)
+        self.assertEqual((1, rev_id), builder_branch.last_revision_info())
+        self.assertEqual((1, rev_id), a_branch.last_revision_info())
+
 
 class TestTestCaseTransports(TestCaseWithTransport):
 

=== modified file 'doc/developers/HACKING.txt'
--- a/doc/developers/HACKING.txt	2008-07-29 09:00:37 +0000
+++ b/doc/developers/HACKING.txt	2008-07-29 17:03:43 +0000
@@ -406,6 +406,17 @@
     subprocess you can use ``run_bzr_subprocess``. By default the spawned
     process will not load plugins unless ``--allow-plugins`` is supplied.
 
+When writing library functionality, it is often necessary to set up a
+branch with a certain history. Most current tests do this by inheriting
+from ``TestCaseWithTransport`` and using the ``make_branch_and_tree``
+helper to give them a ``WorkingTree`` that they can commit to. However,
+there is a newer api available from ``TestCaseWithMemoryTransport`` using
+the ``make_branch_builder`` helper. This helper is preferred, because it
+can build the changes in memory, rather than on disk. Tests that are
+explictly testing how we work with disk objects should, of course, use a
+real ``WorkingTree``. See ``bzrlib/branch_builder.py`` for how to use the
+class.
+
 
 Doctests
 --------




More information about the bazaar-commits mailing list