Merge lp:~jelmer/bzr/hpss-get-inventories into lp:bzr

Proposed by Jelmer Vernooij
Status: Superseded
Proposed branch: lp:~jelmer/bzr/hpss-get-inventories
Merge into: lp:bzr
Prerequisite: lp:~jelmer/bzr/hpss-_get-checkout-format
Diff against target: 969 lines (+466/-182)
17 files modified
bzrlib/remote.py (+207/-50)
bzrlib/smart/branch.py (+0/-19)
bzrlib/smart/bzrdir.py (+19/-0)
bzrlib/smart/repository.py (+58/-1)
bzrlib/smart/request.py (+6/-3)
bzrlib/tests/blackbox/test_annotate.py (+1/-1)
bzrlib/tests/blackbox/test_branch.py (+1/-1)
bzrlib/tests/blackbox/test_cat.py (+2/-3)
bzrlib/tests/blackbox/test_checkout.py (+3/-8)
bzrlib/tests/blackbox/test_export.py (+2/-3)
bzrlib/tests/blackbox/test_log.py (+4/-6)
bzrlib/tests/blackbox/test_ls.py (+2/-3)
bzrlib/tests/blackbox/test_sign_my_commits.py (+3/-10)
bzrlib/tests/per_interbranch/test_push.py (+2/-2)
bzrlib/tests/test_remote.py (+78/-41)
bzrlib/tests/test_smart.py (+68/-31)
doc/en/release-notes/bzr-2.5.txt (+10/-0)
To merge this branch: bzr merge lp:~jelmer/bzr/hpss-get-inventories
Reviewer Review Type Date Requested Status
Vincent Ladeuil Approve
Review via email: mp+83837@code.launchpad.net

This proposal has been superseded by a proposal from 2011-12-11.

Commit message

Add HPSS call for ``Repository.iter_inventories``.

Description of the change

Add a HPSS call for ``Repository.iter_inventories``.

This massively reduces the number of roundtrips for various commands, and causes a fair number to no longer use VFS calls at all when used against a modern remote server.

In the process I removed the HistoryMissing exception that was thrown in the private Repository._get_inventory_xml method, and never caught anywhere except to be converted to a NoSuchRevision exception. The private Repository._iter_inventories no longer directly raises NoSuchRevision but instead indicates when it can't find an inventory so that it is possible to fetch some inventories from one repository and others from another (such as a fallback repository).

Repository.get_deltas_for_revisions() now has its own implementation for remote repositories, which means the client won't use the VFS to get at the inventories to generate the deltas from.

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Timings when checking out bzr.dev:

~/src/bzr/hpss-get-inventories/bzr co --lightweight bzr://people.samba.org/bzr.dev 2.25s user 0.46s system 15% cpu 17.277 total
~/src/bzr/hpss-get-inventories/bzr export /tmp/bzr.dev2 bzr://people.samba.org/bzr.dev 1.54s user 0.40s system 12% cpu 15.435 total

versus against an older bzr server:

~/src/bzr/hpss-get-inventories/bzr co --lightweight bzr://people.samba.org/bzr.dev 4.91s user 1.03s system 10% cpu 58.807 total
~/src/bzr/hpss-get-inventories/bzr export /tmp/bzr.dev5 bzr://people.samba.org/bzr.dev 4.35s user 0.98s system 9% cpu 58.357 total

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

For comparison (though I should it's a different server):

apt-get source bzr 2.79s user 0.54s system 27% cpu 12.338 total

so we're getting closer.

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

Jelmer Vernooij wrote:
> Add a HPSS call for ``Repository.iter_inventories``.

I am a bit concerned that by adding verbs like this, with their own ad hoc
record stream-like wire format, that we're growing the maintenance burden
unnecessarily, and not reusing improvements everywhere we could.

I'm glad this is using inventory-deltas. But if it's worth zlib compressing
them here, shouldn't we do that for inventory-deltas from get_record_stream too?

Also, could you implement this via the get_record_stream RPC with a new
parameter that means “just inventories (rather than rev+inv+texts+chks+sigs for
given keys)”?

On one hand it might feel a bit ugly to make one RPC do so many things,
get_record_stream, do so many things, but on the other I think it provides a
useful pressure to keep the interface minimal and consistent (e.g. whether
records are zlib-compressed).

Adding this iter_inventories RPC might be the right thing (although putting
“iter” in the name of an RPC feels weird to me, that's surely a property of the
Python API rather than a property of the RPC), but I'd like hear what you think
about the tradeoffs of doing that vs. extending get_record_stream.

-Andrew.

Revision history for this message
Martin Pool (mbp) wrote :

On 30 November 2011 09:37, Andrew Bennetts
<email address hidden> wrote:
> Jelmer Vernooij wrote:
>> Add a HPSS call for ``Repository.iter_inventories``.
>
> I am a bit concerned that by adding verbs like this, with their own ad hoc
> record stream-like wire format, that we're growing the maintenance burden
> unnecessarily, and not reusing improvements everywhere we could.

+1

--
Martin

Revision history for this message
Vincent Ladeuil (vila) wrote :

>> I am a bit concerned that by adding verbs like this, with their own ad hoc
>> record stream-like wire format, that we're growing the maintenance burden
>> unnecessarily, and not reusing improvements everywhere we could.

> +1

I kind of had the same feeling when jelmer started adding a bunch of verbs that were basically replacing a vfs roundtrip by a smart request roundtrip.

BUT

If we stop maintaining these verbs on the server side, the clients will fallback to vfs.

So we introduce different/better verbs, we can remove the old ones in both the server and the client and all but the newest clients can still fallback to vfs.

The net effect is at least to get to a point where the most recent client/server do not use vfs at all.

This sounds like a good incremental step to me.

'reusing improvements everywhere we could' is still valuable but doesn't to block progress.

review: Approve
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Hi Andrew,

> Jelmer Vernooij wrote:
> > Add a HPSS call for ``Repository.iter_inventories``.
> I am a bit concerned that by adding verbs like this, with their own ad hoc
> record stream-like wire format, that we're growing the maintenance burden
> unnecessarily, and not reusing improvements everywhere we could.
>
> I'm glad this is using inventory-deltas. But if it's worth zlib compressing
> them here, shouldn't we do that for inventory-deltas from get_record_stream
> too?
It's a pretty big inventory delta in this case - for something like gcc it matters for the delta between null: and the initial revision that was requested. I haven't investigated whether it would help for record streams too, but I imagine it would have less of an effect.

> Also, could you implement this via the get_record_stream RPC with a new
> parameter that means “just inventories (rather than rev+inv+texts+chks+sigs
> for
> given keys)”?
>
> On one hand it might feel a bit ugly to make one RPC do so many things,
> get_record_stream, do so many things, but on the other I think it provides a
> useful pressure to keep the interface minimal and consistent (e.g. whether
> records are zlib-compressed).
>
> Adding this iter_inventories RPC might be the right thing (although putting
> “iter” in the name of an RPC feels weird to me, that's surely a property of
> the Python API rather than a property of the RPC), but I'd like hear what you
> think about the tradeoffs of doing that vs. extending get_record_stream.

With regard to the name: Most of the other calls seem to be named after the equivalent method in the client / server, that's why I went with Repository.iter_inventories. I'm not particularly tied to that name, something like "Repository.get_inventories" or "Repository.stream_inventories" seems reasonable to me too.

I'm not sure whether this should be part of get_record_stream. I have a hard time understanding the get_record_stream code as it is, so I'd rather not make it even more complex by adding more parameters - for example, get_record_streams seem to be dependent on the repository on-disk format to an extent - the inventory stream is not, as it's using inventory deltas. In other words, adding another verb was simpler, while keeping it all understandable for a mere mortal like me. :-)

Either way, I agree we should be reusing more code between the work I've done recently and the existing record stream
 calls. But it seems to me that would best be done by refactoring so they e.g. use the same code for sending a stream of blobs of indeterminate length, rather than by all being a part of the same verb.

What do you think?

Cheers,

Jelmer

Revision history for this message
Andrew Bennetts (spiv) wrote :
Download full text (4.5 KiB)

Jelmer Vernooij wrote:

> > I'm glad this is using inventory-deltas. But if it's worth zlib compressing
> > them here, shouldn't we do that for inventory-deltas from get_record_stream
> > too?
> It's a pretty big inventory delta in this case - for something like gcc it
> matters for the delta between null: and the initial revision that was
> requested. I haven't investigated whether it would help for record streams
> too, but I imagine it would have less of an effect.

Well, I know that there are already cases that send deltas from null: —
something to do with stacking perhaps? So reusing this improvement globally
would be nice.

> > Also, could you implement this via the get_record_stream RPC with a new
> > parameter that means “just inventories (rather than rev+inv+texts+chks+sigs
> > for
> > given keys)”?
> >
> > On one hand it might feel a bit ugly to make one RPC do so many things,
> > get_record_stream, do so many things, but on the other I think it provides a
> > useful pressure to keep the interface minimal and consistent (e.g. whether
> > records are zlib-compressed).
> >
> > Adding this iter_inventories RPC might be the right thing (although putting
> > “iter” in the name of an RPC feels weird to me, that's surely a property of
> > the Python API rather than a property of the RPC), but I'd like hear what you
> > think about the tradeoffs of doing that vs. extending get_record_stream.
>
> With regard to the name: Most of the other calls seem to be named after the
> equivalent method in the client / server, that's why I went with
> Repository.iter_inventories. I'm not particularly tied to that name, something
> like "Repository.get_inventories" or "Repository.stream_inventories" seems
> reasonable to me too.

I think of using the name of the Python API as a good guide, not a strict rule.
Certainly there's no insert_stream_locked method on Repository…

“Repository.get_inventories” sounds fine to me.

> I'm not sure whether this should be part of get_record_stream. I have a hard
> time understanding the get_record_stream code as it is, so I'd rather not make
> it even more complex by adding more parameters - for example,
> get_record_streams seem to be dependent on the repository on-disk format to an
> extent - the inventory stream is not, as it's using inventory deltas. In other
> words, adding another verb was simpler, while keeping it all understandable
> for a mere mortal like me. :-)
>
> Either way, I agree we should be reusing more code between the work I've done
> recently and the existing record stream calls. But it seems to me that would
> best be done by refactoring so they e.g. use the same code for sending a
> stream of blobs of indeterminate length, rather than by all being a part of
> the same verb.

