Merge lp:~jameinel/bzr/1.15-gc-stacking into lp:~bzr/bzr/trunk-old

Proposed by John A Meinel
Status: Merged
Merged at revision: not available
Proposed branch: lp:~jameinel/bzr/1.15-gc-stacking
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 2014 lines
To merge this branch: bzr merge lp:~jameinel/bzr/1.15-gc-stacking
Reviewer Review Type Date Requested Status
Andrew Bennetts Approve
Review via email: mp+6880@code.launchpad.net

This proposal supersedes a proposal from 2009-05-28.

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote : Posted in a previous version of this proposal

This change enables --development6-rich-root to stack. It ends up including the Repository fallback locking fixes, and a few other code cleanups that we encountered along the way.

It unfortunately adds a bit more direct connection between PackRepository and PackRepository.revisions._index._key_dependencies

We already had an explicit connection, because get_missing_parent_inventories() was directly accessing that variable. The bit we added was to just add a reset() of that cache when a write group is committed, aborted, or suspended. We felt that this was the 'right thing', but it was also required to fix a test about ghosts.

(We had a test that ghosts aren't filled in, but without resetting key deps, an earlier commit that introduced the ghost still tracks that the ghost is missing. Existing Pack fetching would suffer this as well if it used the Stream code for fetching, rather than Pack => Pack.)

This is potentially up for backporting to a 1.15.1 release.

Revision history for this message
Andrew Bennetts (spiv) wrote : Posted in a previous version of this proposal

It's a shame that you add both "_find_present_inventory_ids" and "_find_present_inventories" to groupcompress_repo.py, but it's not trivial to factor out that duplication. Similarly, around line 950 of that file you have a duplication of the logic of find_parent_ids_of_revisions, but again reusing that code isn't trivial. Something to cleanup in the future I guess...

In test_sprout_from_stacked_with_short_history in bzrlib/tests/per_repository_reference/test_fetch.py you start with a comment saying "Now copy this ...", which is a bit weird as the first thing in a test. Probably this comment hasn't been updated after you refactored the test? Anyway, please update it.

1353 + for record in stream:
1354 + records.append(record.key)
1355 + if record.key == ('a-id', 'A-id'):
1356 + self.assertEqual(''.join(content[:-2]),
1357 + record.get_bytes_as('fulltext'))
1358 + elif record.key == ('a-id', 'B-id'):
1359 + self.assertEqual(''.join(content[:-1]),
1360 + record.get_bytes_as('fulltext'))
1361 + elif record.key == ('a-id', 'C-id'):
1362 + self.assertEqual(''.join(content),
1363 + record.get_bytes_as('fulltext'))
1364 + else:
1365 + self.fail('Unexpected record: %s' % (record.key,))

This is ok, but I think I'd rather:

for record in stream:
    records.append((record.key, record.get_bytes_as('fulltext')))
records.sort()
self.assertEqual(
    [(('a-id', 'A-id'), ''.join(content[:-2])), (('a-id', 'B-id'), ''.join(content[:-1])),
     (('a-id', 'C-id'), ''.join(content))],
    records)

Which is more compact and doesn't have any need for conditionals in the test, and will probably give more informative failures.

bzrlib/tests/per_repository_reference/test_initialize.py adds a test with no assert* calls. Is that intentional?

In bzrlib/tests/test_pack_repository.py, test_resume_chk_bytes has a line of unreachable code after a raise statement.

In bzrlib/tests/test_repository.py, is the typo in 'abcdefghijklmnopqrstuvwxzy123456789' meant to be a test to see how attentive your reviewer is? ;)

Other than those, this seems fine to me though.

review: Needs Fixing
Revision history for this message
John A Meinel (jameinel) wrote : Posted in a previous version of this proposal

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Andrew Bennetts wrote:
> Review: Needs Fixing
> It's a shame that you add both "_find_present_inventory_ids" and "_find_present_inventories" to groupcompress_repo.py, but it's not trivial to factor out that duplication. Similarly, around line 950 of that file you have a duplication of the logic of find_parent_ids_of_revisions, but again reusing that code isn't trivial. Something to cleanup in the future I guess...
>

_find_present_inventory_ids, and _find_present_inventories are actually
exchangeable, it is just
self.from_repository._find_present_inventory_ids
rather than
self._find_present_inventories.

I'm glad you caught the duplication.

And for "_find_parent_ids_of_revisions()" it also is available as
self.from_repository....

Mostly because this is GroupCHKStreamSource which can assume that it has
a RepositoryCHK1 as the .from_repository.

Ultimately, we should probably move those functions to be on Repository,
and potentially make them public. I don't really like widening the
Repository api, but as it has a default implementation that works just
fine for all other implementations, it doesn't really cause a burden for
something like SVNRepository.

> In test_sprout_from_stacked_with_short_history in bzrlib/tests/per_repository_reference/test_fetch.py you start with a comment saying "Now copy this ...", which is a bit weird as the first thing in a test. Probably this comment hasn't been updated after you refactored the test? Anyway, please update it.

Done.

...

> for record in stream:
> records.append((record.key, record.get_bytes_as('fulltext')))
> records.sort()
> self.assertEqual(
> [(('a-id', 'A-id'), ''.join(content[:-2])), (('a-id', 'B-id'), ''.join(content[:-1])),
> (('a-id', 'C-id'), ''.join(content))],
> records)
>
> Which is more compact and doesn't have any need for conditionals in the test, and will probably give more informative failures.

Done.

>
> bzrlib/tests/per_repository_reference/test_initialize.py adds a test with no assert* calls. Is that intentional?
>

It exercises the code that was broken by doing things differently. (As
in you would get an exception.)
I can add arbitrary assertions, but the reason for the test was to have
a simple call to "initialize_on_transport_ex()" given all repository
formats, and remote requests, etc, etc.

I'll add some basic bits, just to make it look like a real test. I'll
even add one that tests we can initialize all formats over the smart server.

> In bzrlib/tests/test_pack_repository.py, test_resume_chk_bytes has a line of unreachable code after a raise statement.
>
> In bzrlib/tests/test_repository.py, is the typo in 'abcdefghijklmnopqrstuvwxzy123456789' meant to be a test to see how attentive your reviewer is? ;)
>
> Other than those, this seems fine to me though.

Fixed.
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkofsfMACgkQJdeBCYSNAAP7BgCfeZehp6iRn0THWW1lDnOEzs1p
PxoAnjCXPs75oPLPiZTtSrrDT6jebUkt
=zVXm
-----END PGP SIGNATURE-----

Revision history for this message
Andrew Bennetts (spiv) wrote :

