Rev 2795: (robertc) Deprecated find_previous_heads and add Graph.heads detangling some of the inventory related commit code. (Robert Collins) in file:///home/pqm/archives/thelove/bzr/%2Btrunk/
Canonical.com Patch Queue Manager
pqm at pqm.ubuntu.com
Wed Sep 5 01:17:01 BST 2007
At file:///home/pqm/archives/thelove/bzr/%2Btrunk/
------------------------------------------------------------
revno: 2795
revision-id: pqm at pqm.ubuntu.com-20070905001648-0iigag4tq1u8mywn
parent: pqm at pqm.ubuntu.com-20070904035759-iv4xl6d7ez69txba
parent: robertc at robertcollins.net-20070904041207-zb3l8hfco0sp6hu8
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Wed 2007-09-05 01:16:48 +0100
message:
(robertc) Deprecated find_previous_heads and add Graph.heads detangling some of the inventory related commit code. (Robert Collins)
modified:
NEWS NEWS-20050323055033-4e00b5db738777ff
bzrlib/bzrdir.py bzrdir.py-20060131065624-156dfea39c4387cb
bzrlib/graph.py graph_walker.py-20070525030359-y852guab65d4wtn0-1
bzrlib/inventory.py inventory.py-20050309040759-6648b84ca2005b37
bzrlib/repository.py rev_storage.py-20051111201905-119e9401e46257e3
bzrlib/tests/__init__.py selftest.py-20050531073622-8d0e3c8845c97a64
bzrlib/tests/repository_implementations/test_commit_builder.py test_commit_builder.py-20060606110838-76e3ra5slucqus81-1
bzrlib/tests/test_graph.py test_graph_walker.py-20070525030405-enq4r60hhi9xrujc-1
bzrlib/tests/tree_implementations/test_inv.py test_inv.py-20070312023226-0cdvk5uwhutis9vg-1
------------------------------------------------------------
revno: 2776.1.6
merged: robertc at robertcollins.net-20070904041207-zb3l8hfco0sp6hu8
parent: robertc at robertcollins.net-20070904035307-y5jacs7zl15v2nvo
parent: pqm at pqm.ubuntu.com-20070904035759-iv4xl6d7ez69txba
committer: Robert Collins <robertc at robertcollins.net>
branch nick: commit
timestamp: Tue 2007-09-04 14:12:07 +1000
message:
Merge bzr.dev.
------------------------------------------------------------
revno: 2776.1.5
merged: robertc at robertcollins.net-20070904035307-y5jacs7zl15v2nvo
parent: robertc at robertcollins.net-20070903221720-9k1y25x0x60rk38b
committer: Robert Collins <robertc at robertcollins.net>
branch nick: commit
timestamp: Tue 2007-09-04 13:53:07 +1000
message:
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
------------------------------------------------------------
revno: 2776.1.4
merged: robertc at robertcollins.net-20070903221720-9k1y25x0x60rk38b
parent: robertc at robertcollins.net-20070903202305-yj4obhvvw3j7t4jc
parent: robertc at robertcollins.net-20070903015712-oo8vn04vryo75xjb
committer: Robert Collins <robertc at robertcollins.net>
branch nick: commit
timestamp: Tue 2007-09-04 08:17:20 +1000
message:
Trivial review feedback changes.
------------------------------------------------------------
revno: 2776.3.1
merged: robertc at robertcollins.net-20070903015712-oo8vn04vryo75xjb
parent: pqm at pqm.ubuntu.com-20070901160444-hcr66zejwyy0jezc
committer: Robert Collins <robertc at robertcollins.net>
branch nick: commit
timestamp: Mon 2007-09-03 11:57:12 +1000
message:
* Deprecated method ``find_previous_heads`` on
``bzrlib.inventory.InventoryEntry``. This has been superceded by the use
of ``parent_candidates`` and a separate heads check via the repository
API. (Robert Collins)
* New public method ``heads`` on the ``bzrlib.graph.Graph`` class. This is
a simple rename of a previously internal method which implements the same
semantics as heads(). (Robert Collins, John A Meinel)
=== modified file 'NEWS'
--- a/NEWS 2007-09-04 01:20:26 +0000
+++ b/NEWS 2007-09-04 04:12:07 +0000
@@ -204,6 +204,11 @@
This is done in order to mix the commit messages (which is a unicode
string), and the diff which is a raw string. (Goffredo Baroncelli)
+ * Deprecated method ``find_previous_heads`` on
+ ``bzrlib.inventory.InventoryEntry``. This has been superseded by the use
+ of ``parent_candidates`` and a separate heads check via the repository
+ API. (Robert Collins)
+
* New trace function ``mutter_callsite`` will print out a subset of the
stack to the log, which can be useful for gathering debug details.
(Robert Collins)
=== modified file 'bzrlib/bzrdir.py'
--- a/bzrlib/bzrdir.py 2007-08-20 05:48:40 +0000
+++ b/bzrlib/bzrdir.py 2007-09-04 03:53:07 +0000
@@ -35,6 +35,7 @@
import bzrlib
from bzrlib import (
errors,
+ graph,
lockable_files,
lockdir,
registry,
@@ -1934,18 +1935,24 @@
w = Weave(file_id)
self.text_weaves[file_id] = w
text_changed = False
- previous_entries = ie.find_previous_heads(parent_invs,
- None,
- None,
- entry_vf=w)
- for old_revision in previous_entries:
- # if this fails, its a ghost ?
- assert old_revision in self.converted_revs, \
- "Revision {%s} not in converted_revs" % old_revision
+ parent_candiate_entries = ie.parent_candidates(parent_invs)
+ for old_revision in parent_candiate_entries.keys():
+ # if this fails, its a ghost ?
+ assert old_revision in self.converted_revs, \
+ "Revision {%s} not in converted_revs" % old_revision
+ heads = graph.Graph(self).heads(parent_candiate_entries.keys())
+ # XXX: Note that this is unordered - and this is tolerable because
+ # the previous code was also unordered.
+ previous_entries = dict((head, parent_candiate_entries[head]) for head
+ in heads)
self.snapshot_ie(previous_entries, ie, w, rev_id)
del ie.text_id
assert getattr(ie, 'revision', None) is not None
+ def get_parents(self, revision_ids):
+ for revision_id in revision_ids:
+ yield self.revisions[revision_id].parent_ids
+
def snapshot_ie(self, previous_revisions, ie, w, rev_id):
# TODO: convert this logic, which is ~= snapshot to
# a call to:. This needs the path figured out. rather than a work_tree
=== modified file 'bzrlib/graph.py'
--- a/bzrlib/graph.py 2007-08-07 15:26:26 +0000
+++ b/bzrlib/graph.py 2007-09-03 22:17:20 +0000
@@ -134,7 +134,10 @@
ancestor of all border ancestors.
"""
border_common, common, sides = self._find_border_ancestors(revisions)
- return self._filter_candidate_lca(border_common)
+ # We may have common ancestors that can be reached from each other.
+ # - ask for the heads of them to filter it down to only ones that
+ # cannot be reached from each other - phase 2.
+ return self.heads(border_common)
def find_difference(self, left_revision, right_revision):
"""Determine the graph difference between two revisions"""
@@ -212,23 +215,24 @@
for searcher in searchers:
update_common(searcher, revision)
- def _filter_candidate_lca(self, candidate_lca):
- """Remove candidates which are ancestors of other candidates.
-
- This is done by searching the ancestries of each border ancestor. It
- is perfomed on the principle that a border ancestor that is not an
- ancestor of any other border ancestor is a lowest common ancestor.
-
- Searches are stopped when they find a node that is determined to be a
- common ancestor of all border ancestors, because this shows that it
- cannot be a descendant of any border ancestor.
-
- This will scale with the number of candidate ancestors and the length
- of the shortest path from a candidate to an ancestor common to all
- candidates.
+ def heads(self, keys):
+ """Return the heads from amongst keys.
+
+ This is done by searching the ancestries of each key. Any key that is
+ reachable from another key is not returned; all the others are.
+
+ This operation scales with the relative depth between any two keys. If
+ any two keys are completely disconnected all ancestry of both sides
+ will be retrieved.
+
+ :param keys: An iterable of keys.
+ :return: A set of the heads. Note that as a set there is no ordering
+ information. Callers will need to filter their input to create
+ order if they need it.
"""
+ candidate_heads = set(keys)
searchers = dict((c, self._make_breadth_first_searcher([c]))
- for c in candidate_lca)
+ for c in candidate_heads)
active_searchers = dict(searchers)
# skip over the actual candidate for each searcher
for searcher in active_searchers.itervalues():
@@ -248,8 +252,8 @@
del active_searchers[candidate]
continue
for ancestor in ancestors:
- if ancestor in candidate_lca:
- candidate_lca.remove(ancestor)
+ if ancestor in candidate_heads:
+ candidate_heads.remove(ancestor)
del searchers[ancestor]
if ancestor in active_searchers:
del active_searchers[ancestor]
@@ -264,7 +268,7 @@
seen_ancestors =\
searcher.find_seen_ancestors(ancestor)
searcher.stop_searching_any(seen_ancestors)
- return candidate_lca
+ return candidate_heads
def find_unique_lca(self, left_revision, right_revision):
"""Find a unique LCA.
=== modified file 'bzrlib/inventory.py'
--- a/bzrlib/inventory.py 2007-08-31 05:54:21 +0000
+++ b/bzrlib/inventory.py 2007-09-04 03:53:07 +0000
@@ -50,6 +50,7 @@
BzrCheckError,
BzrError,
)
+from bzrlib.symbol_versioning import deprecated_method, zero_ninetyone
from bzrlib.trace import mutter
@@ -161,41 +162,23 @@
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
output_to, reverse=False):
"""Perform a diff between two entries of the same kind."""
-
- def find_previous_heads(self, previous_inventories,
- versioned_file_store,
- transaction,
- entry_vf=None):
- """Return the revisions and entries that directly precede this.
-
- Returned as a map from revision to inventory entry.
-
- This is a map containing the file revisions in all parents
- for which the file exists, and its revision is not a parent of
- any other. If the file is new, the set will be empty.
-
- :param versioned_file_store: A store where ancestry data on this
- file id can be queried.
- :param transaction: The transaction that queries to the versioned
- file store should be completed under.
- :param entry_vf: The entry versioned file, if its already available.
+
+ def parent_candidates(self, previous_inventories):
+ """Find possible per-file graph parents.
+
+ This is currently defined by:
+ - Select the last changed revision in the parent inventory.
+ - Do deal with a short lived bug in bzr 0.8's development two entries
+ that have the same last changed but different 'x' bit settings are
+ changed in-place.
"""
- def get_ancestors(weave, entry):
- return set(weave.get_ancestry(entry.revision, topo_sorted=False))
# revision:ie mapping for each ie found in previous_inventories.
candidates = {}
- # revision:ie mapping with one revision for each head.
- heads = {}
- # revision: ancestor list for each head
- head_ancestors = {}
# identify candidate head revision ids.
for inv in previous_inventories:
if self.file_id in inv:
ie = inv[self.file_id]
assert ie.file_id == self.file_id
- if ie.kind != self.kind:
- # Can't be a candidate if the kind has changed.
- continue
if ie.revision in candidates:
# same revision value in two different inventories:
# correct possible inconsistencies:
@@ -212,19 +195,52 @@
else:
# add this revision as a candidate.
candidates[ie.revision] = ie
-
+ return candidates
+
+ @deprecated_method(zero_ninetyone)
+ def find_previous_heads(self, previous_inventories,
+ versioned_file_store,
+ transaction,
+ entry_vf=None):
+ """Return the revisions and entries that directly precede this.
+
+ Returned as a map from revision to inventory entry.
+
+ This is a map containing the file revisions in all parents
+ for which the file exists, and its revision is not a parent of
+ any other. If the file is new, the set will be empty.
+
+ :param versioned_file_store: A store where ancestry data on this
+ file id can be queried.
+ :param transaction: The transaction that queries to the versioned
+ file store should be completed under.
+ :param entry_vf: The entry versioned file, if its already available.
+ """
+ candidates = self.parent_candidates(previous_inventories)
+
+ # revision:ie mapping with one revision for each head.
+ heads = {}
# common case optimisation
if len(candidates) == 1:
# if there is only one candidate revision found
- # then we can opening the versioned file to access ancestry:
+ # then we can avoid opening the versioned file to access ancestry:
# there cannot be any ancestors to eliminate when there is
# only one revision available.
- heads[ie.revision] = ie
- return heads
+ return candidates
+
+ # --- what follows is now encapsulated in repository.get_graph.heads(),
+ # but that is not accessible from here as we have no repository
+ # pointer. Note that the repository.get_graph.heads() call can return
+ # different results *at the moment* because of the kind-changing check
+ # we have in parent_candidates().
# eliminate ancestors amongst the available candidates:
# heads are those that are not an ancestor of any other candidate
# - this provides convergence at a per-file level.
+ def get_ancestors(weave, entry):
+ return set(weave.get_ancestry(entry.revision, topo_sorted=False))
+ # revision: ancestor list for each head
+ head_ancestors = {}
for ie in candidates.values():
# may be an ancestor of a known head:
already_present = 0 != len(
=== modified file 'bzrlib/repository.py'
--- a/bzrlib/repository.py 2007-09-03 03:03:26 +0000
+++ b/bzrlib/repository.py 2007-09-03 22:17:20 +0000
@@ -2184,44 +2184,57 @@
if self._new_revision_id is None:
self._new_revision_id = self._gen_revision_id()
- def record_entry_contents(self, ie, parent_invs, path, tree):
- """Record the content of ie from tree into the commit if needed.
-
- Side effect: sets ie.revision when unchanged
-
- :param ie: An inventory entry present in the commit.
+ def _check_root(self, ie, parent_invs, tree):
+ """Helper for record_entry_contents.
+
+ :param ie: An entry being added.
:param parent_invs: The inventories of the parent revisions of the
commit.
- :param path: The path the entry is at in the tree.
- :param tree: The tree which contains this entry and should be used to
- obtain content.
+ :param tree: The tree that is being committed.
"""
- if self.new_inventory.root is None and ie.parent_id is not None:
+ if ie.parent_id is not None:
+ # if ie is not root, add a root automatically.
symbol_versioning.warn('Root entry should be supplied to'
' record_entry_contents, as of bzr 0.10.',
DeprecationWarning, stacklevel=2)
self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
'', tree)
- self.new_inventory.add(ie)
-
- # ie.revision is always None if the InventoryEntry is considered
- # for committing. ie.snapshot will record the correct revision
- # which may be the sole parent if it is untouched.
- if ie.revision is not None:
- return
-
- # In this revision format, root entries have no knit or weave
- if ie is self.new_inventory.root:
- # When serializing out to disk and back in
- # root.revision is always _new_revision_id
+ else:
+ # In this revision format, root entries have no knit or weave When
+ # serializing out to disk and back in root.revision is always
+ # _new_revision_id
ie.revision = self._new_revision_id
+
+ def record_entry_contents(self, ie, parent_invs, path, tree):
+ """Record the content of ie from tree into the commit if needed.
+
+ Side effect: sets ie.revision when unchanged
+
+ :param ie: An inventory entry present in the commit.
+ :param parent_invs: The inventories of the parent revisions of the
+ commit.
+ :param path: The path the entry is at in the tree.
+ :param tree: The tree which contains this entry and should be used to
+ obtain content.
+ """
+ if self.new_inventory.root is None:
+ self._check_root(ie, parent_invs, tree)
+ self.new_inventory.add(ie)
+
+ # ie.revision is always None if the InventoryEntry is considered
+ # for committing. ie.snapshot will record the correct revision
+ # which may be the sole parent if it is untouched.
+ if ie.revision is not None:
return
- previous_entries = ie.find_previous_heads(
- parent_invs,
- self.repository.weave_store,
- self.repository.get_transaction())
- # we are creating a new revision for ie in the history store
- # and inventory.
+
+ parent_candiate_entries = ie.parent_candidates(parent_invs)
+ heads = self.repository.get_graph().heads(parent_candiate_entries.keys())
+ # XXX: Note that this is unordered - and this is tolerable because
+ # the previous code was also unordered.
+ previous_entries = dict((head, parent_candiate_entries[head]) for head
+ in heads)
+ # we are creating a new revision for ie in the history store and
+ # inventory.
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
def modified_directory(self, file_id, file_parents):
@@ -2302,34 +2315,16 @@
record_root_entry = True
- def record_entry_contents(self, ie, parent_invs, path, tree):
- """Record the content of ie from tree into the commit if needed.
-
- Side effect: sets ie.revision when unchanged
-
- :param ie: An inventory entry present in the commit.
+ def _check_root(self, ie, parent_invs, tree):
+ """Helper for record_entry_contents.
+
+ :param ie: An entry being added.
:param parent_invs: The inventories of the parent revisions of the
commit.
- :param path: The path the entry is at in the tree.
- :param tree: The tree which contains this entry and should be used to
- obtain content.
+ :param tree: The tree that is being committed.
"""
- assert self.new_inventory.root is not None or ie.parent_id is None
- self.new_inventory.add(ie)
-
- # ie.revision is always None if the InventoryEntry is considered
- # for committing. ie.snapshot will record the correct revision
- # which may be the sole parent if it is untouched.
- if ie.revision is not None:
- return
-
- previous_entries = ie.find_previous_heads(
- parent_invs,
- self.repository.weave_store,
- self.repository.get_transaction())
- # we are creating a new revision for ie in the history store
- # and inventory.
- ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
+ # ie must be root for this builder
+ assert ie.parent_id is None
_unescape_map = {
=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py 2007-09-03 07:02:12 +0000
+++ b/bzrlib/tests/__init__.py 2007-09-04 04:12:07 +0000
@@ -2614,6 +2614,17 @@
return self.__class__.__name__
+class _SymlinkFeature(Feature):
+
+ def _probe(self):
+ return osutils.has_symlinks()
+
+ def feature_name(self):
+ return 'symlinks'
+
+SymlinkFeature = _SymlinkFeature()
+
+
class TestScenarioApplier(object):
"""A tool to apply scenarios to tests."""
=== modified file 'bzrlib/tests/repository_implementations/test_commit_builder.py'
--- a/bzrlib/tests/repository_implementations/test_commit_builder.py 2007-08-28 01:58:42 +0000
+++ b/bzrlib/tests/repository_implementations/test_commit_builder.py 2007-09-04 03:53:07 +0000
@@ -16,6 +16,9 @@
"""Tests for repository commit builder."""
+from errno import EISDIR
+import os
+
from bzrlib import inventory
from bzrlib.errors import NonAsciiRevisionId, CannotSetRevisionId
from bzrlib.repository import CommitBuilder
@@ -173,3 +176,268 @@
self.addCleanup(basis_tree.unlock)
self.assertEqual(rev_id, basis_tree.inventory.root.revision)
+ def _get_revtrees(self, tree, revision_ids):
+ trees = list(tree.branch.repository.revision_trees(revision_ids))
+ for tree in trees:
+ tree.lock_read()
+ self.addCleanup(tree.unlock)
+ return trees
+
+ def test_last_modified_revision_after_commit_root_unchanged(self):
+ # commiting without changing the root does not change the
+ # last modified except on non-rich-root-repositories.
+ tree = self.make_branch_and_tree('.')
+ rev1 = tree.commit('')
+ rev2 = tree.commit('')
+ tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
+ self.assertEqual(rev1, tree1.inventory.root.revision)
+ if tree.branch.repository.supports_rich_root():
+ self.assertEqual(rev1, tree2.inventory.root.revision)
+ else:
+ self.assertEqual(rev2, tree2.inventory.root.revision)
+
+ def _add_commit_check_unchanged(self, tree, name):
+ tree.add([name], [name + 'id'])
+ rev1 = tree.commit('')
+ rev2 = tree.commit('')
+ tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
+ self.assertEqual(rev1, tree1.inventory[name + 'id'].revision)
+ self.assertEqual(rev1, tree2.inventory[name + 'id'].revision)
+ self.assertFileAncestry([rev1], tree, name)
+
+ def test_last_modified_revision_after_commit_dir_unchanged(self):
+ # committing without changing a dir does not change the last modified.
+ tree = self.make_branch_and_tree('.')
+ self.build_tree(['dir/'])
+ self._add_commit_check_unchanged(tree, 'dir')
+
+ def test_last_modified_revision_after_commit_dir_contents_unchanged(self):
+ # committing without changing a dir does not change the last modified
+ # of the dir even the dirs contents are changed.
+ tree = self.make_branch_and_tree('.')
+ self.build_tree(['dir/'])
+ tree.add(['dir'], ['dirid'])
+ rev1 = tree.commit('')
+ self.build_tree(['dir/content'])
+ tree.add(['dir/content'], ['contentid'])
+ rev2 = tree.commit('')
+ tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
+ self.assertEqual(rev1, tree1.inventory['dirid'].revision)
+ self.assertEqual(rev1, tree2.inventory['dirid'].revision)
+ self.assertFileAncestry([rev1], tree, 'dir')
+
+ def test_last_modified_revision_after_commit_file_unchanged(self):
+ # committing without changing a file does not change the last modified.
+ tree = self.make_branch_and_tree('.')
+ self.build_tree(['file'])
+ self._add_commit_check_unchanged(tree, 'file')
+
+ def test_last_modified_revision_after_commit_link_unchanged(self):
+ # committing without changing a link does not change the last modified.
+ self.requireFeature(tests.SymlinkFeature)
+ tree = self.make_branch_and_tree('.')
+ os.symlink('target', 'link')
+ self._add_commit_check_unchanged(tree, 'link')
+
+ def _add_commit_renamed_check_changed(self, tree, name):
+ def rename():
+ tree.rename_one(name, 'new_' + name)
+ self._add_commit_change_check_changed(tree, name, rename)
+
+ def test_last_modified_revision_after_rename_dir_changes(self):
+ # renaming a dir changes the last modified.
+ tree = self.make_branch_and_tree('.')
+ self.build_tree(['dir/'])
+ self._add_commit_renamed_check_changed(tree, 'dir')
+
+ def test_last_modified_revision_after_rename_file_changes(self):
+ # renaming a file changes the last modified.
+ tree = self.make_branch_and_tree('.')
+ self.build_tree(['file'])
+ self._add_commit_renamed_check_changed(tree, 'file')
+
+ def test_last_modified_revision_after_rename_link_changes(self):
+ # renaming a link changes the last modified.
+ self.requireFeature(tests.SymlinkFeature)
+ tree = self.make_branch_and_tree('.')
+ os.symlink('target', 'link')
+ self._add_commit_renamed_check_changed(tree, 'link')
+
+ def _add_commit_reparent_check_changed(self, tree, name):
+ self.build_tree(['newparent/'])
+ tree.add(['newparent'])
+ def reparent():
+ tree.rename_one(name, 'newparent/new_' + name)
+ self._add_commit_change_check_changed(tree, name, reparent)
+
+ def test_last_modified_revision_after_reparent_dir_changes(self):
+ # reparenting a dir changes the last modified.
+ tree = self.make_branch_and_tree('.')
+ self.build_tree(['dir/'])
+ self._add_commit_reparent_check_changed(tree, 'dir')
+
+ def test_last_modified_revision_after_reparent_file_changes(self):
+ # reparenting a file changes the last modified.
+ tree = self.make_branch_and_tree('.')
+ self.build_tree(['file'])
+ self._add_commit_reparent_check_changed(tree, 'file')
+
+ def test_last_modified_revision_after_reparent_link_changes(self):
+ # reparenting a link changes the last modified.
+ self.requireFeature(tests.SymlinkFeature)
+ tree = self.make_branch_and_tree('.')
+ os.symlink('target', 'link')
+ self._add_commit_reparent_check_changed(tree, 'link')
+
+ def _add_commit_change_check_changed(self, tree, name, changer):
+ tree.add([name], [name + 'id'])
+ rev1 = tree.commit('')
+ changer()
+ rev2 = tree.commit('')
+ tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
+ self.assertEqual(rev1, tree1.inventory[name + 'id'].revision)
+ self.assertEqual(rev2, tree2.inventory[name + 'id'].revision)
+ self.assertFileAncestry([rev1, rev2], tree, name)
+
+ def assertFileAncestry(self, ancestry, tree, name, alt_ancestry=None):
+ # all the changes that have occured should be in the ancestry
+ # (closest to a public per-file graph API we have today)
+ tree.lock_read()
+ self.addCleanup(tree.unlock)
+ vw = tree.branch.repository.weave_store.get_weave(name + 'id',
+ tree.branch.repository.get_transaction())
+ result = vw.get_ancestry([ancestry[-1]])
+ if alt_ancestry is None:
+ self.assertEqual(ancestry, result)
+ else:
+ self.assertSubset([tuple(result)],
+ [tuple(ancestry), tuple(alt_ancestry)])
+
+ def test_last_modified_revision_after_content_file_changes(self):
+ # altering a file changes the last modified.
+ tree = self.make_branch_and_tree('.')
+ self.build_tree(['file'])
+ def change_file():
+ tree.put_file_bytes_non_atomic('fileid', 'new content')
+ self._add_commit_change_check_changed(tree, 'file', change_file)
+
+ def test_last_modified_revision_after_content_link_changes(self):
+ # changing a link changes the last modified.
+ self.requireFeature(tests.SymlinkFeature)
+ tree = self.make_branch_and_tree('.')
+ os.symlink('target', 'link')
+ def change_link():
+ os.unlink('link')
+ os.symlink('newtarget', 'link')
+ self._add_commit_change_check_changed(tree, 'link', change_link)
+
+ def _commit_sprout(self, tree, name):
+ tree.add([name], [name + 'id'])
+ rev_id = tree.commit('')
+ return rev_id, tree.bzrdir.sprout('t2').open_workingtree()
+
+ def _rename_in_tree(self, tree, name):
+ tree.rename_one(name, 'new_' + name)
+ return tree.commit('')
+
+ def _commit_sprout_rename_merge(self, tree1, name):
+ rev1, tree2 = self._commit_sprout(tree1, name)
+ # change both sides equally
+ rev2 = self._rename_in_tree(tree1, name)
+ rev3 = self._rename_in_tree(tree2, name)
+ tree1.merge_from_branch(tree2.branch)
+ rev4 = tree1.commit('')
+ tree3, = self._get_revtrees(tree1, [rev4])
+ self.assertEqual(rev4, tree3.inventory[name + 'id'].revision)
+ self.assertFileAncestry([rev1, rev2, rev3, rev4], tree1, name,
+ [rev1, rev3, rev2, rev4])
+
+ def test_last_modified_revision_after_merge_dir_changes(self):
+ # merge a dir changes the last modified.
+ tree1 = self.make_branch_and_tree('t1')
+ self.build_tree(['t1/dir/'])
+ self._commit_sprout_rename_merge(tree1, 'dir')
+
+ def test_last_modified_revision_after_merge_file_changes(self):
+ # merge a file changes the last modified.
+ tree1 = self.make_branch_and_tree('t1')
+ self.build_tree(['t1/file'])
+ self._commit_sprout_rename_merge(tree1, 'file')
+
+ def test_last_modified_revision_after_merge_link_changes(self):
+ # merge a link changes the last modified.
+ self.requireFeature(tests.SymlinkFeature)
+ tree1 = self.make_branch_and_tree('t1')
+ os.symlink('target', 't1/link')
+ self._commit_sprout_rename_merge(tree1, 'link')
+
+ def _commit_sprout_rename_merge_converged(self, tree1, name):
+ rev1, tree2 = self._commit_sprout(tree1, name)
+ # change on the other side to merge back
+ rev2 = self._rename_in_tree(tree2, name)
+ tree1.merge_from_branch(tree2.branch)
+ rev3 = tree1.commit('')
+ tree3, = self._get_revtrees(tree1, [rev2])
+ self.assertEqual(rev2, tree3.inventory[name + 'id'].revision)
+ self.assertFileAncestry([rev1, rev2], tree1, name)
+
+ def test_last_modified_revision_after_converged_merge_dir_changes(self):
+ # merge a dir changes the last modified.
+ tree1 = self.make_branch_and_tree('t1')
+ self.build_tree(['t1/dir/'])
+ self._commit_sprout_rename_merge_converged(tree1, 'dir')
+
+ def test_last_modified_revision_after_converged_merge_file_changes(self):
+ # merge a file changes the last modified.
+ tree1 = self.make_branch_and_tree('t1')
+ self.build_tree(['t1/file'])
+ self._commit_sprout_rename_merge_converged(tree1, 'file')
+
+ def test_last_modified_revision_after_converged_merge_link_changes(self):
+ # merge a link changes the last modified.
+ self.requireFeature(tests.SymlinkFeature)
+ tree1 = self.make_branch_and_tree('t1')
+ os.symlink('target', 't1/link')
+ self._commit_sprout_rename_merge_converged(tree1, 'link')
+
+ def make_dir(self, name):
+ self.build_tree([name + '/'])
+
+ def make_file(self, name):
+ self.build_tree([name])
+
+ def make_link(self, name):
+ self.requireFeature(tests.SymlinkFeature)
+ os.symlink('target', name)
+
+ def _check_kind_change(self, make_before, make_after):
+ tree = self.make_branch_and_tree('.')
+ path = 'name'
+ make_before(path)
+ def change_kind():
+ try:
+ os.unlink(path)
+ except OSError, e:
+ if e.errno != EISDIR:
+ raise
+ os.rmdir(path)
+ make_after(path)
+ self._add_commit_change_check_changed(tree, path, change_kind)
+
+ def test_last_modified_dir_file(self):
+ self._check_kind_change(self.make_dir, self.make_file)
+
+ def test_last_modified_dir_link(self):
+ self._check_kind_change(self.make_dir, self.make_link)
+
+ def test_last_modified_link_file(self):
+ self._check_kind_change(self.make_link, self.make_file)
+
+ def test_last_modified_link_dir(self):
+ self._check_kind_change(self.make_link, self.make_dir)
+
+ def test_last_modified_file_dir(self):
+ self._check_kind_change(self.make_file, self.make_dir)
+
+ def test_last_modified_file_link(self):
+ self._check_kind_change(self.make_file, self.make_link)
=== modified file 'bzrlib/tests/test_graph.py'
--- a/bzrlib/tests/test_graph.py 2007-08-07 20:45:47 +0000
+++ b/bzrlib/tests/test_graph.py 2007-09-03 22:17:20 +0000
@@ -373,4 +373,83 @@
# c
graph = self.make_graph({'c': ['b', 'd'], 'd': ['e'], 'b': ['a'],
'a': [NULL_REVISION], 'e': [NULL_REVISION]})
- self.assertEqual(['c'], graph._filter_candidate_lca(['a', 'c', 'e']))
+ self.assertEqual(set(['c']), graph.heads(['a', 'c', 'e']))
+
+ def test_heads_null(self):
+ graph = self.make_graph(ancestry_1)
+ self.assertEqual(set(['null:']), graph.heads(['null:']))
+ self.assertEqual(set(['rev1']), graph.heads(['null:', 'rev1']))
+ self.assertEqual(set(['rev1']), graph.heads(['rev1', 'null:']))
+ self.assertEqual(set(['rev1']), graph.heads(set(['rev1', 'null:'])))
+ self.assertEqual(set(['rev1']), graph.heads(('rev1', 'null:')))
+
+ def test_heads_one(self):
+ # A single node will alwaya be a head
+ graph = self.make_graph(ancestry_1)
+ self.assertEqual(set(['null:']), graph.heads(['null:']))
+ self.assertEqual(set(['rev1']), graph.heads(['rev1']))
+ self.assertEqual(set(['rev2a']), graph.heads(['rev2a']))
+ self.assertEqual(set(['rev2b']), graph.heads(['rev2b']))
+ self.assertEqual(set(['rev3']), graph.heads(['rev3']))
+ self.assertEqual(set(['rev4']), graph.heads(['rev4']))
+
+ def test_heads_single(self):
+ graph = self.make_graph(ancestry_1)
+ self.assertEqual(set(['rev4']), graph.heads(['null:', 'rev4']))
+ self.assertEqual(set(['rev2a']), graph.heads(['rev1', 'rev2a']))
+ self.assertEqual(set(['rev2b']), graph.heads(['rev1', 'rev2b']))
+ self.assertEqual(set(['rev3']), graph.heads(['rev1', 'rev3']))
+ self.assertEqual(set(['rev4']), graph.heads(['rev1', 'rev4']))
+ self.assertEqual(set(['rev4']), graph.heads(['rev2a', 'rev4']))
+ self.assertEqual(set(['rev4']), graph.heads(['rev2b', 'rev4']))
+ self.assertEqual(set(['rev4']), graph.heads(['rev3', 'rev4']))
+
+ def test_heads_two_heads(self):
+ graph = self.make_graph(ancestry_1)
+ self.assertEqual(set(['rev2a', 'rev2b']),
+ graph.heads(['rev2a', 'rev2b']))
+ self.assertEqual(set(['rev3', 'rev2b']),
+ graph.heads(['rev3', 'rev2b']))
+
+ def test_heads_criss_cross(self):
+ graph = self.make_graph(criss_cross)
+ self.assertEqual(set(['rev2a']),
+ graph.heads(['rev2a', 'rev1']))
+ self.assertEqual(set(['rev2b']),
+ graph.heads(['rev2b', 'rev1']))
+ self.assertEqual(set(['rev3a']),
+ graph.heads(['rev3a', 'rev1']))
+ self.assertEqual(set(['rev3b']),
+ graph.heads(['rev3b', 'rev1']))
+ self.assertEqual(set(['rev2a', 'rev2b']),
+ graph.heads(['rev2a', 'rev2b']))
+ self.assertEqual(set(['rev3a']),
+ graph.heads(['rev3a', 'rev2a']))
+ self.assertEqual(set(['rev3a']),
+ graph.heads(['rev3a', 'rev2b']))
+ self.assertEqual(set(['rev3a']),
+ graph.heads(['rev3a', 'rev2a', 'rev2b']))
+ self.assertEqual(set(['rev3b']),
+ graph.heads(['rev3b', 'rev2a']))
+ self.assertEqual(set(['rev3b']),
+ graph.heads(['rev3b', 'rev2b']))
+ self.assertEqual(set(['rev3b']),
+ graph.heads(['rev3b', 'rev2a', 'rev2b']))
+ self.assertEqual(set(['rev3a', 'rev3b']),
+ graph.heads(['rev3a', 'rev3b']))
+ self.assertEqual(set(['rev3a', 'rev3b']),
+ graph.heads(['rev3a', 'rev3b', 'rev2a', 'rev2b']))
+
+ def test_heads_shortcut(self):
+ graph = self.make_graph(history_shortcut)
+
+ self.assertEqual(set(['rev2a', 'rev2b', 'rev2c']),
+ graph.heads(['rev2a', 'rev2b', 'rev2c']))
+ self.assertEqual(set(['rev3a', 'rev3b']),
+ graph.heads(['rev3a', 'rev3b']))
+ self.assertEqual(set(['rev3a', 'rev3b']),
+ graph.heads(['rev2a', 'rev3a', 'rev3b']))
+ self.assertEqual(set(['rev2a', 'rev3b']),
+ graph.heads(['rev2a', 'rev3b']))
+ self.assertEqual(set(['rev2c', 'rev3a']),
+ graph.heads(['rev2c', 'rev3a']))
=== modified file 'bzrlib/tests/tree_implementations/test_inv.py'
--- a/bzrlib/tests/tree_implementations/test_inv.py 2007-04-14 11:55:51 +0000
+++ b/bzrlib/tests/tree_implementations/test_inv.py 2007-09-03 01:57:12 +0000
@@ -25,6 +25,7 @@
from bzrlib.osutils import (
has_symlinks,
)
+from bzrlib.symbol_versioning import zero_ninetyone
from bzrlib.tests import TestSkipped
from bzrlib.tests.tree_implementations import TestCaseWithTree
from bzrlib.uncommit import uncommit
@@ -177,8 +178,9 @@
self.branch.repository.get_transaction())
def get_previous_heads(self, inventories):
- return self.file_active.find_previous_heads(
- inventories,
+ return self.applyDeprecated(zero_ninetyone,
+ self.file_active.find_previous_heads,
+ inventories,
self.branch.repository.weave_store,
self.branch.repository.get_transaction())
More information about the bazaar-commits
mailing list