Well, the path to reuse we've developed *is* record streams. I'm quite willing
to believe that get_record_stream's parameters aren't a convenient way to
express all needs. But I'd really like it the thing that was returned by a new
verb was a true record stream — something you could pass directly to
insert_record_stream. The idea is that record streams should be the One True
Way we have to say “here is a stream of da...

Read more...

Revision history for this message
Jelmer Vernooij (jelmer) wrote :
Download full text (6.7 KiB)

Am 05/12/11 11:31, schrieb Andrew Bennetts:
> Jelmer Vernooij wrote:
> …
>>> I'm glad this is using inventory-deltas. But if it's worth zlib compressing
>>> them here, shouldn't we do that for inventory-deltas from get_record_stream
>>> too?
>> It's a pretty big inventory delta in this case - for something like gcc it
>> matters for the delta between null: and the initial revision that was
>> requested. I haven't investigated whether it would help for record streams
>> too, but I imagine it would have less of an effect.
> Well, I know that there are already cases that send deltas from null: —
> something to do with stacking perhaps? So reusing this improvement globally
> would be nice.
I guess the best way to do this would be to add a zlib pack record kind
for network streams? I.e. a new entry in
NetworkRecordStream._kind_factory, and something equivalent on the
server side?
>>> Also, could you implement this via the get_record_stream RPC with a new
>>> parameter that means “just inventories (rather than rev+inv+texts+chks+sigs
>>> for
>>> given keys)”?
>>>
>>> On one hand it might feel a bit ugly to make one RPC do so many things,
>>> get_record_stream, do so many things, but on the other I think it provides a
>>> useful pressure to keep the interface minimal and consistent (e.g. whether
>>> records are zlib-compressed).
>>>
>>> Adding this iter_inventories RPC might be the right thing (although putting
>>> “iter” in the name of an RPC feels weird to me, that's surely a property of
>>> the Python API rather than a property of the RPC), but I'd like hear what you
>>> think about the tradeoffs of doing that vs. extending get_record_stream.
>> With regard to the name: Most of the other calls seem to be named after the
>> equivalent method in the client / server, that's why I went with
>> Repository.iter_inventories. I'm not particularly tied to that name, something
>> like "Repository.get_inventories" or "Repository.stream_inventories" seems
>> reasonable to me too.
> I think of using the name of the Python API as a good guide, not a strict rule.
> Certainly there's no insert_stream_locked method on Repository…
>
> “Repository.get_inventories” sounds fine to me.
I've renamed it.
>
>> I'm not sure whether this should be part of get_record_stream. I have a hard
>> time understanding the get_record_stream code as it is, so I'd rather not make
>> it even more complex by adding more parameters - for example,
>> get_record_streams seem to be dependent on the repository on-disk format to an
>> extent - the inventory stream is not, as it's using inventory deltas. In other
>> words, adding another verb was simpler, while keeping it all understandable
>> for a mere mortal like me. :-)
>>
>> Either way, I agree we should be reusing more code between the work I've done
>> recently and the existing record stream calls. But it seems to me that would
>> best be done by refactoring so they e.g. use the same code for sending a
>> stream of blobs of indeterminate length, rather than by all being a part of
>> the same verb.
> Well, the path to reuse we've developed *is* record streams. I'm quite willing
> to believe that get_record_stream's parameters aren'...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/remote.py'
2--- bzrlib/remote.py 2011-12-11 16:36:30 +0000
3+++ bzrlib/remote.py 2011-12-11 16:36:30 +0000
4@@ -27,6 +27,7 @@
5 errors,
6 gpg,
7 graph,
8+ inventory_delta,
9 lock,
10 lockdir,
11 osutils,
12@@ -490,6 +491,45 @@
13 self._next_open_branch_result = None
14 return _mod_bzrdir.BzrDir.break_lock(self)
15
16+ def _vfs_checkout_metadir(self):
17+ self._ensure_real()
18+ return self._real_bzrdir.checkout_metadir()
19+
20+ def checkout_metadir(self):
21+ medium = self._client._medium
22+ if medium._is_remote_before((2, 5)):
23+ return self._vfs_checkout_metadir()
24+ path = self._path_for_remote_call(self._client)
25+ try:
26+ response = self._client.call('BzrDir.checkout_metadir',
27+ path)
28+ except errors.UnknownSmartMethod:
29+ medium._remember_remote_is_before((2, 5))
30+ return self._vfs_checkout_metadir()
31+ if len(response) != 3:
32+ raise errors.UnexpectedSmartServerResponse(response)
33+ control_name, repo_name, branch_name = response
34+ try:
35+ format = controldir.network_format_registry.get(control_name)
36+ except KeyError:
37+ raise errors.UnknownFormatError(kind='control', format=control_name)
38+ if repo_name:
39+ try:
40+ repo_format = _mod_repository.network_format_registry.get(
41+ repo_name)
42+ except KeyError:
43+ raise errors.UnknownFormatError(kind='repository',
44+ format=repo_name)
45+ format.repository_format = repo_format
46+ if branch_name:
47+ try:
48+ format.set_branch_format(
49+ branch.network_format_registry.get(branch_name))
50+ except KeyError:
51+ raise errors.UnknownFormatError(kind='branch',
52+ format=branch_name)
53+ return format
54+
55 def _vfs_cloning_metadir(self, require_stacking=False):
56 self._ensure_real()
57 return self._real_bzrdir.cloning_metadir(
58@@ -1788,9 +1828,122 @@
59 def get_inventory(self, revision_id):
60 return list(self.iter_inventories([revision_id]))[0]
61
62+ def _iter_inventories_rpc(self, revision_ids, ordering):
63+ if ordering is None:
64+ ordering = 'unordered'
65+ path = self.bzrdir._path_for_remote_call(self._client)
66+ body = "\n".join(revision_ids)
67+ response_tuple, response_handler = (
68+ self._call_with_body_bytes_expecting_body(
69+ "VersionedFileRepository.get_inventories",
70+ (path, ordering), body))
71+ if response_tuple[0] != "ok":
72+ raise errors.UnexpectedSmartServerResponse(response_tuple)
73+ deserializer = inventory_delta.InventoryDeltaDeserializer()
74+ byte_stream = response_handler.read_streamed_body()
75+ decoded = smart_repo._byte_stream_to_stream(byte_stream)
76+ if decoded is None:
77+ # no results whatsoever
78+ return
79+ src_format, stream = decoded
80+ if src_format.network_name() != self._format.network_name():
81+ raise AssertionError(
82+ "Mismatched RemoteRepository and stream src %r, %r" % (
83+ src_format.network_name(), self._format.network_name()))
84+ # ignore the src format, it's not really relevant
85+ prev_inv = Inventory(root_id=None,
86+ revision_id=_mod_revision.NULL_REVISION)
87+ # there should be just one substream, with inventory deltas
88+ substream_kind, substream = stream.next()
89+ for record in substream:
90+ (parent_id, new_id, versioned_root, tree_references, invdelta) = (
91+ deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
92+ if parent_id != prev_inv.revision_id:
93+ raise AssertionError("invalid base %r != %r" % (parent_id,
94+ prev_inv.revision_id))
95+ inv = prev_inv.create_by_apply_delta(invdelta, new_id)
96+ yield inv, inv.revision_id
97+ prev_inv = inv
98+
99+ def _iter_inventories_vfs(self, revision_ids, ordering=None):
100+ self._ensure_real()
101+ return self._real_repository._iter_inventories(revision_ids, ordering)
102+
103 def iter_inventories(self, revision_ids, ordering=None):
104- self._ensure_real()
105- return self._real_repository.iter_inventories(revision_ids, ordering)
106+ """Get many inventories by revision_ids.
107+
108+ This will buffer some or all of the texts used in constructing the
109+ inventories in memory, but will only parse a single inventory at a
110+ time.
111+
112+ :param revision_ids: The expected revision ids of the inventories.
113+ :param ordering: optional ordering, e.g. 'topological'. If not
114+ specified, the order of revision_ids will be preserved (by
115+ buffering if necessary).
116+ :return: An iterator of inventories.
117+ """
118+ if ((None in revision_ids)
119+ or (_mod_revision.NULL_REVISION in revision_ids)):
120+ raise ValueError('cannot get null revision inventory')
121+ for inv, revid in self._iter_inventories(revision_ids, ordering):
122+ if inv is None:
123+ raise errors.NoSuchRevision(self, revid)
124+ yield inv
125+
126+ def _iter_inventories(self, revision_ids, ordering=None):
127+ if len(revision_ids) == 0:
128+ return
129+ missing = set(revision_ids)
130+ if ordering is None:
131+ order_as_requested = True
132+ invs = {}
133+ order = list(revision_ids)
134+ order.reverse()
135+ next_revid = order.pop()
136+ else:
137+ order_as_requested = False
138+ if ordering != 'unordered' and self._fallback_repositories:
139+ raise ValueError('unsupported ordering %r' % ordering)
140+ iter_inv_fns = [self._iter_inventories_rpc] + [
141+ fallback._iter_inventories for fallback in
142+ self._fallback_repositories]
143+ try:
144+ for iter_inv in iter_inv_fns:
145+ request = [revid for revid in revision_ids if revid in missing]
146+ for inv, revid in iter_inv(request, ordering):
147+ if inv is None:
148+ continue
149+ missing.remove(inv.revision_id)
150+ if ordering != 'unordered':
151+ invs[revid] = inv
152+ else:
153+ yield inv, revid
154+ if order_as_requested:
155+ # Yield as many results as we can while preserving order.
156+ while next_revid in invs:
157+ inv = invs.pop(next_revid)
158+ yield inv, inv.revision_id
159+ try:
160+ next_revid = order.pop()
161+ except IndexError:
162+ # We still want to fully consume the stream, just
163+ # in case it is not actually finished at this point
164+ next_revid = None
165+ break
166+ except errors.UnknownSmartMethod:
167+ for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
168+ yield inv, revid
169+ return
170+ # Report missing
171+ if order_as_requested:
172+ if next_revid is not None:
173+ yield None, next_revid
174+ while order:
175+ revid = order.pop()
176+ yield invs.get(revid), revid
177+ else:
178+ while missing:
179+ yield None, missing.pop()
180
181 @needs_read_lock
182 def get_revision(self, revision_id):
183@@ -2149,6 +2302,8 @@
184
185 @needs_read_lock
186 def _get_inventory_xml(self, revision_id):
187+ # This call is used by older working tree formats,
188+ # which stored a serialized basis inventory.
189 self._ensure_real()
190 return self._real_repository._get_inventory_xml(revision_id)
191
192@@ -2171,11 +2326,58 @@
193 revids.update(set(fallback.all_revision_ids()))
194 return list(revids)
195
196+ def _filtered_revision_trees(self, revision_ids, file_ids):
197+ """Return Tree for a revision on this branch with only some files.
198+
199+ :param revision_ids: a sequence of revision-ids;
200+ a revision-id may not be None or 'null:'
201+ :param file_ids: if not None, the result is filtered
202+ so that only those file-ids, their parents and their
203+ children are included.
204+ """
205+ inventories = self.iter_inventories(revision_ids)
206+ for inv in inventories:
207+ # Should we introduce a FilteredRevisionTree class rather
208+ # than pre-filter the inventory here?
209+ filtered_inv = inv.filter(file_ids)
210+ yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
211+
212 @needs_read_lock
213 def get_deltas_for_revisions(self, revisions, specific_fileids=None):
214- self._ensure_real()
215- return self._real_repository.get_deltas_for_revisions(revisions,
216- specific_fileids=specific_fileids)
217+ medium = self._client._medium
218+ if medium._is_remote_before((1, 2)):
219+ self._ensure_real()
220+ for delta in self._real_repository.get_deltas_for_revisions(
221+ revisions, specific_fileids):
222+ yield delta
223+ return
224+ # Get the revision-ids of interest
225+ required_trees = set()
226+ for revision in revisions:
227+ required_trees.add(revision.revision_id)
228+ required_trees.update(revision.parent_ids[:1])
229+
230+ # Get the matching filtered trees. Note that it's more
231+ # efficient to pass filtered trees to changes_from() rather
232+ # than doing the filtering afterwards. changes_from() could
233+ # arguably do the filtering itself but it's path-based, not
234+ # file-id based, so filtering before or afterwards is
235+ # currently easier.
236+ if specific_fileids is None:
237+ trees = dict((t.get_revision_id(), t) for
238+ t in self.revision_trees(required_trees))
239+ else:
240+ trees = dict((t.get_revision_id(), t) for
241+ t in self._filtered_revision_trees(required_trees,
242+ specific_fileids))
243+
244+ # Calculate the deltas
245+ for revision in revisions:
246+ if not revision.parent_ids:
247+ old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
248+ else:
249+ old_tree = trees[revision.parent_ids[0]]
250+ yield trees[revision.revision_id].changes_from(old_tree)
251
252 @needs_read_lock
253 def get_revision_delta(self, revision_id, specific_fileids=None):
254@@ -3157,51 +3359,6 @@
255 self.bzrdir, self._client)
256 return self._control_files
257
258- def _get_checkout_format_vfs(self, lightweight=False):
259- self._ensure_real()
260- if lightweight:
261- format = RemoteBzrDirFormat()
262- self.bzrdir._format._supply_sub_formats_to(format)
263- format.workingtree_format = self._real_branch._get_checkout_format(
264- lightweight=lightweight).workingtree_format
265- return format
266- else:
267- return self._real_branch._get_checkout_format(lightweight=False)
268-
269- def _get_checkout_format(self, lightweight=False):
270- medium = self._client._medium
271- if medium._is_remote_before((2, 5)):
272- return self._get_checkout_format_vfs(lightweight)
273- try:
274- response = self._client.call('Branch.get_checkout_format',
275- self._remote_path(), lightweight)
276- except errors.UnknownSmartMethod:
277- medium._remember_remote_is_before((2, 5))
278- return self._get_checkout_format_vfs(lightweight)
279- if len(response) != 3:
280- raise errors.UnexpectedSmartServerResponse(response)
281- control_name, repo_name, branch_name = response
282- try:
283- format = controldir.network_format_registry.get(control_name)
284- except KeyError:
285- raise errors.UnknownFormatError(kind='control', format=control_name)
286- if repo_name:
287- try:
288- repo_format = _mod_repository.network_format_registry.get(
289- repo_name)
290- except KeyError:
291- raise errors.UnknownFormatError(kind='repository',
292- format=repo_name)
293- format.repository_format = repo_format
294- if branch_name:
295- try:
296- format.set_branch_format(
297- branch.network_format_registry.get(branch_name))
298- except KeyError:
299- raise errors.UnknownFormatError(kind='branch',
300- format=branch_name)
301- return format
302-
303 def get_physical_lock_status(self):
304 """See Branch.get_physical_lock_status()."""
305 try:
306
307=== modified file 'bzrlib/smart/branch.py'
308--- bzrlib/smart/branch.py 2011-12-11 16:36:30 +0000
309+++ bzrlib/smart/branch.py 2011-11-25 14:04:12 +0000
310@@ -446,22 +446,3 @@
311 return SuccessfulSmartServerResponse(('yes',))
312 else:
313 return SuccessfulSmartServerResponse(('no',))
314-
315-
316-class SmartServerBranchRequestGetCheckoutFormat(SmartServerBranchRequest):
317- """Get the format to use for checkouts of a branch.
318-
319- New in 2.5.
320- """
321-
322- def do_with_branch(self, branch, lightweight):
323- control_format = branch._get_checkout_format(lightweight)
324- control_name = control_format.network_name()
325- if not control_format.fixed_components:
326- branch_name = control_format.get_branch_format().network_name()
327- repo_name = control_format.repository_format.network_name()
328- else:
329- branch_name = ''
330- repo_name = ''
331- return SuccessfulSmartServerResponse(
332- (control_name, repo_name, branch_name))
333
334=== modified file 'bzrlib/smart/bzrdir.py'
335--- bzrlib/smart/bzrdir.py 2011-11-21 14:44:03 +0000
336+++ bzrlib/smart/bzrdir.py 2011-12-11 16:36:30 +0000
337@@ -208,6 +208,25 @@
338 branch_name))
339
340
341+class SmartServerBzrDirRequestCheckoutMetaDir(SmartServerRequestBzrDir):
342+ """Get the format to use for checkouts.
343+
344+ New in 2.5.
345+ """
346+
347+ def do_bzrdir_request(self):
348+ control_format = self._bzrdir.checkout_metadir()
349+ control_name = control_format.network_name()
350+ if not control_format.fixed_components:
351+ branch_name = control_format.get_branch_format().network_name()
352+ repo_name = control_format.repository_format.network_name()
353+ else:
354+ branch_name = ''
355+ repo_name = ''
356+ return SuccessfulSmartServerResponse(
357+ (control_name, repo_name, branch_name))
358+
359+
360 class SmartServerRequestCreateBranch(SmartServerRequestBzrDir):
361
362 def do(self, path, network_name):
363
364=== modified file 'bzrlib/smart/repository.py'
365--- bzrlib/smart/repository.py 2011-12-05 15:16:52 +0000
366+++ bzrlib/smart/repository.py 2011-12-11 16:36:30 +0000
367@@ -14,7 +14,7 @@
368 # along with this program; if not, write to the Free Software
369 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
370
371-"""Server-side repository related request implmentations."""
372+"""Server-side repository related request implementations."""
373
374 import bz2
375 import os
376@@ -28,6 +28,8 @@
377 bencode,
378 errors,
379 estimate_compressed_size,
380+ inventory as _mod_inventory,
381+ inventory_delta,
382 osutils,
383 pack,
384 trace,
385@@ -43,6 +45,7 @@
386 from bzrlib.repository import _strip_NULL_ghosts, network_format_registry
387 from bzrlib import revision as _mod_revision
388 from bzrlib.versionedfile import (
389+ ChunkedContentFactory,
390 NetworkRecordStream,
391 record_to_fulltext_bytes,
392 )
393@@ -1220,3 +1223,57 @@
394 yield zlib.compress(record.get_bytes_as('fulltext'))
395 finally:
396 self._repository.unlock()
397+
398+
399+class SmartServerRepositoryGetInventories(SmartServerRepositoryRequest):
400+ """Get the inventory deltas for a set of revision ids.
401+
402+ This accepts a list of revision ids, and then sends a chain
403+ of deltas for the inventories of those revisions. The first
404+ revision will be empty.
405+
406+ The server writes back zlibbed serialized inventory deltas,
407+ in the ordering specified. The base for each delta is the
408+ inventory generated by the previous delta.
409+
410+ New in 2.5.
411+ """
412+
413+ def _inventory_delta_stream(self, repository, ordering, revids):
414+ prev_inv = _mod_inventory.Inventory(root_id=None,
415+ revision_id=_mod_revision.NULL_REVISION)
416+ serializer = inventory_delta.InventoryDeltaSerializer(
417+ repository.supports_rich_root(),
418+ repository._format.supports_tree_reference)
419+ repository.lock_read()
420+ try:
421+ for inv, revid in repository._iter_inventories(revids, ordering):
422+ if inv is None:
423+ continue
424+ inv_delta = inv._make_delta(prev_inv)
425+ lines = serializer.delta_to_lines(
426+ prev_inv.revision_id, inv.revision_id, inv_delta)
427+ yield ChunkedContentFactory(inv.revision_id, None, None, lines)
428+ prev_inv = inv
429+ finally:
430+ repository.unlock()
431+
432+ def body_stream(self, repository, ordering, revids):
433+ substream = self._inventory_delta_stream(repository,
434+ ordering, revids)
435+ return _stream_to_byte_stream([('inventory-delta', substream)],
436+ repository._format)
437+
438+ def do_body(self, body_bytes):
439+ return SuccessfulSmartServerResponse(('ok', ),
440+ body_stream=self.body_stream(self._repository, self._ordering,
441+ body_bytes.splitlines()))
442+
443+ def do_repository_request(self, repository, ordering):
444+ if ordering == 'unordered':
445+ # inventory deltas for a topologically sorted stream
446+ # are likely to be smaller
447+ ordering = 'topological'
448+ self._ordering = ordering
449+ # Signal that we want a body
450+ return None
451
452=== modified file 'bzrlib/smart/request.py'
453--- bzrlib/smart/request.py 2011-12-11 16:36:30 +0000
454+++ bzrlib/smart/request.py 2011-12-11 16:36:30 +0000
455@@ -547,9 +547,6 @@
456 'Branch.get_stacked_on_url', 'bzrlib.smart.branch',
457 'SmartServerBranchRequestGetStackedOnURL', info='read')
458 request_handlers.register_lazy(
459- 'Branch.get_checkout_format', 'bzrlib.smart.branch',
460- 'SmartServerBranchRequestGetCheckoutFormat', info='read')
461-request_handlers.register_lazy(
462 'Branch.get_physical_lock_status', 'bzrlib.smart.branch',
463 'SmartServerBranchRequestGetPhysicalLockStatus', info='read')
464 request_handlers.register_lazy(
465@@ -586,6 +583,9 @@
466 'Branch.revision_id_to_revno', 'bzrlib.smart.branch',
467 'SmartServerBranchRequestRevisionIdToRevno', info='read')
468 request_handlers.register_lazy(
469+ 'BzrDir.checkout_metadir', 'bzrlib.smart.bzrdir',
470+ 'SmartServerBzrDirRequestCheckoutMetaDir', info='read')
471+request_handlers.register_lazy(
472 'BzrDir.cloning_metadir', 'bzrlib.smart.bzrdir',
473 'SmartServerBzrDirRequestCloningMetaDir', info='read')
474 request_handlers.register_lazy(
475@@ -754,6 +754,9 @@
476 'VersionedFileRepository.get_serializer_format', 'bzrlib.smart.repository',
477 'SmartServerRepositoryGetSerializerFormat', info='read')
478 request_handlers.register_lazy(
479+ 'VersionedFileRepository.get_inventories', 'bzrlib.smart.repository',
480+ 'SmartServerRepositoryGetInventories', info='read')
481+request_handlers.register_lazy(
482 'Repository.tarball', 'bzrlib.smart.repository',
483 'SmartServerRepositoryTarball', info='read')
484 request_handlers.register_lazy(
485
486=== modified file 'bzrlib/tests/blackbox/test_annotate.py'
487--- bzrlib/tests/blackbox/test_annotate.py 2011-12-11 16:36:30 +0000
488+++ bzrlib/tests/blackbox/test_annotate.py 2011-12-11 16:36:30 +0000
489@@ -326,6 +326,6 @@
490 # being too low. If rpc_count increases, more network roundtrips have
491 # become necessary for this use case. Please do not adjust this number
492 # upwards without agreement from bzr's network support maintainers.
493- self.assertLength(19, self.hpss_calls)
494+ self.assertLength(15, self.hpss_calls)
495 self.expectFailure("annotate accesses inventories, which require VFS access",
496 self.assertThat, self.hpss_calls, NoVfsCalls)
497
498=== modified file 'bzrlib/tests/blackbox/test_branch.py'
499--- bzrlib/tests/blackbox/test_branch.py 2011-12-11 16:36:30 +0000
500+++ bzrlib/tests/blackbox/test_branch.py 2011-12-11 16:36:30 +0000
501@@ -483,7 +483,7 @@
502 # being too low. If rpc_count increases, more network roundtrips have
503 # become necessary for this use case. Please do not adjust this number
504 # upwards without agreement from bzr's network support maintainers.
505- self.assertLength(40, self.hpss_calls)
506+ self.assertLength(33, self.hpss_calls)
507 self.expectFailure("branching to the same branch requires VFS access",
508 self.assertThat, self.hpss_calls, NoVfsCalls)
509
510
511=== modified file 'bzrlib/tests/blackbox/test_cat.py'
512--- bzrlib/tests/blackbox/test_cat.py 2011-12-11 16:36:30 +0000
513+++ bzrlib/tests/blackbox/test_cat.py 2011-12-11 16:36:30 +0000
514@@ -239,6 +239,5 @@
515 # being too low. If rpc_count increases, more network roundtrips have
516 # become necessary for this use case. Please do not adjust this number
517 # upwards without agreement from bzr's network support maintainers.
518- self.assertLength(16, self.hpss_calls)
519- self.expectFailure("cat still uses VFS calls",
520- self.assertThat, self.hpss_calls, NoVfsCalls)
521+ self.assertLength(9, self.hpss_calls)
522+ self.assertThat(self.hpss_calls, NoVfsCalls)
523
524=== modified file 'bzrlib/tests/blackbox/test_checkout.py'
525--- bzrlib/tests/blackbox/test_checkout.py 2011-12-11 16:36:30 +0000
526+++ bzrlib/tests/blackbox/test_checkout.py 2011-12-11 16:36:30 +0000
527@@ -179,8 +179,7 @@
528 for count in range(9):
529 t.commit(message='commit %d' % count)
530 self.reset_smart_call_log()
531- out, err = self.run_bzr(['checkout', self.get_url('from'),
532- 'target'])
533+ out, err = self.run_bzr(['checkout', self.get_url('from'), 'target'])
534 # This figure represent the amount of work to perform this use case. It
535 # is entirely ok to reduce this number if a test fails due to rpc_count
536 # being too low. If rpc_count increases, more network roundtrips have
537@@ -202,9 +201,5 @@
538 # being too low. If rpc_count increases, more network roundtrips have
539 # become necessary for this use case. Please do not adjust this number
540 # upwards without agreement from bzr's network support maintainers.
541- if len(self.hpss_calls) < 28 or len(self.hpss_calls) > 40:
542- self.fail(
543- "Incorrect length: wanted between 28 and 40, got %d for %r" % (
544- len(self.hpss_calls), self.hpss_calls))
545- self.expectFailure("lightweight checkouts require VFS calls",
546- self.assertThat, self.hpss_calls, NoVfsCalls)
547+ self.assertLength(15, self.hpss_calls)
548+ self.assertThat(self.hpss_calls, NoVfsCalls)
549
550=== modified file 'bzrlib/tests/blackbox/test_export.py'
551--- bzrlib/tests/blackbox/test_export.py 2011-12-11 16:36:30 +0000
552+++ bzrlib/tests/blackbox/test_export.py 2011-12-11 16:36:30 +0000
553@@ -448,6 +448,5 @@
554 # being too low. If rpc_count increases, more network roundtrips have
555 # become necessary for this use case. Please do not adjust this number
556 # upwards without agreement from bzr's network support maintainers.
557- self.assertLength(16, self.hpss_calls)
558- self.expectFailure("export requires inventory access which requires VFS",
559- self.assertThat, self.hpss_calls, NoVfsCalls)
560+ self.assertLength(7, self.hpss_calls)
561+ self.assertThat(self.hpss_calls, NoVfsCalls)
562
563=== modified file 'bzrlib/tests/blackbox/test_log.py'
564--- bzrlib/tests/blackbox/test_log.py 2011-12-11 16:36:30 +0000
565+++ bzrlib/tests/blackbox/test_log.py 2011-12-11 16:36:30 +0000
566@@ -1086,9 +1086,8 @@
567 # being too low. If rpc_count increases, more network roundtrips have
568 # become necessary for this use case. Please do not adjust this number
569 # upwards without agreement from bzr's network support maintainers.
570- self.assertLength(19, self.hpss_calls)
571- self.expectFailure("verbose log accesses inventories, which require VFS",
572- self.assertThat, self.hpss_calls, NoVfsCalls)
573+ self.assertLength(11, self.hpss_calls)
574+ self.assertThat(self.hpss_calls, NoVfsCalls)
575
576 def test_per_file(self):
577 self.setup_smart_server_with_call_log()
578@@ -1103,6 +1102,5 @@
579 # being too low. If rpc_count increases, more network roundtrips have
580 # become necessary for this use case. Please do not adjust this number
581 # upwards without agreement from bzr's network support maintainers.
582- self.assertLength(21, self.hpss_calls)
583- self.expectFailure("per-file graph access requires VFS",
584- self.assertThat, self.hpss_calls, NoVfsCalls)
585+ self.assertLength(15, self.hpss_calls)
586+ self.assertThat(self.hpss_calls, NoVfsCalls)
587
588=== modified file 'bzrlib/tests/blackbox/test_ls.py'
589--- bzrlib/tests/blackbox/test_ls.py 2011-12-11 16:36:30 +0000
590+++ bzrlib/tests/blackbox/test_ls.py 2011-12-11 16:36:30 +0000
591@@ -262,6 +262,5 @@
592 # being too low. If rpc_count increases, more network roundtrips have
593 # become necessary for this use case. Please do not adjust this number
594 # upwards without agreement from bzr's network support maintainers.
595- self.assertLength(15, self.hpss_calls)
596- self.expectFailure("inventories can only be accessed over VFS",
597- self.assertThat, self.hpss_calls, NoVfsCalls)
598+ self.assertLength(6, self.hpss_calls)
599+ self.assertThat(self.hpss_calls, NoVfsCalls)
600
601=== modified file 'bzrlib/tests/blackbox/test_sign_my_commits.py'
602--- bzrlib/tests/blackbox/test_sign_my_commits.py 2011-12-11 16:36:30 +0000
603+++ bzrlib/tests/blackbox/test_sign_my_commits.py 2011-12-11 16:36:30 +0000
604@@ -165,7 +165,7 @@
605 # being too low. If rpc_count increases, more network roundtrips have
606 # become necessary for this use case. Please do not adjust this number
607 # upwards without agreement from bzr's network support maintainers.
608- self.assertLength(54, self.hpss_calls)
609+ self.assertLength(50, self.hpss_calls)
610 self.expectFailure("signing commits requires VFS access",
611 self.assertThat, self.hpss_calls, NoVfsCalls)
612
613@@ -185,12 +185,5 @@
614 # being too low. If rpc_count increases, more network roundtrips have
615 # become necessary for this use case. Please do not adjust this number
616 # upwards without agreement from bzr's network support maintainers.
617-
618- # The number of readv requests seems to vary depending on the generated
619- # repository and how well it compresses, so allow for a bit of
620- # variation:
621- if len(self.hpss_calls) not in (18, 19):
622- self.fail("Incorrect length: wanted 18 or 19, got %d for %r" % (
623- len(self.hpss_calls), self.hpss_calls))
624- self.expectFailure("verifying commits requires VFS access",
625- self.assertThat, self.hpss_calls, NoVfsCalls)
626+ self.assertLength(10, self.hpss_calls)
627+ self.assertThat(self.hpss_calls, NoVfsCalls)
628
629=== modified file 'bzrlib/tests/per_interbranch/test_push.py'
630--- bzrlib/tests/per_interbranch/test_push.py 2011-10-15 01:09:01 +0000
631+++ bzrlib/tests/per_interbranch/test_push.py 2011-12-11 16:36:30 +0000
632@@ -279,10 +279,10 @@
633 # remote graph any further.
634 bzr_core_trace = Equals(
635 ['Repository.insert_stream_1.19', 'Repository.insert_stream_1.19',
636- 'get', 'Branch.set_last_revision_info', 'Branch.unlock'])
637+ 'Branch.set_last_revision_info', 'Branch.unlock'])
638 bzr_loom_trace = Equals(
639 ['Repository.insert_stream_1.19', 'Repository.insert_stream_1.19',
640- 'get', 'Branch.set_last_revision_info', 'get', 'Branch.unlock'])
641+ 'Branch.set_last_revision_info', 'get', 'Branch.unlock'])
642 self.assertThat(calls_after_insert_stream,
643 MatchesAny(bzr_core_trace, bzr_loom_trace))
644
645
646=== modified file 'bzrlib/tests/test_remote.py'
647--- bzrlib/tests/test_remote.py 2011-12-11 16:36:30 +0000
648+++ bzrlib/tests/test_remote.py 2011-12-11 16:36:30 +0000
649@@ -69,6 +69,7 @@
650 from bzrlib.smart.repository import (
651 SmartServerRepositoryGetParentMap,
652 SmartServerRepositoryGetStream_1_19,
653+ _stream_to_byte_stream,
654 )
655 from bzrlib.symbol_versioning import deprecated_in
656 from bzrlib.tests import (
657@@ -503,6 +504,43 @@
658 self.assertRaises(errors.UnknownFormatError, a_bzrdir.cloning_metadir)
659
660
661+class TestBzrDirCheckoutMetaDir(TestRemote):
662+
663+ def test__get_checkout_format(self):
664+ transport = MemoryTransport()
665+ client = FakeClient(transport.base)
666+ reference_bzrdir_format = bzrdir.format_registry.get('default')()
667+ control_name = reference_bzrdir_format.network_name()
668+ client.add_expected_call(
669+ 'BzrDir.checkout_metadir', ('quack/', ),
670+ 'success', (control_name, '', ''))
671+ transport.mkdir('quack')
672+ transport = transport.clone('quack')
673+ a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
674+ _client=client)
675+ result = a_bzrdir.checkout_metadir()
676+ # We should have got a reference control dir with default branch and
677+ # repository formats.
678+ self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
679+ self.assertEqual(None, result._repository_format)
680+ self.assertEqual(None, result._branch_format)
681+ self.assertFinished(client)
682+
683+ def test_unknown_format(self):
684+ transport = MemoryTransport()
685+ client = FakeClient(transport.base)
686+ client.add_expected_call(
687+ 'BzrDir.checkout_metadir', ('quack/',),
688+ 'success', ('dontknow', '', ''))
689+ transport.mkdir('quack')
690+ transport = transport.clone('quack')
691+ a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
692+ _client=client)
693+ self.assertRaises(errors.UnknownFormatError,
694+ a_bzrdir.checkout_metadir)
695+ self.assertFinished(client)
696+
697+
698 class TestBzrDirDestroyBranch(TestRemote):
699
700 def test_destroy_default(self):
701@@ -1102,47 +1140,6 @@
702 self.assertFinished(client)
703
704
705-class TestBranchGetCheckoutFormat(RemoteBranchTestCase):
706-
707- def test__get_checkout_format(self):
708- transport = MemoryTransport()
709- client = FakeClient(transport.base)
710- reference_bzrdir_format = bzrdir.format_registry.get('default')()
711- control_name = reference_bzrdir_format.network_name()
712- client.add_expected_call(
713- 'Branch.get_stacked_on_url', ('quack/',),
714- 'error', ('NotStacked',))
715- client.add_expected_call(
716- 'Branch.get_checkout_format', ('quack/', False),
717- 'success', (control_name, '', ''))
718- transport.mkdir('quack')
719- transport = transport.clone('quack')
720- branch = self.make_remote_branch(transport, client)
721- result = branch._get_checkout_format()
722- # We should have got a reference control dir with default branch and
723- # repository formats.
724- self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
725- self.assertEqual(None, result._repository_format)
726- self.assertEqual(None, result._branch_format)
727- self.assertFinished(client)
728-
729- def test_unknown_format(self):
730- transport = MemoryTransport()
731- client = FakeClient(transport.base)
732- client.add_expected_call(
733- 'Branch.get_stacked_on_url', ('quack/',),
734- 'error', ('NotStacked',))
735- client.add_expected_call(
736- 'Branch.get_checkout_format', ('quack/', False),
737- 'success', ('dontknow', '', ''))
738- transport.mkdir('quack')
739- transport = transport.clone('quack')
740- branch = self.make_remote_branch(transport, client)
741- self.assertRaises(errors.UnknownFormatError,
742- branch._get_checkout_format)
743- self.assertFinished(client)
744-
745-
746 class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
747
748 def test_get_physical_lock_status_yes(self):
749@@ -4216,3 +4213,43 @@
750 'Repository.unlock', ('quack/', 'token', 'False'),
751 'success', ('ok', ))
752 repo.pack(['hinta', 'hintb'])
753+
754+
755+class TestRepositoryIterInventories(TestRemoteRepository):
756+ """Test Repository.iter_inventories."""
757+
758+ def _serialize_inv_delta(self, old_name, new_name, delta):
759+ serializer = inventory_delta.InventoryDeltaSerializer(True, False)
760+ return "".join(serializer.delta_to_lines(old_name, new_name, delta))
761+
762+ def test_single_empty(self):
763+ transport_path = 'quack'
764+ repo, client = self.setup_fake_client_and_repository(transport_path)
765+ fmt = bzrdir.format_registry.get('2a')().repository_format
766+ repo._format = fmt
767+ stream = [('inventory-delta', [
768+ versionedfile.FulltextContentFactory('somerevid', None, None,
769+ self._serialize_inv_delta('null:', 'somerevid', []))])]
770+ client.add_expected_call(
771+ 'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
772+ 'success', ('ok', ),
773+ _stream_to_byte_stream(stream, fmt))
774+ ret = list(repo.iter_inventories(["somerevid"]))
775+ self.assertLength(1, ret)
776+ inv = ret[0]
777+ self.assertEquals("somerevid", inv.revision_id)
778+
779+ def test_empty(self):
780+ transport_path = 'quack'
781+ repo, client = self.setup_fake_client_and_repository(transport_path)
782+ ret = list(repo.iter_inventories([]))
783+ self.assertEquals(ret, [])
784+
785+ def test_missing(self):
786+ transport_path = 'quack'
787+ repo, client = self.setup_fake_client_and_repository(transport_path)
788+ client.add_expected_call(
789+ 'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
790+ 'success', ('ok', ), iter([]))
791+ self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(
792+ ["somerevid"]))
793
794=== modified file 'bzrlib/tests/test_smart.py'
795--- bzrlib/tests/test_smart.py 2011-12-11 16:36:30 +0000
796+++ bzrlib/tests/test_smart.py 2011-12-11 16:36:30 +0000
797@@ -32,6 +32,7 @@
798 bzrdir,
799 errors,
800 gpg,
801+ inventory_delta,
802 tests,
803 transport,
804 urlutils,
805@@ -224,6 +225,24 @@
806 self.assertEqual(expected, request.execute('', 'False'))
807
808
809+class TestSmartServerBzrDirRequestCloningMetaDir(
810+ tests.TestCaseWithMemoryTransport):
811+ """Tests for BzrDir.checkout_metadir."""
812+
813+ def test_checkout_metadir(self):
814+ backing = self.get_transport()
815+ request = smart_dir.SmartServerBzrDirRequestCheckoutMetaDir(
816+ backing)
817+ branch = self.make_branch('.', format='2a')
818+ response = request.execute('')
819+ self.assertEqual(
820+ smart_req.SmartServerResponse(
821+ ('Bazaar-NG meta directory, format 1\n',
822+ 'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
823+ 'Bazaar Branch Format 7 (needs bzr 1.6)\n')),
824+ response)
825+
826+
827 class TestSmartServerBzrDirRequestDestroyBranch(
828 tests.TestCaseWithMemoryTransport):
829 """Tests for BzrDir.destroy_branch."""
830@@ -1458,35 +1477,6 @@
831 smart_req.SmartServerResponse(('no',)), response)
832
833
834-class TestSmartServerBranchRequestGetCheckoutFormat(TestLockedBranch):
835-
836- def test_lightweight(self):
837- backing = self.get_transport()
838- request = smart_branch.SmartServerBranchRequestGetCheckoutFormat(
839- backing)
840- branch = self.make_branch('.', format='2a')
841- response = request.execute('', 'True')
842- self.assertEqual(
843- smart_req.SmartServerResponse(
844- ('Bazaar-NG meta directory, format 1\n',
845- 'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
846- 'Bazaar Branch Format 7 (needs bzr 1.6)\n')),
847- response)
848-
849- def test_heavyweight(self):
850- backing = self.get_transport()
851- request = smart_branch.SmartServerBranchRequestGetCheckoutFormat(
852- backing)
853- branch = self.make_branch('.', format='2a')
854- response = request.execute('', 'False')
855- self.assertEqual(
856- smart_req.SmartServerResponse((
857- 'Bazaar-NG meta directory, format 1\n',
858- 'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
859- 'Bazaar Branch Format 7 (needs bzr 1.6)\n')),
860- response)
861-
862-
863 class TestSmartServerBranchRequestUnlock(TestLockedBranch):
864
865 def setUp(self):
866@@ -2456,8 +2446,6 @@
867 smart_branch.SmartServerBranchPutConfigFile)
868 self.assertHandlerEqual('Branch.get_parent',
869 smart_branch.SmartServerBranchGetParent)
870- self.assertHandlerEqual('Branch.get_checkout_format',
871- smart_branch.SmartServerBranchRequestGetCheckoutFormat)
872 self.assertHandlerEqual('Branch.get_physical_lock_status',
873 smart_branch.SmartServerBranchRequestGetPhysicalLockStatus)
874 self.assertHandlerEqual('Branch.get_tags_bytes',
875@@ -2492,6 +2480,8 @@
876 smart_dir.SmartServerRequestInitializeBzrDir)
877 self.assertHandlerEqual('BzrDirFormat.initialize_ex_1.16',
878 smart_dir.SmartServerRequestBzrDirInitializeEx)
879+ self.assertHandlerEqual('BzrDir.checkout_metadir',
880+ smart_dir.SmartServerBzrDirRequestCheckoutMetaDir)
881 self.assertHandlerEqual('BzrDir.cloning_metadir',
882 smart_dir.SmartServerBzrDirRequestCloningMetaDir)
883 self.assertHandlerEqual('BzrDir.get_config_file',
884@@ -2558,6 +2548,8 @@
885 smart_repo.SmartServerRepositoryAbortWriteGroup)
886 self.assertHandlerEqual('VersionedFileRepository.get_serializer_format',
887 smart_repo.SmartServerRepositoryGetSerializerFormat)
888+ self.assertHandlerEqual('VersionedFileRepository.get_inventories',
889+ smart_repo.SmartServerRepositoryGetInventories)
890 self.assertHandlerEqual('Transport.is_readonly',
891 smart_req.SmartServerIsReadonly)
892
893@@ -2623,3 +2615,48 @@
894 smart_req.SuccessfulSmartServerResponse(('ok', ), ),
895 request.do_body(''))
896
897+
898+class TestSmartServerRepositoryGetInventories(tests.TestCaseWithTransport):
899+
900+ def _get_serialized_inventory_delta(self, repository, base_revid, revid):
901+ base_inv = repository.revision_tree(base_revid).inventory
902+ inv = repository.revision_tree(revid).inventory
903+ inv_delta = inv._make_delta(base_inv)
904+ serializer = inventory_delta.InventoryDeltaSerializer(True, False)
905+ return "".join(serializer.delta_to_lines(base_revid, revid, inv_delta))
906+
907+ def test_single(self):
908+ backing = self.get_transport()
909+ request = smart_repo.SmartServerRepositoryGetInventories(backing)
910+ t = self.make_branch_and_tree('.', format='2a')
911+ self.addCleanup(t.lock_write().unlock)
912+ self.build_tree_contents([("file", "somecontents")])
913+ t.add(["file"], ["thefileid"])
914+ t.commit(rev_id='somerev', message="add file")
915+ self.assertIs(None, request.execute('', 'unordered'))
916+ response = request.do_body("somerev\n")
917+ self.assertTrue(response.is_successful())
918+ self.assertEquals(response.args, ("ok", ))
919+ stream = [('inventory-delta', [
920+ versionedfile.FulltextContentFactory('somerev', None, None,
921+ self._get_serialized_inventory_delta(
922+ t.branch.repository, 'null:', 'somerev'))])]
923+ fmt = bzrdir.format_registry.get('2a')().repository_format
924+ self.assertEquals(
925+ "".join(response.body_stream),
926+ "".join(smart_repo._stream_to_byte_stream(stream, fmt)))
927+
928+ def test_empty(self):
929+ backing = self.get_transport()
930+ request = smart_repo.SmartServerRepositoryGetInventories(backing)
931+ t = self.make_branch_and_tree('.', format='2a')
932+ self.addCleanup(t.lock_write().unlock)
933+ self.build_tree_contents([("file", "somecontents")])
934+ t.add(["file"], ["thefileid"])
935+ t.commit(rev_id='somerev', message="add file")
936+ self.assertIs(None, request.execute('', 'unordered'))
937+ response = request.do_body("")
938+ self.assertTrue(response.is_successful())
939+ self.assertEquals(response.args, ("ok", ))
940+ self.assertEquals("".join(response.body_stream),
941+ "Bazaar pack format 1 (introduced in 0.18)\nB54\n\nBazaar repository format 2a (needs bzr 1.16 or later)\nE")
942
943=== modified file 'doc/en/release-notes/bzr-2.5.txt'
944--- doc/en/release-notes/bzr-2.5.txt 2011-12-11 16:36:30 +0000
945+++ doc/en/release-notes/bzr-2.5.txt 2011-12-11 16:36:30 +0000
946@@ -111,6 +111,9 @@
947 * Plugins can now register additional "location aliases".
948 (Jelmer Vernooij)
949
950+* ``bzr status`` no longer shows shelves if files are specified.
951+ (Francis Devereux)
952+
953 * Revision specifiers will now only browse as much history as they
954 need to, rather than grabbing the whole history unnecessarily in some
955 cases. (Jelmer Vernooij)
956@@ -236,6 +239,13 @@
957 ``Repository.get_revision_signature_text``.
958 (Jelmer Vernooij)
959
960+* Add HPSS calls for ``Repository.iter_files_bytes`` and
961+ ``VersionedFileRepository.get_inventories``, speeding up
962+ several commands including ``bzr export`` and ``bzr co --lightweight``.
963+ (Jelmer Vernooij, #608640)
964+
965+* Add HPSS call for ``Branch.get_checkout_format``. (Jelmer Vernooij, #894459)
966+
967 * Add HPSS call for ``Repository.pack``. (Jelmer Vernooij, #894461)
968
969 * Custom HPSS error handlers can now be installed in the smart server client