Looks good to me now.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2009-05-28 18:56:55 +0000
+++ NEWS 2009-05-29 10:35:20 +0000
@@ -1,6 +1,6 @@
1====================1####################
2Bazaar Release Notes2Bazaar Release Notes
3====================3####################
44
55
6.. contents:: List of Releases6.. contents:: List of Releases
@@ -25,12 +25,22 @@
2525
26* ``bzr diff`` is now faster on large trees. (Ian Clatworthy)26* ``bzr diff`` is now faster on large trees. (Ian Clatworthy)
2727
28* ``--development6-rich-root`` can now stack. (Modulo some smart-server
29 bugs with stacking and non default formats.)
30 (John Arbash Meinel, #373455)
31
32
28Bug Fixes33Bug Fixes
29*********34*********
3035
31* Better message in ``bzr add`` output suggesting using ``bzr ignored`` to36* Better message in ``bzr add`` output suggesting using ``bzr ignored`` to
32 see which files can also be added. (Jason Spashett, #76616)37 see which files can also be added. (Jason Spashett, #76616)
3338
39* Clarify the rules for locking and fallback repositories. Fix bugs in how
40 ``RemoteRepository`` was handling fallbacks along with the
41 ``_real_repository``. (Andrew Bennetts, John Arbash Meinel, #375496)
42
43
34Documentation44Documentation
35*************45*************
3646
@@ -76,6 +86,15 @@
76New Features86New Features
77************87************
7888
89* New command ``bzr dpush`` that can push changes to foreign
90 branches (svn, git) without setting custom bzr-specific metadata.
91 (Jelmer Vernooij)
92
93* The new development format ``--development6-rich-root`` now supports
94 stacking. We chose not to use a new format marker, since old clients
95 will just fail to open stacked branches, the same as if we used a new
96 format flag. (John Arbash Meinel, #373455)
97
79* Plugins can now define their own annotation tie-breaker when two revisions98* Plugins can now define their own annotation tie-breaker when two revisions
80 introduce the exact same line. See ``bzrlib.annotate._break_annotation_tie``99 introduce the exact same line. See ``bzrlib.annotate._break_annotation_tie``
81 Be aware though that this is temporary, private (as indicated by the leading100 Be aware though that this is temporary, private (as indicated by the leading
82101
=== modified file 'bzrlib/branch.py'
--- bzrlib/branch.py 2009-05-26 20:32:34 +0000
+++ bzrlib/branch.py 2009-05-29 10:35:20 +0000
@@ -101,13 +101,9 @@
101 def _open_hook(self):101 def _open_hook(self):
102 """Called by init to allow simpler extension of the base class."""102 """Called by init to allow simpler extension of the base class."""
103103
104 def _activate_fallback_location(self, url, lock_style):104 def _activate_fallback_location(self, url):
105 """Activate the branch/repository from url as a fallback repository."""105 """Activate the branch/repository from url as a fallback repository."""
106 repo = self._get_fallback_repository(url)106 repo = self._get_fallback_repository(url)
107 if lock_style == 'write':
108 repo.lock_write()
109 elif lock_style == 'read':
110 repo.lock_read()
111 self.repository.add_fallback_repository(repo)107 self.repository.add_fallback_repository(repo)
112108
113 def break_lock(self):109 def break_lock(self):
@@ -656,7 +652,7 @@
656 self.repository.fetch(source_repository, revision_id,652 self.repository.fetch(source_repository, revision_id,
657 find_ghosts=True)653 find_ghosts=True)
658 else:654 else:
659 self._activate_fallback_location(url, 'write')655 self._activate_fallback_location(url)
660 # write this out after the repository is stacked to avoid setting a656 # write this out after the repository is stacked to avoid setting a
661 # stacked config that doesn't work.657 # stacked config that doesn't work.
662 self._set_config_location('stacked_on_location', url)658 self._set_config_location('stacked_on_location', url)
@@ -2370,7 +2366,7 @@
2370 raise AssertionError(2366 raise AssertionError(
2371 "'transform_fallback_location' hook %s returned "2367 "'transform_fallback_location' hook %s returned "
2372 "None, not a URL." % hook_name)2368 "None, not a URL." % hook_name)
2373 self._activate_fallback_location(url, None)2369 self._activate_fallback_location(url)
23742370
2375 def __init__(self, *args, **kwargs):2371 def __init__(self, *args, **kwargs):
2376 self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)2372 self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
23772373
=== modified file 'bzrlib/groupcompress.py'
--- bzrlib/groupcompress.py 2009-05-25 19:04:59 +0000
+++ bzrlib/groupcompress.py 2009-05-29 10:35:20 +0000
@@ -31,13 +31,13 @@
31 diff,31 diff,
32 errors,32 errors,
33 graph as _mod_graph,33 graph as _mod_graph,
34 knit,
34 osutils,35 osutils,
35 pack,36 pack,
36 patiencediff,37 patiencediff,
37 trace,38 trace,
38 )39 )
39from bzrlib.graph import Graph40from bzrlib.graph import Graph
40from bzrlib.knit import _DirectPackAccess
41from bzrlib.btree_index import BTreeBuilder41from bzrlib.btree_index import BTreeBuilder
42from bzrlib.lru_cache import LRUSizeCache42from bzrlib.lru_cache import LRUSizeCache
43from bzrlib.tsort import topo_sort43from bzrlib.tsort import topo_sort
@@ -911,7 +911,7 @@
911 writer.begin()911 writer.begin()
912 index = _GCGraphIndex(graph_index, lambda:True, parents=parents,912 index = _GCGraphIndex(graph_index, lambda:True, parents=parents,
913 add_callback=graph_index.add_nodes)913 add_callback=graph_index.add_nodes)
914 access = _DirectPackAccess({})914 access = knit._DirectPackAccess({})
915 access.set_writer(writer, graph_index, (transport, 'newpack'))915 access.set_writer(writer, graph_index, (transport, 'newpack'))
916 result = GroupCompressVersionedFiles(index, access, delta)916 result = GroupCompressVersionedFiles(index, access, delta)
917 result.stream = stream917 result.stream = stream
@@ -1547,7 +1547,7 @@
1547 """Mapper from GroupCompressVersionedFiles needs into GraphIndex storage."""1547 """Mapper from GroupCompressVersionedFiles needs into GraphIndex storage."""
15481548
1549 def __init__(self, graph_index, is_locked, parents=True,1549 def __init__(self, graph_index, is_locked, parents=True,
1550 add_callback=None):1550 add_callback=None, track_external_parent_refs=False):
1551 """Construct a _GCGraphIndex on a graph_index.1551 """Construct a _GCGraphIndex on a graph_index.
15521552
1553 :param graph_index: An implementation of bzrlib.index.GraphIndex.1553 :param graph_index: An implementation of bzrlib.index.GraphIndex.
@@ -1558,12 +1558,19 @@
1558 :param add_callback: If not None, allow additions to the index and call1558 :param add_callback: If not None, allow additions to the index and call
1559 this callback with a list of added GraphIndex nodes:1559 this callback with a list of added GraphIndex nodes:
1560 [(node, value, node_refs), ...]1560 [(node, value, node_refs), ...]
1561 :param track_external_parent_refs: As keys are added, keep track of the
1562 keys they reference, so that we can query get_missing_parents(),
1563 etc.
1561 """1564 """
1562 self._add_callback = add_callback1565 self._add_callback = add_callback
1563 self._graph_index = graph_index1566 self._graph_index = graph_index
1564 self._parents = parents1567 self._parents = parents
1565 self.has_graph = parents1568 self.has_graph = parents
1566 self._is_locked = is_locked1569 self._is_locked = is_locked
1570 if track_external_parent_refs:
1571 self._key_dependencies = knit._KeyRefs()
1572 else:
1573 self._key_dependencies = None
15671574
1568 def add_records(self, records, random_id=False):1575 def add_records(self, records, random_id=False):
1569 """Add multiple records to the index.1576 """Add multiple records to the index.
@@ -1614,6 +1621,11 @@
1614 for key, (value, node_refs) in keys.iteritems():1621 for key, (value, node_refs) in keys.iteritems():
1615 result.append((key, value))1622 result.append((key, value))
1616 records = result1623 records = result
1624 key_dependencies = self._key_dependencies
1625 if key_dependencies is not None and self._parents:
1626 for key, value, refs in records:
1627 parents = refs[0]
1628 key_dependencies.add_references(key, parents)
1617 self._add_callback(records)1629 self._add_callback(records)
16181630
1619 def _check_read(self):1631 def _check_read(self):
@@ -1668,6 +1680,14 @@
1668 result[node[1]] = None1680 result[node[1]] = None
1669 return result1681 return result
16701682
1683 def get_missing_parents(self):
1684 """Return the keys of missing parents."""
1685 # Copied from _KnitGraphIndex.get_missing_parents
1686 # We may have false positives, so filter those out.
1687 self._key_dependencies.add_keys(
1688 self.get_parent_map(self._key_dependencies.get_unsatisfied_refs()))
1689 return frozenset(self._key_dependencies.get_unsatisfied_refs())
1690
1671 def get_build_details(self, keys):1691 def get_build_details(self, keys):
1672 """Get the various build details for keys.1692 """Get the various build details for keys.
16731693
@@ -1719,6 +1739,23 @@
1719 delta_end = int(bits[3])1739 delta_end = int(bits[3])
1720 return node[0], start, stop, basis_end, delta_end1740 return node[0], start, stop, basis_end, delta_end
17211741
1742 def scan_unvalidated_index(self, graph_index):
1743 """Inform this _GCGraphIndex that there is an unvalidated index.
1744
1745 This allows this _GCGraphIndex to keep track of any missing
1746 compression parents we may want to have filled in to make those
1747 indices valid.
1748
1749 :param graph_index: A GraphIndex
1750 """
1751 if self._key_dependencies is not None:
1752 # Add parent refs from graph_index (and discard parent refs that
1753 # the graph_index has).
1754 add_refs = self._key_dependencies.add_references
1755 for node in graph_index.iter_all_entries():
1756 add_refs(node[1], node[3][0])
1757
1758
17221759
1723from bzrlib._groupcompress_py import (1760from bzrlib._groupcompress_py import (
1724 apply_delta,1761 apply_delta,
17251762
=== modified file 'bzrlib/inventory.py'
--- bzrlib/inventory.py 2009-04-10 12:11:58 +0000
+++ bzrlib/inventory.py 2009-05-29 10:35:20 +0000
@@ -1547,11 +1547,9 @@
1547 def _get_mutable_inventory(self):1547 def _get_mutable_inventory(self):
1548 """See CommonInventory._get_mutable_inventory."""1548 """See CommonInventory._get_mutable_inventory."""
1549 entries = self.iter_entries()1549 entries = self.iter_entries()
1550 if self.root_id is not None:1550 inv = Inventory(None, self.revision_id)
1551 entries.next()
1552 inv = Inventory(self.root_id, self.revision_id)
1553 for path, inv_entry in entries:1551 for path, inv_entry in entries:
1554 inv.add(inv_entry)1552 inv.add(inv_entry.copy())
1555 return inv1553 return inv
15561554
1557 def create_by_apply_delta(self, inventory_delta, new_revision_id,1555 def create_by_apply_delta(self, inventory_delta, new_revision_id,
15581556
=== modified file 'bzrlib/knit.py'
--- bzrlib/knit.py 2009-05-25 19:04:59 +0000
+++ bzrlib/knit.py 2009-05-29 10:35:20 +0000
@@ -2882,6 +2882,8 @@
28822882
2883 def get_missing_parents(self):2883 def get_missing_parents(self):
2884 """Return the keys of missing parents."""2884 """Return the keys of missing parents."""
2885 # If updating this, you should also update
2886 # groupcompress._GCGraphIndex.get_missing_parents
2885 # We may have false positives, so filter those out.2887 # We may have false positives, so filter those out.
2886 self._key_dependencies.add_keys(2888 self._key_dependencies.add_keys(
2887 self.get_parent_map(self._key_dependencies.get_unsatisfied_refs()))2889 self.get_parent_map(self._key_dependencies.get_unsatisfied_refs()))
28882890
=== modified file 'bzrlib/remote.py'
--- bzrlib/remote.py 2009-05-10 23:45:33 +0000
+++ bzrlib/remote.py 2009-05-29 10:35:21 +0000
@@ -670,9 +670,10 @@
670 self._ensure_real()670 self._ensure_real()
671 return self._real_repository.suspend_write_group()671 return self._real_repository.suspend_write_group()
672672
673 def get_missing_parent_inventories(self):673 def get_missing_parent_inventories(self, check_for_missing_texts=True):
674 self._ensure_real()674 self._ensure_real()
675 return self._real_repository.get_missing_parent_inventories()675 return self._real_repository.get_missing_parent_inventories(
676 check_for_missing_texts=check_for_missing_texts)
676677
677 def _ensure_real(self):678 def _ensure_real(self):
678 """Ensure that there is a _real_repository set.679 """Ensure that there is a _real_repository set.
@@ -860,10 +861,10 @@
860 self._unstacked_provider.enable_cache(cache_misses=True)861 self._unstacked_provider.enable_cache(cache_misses=True)
861 if self._real_repository is not None:862 if self._real_repository is not None:
862 self._real_repository.lock_read()863 self._real_repository.lock_read()
864 for repo in self._fallback_repositories:
865 repo.lock_read()
863 else:866 else:
864 self._lock_count += 1867 self._lock_count += 1
865 for repo in self._fallback_repositories:
866 repo.lock_read()
867868
868 def _remote_lock_write(self, token):869 def _remote_lock_write(self, token):
869 path = self.bzrdir._path_for_remote_call(self._client)870 path = self.bzrdir._path_for_remote_call(self._client)
@@ -901,13 +902,13 @@
901 self._lock_count = 1902 self._lock_count = 1
902 cache_misses = self._real_repository is None903 cache_misses = self._real_repository is None
903 self._unstacked_provider.enable_cache(cache_misses=cache_misses)904 self._unstacked_provider.enable_cache(cache_misses=cache_misses)
905 for repo in self._fallback_repositories:
906 # Writes don't affect fallback repos
907 repo.lock_read()
904 elif self._lock_mode == 'r':908 elif self._lock_mode == 'r':
905 raise errors.ReadOnlyError(self)909 raise errors.ReadOnlyError(self)
906 else:910 else:
907 self._lock_count += 1911 self._lock_count += 1
908 for repo in self._fallback_repositories:
909 # Writes don't affect fallback repos
910 repo.lock_read()
911 return self._lock_token or None912 return self._lock_token or None
912913
913 def leave_lock_in_place(self):914 def leave_lock_in_place(self):
@@ -1015,6 +1016,10 @@
1015 self._lock_token = None1016 self._lock_token = None
1016 if not self._leave_lock:1017 if not self._leave_lock:
1017 self._unlock(old_token)1018 self._unlock(old_token)
1019 # Fallbacks are always 'lock_read()' so we don't pay attention to
1020 # self._leave_lock
1021 for repo in self._fallback_repositories:
1022 repo.unlock()
10181023
1019 def break_lock(self):1024 def break_lock(self):
1020 # should hand off to the network1025 # should hand off to the network
@@ -1084,6 +1089,11 @@
1084 # We need to accumulate additional repositories here, to pass them in1089 # We need to accumulate additional repositories here, to pass them in
1085 # on various RPC's.1090 # on various RPC's.
1086 #1091 #
1092 if self.is_locked():
1093 # We will call fallback.unlock() when we transition to the unlocked
1094 # state, so always add a lock here. If a caller passes us a locked
1095 # repository, they are responsible for unlocking it later.
1096 repository.lock_read()
1087 self._fallback_repositories.append(repository)1097 self._fallback_repositories.append(repository)
1088 # If self._real_repository was parameterised already (e.g. because a1098 # If self._real_repository was parameterised already (e.g. because a
1089 # _real_branch had its get_stacked_on_url method called), then the1099 # _real_branch had its get_stacked_on_url method called), then the
@@ -1971,7 +1981,7 @@
1971 except (errors.NotStacked, errors.UnstackableBranchFormat,1981 except (errors.NotStacked, errors.UnstackableBranchFormat,
1972 errors.UnstackableRepositoryFormat), e:1982 errors.UnstackableRepositoryFormat), e:
1973 return1983 return
1974 self._activate_fallback_location(fallback_url, None)1984 self._activate_fallback_location(fallback_url)
19751985
1976 def _get_config(self):1986 def _get_config(self):
1977 return RemoteBranchConfig(self)1987 return RemoteBranchConfig(self)
19781988
=== modified file 'bzrlib/repofmt/groupcompress_repo.py'
--- bzrlib/repofmt/groupcompress_repo.py 2009-05-26 13:12:59 +0000
+++ bzrlib/repofmt/groupcompress_repo.py 2009-05-29 10:35:21 +0000
@@ -51,6 +51,7 @@
51 PackRootCommitBuilder,51 PackRootCommitBuilder,
52 RepositoryPackCollection,52 RepositoryPackCollection,
53 RepositoryFormatPack,53 RepositoryFormatPack,
54 ResumedPack,
54 Packer,55 Packer,
55 )56 )
5657
@@ -163,7 +164,21 @@
163 have deltas based on a fallback repository.164 have deltas based on a fallback repository.
164 (See <https://bugs.launchpad.net/bzr/+bug/288751>)165 (See <https://bugs.launchpad.net/bzr/+bug/288751>)
165 """166 """
166 # Groupcompress packs don't have any external references167 # Groupcompress packs don't have any external references, arguably CHK
168 # pages have external references, but we cannot 'cheaply' determine
169 # them without actually walking all of the chk pages.
170
171
172class ResumedGCPack(ResumedPack):
173
174 def _check_references(self):
175 """Make sure our external compression parents are present."""
176 # See GCPack._check_references for why this is empty
177
178 def _get_external_refs(self, index):
179 # GC repositories don't have compression parents external to a given
180 # pack file
181 return set()
167182
168183
169class GCCHKPacker(Packer):184class GCCHKPacker(Packer):
@@ -540,6 +555,7 @@
540class GCRepositoryPackCollection(RepositoryPackCollection):555class GCRepositoryPackCollection(RepositoryPackCollection):
541556
542 pack_factory = GCPack557 pack_factory = GCPack
558 resumed_pack_factory = ResumedGCPack
543559
544 def _already_packed(self):560 def _already_packed(self):
545 """Is the collection already packed?"""561 """Is the collection already packed?"""
@@ -609,7 +625,8 @@
609 self.revisions = GroupCompressVersionedFiles(625 self.revisions = GroupCompressVersionedFiles(
610 _GCGraphIndex(self._pack_collection.revision_index.combined_index,626 _GCGraphIndex(self._pack_collection.revision_index.combined_index,
611 add_callback=self._pack_collection.revision_index.add_callback,627 add_callback=self._pack_collection.revision_index.add_callback,
612 parents=True, is_locked=self.is_locked),628 parents=True, is_locked=self.is_locked,
629 track_external_parent_refs=True),
613 access=self._pack_collection.revision_index.data_access,630 access=self._pack_collection.revision_index.data_access,
614 delta=False)631 delta=False)
615 self.signatures = GroupCompressVersionedFiles(632 self.signatures = GroupCompressVersionedFiles(
@@ -719,52 +736,21 @@
719 # make it raise to trap naughty direct users.736 # make it raise to trap naughty direct users.
720 raise NotImplementedError(self._iter_inventory_xmls)737 raise NotImplementedError(self._iter_inventory_xmls)
721738
722 def _find_revision_outside_set(self, revision_ids):739 def _find_parent_ids_of_revisions(self, revision_ids):
723 revision_set = frozenset(revision_ids)740 # TODO: we probably want to make this a helper that other code can get
724 for revid in revision_ids:741 # at
725 parent_ids = self.get_parent_map([revid]).get(revid, ())742 parent_map = self.get_parent_map(revision_ids)
726 for parent in parent_ids:743 parents = set()
727 if parent in revision_set:744 map(parents.update, parent_map.itervalues())
728 # Parent is not outside the set745 parents.difference_update(revision_ids)
729 continue746 parents.discard(_mod_revision.NULL_REVISION)
730 if parent not in self.get_parent_map([parent]):747 return parents
731 # Parent is a ghost
732 continue
733 return parent
734 return _mod_revision.NULL_REVISION
735748
736 def _find_file_keys_to_fetch(self, revision_ids, pb):749 def _find_present_inventory_ids(self, revision_ids):
737 rich_root = self.supports_rich_root()750 keys = [(r,) for r in revision_ids]
738 revision_outside_set = self._find_revision_outside_set(revision_ids)751 parent_map = self.inventories.get_parent_map(keys)
739 if revision_outside_set == _mod_revision.NULL_REVISION:752 present_inventory_ids = set(k[-1] for k in parent_map)
740 uninteresting_root_keys = set()753 return present_inventory_ids
741 else:
742 uninteresting_inv = self.get_inventory(revision_outside_set)
743 uninteresting_root_keys = set([uninteresting_inv.id_to_entry.key()])
744 interesting_root_keys = set()
745 for idx, inv in enumerate(self.iter_inventories(revision_ids)):
746 interesting_root_keys.add(inv.id_to_entry.key())
747 revision_ids = frozenset(revision_ids)
748 file_id_revisions = {}
749 bytes_to_info = inventory.CHKInventory._bytes_to_utf8name_key
750 for record, items in chk_map.iter_interesting_nodes(self.chk_bytes,
751 interesting_root_keys, uninteresting_root_keys,
752 pb=pb):
753 # This is cheating a bit to use the last grabbed 'inv', but it
754 # works
755 for name, bytes in items:
756 (name_utf8, file_id, revision_id) = bytes_to_info(bytes)
757 if not rich_root and name_utf8 == '':
758 continue
759 if revision_id in revision_ids:
760 # Would we rather build this up into file_id => revision
761 # maps?
762 try:
763 file_id_revisions[file_id].add(revision_id)
764 except KeyError:
765 file_id_revisions[file_id] = set([revision_id])
766 for file_id, revisions in file_id_revisions.iteritems():
767 yield ('file', file_id, revisions)
768754
769 def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):755 def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
770 """Find the file ids and versions affected by revisions.756 """Find the file ids and versions affected by revisions.
@@ -776,23 +762,39 @@
776 revision_ids. Each altered file-ids has the exact revision_ids that762 revision_ids. Each altered file-ids has the exact revision_ids that
777 altered it listed explicitly.763 altered it listed explicitly.
778 """764 """
779 rich_roots = self.supports_rich_root()765 rich_root = self.supports_rich_root()
780 result = {}766 bytes_to_info = inventory.CHKInventory._bytes_to_utf8name_key
767 file_id_revisions = {}
781 pb = ui.ui_factory.nested_progress_bar()768 pb = ui.ui_factory.nested_progress_bar()
782 try:769 try:
783 total = len(revision_ids)770 parent_ids = self._find_parent_ids_of_revisions(revision_ids)
784 for pos, inv in enumerate(self.iter_inventories(revision_ids)):771 present_parent_inv_ids = self._find_present_inventory_ids(parent_ids)
785 pb.update("Finding text references", pos, total)772 uninteresting_root_keys = set()
786 for entry in inv.iter_just_entries():773 interesting_root_keys = set()
787 if entry.revision != inv.revision_id:774 inventories_to_read = set(present_parent_inv_ids)
788 continue775 inventories_to_read.update(revision_ids)
789 if not rich_roots and entry.file_id == inv.root_id:776 for inv in self.iter_inventories(inventories_to_read):
790 continue777 entry_chk_root_key = inv.id_to_entry.key()
791 alterations = result.setdefault(entry.file_id, set([]))778 if inv.revision_id in present_parent_inv_ids:
792 alterations.add(entry.revision)779 uninteresting_root_keys.add(entry_chk_root_key)
793 return result780 else:
781 interesting_root_keys.add(entry_chk_root_key)
782
783 chk_bytes = self.chk_bytes
784 for record, items in chk_map.iter_interesting_nodes(chk_bytes,
785 interesting_root_keys, uninteresting_root_keys,
786 pb=pb):
787 for name, bytes in items:
788 (name_utf8, file_id, revision_id) = bytes_to_info(bytes)
789 if not rich_root and name_utf8 == '':
790 continue
791 try:
792 file_id_revisions[file_id].add(revision_id)
793 except KeyError:
794 file_id_revisions[file_id] = set([revision_id])
794 finally:795 finally:
795 pb.finished()796 pb.finished()
797 return file_id_revisions
796798
797 def find_text_key_references(self):799 def find_text_key_references(self):
798 """Find the text key references within the repository.800 """Find the text key references within the repository.
@@ -843,12 +845,6 @@
843 return GroupCHKStreamSource(self, to_format)845 return GroupCHKStreamSource(self, to_format)
844 return super(CHKInventoryRepository, self)._get_source(to_format)846 return super(CHKInventoryRepository, self)._get_source(to_format)
845847
846 def suspend_write_group(self):
847 raise errors.UnsuspendableWriteGroup(self)
848
849 def _resume_write_group(self, tokens):
850 raise errors.UnsuspendableWriteGroup(self)
851
852848
853class GroupCHKStreamSource(repository.StreamSource):849class GroupCHKStreamSource(repository.StreamSource):
854 """Used when both the source and target repo are GroupCHK repos."""850 """Used when both the source and target repo are GroupCHK repos."""
@@ -861,7 +857,7 @@
861 self._chk_id_roots = None857 self._chk_id_roots = None
862 self._chk_p_id_roots = None858 self._chk_p_id_roots = None
863859
864 def _get_filtered_inv_stream(self):860 def _get_inventory_stream(self, inventory_keys):
865 """Get a stream of inventory texts.861 """Get a stream of inventory texts.
866862
867 When this function returns, self._chk_id_roots and self._chk_p_id_roots863 When this function returns, self._chk_id_roots and self._chk_p_id_roots
@@ -873,7 +869,7 @@
873 id_roots_set = set()869 id_roots_set = set()
874 p_id_roots_set = set()870 p_id_roots_set = set()
875 source_vf = self.from_repository.inventories871 source_vf = self.from_repository.inventories
876 stream = source_vf.get_record_stream(self._revision_keys,872 stream = source_vf.get_record_stream(inventory_keys,
877 'groupcompress', True)873 'groupcompress', True)
878 for record in stream:874 for record in stream:
879 bytes = record.get_bytes_as('fulltext')875 bytes = record.get_bytes_as('fulltext')
@@ -897,16 +893,29 @@
897 p_id_roots_set.clear()893 p_id_roots_set.clear()
898 return ('inventories', _filtered_inv_stream())894 return ('inventories', _filtered_inv_stream())
899895
900 def _get_filtered_chk_streams(self, excluded_keys):896 def _find_present_inventories(self, revision_ids):
897 revision_keys = [(r,) for r in revision_ids]
898 inventories = self.from_repository.inventories
899 present_inventories = inventories.get_parent_map(revision_keys)
900 return [p[-1] for p in present_inventories]
901
902 def _get_filtered_chk_streams(self, excluded_revision_ids):
901 self._text_keys = set()903 self._text_keys = set()
902 excluded_keys.discard(_mod_revision.NULL_REVISION)904 excluded_revision_ids.discard(_mod_revision.NULL_REVISION)
903 if not excluded_keys:905 if not excluded_revision_ids:
904 uninteresting_root_keys = set()906 uninteresting_root_keys = set()
905 uninteresting_pid_root_keys = set()907 uninteresting_pid_root_keys = set()
906 else:908 else:
909 # filter out any excluded revisions whose inventories are not
910 # actually present
911 # TODO: Update Repository.iter_inventories() to add
912 # ignore_missing=True
913 present_ids = self.from_repository._find_present_inventory_ids(
914 excluded_revision_ids)
915 present_ids = self._find_present_inventories(excluded_revision_ids)
907 uninteresting_root_keys = set()916 uninteresting_root_keys = set()
908 uninteresting_pid_root_keys = set()917 uninteresting_pid_root_keys = set()
909 for inv in self.from_repository.iter_inventories(excluded_keys):918 for inv in self.from_repository.iter_inventories(present_ids):
910 uninteresting_root_keys.add(inv.id_to_entry.key())919 uninteresting_root_keys.add(inv.id_to_entry.key())
911 uninteresting_pid_root_keys.add(920 uninteresting_pid_root_keys.add(
912 inv.parent_id_basename_to_file_id.key())921 inv.parent_id_basename_to_file_id.key())
@@ -922,12 +931,16 @@
922 self._text_keys.add((file_id, revision_id))931 self._text_keys.add((file_id, revision_id))
923 if record is not None:932 if record is not None:
924 yield record933 yield record
934 # Consumed
935 self._chk_id_roots = None
925 yield 'chk_bytes', _filter_id_to_entry()936 yield 'chk_bytes', _filter_id_to_entry()
926 def _get_parent_id_basename_to_file_id_pages():937 def _get_parent_id_basename_to_file_id_pages():
927 for record, items in chk_map.iter_interesting_nodes(chk_bytes,938 for record, items in chk_map.iter_interesting_nodes(chk_bytes,
928 self._chk_p_id_roots, uninteresting_pid_root_keys):939 self._chk_p_id_roots, uninteresting_pid_root_keys):
929 if record is not None:940 if record is not None:
930 yield record941 yield record
942 # Consumed
943 self._chk_p_id_roots = None
931 yield 'chk_bytes', _get_parent_id_basename_to_file_id_pages()944 yield 'chk_bytes', _get_parent_id_basename_to_file_id_pages()
932945
933 def _get_text_stream(self):946 def _get_text_stream(self):
@@ -943,18 +956,43 @@
943 for stream_info in self._fetch_revision_texts(revision_ids):956 for stream_info in self._fetch_revision_texts(revision_ids):
944 yield stream_info957 yield stream_info
945 self._revision_keys = [(rev_id,) for rev_id in revision_ids]958 self._revision_keys = [(rev_id,) for rev_id in revision_ids]
946 yield self._get_filtered_inv_stream()959 yield self._get_inventory_stream(self._revision_keys)
947 # The keys to exclude are part of the search recipe960 # TODO: The keys to exclude might be part of the search recipe
948 _, _, exclude_keys, _ = search.get_recipe()961 # For now, exclude all parents that are at the edge of ancestry, for
949 for stream_info in self._get_filtered_chk_streams(exclude_keys):962 # which we have inventories
963 from_repo = self.from_repository
964 parent_ids = from_repo._find_parent_ids_of_revisions(revision_ids)
965 for stream_info in self._get_filtered_chk_streams(parent_ids):
950 yield stream_info966 yield stream_info
951 yield self._get_text_stream()967 yield self._get_text_stream()
952968
969 def get_stream_for_missing_keys(self, missing_keys):
970 # missing keys can only occur when we are byte copying and not
971 # translating (because translation means we don't send
972 # unreconstructable deltas ever).
973 missing_inventory_keys = set()
974 for key in missing_keys:
975 if key[0] != 'inventories':
976 raise AssertionError('The only missing keys we should'
977 ' be filling in are inventory keys, not %s'
978 % (key[0],))
979 missing_inventory_keys.add(key[1:])
980 if self._chk_id_roots or self._chk_p_id_roots:
981 raise AssertionError('Cannot call get_stream_for_missing_keys'
982 ' untill all of get_stream() has been consumed.')
983 # Yield the inventory stream, so we can find the chk stream
984 yield self._get_inventory_stream(missing_inventory_keys)
985 # We use the empty set for excluded_revision_ids, to make it clear that
986 # we want to transmit all referenced chk pages.
987 for stream_info in self._get_filtered_chk_streams(set()):
988 yield stream_info
989
953990
954class RepositoryFormatCHK1(RepositoryFormatPack):991class RepositoryFormatCHK1(RepositoryFormatPack):
955 """A hashed CHK+group compress pack repository."""992 """A hashed CHK+group compress pack repository."""
956993
957 repository_class = CHKInventoryRepository994 repository_class = CHKInventoryRepository
995 supports_external_lookups = True
958 supports_chks = True996 supports_chks = True
959 # For right now, setting this to True gives us InterModel1And2 rather997 # For right now, setting this to True gives us InterModel1And2 rather
960 # than InterDifferingSerializer998 # than InterDifferingSerializer
961999
=== modified file 'bzrlib/repofmt/pack_repo.py'
--- bzrlib/repofmt/pack_repo.py 2009-04-27 23:14:00 +0000
+++ bzrlib/repofmt/pack_repo.py 2009-05-29 10:35:21 +0000
@@ -268,10 +268,11 @@
268268
269 def __init__(self, name, revision_index, inventory_index, text_index,269 def __init__(self, name, revision_index, inventory_index, text_index,
270 signature_index, upload_transport, pack_transport, index_transport,270 signature_index, upload_transport, pack_transport, index_transport,
271 pack_collection):271 pack_collection, chk_index=None):
272 """Create a ResumedPack object."""272 """Create a ResumedPack object."""
273 ExistingPack.__init__(self, pack_transport, name, revision_index,273 ExistingPack.__init__(self, pack_transport, name, revision_index,
274 inventory_index, text_index, signature_index)274 inventory_index, text_index, signature_index,
275 chk_index=chk_index)
275 self.upload_transport = upload_transport276 self.upload_transport = upload_transport
276 self.index_transport = index_transport277 self.index_transport = index_transport
277 self.index_sizes = [None, None, None, None]278 self.index_sizes = [None, None, None, None]
@@ -281,6 +282,9 @@
281 ('text', text_index),282 ('text', text_index),
282 ('signature', signature_index),283 ('signature', signature_index),
283 ]284 ]
285 if chk_index is not None:
286 indices.append(('chk', chk_index))
287 self.index_sizes.append(None)
284 for index_type, index in indices:288 for index_type, index in indices:
285 offset = self.index_offset(index_type)289 offset = self.index_offset(index_type)
286 self.index_sizes[offset] = index._size290 self.index_sizes[offset] = index._size
@@ -301,6 +305,8 @@
301 self.upload_transport.delete(self.file_name())305 self.upload_transport.delete(self.file_name())
302 indices = [self.revision_index, self.inventory_index, self.text_index,306 indices = [self.revision_index, self.inventory_index, self.text_index,
303 self.signature_index]307 self.signature_index]
308 if self.chk_index is not None:
309 indices.append(self.chk_index)
304 for index in indices:310 for index in indices:
305 index._transport.delete(index._name)311 index._transport.delete(index._name)
306312
@@ -308,7 +314,10 @@
308 self._check_references()314 self._check_references()
309 new_name = '../packs/' + self.file_name()315 new_name = '../packs/' + self.file_name()
310 self.upload_transport.rename(self.file_name(), new_name)316 self.upload_transport.rename(self.file_name(), new_name)
311 for index_type in ['revision', 'inventory', 'text', 'signature']:317 index_types = ['revision', 'inventory', 'text', 'signature']
318 if self.chk_index is not None:
319 index_types.append('chk')
320 for index_type in index_types:
312 old_name = self.index_name(index_type, self.name)321 old_name = self.index_name(index_type, self.name)
313 new_name = '../indices/' + old_name322 new_name = '../indices/' + old_name
314 self.upload_transport.rename(old_name, new_name)323 self.upload_transport.rename(old_name, new_name)
@@ -316,6 +325,11 @@
316 self._state = 'finished'325 self._state = 'finished'
317326
318 def _get_external_refs(self, index):327 def _get_external_refs(self, index):
328 """Return compression parents for this index that are not present.
329
330 This returns any compression parents that are referenced by this index,
331 which are not contained *in* this index. They may be present elsewhere.
332 """
319 return index.external_references(1)333 return index.external_references(1)
320334
321335
@@ -1352,6 +1366,7 @@
1352 """1366 """
13531367
1354 pack_factory = NewPack1368 pack_factory = NewPack
1369 resumed_pack_factory = ResumedPack
13551370
1356 def __init__(self, repo, transport, index_transport, upload_transport,1371 def __init__(self, repo, transport, index_transport, upload_transport,
1357 pack_transport, index_builder_class, index_class,1372 pack_transport, index_builder_class, index_class,
@@ -1680,9 +1695,14 @@
1680 inv_index = self._make_index(name, '.iix', resume=True)1695 inv_index = self._make_index(name, '.iix', resume=True)
1681 txt_index = self._make_index(name, '.tix', resume=True)1696 txt_index = self._make_index(name, '.tix', resume=True)
1682 sig_index = self._make_index(name, '.six', resume=True)1697 sig_index = self._make_index(name, '.six', resume=True)
1683 result = ResumedPack(name, rev_index, inv_index, txt_index,1698 if self.chk_index is not None:
1684 sig_index, self._upload_transport, self._pack_transport,1699 chk_index = self._make_index(name, '.cix', resume=True)
1685 self._index_transport, self)1700 else:
1701 chk_index = None
1702 result = self.resumed_pack_factory(name, rev_index, inv_index,
1703 txt_index, sig_index, self._upload_transport,
1704 self._pack_transport, self._index_transport, self,
1705 chk_index=chk_index)
1686 except errors.NoSuchFile, e:1706 except errors.NoSuchFile, e:
1687 raise errors.UnresumableWriteGroup(self.repo, [name], str(e))1707 raise errors.UnresumableWriteGroup(self.repo, [name], str(e))
1688 self.add_pack_to_memory(result)1708 self.add_pack_to_memory(result)
@@ -1809,14 +1829,11 @@
1809 def reset(self):1829 def reset(self):
1810 """Clear all cached data."""1830 """Clear all cached data."""
1811 # cached revision data1831 # cached revision data
1812 self.repo._revision_knit = None
1813 self.revision_index.clear()1832 self.revision_index.clear()
1814 # cached signature data1833 # cached signature data
1815 self.repo._signature_knit = None
1816 self.signature_index.clear()1834 self.signature_index.clear()
1817 # cached file text data1835 # cached file text data
1818 self.text_index.clear()1836 self.text_index.clear()
1819 self.repo._text_knit = None
1820 # cached inventory data1837 # cached inventory data
1821 self.inventory_index.clear()1838 self.inventory_index.clear()
1822 # cached chk data1839 # cached chk data
@@ -2035,7 +2052,6 @@
2035 except KeyError:2052 except KeyError:
2036 pass2053 pass
2037 del self._resumed_packs[:]2054 del self._resumed_packs[:]
2038 self.repo._text_knit = None
20392055
2040 def _remove_resumed_pack_indices(self):2056 def _remove_resumed_pack_indices(self):
2041 for resumed_pack in self._resumed_packs:2057 for resumed_pack in self._resumed_packs:
@@ -2081,7 +2097,6 @@
2081 # when autopack takes no steps, the names list is still2097 # when autopack takes no steps, the names list is still
2082 # unsaved.2098 # unsaved.
2083 self._save_pack_names()2099 self._save_pack_names()
2084 self.repo._text_knit = None
20852100
2086 def _suspend_write_group(self):2101 def _suspend_write_group(self):
2087 tokens = [pack.name for pack in self._resumed_packs]2102 tokens = [pack.name for pack in self._resumed_packs]
@@ -2095,7 +2110,6 @@
2095 self._new_pack.abort()2110 self._new_pack.abort()
2096 self._new_pack = None2111 self._new_pack = None
2097 self._remove_resumed_pack_indices()2112 self._remove_resumed_pack_indices()
2098 self.repo._text_knit = None
2099 return tokens2113 return tokens
21002114
2101 def _resume_write_group(self, tokens):2115 def _resume_write_group(self, tokens):
@@ -2202,6 +2216,7 @@
2202 % (self._format, self.bzrdir.transport.base))2216 % (self._format, self.bzrdir.transport.base))
22032217
2204 def _abort_write_group(self):2218 def _abort_write_group(self):
2219 self.revisions._index._key_dependencies.refs.clear()
2205 self._pack_collection._abort_write_group()2220 self._pack_collection._abort_write_group()
22062221
2207 def _find_inconsistent_revision_parents(self):2222 def _find_inconsistent_revision_parents(self):
@@ -2262,11 +2277,13 @@
2262 self._pack_collection._start_write_group()2277 self._pack_collection._start_write_group()
22632278
2264 def _commit_write_group(self):2279 def _commit_write_group(self):
2280 self.revisions._index._key_dependencies.refs.clear()
2265 return self._pack_collection._commit_write_group()2281 return self._pack_collection._commit_write_group()
22662282
2267 def suspend_write_group(self):2283 def suspend_write_group(self):
2268 # XXX check self._write_group is self.get_transaction()?2284 # XXX check self._write_group is self.get_transaction()?
2269 tokens = self._pack_collection._suspend_write_group()2285 tokens = self._pack_collection._suspend_write_group()
2286 self.revisions._index._key_dependencies.refs.clear()
2270 self._write_group = None2287 self._write_group = None
2271 return tokens2288 return tokens
22722289
@@ -2295,10 +2312,10 @@
2295 self._write_lock_count += 12312 self._write_lock_count += 1
2296 if self._write_lock_count == 1:2313 if self._write_lock_count == 1:
2297 self._transaction = transactions.WriteTransaction()2314 self._transaction = transactions.WriteTransaction()
2315 if not locked:
2298 for repo in self._fallback_repositories:2316 for repo in self._fallback_repositories:
2299 # Writes don't affect fallback repos2317 # Writes don't affect fallback repos
2300 repo.lock_read()2318 repo.lock_read()
2301 if not locked:
2302 self._refresh_data()2319 self._refresh_data()
23032320
2304 def lock_read(self):2321 def lock_read(self):
@@ -2307,10 +2324,9 @@
2307 self._write_lock_count += 12324 self._write_lock_count += 1
2308 else:2325 else:
2309 self.control_files.lock_read()2326 self.control_files.lock_read()
2327 if not locked:
2310 for repo in self._fallback_repositories:2328 for repo in self._fallback_repositories:
2311 # Writes don't affect fallback repos
2312 repo.lock_read()2329 repo.lock_read()
2313 if not locked:
2314 self._refresh_data()2330 self._refresh_data()
23152331
2316 def leave_lock_in_place(self):2332 def leave_lock_in_place(self):
@@ -2356,10 +2372,10 @@
2356 transaction = self._transaction2372 transaction = self._transaction
2357 self._transaction = None2373 self._transaction = None
2358 transaction.finish()2374 transaction.finish()
2359 for repo in self._fallback_repositories:
2360 repo.unlock()
2361 else:2375 else:
2362 self.control_files.unlock()2376 self.control_files.unlock()
2377
2378 if not self.is_locked():
2363 for repo in self._fallback_repositories:2379 for repo in self._fallback_repositories:
2364 repo.unlock()2380 repo.unlock()
23652381
23662382
=== modified file 'bzrlib/repository.py'
--- bzrlib/repository.py 2009-05-12 04:54:04 +0000
+++ bzrlib/repository.py 2009-05-29 10:35:21 +0000
@@ -969,6 +969,10 @@
969 """969 """
970 if not self._format.supports_external_lookups:970 if not self._format.supports_external_lookups:
971 raise errors.UnstackableRepositoryFormat(self._format, self.base)971 raise errors.UnstackableRepositoryFormat(self._format, self.base)
972 if self.is_locked():
973 # This repository will call fallback.unlock() when we transition to
974 # the unlocked state, so we make sure to increment the lock count
975 repository.lock_read()
972 self._check_fallback_repository(repository)976 self._check_fallback_repository(repository)
973 self._fallback_repositories.append(repository)977 self._fallback_repositories.append(repository)
974 self.texts.add_fallback_versioned_files(repository.texts)978 self.texts.add_fallback_versioned_files(repository.texts)
@@ -1240,19 +1244,19 @@
1240 """1244 """
1241 locked = self.is_locked()1245 locked = self.is_locked()
1242 result = self.control_files.lock_write(token=token)1246 result = self.control_files.lock_write(token=token)
1243 for repo in self._fallback_repositories:
1244 # Writes don't affect fallback repos
1245 repo.lock_read()
1246 if not locked:1247 if not locked:
1248 for repo in self._fallback_repositories:
1249 # Writes don't affect fallback repos
1250 repo.lock_read()
1247 self._refresh_data()1251 self._refresh_data()
1248 return result1252 return result
12491253
1250 def lock_read(self):1254 def lock_read(self):
1251 locked = self.is_locked()1255 locked = self.is_locked()
1252 self.control_files.lock_read()1256 self.control_files.lock_read()
1253 for repo in self._fallback_repositories:
1254 repo.lock_read()
1255 if not locked:1257 if not locked:
1258 for repo in self._fallback_repositories:
1259 repo.lock_read()
1256 self._refresh_data()1260 self._refresh_data()
12571261
1258 def get_physical_lock_status(self):1262 def get_physical_lock_status(self):
@@ -1424,7 +1428,7 @@
1424 def suspend_write_group(self):1428 def suspend_write_group(self):
1425 raise errors.UnsuspendableWriteGroup(self)1429 raise errors.UnsuspendableWriteGroup(self)
14261430
1427 def get_missing_parent_inventories(self):1431 def get_missing_parent_inventories(self, check_for_missing_texts=True):
1428 """Return the keys of missing inventory parents for revisions added in1432 """Return the keys of missing inventory parents for revisions added in
1429 this write group.1433 this write group.
14301434
@@ -1439,7 +1443,7 @@
1439 return set()1443 return set()
1440 if not self.is_in_write_group():1444 if not self.is_in_write_group():
1441 raise AssertionError('not in a write group')1445 raise AssertionError('not in a write group')
1442 1446
1443 # XXX: We assume that every added revision already has its1447 # XXX: We assume that every added revision already has its
1444 # corresponding inventory, so we only check for parent inventories that1448 # corresponding inventory, so we only check for parent inventories that
1445 # might be missing, rather than all inventories.1449 # might be missing, rather than all inventories.
@@ -1448,9 +1452,12 @@
1448 unstacked_inventories = self.inventories._index1452 unstacked_inventories = self.inventories._index
1449 present_inventories = unstacked_inventories.get_parent_map(1453 present_inventories = unstacked_inventories.get_parent_map(
1450 key[-1:] for key in parents)1454 key[-1:] for key in parents)
1451 if len(parents.difference(present_inventories)) == 0:1455 parents.difference_update(present_inventories)
1456 if len(parents) == 0:
1452 # No missing parent inventories.1457 # No missing parent inventories.
1453 return set()1458 return set()
1459 if not check_for_missing_texts:
1460 return set(('inventories', rev_id) for (rev_id,) in parents)
1454 # Ok, now we have a list of missing inventories. But these only matter1461 # Ok, now we have a list of missing inventories. But these only matter
1455 # if the inventories that reference them are missing some texts they1462 # if the inventories that reference them are missing some texts they
1456 # appear to introduce.1463 # appear to introduce.
@@ -1577,8 +1584,8 @@
1577 self.control_files.unlock()1584 self.control_files.unlock()
1578 if self.control_files._lock_count == 0:1585 if self.control_files._lock_count == 0:
1579 self._inventory_entry_cache.clear()1586 self._inventory_entry_cache.clear()
1580 for repo in self._fallback_repositories:1587 for repo in self._fallback_repositories:
1581 repo.unlock()1588 repo.unlock()
15821589
1583 @needs_read_lock1590 @needs_read_lock
1584 def clone(self, a_bzrdir, revision_id=None):1591 def clone(self, a_bzrdir, revision_id=None):
@@ -4003,18 +4010,20 @@
4003 try:4010 try:
4004 if resume_tokens:4011 if resume_tokens:
4005 self.target_repo.resume_write_group(resume_tokens)4012 self.target_repo.resume_write_group(resume_tokens)
4013 is_resume = True
4006 else:4014 else:
4007 self.target_repo.start_write_group()4015 self.target_repo.start_write_group()
4016 is_resume = False
4008 try:4017 try:
4009 # locked_insert_stream performs a commit|suspend.4018 # locked_insert_stream performs a commit|suspend.
4010 return self._locked_insert_stream(stream, src_format)4019 return self._locked_insert_stream(stream, src_format, is_resume)
4011 except:4020 except:
4012 self.target_repo.abort_write_group(suppress_errors=True)4021 self.target_repo.abort_write_group(suppress_errors=True)
4013 raise4022 raise
4014 finally:4023 finally:
4015 self.target_repo.unlock()4024 self.target_repo.unlock()
40164025
4017 def _locked_insert_stream(self, stream, src_format):4026 def _locked_insert_stream(self, stream, src_format, is_resume):
4018 to_serializer = self.target_repo._format._serializer4027 to_serializer = self.target_repo._format._serializer
4019 src_serializer = src_format._serializer4028 src_serializer = src_format._serializer
4020 new_pack = None4029 new_pack = None
@@ -4070,14 +4079,18 @@
4070 if new_pack is not None:4079 if new_pack is not None:
4071 new_pack._write_data('', flush=True)4080 new_pack._write_data('', flush=True)
4072 # Find all the new revisions (including ones from resume_tokens)4081 # Find all the new revisions (including ones from resume_tokens)
4073 missing_keys = self.target_repo.get_missing_parent_inventories()4082 missing_keys = self.target_repo.get_missing_parent_inventories(
4083 check_for_missing_texts=is_resume)
4074 try:4084 try:
4075 for prefix, versioned_file in (4085 for prefix, versioned_file in (
4076 ('texts', self.target_repo.texts),4086 ('texts', self.target_repo.texts),
4077 ('inventories', self.target_repo.inventories),4087 ('inventories', self.target_repo.inventories),
4078 ('revisions', self.target_repo.revisions),4088 ('revisions', self.target_repo.revisions),
4079 ('signatures', self.target_repo.signatures),4089 ('signatures', self.target_repo.signatures),
4090 ('chk_bytes', self.target_repo.chk_bytes),
4080 ):4091 ):
4092 if versioned_file is None:
4093 continue
4081 missing_keys.update((prefix,) + key for key in4094 missing_keys.update((prefix,) + key for key in
4082 versioned_file.get_missing_compression_parent_keys())4095 versioned_file.get_missing_compression_parent_keys())
4083 except NotImplementedError:4096 except NotImplementedError:
@@ -4230,6 +4243,7 @@
4230 keys['texts'] = set()4243 keys['texts'] = set()
4231 keys['revisions'] = set()4244 keys['revisions'] = set()
4232 keys['inventories'] = set()4245 keys['inventories'] = set()
4246 keys['chk_bytes'] = set()
4233 keys['signatures'] = set()4247 keys['signatures'] = set()
4234 for key in missing_keys:4248 for key in missing_keys:
4235 keys[key[0]].add(key[1:])4249 keys[key[0]].add(key[1:])
@@ -4242,6 +4256,13 @@
4242 keys['revisions'],))4256 keys['revisions'],))
4243 for substream_kind, keys in keys.iteritems():4257 for substream_kind, keys in keys.iteritems():
4244 vf = getattr(self.from_repository, substream_kind)4258 vf = getattr(self.from_repository, substream_kind)
4259 if vf is None and keys:
4260 raise AssertionError(
4261 "cannot fill in keys for a versioned file we don't"
4262 " have: %s needs %s" % (substream_kind, keys))
4263 if not keys:
4264 # No need to stream something we don't have
4265 continue
4245 # Ask for full texts always so that we don't need more round trips4266 # Ask for full texts always so that we don't need more round trips
4246 # after this stream.4267 # after this stream.
4247 stream = vf.get_record_stream(keys,4268 stream = vf.get_record_stream(keys,
42484269
=== modified file 'bzrlib/tests/per_repository/test_fileid_involved.py'
--- bzrlib/tests/per_repository/test_fileid_involved.py 2009-03-23 14:59:43 +0000
+++ bzrlib/tests/per_repository/test_fileid_involved.py 2009-05-29 10:35:21 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2005 Canonical Ltd1# Copyright (C) 2005, 2009 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
@@ -16,7 +16,12 @@
1616
17import os17import os
18import sys18import sys
19import time
1920
21from bzrlib import (
22 revision as _mod_revision,
23 tests,
24 )
20from bzrlib.errors import IllegalPath, NonAsciiRevisionId25from bzrlib.errors import IllegalPath, NonAsciiRevisionId
21from bzrlib.tests import TestSkipped26from bzrlib.tests import TestSkipped
22from bzrlib.tests.per_repository.test_repository import TestCaseWithRepository27from bzrlib.tests.per_repository.test_repository import TestCaseWithRepository
@@ -49,11 +54,11 @@
49 super(TestFileIdInvolved, self).setUp()54 super(TestFileIdInvolved, self).setUp()
50 # create three branches, and merge it55 # create three branches, and merge it
51 #56 #
52 # /-->J ------>K (branch2)57 # ,-->J------>K (branch2)
53 # / \58 # / \
54 # A ---> B --->C ---->D->G (main)59 # A --->B --->C---->D-->G (main)
55 # \ / /60 # \ / /
56 # \---> E---/----> F (branch1)61 # '--->E---+---->F (branch1)
5762
58 # A changes:63 # A changes:
59 # B changes: 'a-file-id-2006-01-01-abcd'64 # B changes: 'a-file-id-2006-01-01-abcd'
@@ -137,8 +142,6 @@
137 self.branch = main_branch142 self.branch = main_branch
138143
139 def test_fileids_altered_between_two_revs(self):144 def test_fileids_altered_between_two_revs(self):
140 def foo(old, new):
141 print set(self.branch.repository.get_ancestry(new)).difference(set(self.branch.repository.get_ancestry(old)))
142 self.branch.lock_read()145 self.branch.lock_read()
143 self.addCleanup(self.branch.unlock)146 self.addCleanup(self.branch.unlock)
144 self.branch.repository.fileids_altered_by_revision_ids(["rev-J","rev-K"])147 self.branch.repository.fileids_altered_by_revision_ids(["rev-J","rev-K"])
@@ -295,7 +298,7 @@
295 self.branch = main_branch298 self.branch = main_branch
296299
297 def test_fileid_involved_full_compare2(self):300 def test_fileid_involved_full_compare2(self):
298 # this tests that fileids_alteted_by_revision_ids returns301 # this tests that fileids_altered_by_revision_ids returns
299 # more information than compare_tree can, because it302 # more information than compare_tree can, because it
300 # sees each change rather than the aggregate delta.303 # sees each change rather than the aggregate delta.
301 self.branch.lock_read()304 self.branch.lock_read()
@@ -315,6 +318,73 @@
315 self.assertSubset(l2, l1)318 self.assertSubset(l2, l1)
316319
317320
321class FileIdInvolvedWGhosts(TestCaseWithRepository):
322
323 def create_branch_with_ghost_text(self):
324 builder = self.make_branch_builder('ghost')
325 builder.build_snapshot('A-id', None, [
326 ('add', ('', 'root-id', 'directory', None)),
327 ('add', ('a', 'a-file-id', 'file', 'some content\n'))])
328 b = builder.get_branch()
329 old_rt = b.repository.revision_tree('A-id')
330 new_inv = old_rt.inventory._get_mutable_inventory()
331 new_inv.revision_id = 'B-id'
332 new_inv['a-file-id'].revision = 'ghost-id'
333 new_rev = _mod_revision.Revision('B-id',
334 timestamp=time.time(),
335 timezone=0,
336 message='Committing against a ghost',
337 committer='Joe Foo <joe@foo.com>',
338 properties={},
339 parent_ids=('A-id', 'ghost-id'),
340 )
341 b.lock_write()
342 self.addCleanup(b.unlock)
343 b.repository.start_write_group()
344 b.repository.add_revision('B-id', new_rev, new_inv)
345 b.repository.commit_write_group()
346 return b
347
348 def test_file_ids_include_ghosts(self):
349 b = self.create_branch_with_ghost_text()
350 repo = b.repository
351 self.assertEqual(
352 {'a-file-id':set(['ghost-id'])},
353 repo.fileids_altered_by_revision_ids(['B-id']))
354
355 def test_file_ids_uses_fallbacks(self):
356 builder = self.make_branch_builder('source',
357 format=self.bzrdir_format)
358 repo = builder.get_branch().repository
359 if not repo._format.supports_external_lookups:
360 raise tests.TestNotApplicable('format does not support stacking')
361 builder.start_series()
362 builder.build_snapshot('A-id', None, [
363 ('add', ('', 'root-id', 'directory', None)),
364 ('add', ('file', 'file-id', 'file', 'contents\n'))])
365 builder.build_snapshot('B-id', ['A-id'], [
366 ('modify', ('file-id', 'new-content\n'))])
367 builder.build_snapshot('C-id', ['B-id'], [
368 ('modify', ('file-id', 'yet more content\n'))])
369 builder.finish_series()
370 source_b = builder.get_branch()
371 source_b.lock_read()
372 self.addCleanup(source_b.unlock)
373 base = self.make_branch('base')
374 base.pull(source_b, stop_revision='B-id')
375 stacked = self.make_branch('stacked')
376 stacked.set_stacked_on_url('../base')
377 stacked.pull(source_b, stop_revision='C-id')
378
379 stacked.lock_read()
380 self.addCleanup(stacked.unlock)
381 repo = stacked.repository
382 keys = {'file-id': set(['A-id'])}
383 if stacked.repository.supports_rich_root():
384 keys['root-id'] = set(['A-id'])
385 self.assertEqual(keys, repo.fileids_altered_by_revision_ids(['A-id']))
386
387
318def set_executability(wt, path, executable=True):388def set_executability(wt, path, executable=True):
319 """Set the executable bit for the file at path in the working tree389 """Set the executable bit for the file at path in the working tree
320390
321391
=== modified file 'bzrlib/tests/per_repository/test_write_group.py'
--- bzrlib/tests/per_repository/test_write_group.py 2009-05-12 09:05:30 +0000
+++ bzrlib/tests/per_repository/test_write_group.py 2009-05-29 10:35:21 +0000
@@ -18,7 +18,15 @@
1818
19import sys19import sys
2020
21from bzrlib import bzrdir, errors, graph, memorytree, remote21from bzrlib import (
22 bzrdir,
23 errors,
24 graph,
25 memorytree,
26 osutils,
27 remote,
28 versionedfile,
29 )
22from bzrlib.branch import BzrBranchFormat730from bzrlib.branch import BzrBranchFormat7
23from bzrlib.inventory import InventoryDirectory31from bzrlib.inventory import InventoryDirectory
24from bzrlib.transport import local, memory32from bzrlib.transport import local, memory
@@ -240,9 +248,9 @@
240 inventory) in it must have all the texts in its inventory (even if not248 inventory) in it must have all the texts in its inventory (even if not
241 changed w.r.t. to the absent parent), otherwise it will report missing249 changed w.r.t. to the absent parent), otherwise it will report missing
242 texts/parent inventory.250 texts/parent inventory.
243 251
244 The core of this test is that a file was changed in rev-1, but in a252 The core of this test is that a file was changed in rev-1, but in a
245 stacked repo that only has rev-2 253 stacked repo that only has rev-2
246 """254 """
247 # Make a trunk with one commit.255 # Make a trunk with one commit.
248 trunk_repo = self.make_stackable_repo()256 trunk_repo = self.make_stackable_repo()
@@ -284,6 +292,69 @@
284 set(), reopened_repo.get_missing_parent_inventories())292 set(), reopened_repo.get_missing_parent_inventories())
285 reopened_repo.abort_write_group()293 reopened_repo.abort_write_group()
286294
295 def test_get_missing_parent_inventories_check(self):
296 builder = self.make_branch_builder('test')
297 builder.build_snapshot('A-id', ['ghost-parent-id'], [
298 ('add', ('', 'root-id', 'directory', None)),
299 ('add', ('file', 'file-id', 'file', 'content\n'))],
300 allow_leftmost_as_ghost=True)
301 b = builder.get_branch()
302 b.lock_read()
303 self.addCleanup(b.unlock)
304 repo = self.make_repository('test-repo')
305 repo.lock_write()
306 self.addCleanup(repo.unlock)
307 repo.start_write_group()
308 self.addCleanup(repo.abort_write_group)
309 # Now, add the objects manually
310 text_keys = [('file-id', 'A-id')]
311 if repo.supports_rich_root():
312 text_keys.append(('root-id', 'A-id'))
313 # Directly add the texts, inventory, and revision object for 'A-id'
314 repo.texts.insert_record_stream(b.repository.texts.get_record_stream(
315 text_keys, 'unordered', True))
316 repo.add_revision('A-id', b.repository.get_revision('A-id'),
317 b.repository.get_inventory('A-id'))
318 get_missing = repo.get_missing_parent_inventories
319 if repo._format.supports_external_lookups:
320 self.assertEqual(set([('inventories', 'ghost-parent-id')]),
321 get_missing(check_for_missing_texts=False))
322 self.assertEqual(set(), get_missing(check_for_missing_texts=True))
323 self.assertEqual(set(), get_missing())
324 else:
325 # If we don't support external lookups, we always return empty
326 self.assertEqual(set(), get_missing(check_for_missing_texts=False))
327 self.assertEqual(set(), get_missing(check_for_missing_texts=True))
328 self.assertEqual(set(), get_missing())
329
330 def test_insert_stream_passes_resume_info(self):
331 repo = self.make_repository('test-repo')
332 if not repo._format.supports_external_lookups:
333 raise TestNotApplicable('only valid in resumable repos')
334 # log calls to get_missing_parent_inventories, so that we can assert it
335 # is called with the correct parameters
336 call_log = []
337 orig = repo.get_missing_parent_inventories
338 def get_missing(check_for_missing_texts=True):
339 call_log.append(check_for_missing_texts)
340 return orig(check_for_missing_texts=check_for_missing_texts)
341 repo.get_missing_parent_inventories = get_missing
342 repo.lock_write()
343 self.addCleanup(repo.unlock)
344 sink = repo._get_sink()
345 sink.insert_stream((), repo._format, [])
346 self.assertEqual([False], call_log)
347 del call_log[:]
348 repo.start_write_group()
349 # We need to insert something, or suspend_write_group won't actually
350 # create a token
351 repo.texts.insert_record_stream([versionedfile.FulltextContentFactory(
352 ('file-id', 'rev-id'), (), None, 'lines\n')])
353 tokens = repo.suspend_write_group()
354 self.assertNotEqual([], tokens)
355 sink.insert_stream((), repo._format, tokens)
356 self.assertEqual([True], call_log)
357
287358
288class TestResumeableWriteGroup(TestCaseWithRepository):359class TestResumeableWriteGroup(TestCaseWithRepository):
289360
@@ -518,9 +589,12 @@
518 source_repo.start_write_group()589 source_repo.start_write_group()
519 key_base = ('file-id', 'base')590 key_base = ('file-id', 'base')
520 key_delta = ('file-id', 'delta')591 key_delta = ('file-id', 'delta')
521 source_repo.texts.add_lines(key_base, (), ['lines\n'])592 def text_stream():
522 source_repo.texts.add_lines(593 yield versionedfile.FulltextContentFactory(
523 key_delta, (key_base,), ['more\n', 'lines\n'])594 key_base, (), None, 'lines\n')
595 yield versionedfile.FulltextContentFactory(
596 key_delta, (key_base,), None, 'more\nlines\n')
597 source_repo.texts.insert_record_stream(text_stream())
524 source_repo.commit_write_group()598 source_repo.commit_write_group()
525 return source_repo599 return source_repo
526600
@@ -536,9 +610,20 @@
536 stream = source_repo.texts.get_record_stream(610 stream = source_repo.texts.get_record_stream(
537 [key_delta], 'unordered', False)611 [key_delta], 'unordered', False)
538 repo.texts.insert_record_stream(stream)612 repo.texts.insert_record_stream(stream)
539 # It's not commitable due to the missing compression parent.613 # It's either not commitable due to the missing compression parent, or
540 self.assertRaises(614 # the stacked location has already filled in the fulltext.
541 errors.BzrCheckError, repo.commit_write_group)615 try:
616 repo.commit_write_group()
617 except errors.BzrCheckError:
618 # It refused to commit because we have a missing parent
619 pass
620 else:
621 same_repo = self.reopen_repo(repo)
622 same_repo.lock_read()
623 record = same_repo.texts.get_record_stream([key_delta],
624 'unordered', True).next()
625 self.assertEqual('more\nlines\n', record.get_bytes_as('fulltext'))
626 return
542 # Merely suspending and resuming doesn't make it commitable either.627 # Merely suspending and resuming doesn't make it commitable either.
543 wg_tokens = repo.suspend_write_group()628 wg_tokens = repo.suspend_write_group()
544 same_repo = self.reopen_repo(repo)629 same_repo = self.reopen_repo(repo)
@@ -570,8 +655,19 @@
570 same_repo.texts.insert_record_stream(stream)655 same_repo.texts.insert_record_stream(stream)
571 # Just like if we'd added that record without a suspend/resume cycle,656 # Just like if we'd added that record without a suspend/resume cycle,
572 # commit_write_group fails.657 # commit_write_group fails.
573 self.assertRaises(658 try:
574 errors.BzrCheckError, same_repo.commit_write_group)659 same_repo.commit_write_group()
660 except errors.BzrCheckError:
661 pass
662 else:
663 # If the commit_write_group didn't fail, that is because the
664 # insert_record_stream already gave it a fulltext.
665 same_repo = self.reopen_repo(repo)
666 same_repo.lock_read()
667 record = same_repo.texts.get_record_stream([key_delta],
668 'unordered', True).next()
669 self.assertEqual('more\nlines\n', record.get_bytes_as('fulltext'))
670 return
575 same_repo.abort_write_group()671 same_repo.abort_write_group()
576672
577 def test_add_missing_parent_after_resume(self):673 def test_add_missing_parent_after_resume(self):
578674
=== modified file 'bzrlib/tests/per_repository_reference/__init__.py'
--- bzrlib/tests/per_repository_reference/__init__.py 2009-03-23 14:59:43 +0000
+++ bzrlib/tests/per_repository_reference/__init__.py 2009-05-29 10:35:21 +0000
@@ -97,6 +97,9 @@
97 'bzrlib.tests.per_repository_reference.test_break_lock',97 'bzrlib.tests.per_repository_reference.test_break_lock',
98 'bzrlib.tests.per_repository_reference.test_check',98 'bzrlib.tests.per_repository_reference.test_check',
99 'bzrlib.tests.per_repository_reference.test_default_stacking',99 'bzrlib.tests.per_repository_reference.test_default_stacking',
100 'bzrlib.tests.per_repository_reference.test_fetch',
101 'bzrlib.tests.per_repository_reference.test_initialize',
102 'bzrlib.tests.per_repository_reference.test_unlock',
100 ]103 ]
101 # Parameterize per_repository_reference test modules by format.104 # Parameterize per_repository_reference test modules by format.
102 standard_tests.addTests(loader.loadTestsFromModuleNames(module_list))105 standard_tests.addTests(loader.loadTestsFromModuleNames(module_list))
103106
=== modified file 'bzrlib/tests/per_repository_reference/test_default_stacking.py'
--- bzrlib/tests/per_repository_reference/test_default_stacking.py 2009-03-23 14:59:43 +0000
+++ bzrlib/tests/per_repository_reference/test_default_stacking.py 2009-05-29 10:35:21 +0000
@@ -21,19 +21,13 @@
2121
22class TestDefaultStackingPolicy(TestCaseWithRepository):22class TestDefaultStackingPolicy(TestCaseWithRepository):
2323
24 # XXX: this helper probably belongs on TestCaseWithTransport
25 def make_smart_server(self, path):
26 smart_server = server.SmartTCPServer_for_testing()
27 smart_server.setUp(self.get_server())
28 return smart_server.get_url() + path
29
30 def test_sprout_to_smart_server_stacking_policy_handling(self):24 def test_sprout_to_smart_server_stacking_policy_handling(self):
31 """Obey policy where possible, ignore otherwise."""25 """Obey policy where possible, ignore otherwise."""
32 stack_on = self.make_branch('stack-on')26 stack_on = self.make_branch('stack-on')
33 parent_bzrdir = self.make_bzrdir('.', format='default')27 parent_bzrdir = self.make_bzrdir('.', format='default')
34 parent_bzrdir.get_config().set_default_stack_on('stack-on')28 parent_bzrdir.get_config().set_default_stack_on('stack-on')
35 source = self.make_branch('source')29 source = self.make_branch('source')
36 url = self.make_smart_server('target')30 url = self.make_smart_server('target').abspath('')
37 target = source.bzrdir.sprout(url).open_branch()31 target = source.bzrdir.sprout(url).open_branch()
38 self.assertEqual('../stack-on', target.get_stacked_on_url())32 self.assertEqual('../stack-on', target.get_stacked_on_url())
39 self.assertEqual(33 self.assertEqual(
4034
=== added file 'bzrlib/tests/per_repository_reference/test_fetch.py'
--- bzrlib/tests/per_repository_reference/test_fetch.py 1970-01-01 00:00:00 +0000
+++ bzrlib/tests/per_repository_reference/test_fetch.py 2009-05-29 10:35:21 +0000
@@ -0,0 +1,101 @@
1# Copyright (C) 2009 Canonical Ltd
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17
18from bzrlib.smart import server
19from bzrlib.tests.per_repository import TestCaseWithRepository
20
21
22class TestFetch(TestCaseWithRepository):
23
24 def make_source_branch(self):
25 # It would be nice if there was a way to force this to be memory-only
26 builder = self.make_branch_builder('source')
27 content = ['content lines\n'
28 'for the first revision\n'
29 'which is a marginal amount of content\n'
30 ]
31 builder.start_series()
32 builder.build_snapshot('A-id', None, [
33 ('add', ('', 'root-id', 'directory', None)),
34 ('add', ('a', 'a-id', 'file', ''.join(content))),
35 ])
36 content.append('and some more lines for B\n')
37 builder.build_snapshot('B-id', ['A-id'], [
38 ('modify', ('a-id', ''.join(content)))])
39 content.append('and yet even more content for C\n')
40 builder.build_snapshot('C-id', ['B-id'], [
41 ('modify', ('a-id', ''.join(content)))])
42 builder.finish_series()
43 source_b = builder.get_branch()
44 source_b.lock_read()
45 self.addCleanup(source_b.unlock)
46 return content, source_b
47
48 def test_sprout_from_stacked_with_short_history(self):
49 content, source_b = self.make_source_branch()
50 # Split the generated content into a base branch, and a stacked branch
51 # Use 'make_branch' which gives us a bzr:// branch when appropriate,
52 # rather than creating a branch-on-disk
53 stack_b = self.make_branch('stack-on')
54 stack_b.pull(source_b, stop_revision='B-id')
55 target_b = self.make_branch('target')
56 target_b.set_stacked_on_url('../stack-on')
57 target_b.pull(source_b, stop_revision='C-id')
58 # At this point, we should have a target branch, with 1 revision, on
59 # top of the source.
60 final_b = self.make_branch('final')
61 final_b.pull(target_b)
62 final_b.lock_read()
63 self.addCleanup(final_b.unlock)
64 self.assertEqual('C-id', final_b.last_revision())
65 text_keys = [('a-id', 'A-id'), ('a-id', 'B-id'), ('a-id', 'C-id')]
66 stream = final_b.repository.texts.get_record_stream(text_keys,
67 'unordered', True)
68 records = sorted([(r.key, r.get_bytes_as('fulltext')) for r in stream])
69 self.assertEqual([
70 (('a-id', 'A-id'), ''.join(content[:-2])),
71 (('a-id', 'B-id'), ''.join(content[:-1])),
72 (('a-id', 'C-id'), ''.join(content)),
73 ], records)
74
75 def test_sprout_from_smart_stacked_with_short_history(self):
76 content, source_b = self.make_source_branch()
77 transport = self.make_smart_server('server')
78 transport.ensure_base()
79 url = transport.abspath('')
80 stack_b = source_b.bzrdir.sprout(url + '/stack-on', revision_id='B-id')
81 # self.make_branch only takes relative paths, so we do it the 'hard'
82 # way
83 target_transport = transport.clone('target')
84 target_transport.ensure_base()
85 target_bzrdir = self.bzrdir_format.initialize_on_transport(
86 target_transport)
87 target_bzrdir.create_repository()
88 target_b = target_bzrdir.create_branch()
89 target_b.set_stacked_on_url('../stack-on')
90 target_b.pull(source_b, stop_revision='C-id')
91 # Now we should be able to branch from the remote location to a local
92 # location
93 final_b = target_b.bzrdir.sprout('final').open_branch()
94 self.assertEqual('C-id', final_b.last_revision())
95
96 # bzrdir.sprout() has slightly different code paths if you supply a
97 # revision_id versus not. If you supply revision_id, then you get a
98 # PendingAncestryResult for the search, versus a SearchResult...
99 final2_b = target_b.bzrdir.sprout('final2',
100 revision_id='C-id').open_branch()
101 self.assertEqual('C-id', final_b.last_revision())
0102
=== added file 'bzrlib/tests/per_repository_reference/test_initialize.py'
--- bzrlib/tests/per_repository_reference/test_initialize.py 1970-01-01 00:00:00 +0000
+++ bzrlib/tests/per_repository_reference/test_initialize.py 2009-05-29 10:35:21 +0000
@@ -0,0 +1,59 @@
1# Copyright (C) 2009 Canonical Ltd
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17"""Tests for initializing a repository with external references."""
18
19
20from bzrlib import (
21 errors,
22 tests,
23 )
24from bzrlib.tests.per_repository_reference import (
25 TestCaseWithExternalReferenceRepository,
26 )
27
28
29class TestInitialize(TestCaseWithExternalReferenceRepository):
30
31 def initialize_and_check_on_transport(self, base, trans):
32 network_name = base.repository._format.network_name()
33 result = self.bzrdir_format.initialize_on_transport_ex(
34 trans, use_existing_dir=False, create_prefix=False,
35 stacked_on='../base', stack_on_pwd=base.base,
36 repo_format_name=network_name)
37 result_repo, a_bzrdir, require_stacking, repo_policy = result
38 self.addCleanup(result_repo.unlock)
39 self.assertEqual(1, len(result_repo._fallback_repositories))
40 return result_repo
41
42 def test_initialize_on_transport_ex(self):
43 base = self.make_branch('base')
44 trans = self.get_transport('stacked')
45 repo = self.initialize_and_check_on_transport(base, trans)
46 self.assertEqual(base.repository._format.network_name(),
47 repo._format.network_name())
48
49 def test_remote_initialize_on_transport_ex(self):
50 # All formats can be initialized appropriately over bzr://
51 base = self.make_branch('base')
52 trans = self.make_smart_server('stacked')
53 repo = self.initialize_and_check_on_transport(base, trans)
54 network_name = base.repository._format.network_name()
55 if network_name != repo._format.network_name():
56 raise tests.KnownFailure('Remote initialize_on_transport_ex()'
57 ' tries to "upgrade" the format because it doesn\'t have a'
58 ' branch format, and hard-codes the new repository format.')
59 self.assertEqual(network_name, repo._format.network_name())
060
=== added file 'bzrlib/tests/per_repository_reference/test_unlock.py'
--- bzrlib/tests/per_repository_reference/test_unlock.py 1970-01-01 00:00:00 +0000
+++ bzrlib/tests/per_repository_reference/test_unlock.py 2009-05-29 10:35:21 +0000
@@ -0,0 +1,76 @@
1# Copyright (C) 2009 Canonical Ltd
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17"""Tests for locking/unlocking a repository with external references."""
18
19from bzrlib import (
20 branch,
21 errors,
22 )
23from bzrlib.tests.per_repository_reference import (
24 TestCaseWithExternalReferenceRepository,
25 )
26
27
28class TestUnlock(TestCaseWithExternalReferenceRepository):
29
30 def create_stacked_branch(self):
31 builder = self.make_branch_builder('source',
32 format=self.bzrdir_format)
33 builder.start_series()
34 repo = builder.get_branch().repository
35 if not repo._format.supports_external_lookups:
36 raise tests.TestNotApplicable('format does not support stacking')
37 builder.build_snapshot('A-id', None, [
38 ('add', ('', 'root-id', 'directory', None)),
39 ('add', ('file', 'file-id', 'file', 'contents\n'))])
40 builder.build_snapshot('B-id', ['A-id'], [
41 ('modify', ('file-id', 'new-content\n'))])
42 builder.build_snapshot('C-id', ['B-id'], [
43 ('modify', ('file-id', 'yet more content\n'))])
44 builder.finish_series()
45 source_b = builder.get_branch()
46 source_b.lock_read()
47 self.addCleanup(source_b.unlock)
48 base = self.make_branch('base')
49 base.pull(source_b, stop_revision='B-id')
50 stacked = self.make_branch('stacked')
51 stacked.set_stacked_on_url('../base')
52 stacked.pull(source_b, stop_revision='C-id')
53
54 return base, stacked
55
56 def test_unlock_unlocks_fallback(self):
57 base = self.make_branch('base')
58 stacked = self.make_branch('stacked')
59 repo = stacked.repository
60 stacked.set_stacked_on_url('../base')
61 self.assertEqual(1, len(repo._fallback_repositories))
62 fallback_repo = repo._fallback_repositories[0]
63 self.assertFalse(repo.is_locked())
64 self.assertFalse(fallback_repo.is_locked())
65 repo.lock_read()
66 self.assertTrue(repo.is_locked())
67 self.assertTrue(fallback_repo.is_locked())
68 repo.unlock()
69 self.assertFalse(repo.is_locked())
70 self.assertFalse(fallback_repo.is_locked())
71 repo.lock_write()
72 self.assertTrue(repo.is_locked())
73 self.assertTrue(fallback_repo.is_locked())
74 repo.unlock()
75 self.assertFalse(repo.is_locked())
76 self.assertFalse(fallback_repo.is_locked())
077
=== modified file 'bzrlib/tests/test_graph.py'
--- bzrlib/tests/test_graph.py 2009-03-24 23:19:12 +0000
+++ bzrlib/tests/test_graph.py 2009-05-29 10:35:21 +0000
@@ -1558,6 +1558,19 @@
1558 result = _mod_graph.PendingAncestryResult(['rev-2'], repo)1558 result = _mod_graph.PendingAncestryResult(['rev-2'], repo)
1559 self.assertEqual(set(['rev-1', 'rev-2']), set(result.get_keys()))1559 self.assertEqual(set(['rev-1', 'rev-2']), set(result.get_keys()))
15601560
1561 def test_get_keys_excludes_ghosts(self):
1562 builder = self.make_branch_builder('b')
1563 builder.start_series()
1564 builder.build_snapshot('rev-1', None, [
1565 ('add', ('', 'root-id', 'directory', ''))])
1566 builder.build_snapshot('rev-2', ['rev-1', 'ghost'], [])
1567 builder.finish_series()
1568 repo = builder.get_branch().repository
1569 repo.lock_read()
1570 self.addCleanup(repo.unlock)
1571 result = _mod_graph.PendingAncestryResult(['rev-2'], repo)
1572 self.assertEqual(sorted(['rev-1', 'rev-2']), sorted(result.get_keys()))
1573
1561 def test_get_keys_excludes_null(self):1574 def test_get_keys_excludes_null(self):
1562 # Make a 'graph' with an iter_ancestry that returns NULL_REVISION1575 # Make a 'graph' with an iter_ancestry that returns NULL_REVISION
1563 # somewhere other than the last element, which can happen in real1576 # somewhere other than the last element, which can happen in real
15641577
=== modified file 'bzrlib/tests/test_groupcompress.py'
--- bzrlib/tests/test_groupcompress.py 2009-04-22 17:18:45 +0000
+++ bzrlib/tests/test_groupcompress.py 2009-05-29 10:35:21 +0000
@@ -19,8 +19,10 @@
19import zlib19import zlib
2020
21from bzrlib import (21from bzrlib import (
22 btree_index,
22 groupcompress,23 groupcompress,
23 errors,24 errors,
25 index as _mod_index,
24 osutils,26 osutils,
25 tests,27 tests,
26 versionedfile,28 versionedfile,
@@ -475,6 +477,23 @@
475477
476class TestGroupCompressVersionedFiles(TestCaseWithGroupCompressVersionedFiles):478class TestGroupCompressVersionedFiles(TestCaseWithGroupCompressVersionedFiles):
477479
480 def make_g_index(self, name, ref_lists=0, nodes=[]):
481 builder = btree_index.BTreeBuilder(ref_lists)
482 for node, references, value in nodes:
483 builder.add_node(node, references, value)
484 stream = builder.finish()
485 trans = self.get_transport()
486 size = trans.put_file(name, stream)
487 return btree_index.BTreeGraphIndex(trans, name, size)
488
489 def make_g_index_missing_parent(self):
490 graph_index = self.make_g_index('missing_parent', 1,
491 [(('parent', ), '2 78 2 10', ([],)),
492 (('tip', ), '2 78 2 10',
493 ([('parent', ), ('missing-parent', )],)),
494 ])
495 return graph_index
496
478 def test_get_record_stream_as_requested(self):497 def test_get_record_stream_as_requested(self):
479 # Consider promoting 'as-requested' to general availability, and498 # Consider promoting 'as-requested' to general availability, and
480 # make this a VF interface test499 # make this a VF interface test
@@ -606,6 +625,30 @@
606 else:625 else:
607 self.assertIs(block, record._manager._block)626 self.assertIs(block, record._manager._block)
608627
628 def test_add_missing_noncompression_parent_unvalidated_index(self):
629 unvalidated = self.make_g_index_missing_parent()
630 combined = _mod_index.CombinedGraphIndex([unvalidated])
631 index = groupcompress._GCGraphIndex(combined,
632 is_locked=lambda: True, parents=True,
633 track_external_parent_refs=True)
634 index.scan_unvalidated_index(unvalidated)
635 self.assertEqual(
636 frozenset([('missing-parent',)]), index.get_missing_parents())
637
638 def test_track_external_parent_refs(self):
639 g_index = self.make_g_index('empty', 1, [])
640 mod_index = btree_index.BTreeBuilder(1, 1)
641 combined = _mod_index.CombinedGraphIndex([g_index, mod_index])
642 index = groupcompress._GCGraphIndex(combined,
643 is_locked=lambda: True, parents=True,
644 add_callback=mod_index.add_nodes,
645 track_external_parent_refs=True)
646 index.add_records([
647 (('new-key',), '2 10 2 10', [(('parent-1',), ('parent-2',))])])
648 self.assertEqual(
649 frozenset([('parent-1',), ('parent-2',)]),
650 index.get_missing_parents())
651
609652
610class TestLazyGroupCompress(tests.TestCaseWithTransport):653class TestLazyGroupCompress(tests.TestCaseWithTransport):
611654
612655
=== modified file 'bzrlib/tests/test_pack_repository.py'
--- bzrlib/tests/test_pack_repository.py 2009-05-11 15:30:40 +0000
+++ bzrlib/tests/test_pack_repository.py 2009-05-29 10:35:21 +0000
@@ -620,7 +620,7 @@
620 Also requires that the exception is logged.620 Also requires that the exception is logged.
621 """621 """
622 self.vfs_transport_factory = memory.MemoryServer622 self.vfs_transport_factory = memory.MemoryServer
623 repo = self.make_repository('repo')623 repo = self.make_repository('repo', format=self.get_format())
624 token = repo.lock_write()624 token = repo.lock_write()
625 self.addCleanup(repo.unlock)625 self.addCleanup(repo.unlock)
626 repo.start_write_group()626 repo.start_write_group()
@@ -637,7 +637,7 @@
637637
638 def test_abort_write_group_does_raise_when_not_suppressed(self):638 def test_abort_write_group_does_raise_when_not_suppressed(self):
639 self.vfs_transport_factory = memory.MemoryServer639 self.vfs_transport_factory = memory.MemoryServer
640 repo = self.make_repository('repo')640 repo = self.make_repository('repo', format=self.get_format())
641 token = repo.lock_write()641 token = repo.lock_write()
642 self.addCleanup(repo.unlock)642 self.addCleanup(repo.unlock)
643 repo.start_write_group()643 repo.start_write_group()
@@ -650,23 +650,51 @@
650650
651 def test_suspend_write_group(self):651 def test_suspend_write_group(self):
652 self.vfs_transport_factory = memory.MemoryServer652 self.vfs_transport_factory = memory.MemoryServer
653 repo = self.make_repository('repo')653 repo = self.make_repository('repo', format=self.get_format())
654 token = repo.lock_write()654 token = repo.lock_write()
655 self.addCleanup(repo.unlock)655 self.addCleanup(repo.unlock)
656 repo.start_write_group()656 repo.start_write_group()
657 repo.texts.add_lines(('file-id', 'revid'), (), ['lines'])657 repo.texts.add_lines(('file-id', 'revid'), (), ['lines'])
658 wg_tokens = repo.suspend_write_group()658 wg_tokens = repo.suspend_write_group()
659 expected_pack_name = wg_tokens[0] + '.pack'659 expected_pack_name = wg_tokens[0] + '.pack'
660 expected_names = [wg_tokens[0] + ext for ext in
661 ('.rix', '.iix', '.tix', '.six')]
662 if repo.chk_bytes is not None:
663 expected_names.append(wg_tokens[0] + '.cix')
664 expected_names.append(expected_pack_name)
660 upload_transport = repo._pack_collection._upload_transport665 upload_transport = repo._pack_collection._upload_transport
661 limbo_files = upload_transport.list_dir('')666 limbo_files = upload_transport.list_dir('')
662 self.assertTrue(expected_pack_name in limbo_files, limbo_files)667 self.assertEqual(sorted(expected_names), sorted(limbo_files))
663 md5 = osutils.md5(upload_transport.get_bytes(expected_pack_name))668 md5 = osutils.md5(upload_transport.get_bytes(expected_pack_name))
664 self.assertEqual(wg_tokens[0], md5.hexdigest())669 self.assertEqual(wg_tokens[0], md5.hexdigest())
665670
671 def test_resume_chk_bytes(self):
672 self.vfs_transport_factory = memory.MemoryServer
673 repo = self.make_repository('repo', format=self.get_format())
674 if repo.chk_bytes is None:
675 raise TestNotApplicable('no chk_bytes for this repository')
676 token = repo.lock_write()
677 self.addCleanup(repo.unlock)
678 repo.start_write_group()
679 text = 'a bit of text\n'
680 key = ('sha1:' + osutils.sha_string(text),)
681 repo.chk_bytes.add_lines(key, (), [text])
682 wg_tokens = repo.suspend_write_group()
683 same_repo = repo.bzrdir.open_repository()
684 same_repo.lock_write()
685 self.addCleanup(same_repo.unlock)
686 same_repo.resume_write_group(wg_tokens)
687 self.assertEqual([key], list(same_repo.chk_bytes.keys()))
688 self.assertEqual(
689 text, same_repo.chk_bytes.get_record_stream([key],
690 'unordered', True).next().get_bytes_as('fulltext'))
691 same_repo.abort_write_group()
692 self.assertEqual([], list(same_repo.chk_bytes.keys()))
693
666 def test_resume_write_group_then_abort(self):694 def test_resume_write_group_then_abort(self):
667 # Create a repo, start a write group, insert some data, suspend.695 # Create a repo, start a write group, insert some data, suspend.
668 self.vfs_transport_factory = memory.MemoryServer696 self.vfs_transport_factory = memory.MemoryServer
669 repo = self.make_repository('repo')697 repo = self.make_repository('repo', format=self.get_format())
670 token = repo.lock_write()698 token = repo.lock_write()
671 self.addCleanup(repo.unlock)699 self.addCleanup(repo.unlock)
672 repo.start_write_group()700 repo.start_write_group()
@@ -685,10 +713,38 @@
685 self.assertEqual(713 self.assertEqual(
686 [], same_repo._pack_collection._pack_transport.list_dir(''))714 [], same_repo._pack_collection._pack_transport.list_dir(''))
687715
716 def test_commit_resumed_write_group(self):
717 self.vfs_transport_factory = memory.MemoryServer
718 repo = self.make_repository('repo', format=self.get_format())
719 token = repo.lock_write()
720 self.addCleanup(repo.unlock)
721 repo.start_write_group()
722 text_key = ('file-id', 'revid')
723 repo.texts.add_lines(text_key, (), ['lines'])
724 wg_tokens = repo.suspend_write_group()
725 # Get a fresh repository object for the repo on the filesystem.
726 same_repo = repo.bzrdir.open_repository()
727 # Resume
728 same_repo.lock_write()
729 self.addCleanup(same_repo.unlock)
730 same_repo.resume_write_group(wg_tokens)
731 same_repo.commit_write_group()
732 expected_pack_name = wg_tokens[0] + '.pack'
733 expected_names = [wg_tokens[0] + ext for ext in
734 ('.rix', '.iix', '.tix', '.six')]
735 if repo.chk_bytes is not None:
736 expected_names.append(wg_tokens[0] + '.cix')
737 self.assertEqual(
738 [], same_repo._pack_collection._upload_transport.list_dir(''))
739 index_names = repo._pack_collection._index_transport.list_dir('')
740 self.assertEqual(sorted(expected_names), sorted(index_names))
741 pack_names = repo._pack_collection._pack_transport.list_dir('')
742 self.assertEqual([expected_pack_name], pack_names)
743
688 def test_resume_malformed_token(self):744 def test_resume_malformed_token(self):
689 self.vfs_transport_factory = memory.MemoryServer745 self.vfs_transport_factory = memory.MemoryServer
690 # Make a repository with a suspended write group746 # Make a repository with a suspended write group
691 repo = self.make_repository('repo')747 repo = self.make_repository('repo', format=self.get_format())
692 token = repo.lock_write()748 token = repo.lock_write()
693 self.addCleanup(repo.unlock)749 self.addCleanup(repo.unlock)
694 repo.start_write_group()750 repo.start_write_group()
@@ -696,7 +752,7 @@
696 repo.texts.add_lines(text_key, (), ['lines'])752 repo.texts.add_lines(text_key, (), ['lines'])
697 wg_tokens = repo.suspend_write_group()753 wg_tokens = repo.suspend_write_group()
698 # Make a new repository754 # Make a new repository
699 new_repo = self.make_repository('new_repo')755 new_repo = self.make_repository('new_repo', format=self.get_format())
700 token = new_repo.lock_write()756 token = new_repo.lock_write()
701 self.addCleanup(new_repo.unlock)757 self.addCleanup(new_repo.unlock)
702 hacked_wg_token = (758 hacked_wg_token = (
@@ -732,12 +788,12 @@
732 # can only stack on repositories that have compatible internal788 # can only stack on repositories that have compatible internal
733 # metadata789 # metadata
734 if getattr(repo._format, 'supports_tree_reference', False):790 if getattr(repo._format, 'supports_tree_reference', False):
791 matching_format_name = 'pack-0.92-subtree'
792 else:
735 if repo._format.supports_chks:793 if repo._format.supports_chks:
736 matching_format_name = 'development6-rich-root'794 matching_format_name = 'development6-rich-root'
737 else:795 else:
738 matching_format_name = 'pack-0.92-subtree'796 matching_format_name = 'rich-root-pack'
739 else:
740 matching_format_name = 'rich-root-pack'
741 mismatching_format_name = 'pack-0.92'797 mismatching_format_name = 'pack-0.92'
742 else:798 else:
743 # We don't have a non-rich-root CHK format.799 # We don't have a non-rich-root CHK format.
@@ -763,15 +819,14 @@
763 if getattr(repo._format, 'supports_tree_reference', False):819 if getattr(repo._format, 'supports_tree_reference', False):
764 # can only stack on repositories that have compatible internal820 # can only stack on repositories that have compatible internal
765 # metadata821 # metadata
766 if repo._format.supports_chks:822 matching_format_name = 'pack-0.92-subtree'
767 # No CHK subtree formats in bzr.dev, so this doesn't execute.
768 matching_format_name = 'development6-subtree'
769 else:
770 matching_format_name = 'pack-0.92-subtree'
771 mismatching_format_name = 'rich-root-pack'823 mismatching_format_name = 'rich-root-pack'
772 else:824 else:
773 if repo.supports_rich_root():825 if repo.supports_rich_root():
774 matching_format_name = 'rich-root-pack'826 if repo._format.supports_chks:
827 matching_format_name = 'development6-rich-root'
828 else:
829 matching_format_name = 'rich-root-pack'
775 mismatching_format_name = 'pack-0.92-subtree'830 mismatching_format_name = 'pack-0.92-subtree'
776 else:831 else:
777 raise TestNotApplicable('No formats use non-v5 serializer'832 raise TestNotApplicable('No formats use non-v5 serializer'
@@ -844,6 +899,66 @@
844 self.assertTrue(large_pack_name in pack_names)899 self.assertTrue(large_pack_name in pack_names)
845900
846901
902class TestKeyDependencies(TestCaseWithTransport):
903
904 def get_format(self):
905 return bzrdir.format_registry.make_bzrdir(self.format_name)
906
907 def create_source_and_target(self):
908 builder = self.make_branch_builder('source', format=self.get_format())
909 builder.start_series()
910 builder.build_snapshot('A-id', None, [
911 ('add', ('', 'root-id', 'directory', None))])
912 builder.build_snapshot('B-id', ['A-id', 'ghost-id'], [])
913 builder.finish_series()
914 repo = self.make_repository('target')
915 b = builder.get_branch()
916 b.lock_read()
917 self.addCleanup(b.unlock)
918 repo.lock_write()
919 self.addCleanup(repo.unlock)
920 return b.repository, repo
921
922 def test_key_dependencies_cleared_on_abort(self):
923 source_repo, target_repo = self.create_source_and_target()
924 target_repo.start_write_group()
925 try:
926 stream = source_repo.revisions.get_record_stream([('B-id',)],
927 'unordered', True)
928 target_repo.revisions.insert_record_stream(stream)
929 key_refs = target_repo.revisions._index._key_dependencies
930 self.assertEqual([('B-id',)], sorted(key_refs.get_referrers()))
931 finally:
932 target_repo.abort_write_group()
933 self.assertEqual([], sorted(key_refs.get_referrers()))
934
935 def test_key_dependencies_cleared_on_suspend(self):
936 source_repo, target_repo = self.create_source_and_target()
937 target_repo.start_write_group()
938 try:
939 stream = source_repo.revisions.get_record_stream([('B-id',)],
940 'unordered', True)
941 target_repo.revisions.insert_record_stream(stream)
942 key_refs = target_repo.revisions._index._key_dependencies
943 self.assertEqual([('B-id',)], sorted(key_refs.get_referrers()))
944 finally:
945 target_repo.suspend_write_group()
946 self.assertEqual([], sorted(key_refs.get_referrers()))
947
948 def test_key_dependencies_cleared_on_commit(self):
949 source_repo, target_repo = self.create_source_and_target()
950 target_repo.start_write_group()
951 try:
952 stream = source_repo.revisions.get_record_stream([('B-id',)],
953 'unordered', True)
954 target_repo.revisions.insert_record_stream(stream)
955 key_refs = target_repo.revisions._index._key_dependencies
956 self.assertEqual([('B-id',)], sorted(key_refs.get_referrers()))
957 finally:
958 target_repo.commit_write_group()
959 self.assertEqual([], sorted(key_refs.get_referrers()))
960
961
847class TestSmartServerAutopack(TestCaseWithTransport):962class TestSmartServerAutopack(TestCaseWithTransport):
848963
849 def setUp(self):964 def setUp(self):
@@ -931,7 +1046,7 @@
931 dict(format_name='development6-rich-root',1046 dict(format_name='development6-rich-root',
932 format_string='Bazaar development format - group compression '1047 format_string='Bazaar development format - group compression '
933 'and chk inventory (needs bzr.dev from 1.14)\n',1048 'and chk inventory (needs bzr.dev from 1.14)\n',
934 format_supports_external_lookups=False,1049 format_supports_external_lookups=True,
935 index_class=BTreeGraphIndex),1050 index_class=BTreeGraphIndex),
936 ]1051 ]
937 # name of the scenario is the format name1052 # name of the scenario is the format name
9381053
=== modified file 'bzrlib/tests/test_repository.py'
--- bzrlib/tests/test_repository.py 2009-04-09 20:23:07 +0000
+++ bzrlib/tests/test_repository.py 2009-05-29 10:35:21 +0000
@@ -686,11 +686,11 @@
686 inv.parent_id_basename_to_file_id._root_node.maximum_size)686 inv.parent_id_basename_to_file_id._root_node.maximum_size)
687687
688688
689class TestDevelopment6FindRevisionOutsideSet(TestCaseWithTransport):689class TestDevelopment6FindParentIdsOfRevisions(TestCaseWithTransport):
690 """Tests for _find_revision_outside_set."""690 """Tests for _find_parent_ids_of_revisions."""
691691
692 def setUp(self):692 def setUp(self):
693 super(TestDevelopment6FindRevisionOutsideSet, self).setUp()693 super(TestDevelopment6FindParentIdsOfRevisions, self).setUp()
694 self.builder = self.make_branch_builder('source',694 self.builder = self.make_branch_builder('source',
695 format='development6-rich-root')695 format='development6-rich-root')
696 self.builder.start_series()696 self.builder.start_series()
@@ -699,42 +699,42 @@
699 self.repo = self.builder.get_branch().repository699 self.repo = self.builder.get_branch().repository
700 self.addCleanup(self.builder.finish_series)700 self.addCleanup(self.builder.finish_series)
701701
702 def assertRevisionOutsideSet(self, expected_result, rev_set):702 def assertParentIds(self, expected_result, rev_set):
703 self.assertEqual(703 self.assertEqual(sorted(expected_result),
704 expected_result, self.repo._find_revision_outside_set(rev_set))704 sorted(self.repo._find_parent_ids_of_revisions(rev_set)))
705705
706 def test_simple(self):706 def test_simple(self):
707 self.builder.build_snapshot('revid1', None, [])707 self.builder.build_snapshot('revid1', None, [])
708 self.builder.build_snapshot('revid2', None, [])708 self.builder.build_snapshot('revid2', ['revid1'], [])
709 rev_set = ['revid2']709 rev_set = ['revid2']
710 self.assertRevisionOutsideSet('revid1', rev_set)710 self.assertParentIds(['revid1'], rev_set)
711711
712 def test_not_first_parent(self):712 def test_not_first_parent(self):
713 self.builder.build_snapshot('revid1', None, [])713 self.builder.build_snapshot('revid1', None, [])
714 self.builder.build_snapshot('revid2', None, [])714 self.builder.build_snapshot('revid2', ['revid1'], [])
715 self.builder.build_snapshot('revid3', None, [])715 self.builder.build_snapshot('revid3', ['revid2'], [])
716 rev_set = ['revid3', 'revid2']716 rev_set = ['revid3', 'revid2']
717 self.assertRevisionOutsideSet('revid1', rev_set)717 self.assertParentIds(['revid1'], rev_set)
718718
719 def test_not_null(self):719 def test_not_null(self):
720 rev_set = ['initial']720 rev_set = ['initial']
721 self.assertRevisionOutsideSet(_mod_revision.NULL_REVISION, rev_set)721 self.assertParentIds([], rev_set)
722722
723 def test_not_null_set(self):723 def test_not_null_set(self):
724 self.builder.build_snapshot('revid1', None, [])724 self.builder.build_snapshot('revid1', None, [])
725 rev_set = [_mod_revision.NULL_REVISION]725 rev_set = [_mod_revision.NULL_REVISION]
726 self.assertRevisionOutsideSet(_mod_revision.NULL_REVISION, rev_set)726 self.assertParentIds([], rev_set)
727727
728 def test_ghost(self):728 def test_ghost(self):
729 self.builder.build_snapshot('revid1', None, [])729 self.builder.build_snapshot('revid1', None, [])
730 rev_set = ['ghost', 'revid1']730 rev_set = ['ghost', 'revid1']
731 self.assertRevisionOutsideSet('initial', rev_set)731 self.assertParentIds(['initial'], rev_set)
732732
733 def test_ghost_parent(self):733 def test_ghost_parent(self):
734 self.builder.build_snapshot('revid1', None, [])734 self.builder.build_snapshot('revid1', None, [])
735 self.builder.build_snapshot('revid2', ['revid1', 'ghost'], [])735 self.builder.build_snapshot('revid2', ['revid1', 'ghost'], [])
736 rev_set = ['revid2', 'revid1']736 rev_set = ['revid2', 'revid1']
737 self.assertRevisionOutsideSet('initial', rev_set)737 self.assertParentIds(['ghost', 'initial'], rev_set)
738738
739 def test_righthand_parent(self):739 def test_righthand_parent(self):
740 self.builder.build_snapshot('revid1', None, [])740 self.builder.build_snapshot('revid1', None, [])
@@ -742,7 +742,7 @@
742 self.builder.build_snapshot('revid2b', ['revid1'], [])742 self.builder.build_snapshot('revid2b', ['revid1'], [])
743 self.builder.build_snapshot('revid3', ['revid2a', 'revid2b'], [])743 self.builder.build_snapshot('revid3', ['revid2a', 'revid2b'], [])
744 rev_set = ['revid3', 'revid2a']744 rev_set = ['revid3', 'revid2a']
745 self.assertRevisionOutsideSet('revid2b', rev_set)745 self.assertParentIds(['revid1', 'revid2b'], rev_set)
746746
747747
748class TestWithBrokenRepo(TestCaseWithTransport):748class TestWithBrokenRepo(TestCaseWithTransport):
@@ -1220,3 +1220,68 @@
1220 stream = source._get_source(target._format)1220 stream = source._get_source(target._format)
1221 # We don't want the child GroupCHKStreamSource1221 # We don't want the child GroupCHKStreamSource
1222 self.assertIs(type(stream), repository.StreamSource)1222 self.assertIs(type(stream), repository.StreamSource)
1223
1224 def test_get_stream_for_missing_keys_includes_all_chk_refs(self):
1225 source_builder = self.make_branch_builder('source',
1226 format='development6-rich-root')
1227 # We have to build a fairly large tree, so that we are sure the chk
1228 # pages will have split into multiple pages.
1229 entries = [('add', ('', 'a-root-id', 'directory', None))]
1230 for i in 'abcdefghijklmnopqrstuvwxyz123456789':
1231 for j in 'abcdefghijklmnopqrstuvwxyz123456789':
1232 fname = i + j
1233 fid = fname + '-id'
1234 content = 'content for %s\n' % (fname,)
1235 entries.append(('add', (fname, fid, 'file', content)))
1236 source_builder.start_series()
1237 source_builder.build_snapshot('rev-1', None, entries)
1238 # Now change a few of them, so we get a few new pages for the second
1239 # revision
1240 source_builder.build_snapshot('rev-2', ['rev-1'], [
1241 ('modify', ('aa-id', 'new content for aa-id\n')),
1242 ('modify', ('cc-id', 'new content for cc-id\n')),
1243 ('modify', ('zz-id', 'new content for zz-id\n')),
1244 ])
1245 source_builder.finish_series()
1246 source_branch = source_builder.get_branch()
1247 source_branch.lock_read()
1248 self.addCleanup(source_branch.unlock)
1249 target = self.make_repository('target', format='development6-rich-root')
1250 source = source_branch.repository._get_source(target._format)
1251 self.assertIsInstance(source, groupcompress_repo.GroupCHKStreamSource)
1252
1253 # On a regular pass, getting the inventories and chk pages for rev-2
1254 # would only get the newly created chk pages
1255 search = graph.SearchResult(set(['rev-2']), set(['rev-1']), 1,
1256 set(['rev-2']))
1257 simple_chk_records = []
1258 for vf_name, substream in source.get_stream(search):
1259 if vf_name == 'chk_bytes':
1260 for record in substream:
1261 simple_chk_records.append(record.key)
1262 else:
1263 for _ in substream:
1264 continue
1265 # 3 pages, the root (InternalNode), + 2 pages which actually changed
1266 self.assertEqual([('sha1:91481f539e802c76542ea5e4c83ad416bf219f73',),
1267 ('sha1:4ff91971043668583985aec83f4f0ab10a907d3f',),
1268 ('sha1:81e7324507c5ca132eedaf2d8414ee4bb2226187',),
1269 ('sha1:b101b7da280596c71a4540e9a1eeba8045985ee0',)],
1270 simple_chk_records)
1271 # Now, when we do a similar call using 'get_stream_for_missing_keys'
1272 # we should get a much larger set of pages.
1273 missing = [('inventories', 'rev-2')]
1274 full_chk_records = []
1275 for vf_name, substream in source.get_stream_for_missing_keys(missing):
1276 if vf_name == 'inventories':
1277 for record in substream:
1278 self.assertEqual(('rev-2',), record.key)
1279 elif vf_name == 'chk_bytes':
1280 for record in substream:
1281 full_chk_records.append(record.key)
1282 else:
1283 self.fail('Should not be getting a stream of %s' % (vf_name,))
1284 # We have 257 records now. This is because we have 1 root page, and 256
1285 # leaf pages in a complete listing.
1286 self.assertEqual(257, len(full_chk_records))
1287 self.assertSubset(simple_chk_records, full_chk_records)