Rev 6018: (spiv) Add a 'flush' action to BranchBuilder.build_snapshot. (Andrew in file:///home/pqm/archives/thelove/bzr/%2Btrunk/
Canonical.com Patch Queue Manager
pqm at pqm.ubuntu.com
Mon Jul 11 00:43:10 UTC 2011
At file:///home/pqm/archives/thelove/bzr/%2Btrunk/
------------------------------------------------------------
revno: 6018 [merge]
revision-id: pqm at pqm.ubuntu.com-20110711004307-81znwhxfsrz1fqx0
parent: pqm at pqm.ubuntu.com-20110709214353-wwne4f7832x0722w
parent: andrew.bennetts at canonical.com-20110708074136-tk9sa36liu14f8pj
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Mon 2011-07-11 00:43:07 +0000
message:
(spiv) Add a 'flush' action to BranchBuilder.build_snapshot. (Andrew
Bennetts)
modified:
bzrlib/branchbuilder.py branchbuilder.py-20070427022007-zlxpqz2lannhk6y8-1
bzrlib/tests/per_repository/test_fetch.py test_fetch.py-20070814052151-5cxha9slx4c93uog-1
bzrlib/tests/test_branchbuilder.py test_branchbuilder.p-20070427022007-zlxpqz2lannhk6y8-2
bzrlib/tests/test_merge.py testmerge.py-20050905070950-c1b5aa49ff911024
doc/en/release-notes/bzr-2.4.txt bzr2.4.txt-20110114053217-k7ym9jfz243fddjm-1
=== modified file 'bzrlib/branchbuilder.py'
--- a/bzrlib/branchbuilder.py 2010-12-20 11:47:15 +0000
+++ b/bzrlib/branchbuilder.py 2011-07-08 07:36:03 +0000
@@ -166,6 +166,11 @@
committer=None, timezone=None, message_callback=None):
"""Build a commit, shaped in a specific way.
+ Most of the actions are self-explanatory. 'flush' is special action to
+ break a series of actions into discrete steps so that complex changes
+ (such as unversioning a file-id and re-adding it with a different kind)
+ can be expressed in a way that will clearly work.
+
: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.
@@ -174,6 +179,7 @@
('modify', ('file-id', 'new-content'))
('unversion', 'file-id')
('rename', ('orig-path', 'new-path'))
+ ('flush', None)
:param message: An optional commit message, if not supplied, a default
commit message will be written.
:param message_callback: A message callback to use for the commit, as
@@ -208,53 +214,76 @@
# 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 = []
- to_rename = []
+ pending = _PendingActions()
for action, info in actions:
if action == 'add':
path, file_id, kind, content = info
if kind == 'directory':
- to_add_directories.append((path, file_id))
+ pending.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)
+ pending.to_add_files.append(path)
+ pending.to_add_file_ids.append(file_id)
+ pending.to_add_kinds.append(kind)
if content is not None:
- new_contents[file_id] = content
+ pending.new_contents[file_id] = content
elif action == 'modify':
file_id, content = info
- new_contents[file_id] = content
+ pending.new_contents[file_id] = content
elif action == 'unversion':
- to_unversion_ids.append(info)
+ pending.to_unversion_ids.add(info)
elif action == 'rename':
from_relpath, to_relpath = info
- to_rename.append((from_relpath, to_relpath))
+ pending.to_rename.append((from_relpath, to_relpath))
+ elif action == 'flush':
+ self._flush_pending(tree, pending)
+ pending = _PendingActions()
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)
- for from_relpath, to_relpath in to_rename:
- tree.rename_one(from_relpath, to_relpath)
- 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)
+ self._flush_pending(tree, pending)
return self._do_commit(tree, message=message, rev_id=revision_id,
timestamp=timestamp, timezone=timezone, committer=committer,
message_callback=message_callback)
finally:
tree.unlock()
+ def _flush_pending(self, tree, pending):
+ """Flush the pending actions in 'pending', i.e. apply them to 'tree'."""
+ for path, file_id in pending.to_add_directories:
+ if path == '':
+ old_id = tree.path2id(path)
+ if old_id is not None and old_id in pending.to_unversion_ids:
+ # We're overwriting this path, no need to unversion
+ pending.to_unversion_ids.discard(old_id)
+ # Special case, because the path already exists
+ tree.add([path], [file_id], ['directory'])
+ else:
+ tree.mkdir(path, file_id)
+ for from_relpath, to_relpath in pending.to_rename:
+ tree.rename_one(from_relpath, to_relpath)
+ if pending.to_unversion_ids:
+ tree.unversion(pending.to_unversion_ids)
+ tree.add(pending.to_add_files, pending.to_add_file_ids, pending.to_add_kinds)
+ for file_id, content in pending.new_contents.iteritems():
+ tree.put_file_bytes_non_atomic(file_id, content)
+
def get_branch(self):
"""Return the branch created by the builder."""
return self._branch
+
+
+class _PendingActions(object):
+ """Pending actions for build_snapshot to take.
+
+ This is just a simple class to hold a bunch of the intermediate state of
+ build_snapshot in single object.
+ """
+
+ def __init__(self):
+ self.to_add_directories = []
+ self.to_add_files = []
+ self.to_add_file_ids = []
+ self.to_add_kinds = []
+ self.new_contents = {}
+ self.to_unversion_ids = set()
+ self.to_rename = []
+
=== modified file 'bzrlib/tests/per_repository/test_fetch.py'
--- a/bzrlib/tests/per_repository/test_fetch.py 2011-05-13 12:51:16 +0000
+++ b/bzrlib/tests/per_repository/test_fetch.py 2011-07-08 07:36:03 +0000
@@ -200,6 +200,7 @@
('base', None, []),
('tip', None, [('unversion', 'my-root'),
('unversion', ROOT_ID),
+ ('flush', None),
('add', ('', 'my-root', 'directory', '')),
]),
], root_id='my-root')
@@ -228,9 +229,11 @@
# 'my-root' at root
('right', None, [('unversion', 'my-root'),
('unversion', ROOT_ID),
+ ('flush', None),
('add', ('', 'my-root', 'directory', ''))]),
('tip', ['base', 'right'], [('unversion', 'my-root'),
('unversion', ROOT_ID),
+ ('flush', None),
('add', ('', 'my-root', 'directory', '')),
]),
], root_id='my-root')
=== modified file 'bzrlib/tests/test_branchbuilder.py'
--- a/bzrlib/tests/test_branchbuilder.py 2011-04-17 23:06:22 +0000
+++ b/bzrlib/tests/test_branchbuilder.py 2011-07-08 07:36:03 +0000
@@ -247,6 +247,18 @@
(u'dir', 'dir-id', 'directory'),
(u'dir/a', 'a-id', 'file')], rev_tree)
+ def test_rename_out_of_unversioned_subdir(self):
+ builder = self.build_a_rev()
+ builder.build_snapshot('B-id', None,
+ [('add', ('dir', 'dir-id', 'directory', None)),
+ ('rename', ('a', 'dir/a'))])
+ builder.build_snapshot('C-id', None,
+ [('rename', ('dir/a', 'a')),
+ ('unversion', 'dir-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_set_parent(self):
builder = self.build_a_rev()
builder.start_series()
@@ -364,3 +376,63 @@
self.addCleanup(b.unlock)
self.assertEqual(('ghost',),
b.repository.get_graph().get_parent_map(['tip'])['tip'])
+
+ def test_unversion_root_add_new_root(self):
+ builder = BranchBuilder(self.get_transport().clone('foo'))
+ builder.start_series()
+ builder.build_snapshot('rev-1', None,
+ [('add', ('', 'TREE_ROOT', 'directory', ''))])
+ builder.build_snapshot('rev-2', None,
+ [('unversion', 'TREE_ROOT'),
+ ('add', ('', 'my-root', 'directory', ''))])
+ builder.finish_series()
+ rev_tree = builder.get_branch().repository.revision_tree('rev-2')
+ self.assertTreeShape([(u'', 'my-root', 'directory')], rev_tree)
+
+ def test_empty_flush(self):
+ """A flush with no actions before it is a no-op."""
+ builder = BranchBuilder(self.get_transport().clone('foo'))
+ builder.start_series()
+ builder.build_snapshot('rev-1', None,
+ [('add', ('', 'TREE_ROOT', 'directory', ''))])
+ builder.build_snapshot('rev-2', None, [('flush', None)])
+ builder.finish_series()
+ rev_tree = builder.get_branch().repository.revision_tree('rev-2')
+ self.assertTreeShape([(u'', 'TREE_ROOT', 'directory')], rev_tree)
+
+ def test_kind_change(self):
+ """It's possible to change the kind of an entry in a single snapshot
+ with a bit of help from the 'flush' action.
+ """
+ builder = BranchBuilder(self.get_transport().clone('foo'))
+ builder.start_series()
+ builder.build_snapshot('A-id', None,
+ [('add', (u'', 'a-root-id', 'directory', None)),
+ ('add', (u'a', 'a-id', 'file', 'content\n'))])
+ builder.build_snapshot('B-id', None,
+ [('unversion', 'a-id'),
+ ('flush', None),
+ ('add', (u'a', 'a-id', 'directory', None))])
+ builder.finish_series()
+ rev_tree = builder.get_branch().repository.revision_tree('B-id')
+ self.assertTreeShape(
+ [(u'', 'a-root-id', 'directory'), (u'a', 'a-id', 'directory')],
+ rev_tree)
+
+ def test_pivot_root(self):
+ """It's possible (albeit awkward) to move an existing dir to the root
+ in a single snapshot by using unversion then flush then add.
+ """
+ builder = BranchBuilder(self.get_transport().clone('foo'))
+ builder.start_series()
+ builder.build_snapshot('A-id', None,
+ [('add', (u'', 'orig-root', 'directory', None)),
+ ('add', (u'dir', 'dir-id', 'directory', None))])
+ builder.build_snapshot('B-id', None,
+ [('unversion', 'orig-root'), # implicitly unversions all children
+ ('flush', None),
+ ('add', (u'', 'dir-id', 'directory', None))])
+ builder.finish_series()
+ rev_tree = builder.get_branch().repository.revision_tree('B-id')
+ self.assertTreeShape([(u'', 'dir-id', 'directory')], rev_tree)
+
=== modified file 'bzrlib/tests/test_merge.py'
--- a/bzrlib/tests/test_merge.py 2011-07-07 10:04:39 +0000
+++ b/bzrlib/tests/test_merge.py 2011-07-08 07:38:03 +0000
@@ -1902,6 +1902,7 @@
builder.build_snapshot('C-id', ['A-id'], [])
builder.build_snapshot('E-id', ['C-id', 'B-id'],
[('unversion', 'a-id'),
+ ('flush', None),
('add', (u'a', 'a-id', 'directory', None))])
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
merge_obj = self.make_merge_obj(builder, 'E-id')
@@ -1925,6 +1926,7 @@
builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
builder.build_snapshot('D-id', ['B-id', 'C-id'],
[('unversion', 'a-id'),
+ ('flush', None),
('add', (u'a', 'a-id', 'directory', None))])
merge_obj = self.make_merge_obj(builder, 'E-id')
entries = list(merge_obj._entries_lca())
=== modified file 'doc/en/release-notes/bzr-2.4.txt'
--- a/doc/en/release-notes/bzr-2.4.txt 2011-07-07 15:06:49 +0000
+++ b/doc/en/release-notes/bzr-2.4.txt 2011-07-08 07:41:36 +0000
@@ -56,6 +56,13 @@
suite. This can include new facilities for writing tests, fixes to
spurious test failures and changes to the way things should be tested.
+* `BranchBuilder.build_snapshot` now supports a "flush" action. This
+ cleanly and reliably allows tests using `BranchBuilder` to construct
+ branches that e.g. rename files out of a directory and unversion that
+ directory in the same revision. Previously some changes were impossible
+ due to the order that `build_snapshot` performs its actions.
+ (Andrew Bennetts)
+
bzr 2.4b5
#########
More information about the bazaar-commits
mailing list