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

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: no longer in the source branch.
Merged at revision: 6372
Proposed branch: lp:~jelmer/bzr/hpss-get-inventories
Merge into: lp:bzr
Prerequisite: lp:~jelmer/bzr/hpss-_get-checkout-format
Diff against target: 728 lines (+372/-52)
17 files modified
bzrlib/remote.py (+171/-5)
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 (+3/-3)
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 (+4/-12)
bzrlib/tests/per_interbranch/test_push.py (+2/-2)
bzrlib/tests/per_repository_chk/test_supported.py (+11/-1)
bzrlib/tests/per_repository_vf/test_add_inventory_by_delta.py (+1/-1)
bzrlib/tests/test_remote.py (+41/-0)
bzrlib/tests/test_smart.py (+48/-0)
doc/en/release-notes/bzr-2.5.txt (+13/-0)
To merge this branch: bzr merge lp:~jelmer/bzr/hpss-get-inventories
Reviewer Review Type Date Requested Status
Jelmer Vernooij (community) Approve
Vincent Ladeuil Approve
Andrew Bennetts Pending
Review via email: mp+85252@code.launchpad.net

This proposal supersedes a proposal from 2011-11-29.

Commit message

Add HPSS call for retrieving 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.

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.

Unlike my previous attempt at this, this now uses record streams with inventory deltas. It is still a separate verb though.

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

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

>> 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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal
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 : Posted in a previous version of this proposal
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...

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

Based on the tests and the decrease of roundtrips: approved !

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()

_vfs_checkout_metadir -> checkout_metadir -> _vfs_checkout_metadir ?

Is that a loop or do I miss something obvious ?

Up to you to wait for spiv's review.

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

Am 13/12/11 11:14, schrieb Vincent Ladeuil:
> Review: Approve
>
> Based on the tests and the decrease of roundtrips: approved !
>
> 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()
>
> _vfs_checkout_metadir -> checkout_metadir -> _vfs_checkout_metadir ?
It's not checkout_metadir, but _real_bzrdir.checkout_metadir. :-)
>
> Is that a loop or do I miss something obvious ?
>
> Up to you to wait for spiv's review.
Cheers,

Jelmer

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

> Up to you to wait for spiv's review.
Thanks.

I haven't heard from spiv in the last week, and I'd really like to get this landed so it gets some decent testing for 2.5.0. My recent changes should have addressed his concerns regarding the use of record streams, and I'm happy to tweak whatever is necessary later based on post-commit reviews.

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

sent to pqm by email

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-14 12:33:04 +0000
3+++ bzrlib/remote.py 2011-12-14 21:28:31 +0000
4@@ -27,6 +27,7 @@
5 errors,
6 gpg,
7 graph,
8+ inventory_delta,
9 lock,
10 lockdir,
11 osutils,
12@@ -1830,9 +1831,125 @@
13 def get_inventory(self, revision_id):
14 return list(self.iter_inventories([revision_id]))[0]
15
16+ def _iter_inventories_rpc(self, revision_ids, ordering):
17+ if ordering is None:
18+ ordering = 'unordered'
19+ path = self.bzrdir._path_for_remote_call(self._client)
20+ body = "\n".join(revision_ids)
21+ response_tuple, response_handler = (
22+ self._call_with_body_bytes_expecting_body(
23+ "VersionedFileRepository.get_inventories",
24+ (path, ordering), body))
25+ if response_tuple[0] != "ok":
26+ raise errors.UnexpectedSmartServerResponse(response_tuple)
27+ deserializer = inventory_delta.InventoryDeltaDeserializer()
28+ byte_stream = response_handler.read_streamed_body()
29+ decoded = smart_repo._byte_stream_to_stream(byte_stream)
30+ if decoded is None:
31+ # no results whatsoever
32+ return
33+ src_format, stream = decoded
34+ if src_format.network_name() != self._format.network_name():
35+ raise AssertionError(
36+ "Mismatched RemoteRepository and stream src %r, %r" % (
37+ src_format.network_name(), self._format.network_name()))
38+ # ignore the src format, it's not really relevant
39+ prev_inv = Inventory(root_id=None,
40+ revision_id=_mod_revision.NULL_REVISION)
41+ # there should be just one substream, with inventory deltas
42+ substream_kind, substream = stream.next()
43+ if substream_kind != "inventory-deltas":
44+ raise AssertionError(
45+ "Unexpected stream %r received" % substream_kind)
46+ for record in substream:
47+ (parent_id, new_id, versioned_root, tree_references, invdelta) = (
48+ deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
49+ if parent_id != prev_inv.revision_id:
50+ raise AssertionError("invalid base %r != %r" % (parent_id,
51+ prev_inv.revision_id))
52+ inv = prev_inv.create_by_apply_delta(invdelta, new_id)
53+ yield inv, inv.revision_id
54+ prev_inv = inv
55+
56+ def _iter_inventories_vfs(self, revision_ids, ordering=None):
57+ self._ensure_real()
58+ return self._real_repository._iter_inventories(revision_ids, ordering)
59+
60 def iter_inventories(self, revision_ids, ordering=None):
61- self._ensure_real()
62- return self._real_repository.iter_inventories(revision_ids, ordering)
63+ """Get many inventories by revision_ids.
64+
65+ This will buffer some or all of the texts used in constructing the
66+ inventories in memory, but will only parse a single inventory at a
67+ time.
68+
69+ :param revision_ids: The expected revision ids of the inventories.
70+ :param ordering: optional ordering, e.g. 'topological'. If not
71+ specified, the order of revision_ids will be preserved (by
72+ buffering if necessary).
73+ :return: An iterator of inventories.
74+ """
75+ if ((None in revision_ids)
76+ or (_mod_revision.NULL_REVISION in revision_ids)):
77+ raise ValueError('cannot get null revision inventory')
78+ for inv, revid in self._iter_inventories(revision_ids, ordering):
79+ if inv is None:
80+ raise errors.NoSuchRevision(self, revid)
81+ yield inv
82+
83+ def _iter_inventories(self, revision_ids, ordering=None):
84+ if len(revision_ids) == 0:
85+ return
86+ missing = set(revision_ids)
87+ if ordering is None:
88+ order_as_requested = True
89+ invs = {}
90+ order = list(revision_ids)
91+ order.reverse()
92+ next_revid = order.pop()
93+ else:
94+ order_as_requested = False
95+ if ordering != 'unordered' and self._fallback_repositories:
96+ raise ValueError('unsupported ordering %r' % ordering)
97+ iter_inv_fns = [self._iter_inventories_rpc] + [
98+ fallback._iter_inventories for fallback in
99+ self._fallback_repositories]
100+ try:
101+ for iter_inv in iter_inv_fns:
102+ request = [revid for revid in revision_ids if revid in missing]
103+ for inv, revid in iter_inv(request, ordering):
104+ if inv is None:
105+ continue
106+ missing.remove(inv.revision_id)
107+ if ordering != 'unordered':
108+ invs[revid] = inv
109+ else:
110+ yield inv, revid
111+ if order_as_requested:
112+ # Yield as many results as we can while preserving order.
113+ while next_revid in invs:
114+ inv = invs.pop(next_revid)
115+ yield inv, inv.revision_id
116+ try:
117+ next_revid = order.pop()
118+ except IndexError:
119+ # We still want to fully consume the stream, just
120+ # in case it is not actually finished at this point
121+ next_revid = None
122+ break
123+ except errors.UnknownSmartMethod:
124+ for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
125+ yield inv, revid
126+ return
127+ # Report missing
128+ if order_as_requested:
129+ if next_revid is not None:
130+ yield None, next_revid
131+ while order:
132+ revid = order.pop()
133+ yield invs.get(revid), revid
134+ else:
135+ while missing:
136+ yield None, missing.pop()
137
138 @needs_read_lock
139 def get_revision(self, revision_id):
140@@ -2191,6 +2308,8 @@
141
142 @needs_read_lock
143 def _get_inventory_xml(self, revision_id):
144+ # This call is used by older working tree formats,
145+ # which stored a serialized basis inventory.
146 self._ensure_real()
147 return self._real_repository._get_inventory_xml(revision_id)
148
149@@ -2235,11 +2354,58 @@
150 revids.update(set(fallback.all_revision_ids()))
151 return list(revids)
152
153+ def _filtered_revision_trees(self, revision_ids, file_ids):
154+ """Return Tree for a revision on this branch with only some files.
155+
156+ :param revision_ids: a sequence of revision-ids;
157+ a revision-id may not be None or 'null:'
158+ :param file_ids: if not None, the result is filtered
159+ so that only those file-ids, their parents and their
160+ children are included.
161+ """
162+ inventories = self.iter_inventories(revision_ids)
163+ for inv in inventories:
164+ # Should we introduce a FilteredRevisionTree class rather
165+ # than pre-filter the inventory here?
166+ filtered_inv = inv.filter(file_ids)
167+ yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
168+
169 @needs_read_lock
170 def get_deltas_for_revisions(self, revisions, specific_fileids=None):
171- self._ensure_real()
172- return self._real_repository.get_deltas_for_revisions(revisions,
173- specific_fileids=specific_fileids)
174+ medium = self._client._medium
175+ if medium._is_remote_before((1, 2)):
176+ self._ensure_real()
177+ for delta in self._real_repository.get_deltas_for_revisions(
178+ revisions, specific_fileids):
179+ yield delta
180+ return
181+ # Get the revision-ids of interest
182+ required_trees = set()
183+ for revision in revisions:
184+ required_trees.add(revision.revision_id)
185+ required_trees.update(revision.parent_ids[:1])
186+
187+ # Get the matching filtered trees. Note that it's more
188+ # efficient to pass filtered trees to changes_from() rather
189+ # than doing the filtering afterwards. changes_from() could
190+ # arguably do the filtering itself but it's path-based, not
191+ # file-id based, so filtering before or afterwards is
192+ # currently easier.
193+ if specific_fileids is None:
194+ trees = dict((t.get_revision_id(), t) for
195+ t in self.revision_trees(required_trees))
196+ else:
197+ trees = dict((t.get_revision_id(), t) for
198+ t in self._filtered_revision_trees(required_trees,
199+ specific_fileids))
200+
201+ # Calculate the deltas
202+ for revision in revisions:
203+ if not revision.parent_ids:
204+ old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
205+ else:
206+ old_tree = trees[revision.parent_ids[0]]
207+ yield trees[revision.revision_id].changes_from(old_tree)
208
209 @needs_read_lock
210 def get_revision_delta(self, revision_id, specific_fileids=None):
211
212=== modified file 'bzrlib/smart/repository.py'
213--- bzrlib/smart/repository.py 2011-12-11 13:30:10 +0000
214+++ bzrlib/smart/repository.py 2011-12-14 21:28:31 +0000
215@@ -14,7 +14,7 @@
216 # along with this program; if not, write to the Free Software
217 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
218
219-"""Server-side repository related request implmentations."""
220+"""Server-side repository related request implementations."""
221
222 import bz2
223 import os
224@@ -28,6 +28,8 @@
225 bencode,
226 errors,
227 estimate_compressed_size,
228+ inventory as _mod_inventory,
229+ inventory_delta,
230 osutils,
231 pack,
232 trace,
233@@ -43,6 +45,7 @@
234 from bzrlib.repository import _strip_NULL_ghosts, network_format_registry
235 from bzrlib import revision as _mod_revision
236 from bzrlib.versionedfile import (
237+ ChunkedContentFactory,
238 NetworkRecordStream,
239 record_to_fulltext_bytes,
240 )
241@@ -1243,3 +1246,57 @@
242 yield zlib.compress(record.get_bytes_as('fulltext'))
243 finally:
244 self._repository.unlock()
245+
246+
247+class SmartServerRepositoryGetInventories(SmartServerRepositoryRequest):
248+ """Get the inventory deltas for a set of revision ids.
249+
250+ This accepts a list of revision ids, and then sends a chain
251+ of deltas for the inventories of those revisions. The first
252+ revision will be empty.
253+
254+ The server writes back zlibbed serialized inventory deltas,
255+ in the ordering specified. The base for each delta is the
256+ inventory generated by the previous delta.
257+
258+ New in 2.5.
259+ """
260+
261+ def _inventory_delta_stream(self, repository, ordering, revids):
262+ prev_inv = _mod_inventory.Inventory(root_id=None,
263+ revision_id=_mod_revision.NULL_REVISION)
264+ serializer = inventory_delta.InventoryDeltaSerializer(
265+ repository.supports_rich_root(),
266+ repository._format.supports_tree_reference)
267+ repository.lock_read()
268+ try:
269+ for inv, revid in repository._iter_inventories(revids, ordering):
270+ if inv is None:
271+ continue
272+ inv_delta = inv._make_delta(prev_inv)
273+ lines = serializer.delta_to_lines(
274+ prev_inv.revision_id, inv.revision_id, inv_delta)
275+ yield ChunkedContentFactory(inv.revision_id, None, None, lines)
276+ prev_inv = inv
277+ finally:
278+ repository.unlock()
279+
280+ def body_stream(self, repository, ordering, revids):
281+ substream = self._inventory_delta_stream(repository,
282+ ordering, revids)
283+ return _stream_to_byte_stream([('inventory-deltas', substream)],
284+ repository._format)
285+
286+ def do_body(self, body_bytes):
287+ return SuccessfulSmartServerResponse(('ok', ),
288+ body_stream=self.body_stream(self._repository, self._ordering,
289+ body_bytes.splitlines()))
290+
291+ def do_repository_request(self, repository, ordering):
292+ if ordering == 'unordered':
293+ # inventory deltas for a topologically sorted stream
294+ # are likely to be smaller
295+ ordering = 'topological'
296+ self._ordering = ordering
297+ # Signal that we want a body
298+ return None
299
300=== modified file 'bzrlib/smart/request.py'
301--- bzrlib/smart/request.py 2011-12-14 12:20:36 +0000
302+++ bzrlib/smart/request.py 2011-12-14 21:28:31 +0000
303@@ -751,15 +751,18 @@
304 'Repository.check_write_group', 'bzrlib.smart.repository',
305 'SmartServerRepositoryCheckWriteGroup', info='read')
306 request_handlers.register_lazy(
307- 'VersionedFileRepository.get_serializer_format', 'bzrlib.smart.repository',
308- 'SmartServerRepositoryGetSerializerFormat', info='read')
309-request_handlers.register_lazy(
310 'Repository.reconcile', 'bzrlib.smart.repository',
311 'SmartServerRepositoryReconcile', info='idem')
312 request_handlers.register_lazy(
313 'Repository.tarball', 'bzrlib.smart.repository',
314 'SmartServerRepositoryTarball', info='read')
315 request_handlers.register_lazy(
316+ 'VersionedFileRepository.get_serializer_format', 'bzrlib.smart.repository',
317+ 'SmartServerRepositoryGetSerializerFormat', info='read')
318+request_handlers.register_lazy(
319+ 'VersionedFileRepository.get_inventories', 'bzrlib.smart.repository',
320+ 'SmartServerRepositoryGetInventories', info='read')
321+request_handlers.register_lazy(
322 'rmdir', 'bzrlib.smart.vfs', 'RmdirRequest', info='semivfs')
323 request_handlers.register_lazy(
324 'stat', 'bzrlib.smart.vfs', 'StatRequest', info='read')
325
326=== modified file 'bzrlib/tests/blackbox/test_annotate.py'
327--- bzrlib/tests/blackbox/test_annotate.py 2011-12-14 12:15:44 +0000
328+++ bzrlib/tests/blackbox/test_annotate.py 2011-12-14 21:28:31 +0000
329@@ -326,6 +326,6 @@
330 # being too low. If rpc_count increases, more network roundtrips have
331 # become necessary for this use case. Please do not adjust this number
332 # upwards without agreement from bzr's network support maintainers.
333- self.assertLength(19, self.hpss_calls)
334+ self.assertLength(15, self.hpss_calls)
335 self.expectFailure("annotate accesses inventories, which require VFS access",
336 self.assertThat, self.hpss_calls, ContainsNoVfsCalls)
337
338=== modified file 'bzrlib/tests/blackbox/test_branch.py'
339--- bzrlib/tests/blackbox/test_branch.py 2011-12-14 18:17:43 +0000
340+++ bzrlib/tests/blackbox/test_branch.py 2011-12-14 21:28:31 +0000
341@@ -483,7 +483,7 @@
342 # being too low. If rpc_count increases, more network roundtrips have
343 # become necessary for this use case. Please do not adjust this number
344 # upwards without agreement from bzr's network support maintainers.
345- self.assertLength(40, self.hpss_calls)
346+ self.assertLength(33, self.hpss_calls)
347 self.expectFailure("branching to the same branch requires VFS access",
348 self.assertThat, self.hpss_calls, ContainsNoVfsCalls)
349
350@@ -521,8 +521,8 @@
351 # being too low. If rpc_count increases, more network roundtrips have
352 # become necessary for this use case. Please do not adjust this number
353 # upwards without agreement from bzr's network support maintainers.
354- self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
355 self.assertLength(15, self.hpss_calls)
356+ self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
357
358 def test_branch_from_branch_with_tags(self):
359 self.setup_smart_server_with_call_log()
360@@ -539,8 +539,8 @@
361 # being too low. If rpc_count increases, more network roundtrips have
362 # become necessary for this use case. Please do not adjust this number
363 # upwards without agreement from bzr's network support maintainers.
364+ self.assertLength(10, self.hpss_calls)
365 self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
366- self.assertLength(10, self.hpss_calls)
367
368 def test_branch_to_stacked_from_trivial_branch_streaming_acceptance(self):
369 self.setup_smart_server_with_call_log()
370
371=== modified file 'bzrlib/tests/blackbox/test_cat.py'
372--- bzrlib/tests/blackbox/test_cat.py 2011-12-14 12:15:44 +0000
373+++ bzrlib/tests/blackbox/test_cat.py 2011-12-14 21:28:31 +0000
374@@ -239,6 +239,5 @@
375 # being too low. If rpc_count increases, more network roundtrips have
376 # become necessary for this use case. Please do not adjust this number
377 # upwards without agreement from bzr's network support maintainers.
378- self.assertLength(16, self.hpss_calls)
379- self.expectFailure("cat still uses VFS calls",
380- self.assertThat, self.hpss_calls, ContainsNoVfsCalls)
381+ self.assertLength(9, self.hpss_calls)
382+ self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
383
384=== modified file 'bzrlib/tests/blackbox/test_checkout.py'
385--- bzrlib/tests/blackbox/test_checkout.py 2011-12-14 18:17:43 +0000
386+++ bzrlib/tests/blackbox/test_checkout.py 2011-12-14 21:28:31 +0000
387@@ -179,8 +179,7 @@
388 for count in range(9):
389 t.commit(message='commit %d' % count)
390 self.reset_smart_call_log()
391- out, err = self.run_bzr(['checkout', self.get_url('from'),
392- 'target'])
393+ out, err = self.run_bzr(['checkout', self.get_url('from'), 'target'])
394 # This figure represent the amount of work to perform this use case. It
395 # is entirely ok to reduce this number if a test fails due to rpc_count
396 # being too low. If rpc_count increases, more network roundtrips have
397@@ -202,9 +201,5 @@
398 # being too low. If rpc_count increases, more network roundtrips have
399 # become necessary for this use case. Please do not adjust this number
400 # upwards without agreement from bzr's network support maintainers.
401- if len(self.hpss_calls) < 28 or len(self.hpss_calls) > 40:
402- self.fail(
403- "Incorrect length: wanted between 28 and 40, got %d for %r" % (
404- len(self.hpss_calls), self.hpss_calls))
405- self.expectFailure("lightweight checkouts require VFS calls",
406- self.assertThat, self.hpss_calls, ContainsNoVfsCalls)
407+ self.assertLength(15, self.hpss_calls)
408+ self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
409
410=== modified file 'bzrlib/tests/blackbox/test_export.py'
411--- bzrlib/tests/blackbox/test_export.py 2011-12-14 12:15:44 +0000
412+++ bzrlib/tests/blackbox/test_export.py 2011-12-14 21:28:31 +0000
413@@ -448,6 +448,5 @@
414 # being too low. If rpc_count increases, more network roundtrips have
415 # become necessary for this use case. Please do not adjust this number
416 # upwards without agreement from bzr's network support maintainers.
417- self.assertLength(16, self.hpss_calls)
418- self.expectFailure("export requires inventory access which requires VFS",
419- self.assertThat, self.hpss_calls, ContainsNoVfsCalls)
420+ self.assertLength(7, self.hpss_calls)
421+ self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
422
423=== modified file 'bzrlib/tests/blackbox/test_log.py'
424--- bzrlib/tests/blackbox/test_log.py 2011-12-14 12:15:44 +0000
425+++ bzrlib/tests/blackbox/test_log.py 2011-12-14 21:28:31 +0000
426@@ -1086,9 +1086,8 @@
427 # being too low. If rpc_count increases, more network roundtrips have
428 # become necessary for this use case. Please do not adjust this number
429 # upwards without agreement from bzr's network support maintainers.
430- self.assertLength(19, self.hpss_calls)
431- self.expectFailure("verbose log accesses inventories, which require VFS",
432- self.assertThat, self.hpss_calls, ContainsNoVfsCalls)
433+ self.assertLength(11, self.hpss_calls)
434+ self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
435
436 def test_per_file(self):
437 self.setup_smart_server_with_call_log()
438@@ -1103,6 +1102,5 @@
439 # being too low. If rpc_count increases, more network roundtrips have
440 # become necessary for this use case. Please do not adjust this number
441 # upwards without agreement from bzr's network support maintainers.
442- self.assertLength(21, self.hpss_calls)
443- self.expectFailure("per-file graph access requires VFS",
444- self.assertThat, self.hpss_calls, ContainsNoVfsCalls)
445+ self.assertLength(15, self.hpss_calls)
446+ self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
447
448=== modified file 'bzrlib/tests/blackbox/test_ls.py'
449--- bzrlib/tests/blackbox/test_ls.py 2011-12-14 12:15:44 +0000
450+++ bzrlib/tests/blackbox/test_ls.py 2011-12-14 21:28:31 +0000
451@@ -262,6 +262,5 @@
452 # being too low. If rpc_count increases, more network roundtrips have
453 # become necessary for this use case. Please do not adjust this number
454 # upwards without agreement from bzr's network support maintainers.
455- self.assertLength(15, self.hpss_calls)
456- self.expectFailure("inventories can only be accessed over VFS",
457- self.assertThat, self.hpss_calls, ContainsNoVfsCalls)
458+ self.assertLength(6, self.hpss_calls)
459+ self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
460
461=== modified file 'bzrlib/tests/blackbox/test_sign_my_commits.py'
462--- bzrlib/tests/blackbox/test_sign_my_commits.py 2011-12-14 18:17:43 +0000
463+++ bzrlib/tests/blackbox/test_sign_my_commits.py 2011-12-14 21:28:31 +0000
464@@ -165,9 +165,8 @@
465 # being too low. If rpc_count increases, more network roundtrips have
466 # become necessary for this use case. Please do not adjust this number
467 # upwards without agreement from bzr's network support maintainers.
468- self.assertLength(51, self.hpss_calls)
469- self.expectFailure("signing commits requires VFS access",
470- self.assertThat, self.hpss_calls, ContainsNoVfsCalls)
471+ self.assertLength(15, self.hpss_calls)
472+ self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
473
474 def test_verify_commits(self):
475 self.setup_smart_server_with_call_log()
476@@ -185,12 +184,5 @@
477 # being too low. If rpc_count increases, more network roundtrips have
478 # become necessary for this use case. Please do not adjust this number
479 # upwards without agreement from bzr's network support maintainers.
480-
481- # The number of readv requests seems to vary depending on the generated
482- # repository and how well it compresses, so allow for a bit of
483- # variation:
484- if len(self.hpss_calls) not in (18, 19):
485- self.fail("Incorrect length: wanted 18 or 19, got %d for %r" % (
486- len(self.hpss_calls), self.hpss_calls))
487- self.expectFailure("verifying commits requires VFS access",
488- self.assertThat, self.hpss_calls, ContainsNoVfsCalls)
489+ self.assertLength(10, self.hpss_calls)
490+ self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
491
492=== modified file 'bzrlib/tests/per_interbranch/test_push.py'
493--- bzrlib/tests/per_interbranch/test_push.py 2011-10-15 01:09:01 +0000
494+++ bzrlib/tests/per_interbranch/test_push.py 2011-12-14 21:28:31 +0000
495@@ -279,10 +279,10 @@
496 # remote graph any further.
497 bzr_core_trace = Equals(
498 ['Repository.insert_stream_1.19', 'Repository.insert_stream_1.19',
499- 'get', 'Branch.set_last_revision_info', 'Branch.unlock'])
500+ 'Branch.set_last_revision_info', 'Branch.unlock'])
501 bzr_loom_trace = Equals(
502 ['Repository.insert_stream_1.19', 'Repository.insert_stream_1.19',
503- 'get', 'Branch.set_last_revision_info', 'get', 'Branch.unlock'])
504+ 'Branch.set_last_revision_info', 'get', 'Branch.unlock'])
505 self.assertThat(calls_after_insert_stream,
506 MatchesAny(bzr_core_trace, bzr_loom_trace))
507
508
509=== modified file 'bzrlib/tests/per_repository_chk/test_supported.py'
510--- bzrlib/tests/per_repository_chk/test_supported.py 2009-09-09 18:52:56 +0000
511+++ bzrlib/tests/per_repository_chk/test_supported.py 2011-12-14 21:28:31 +0000
512@@ -22,7 +22,9 @@
513 osutils,
514 repository,
515 )
516+from bzrlib.remote import RemoteRepository
517 from bzrlib.versionedfile import VersionedFiles
518+from bzrlib.tests import TestNotApplicable
519 from bzrlib.tests.per_repository_chk import TestCaseWithRepositoryCHK
520
521
522@@ -221,7 +223,9 @@
523 # check our setup: B-id and C-id should have identical chk root keys.
524 inv_b = b.repository.get_inventory('B-id')
525 inv_c = b.repository.get_inventory('C-id')
526- self.assertEqual(inv_b.id_to_entry.key(), inv_c.id_to_entry.key())
527+ if not isinstance(repo, RemoteRepository):
528+ # Remote repositories always return plain inventories
529+ self.assertEqual(inv_b.id_to_entry.key(), inv_c.id_to_entry.key())
530 # Now, manually insert objects for a stacked repo with only revision
531 # C-id:
532 # We need ('revisions', 'C-id'), ('inventories', 'C-id'),
533@@ -250,6 +254,9 @@
534 for a parent inventory of a new revision is missing.
535 """
536 repo = self.make_repository('damaged-repo')
537+ if isinstance(repo, RemoteRepository):
538+ raise TestNotApplicable(
539+ "Unable to obtain CHKInventory from remote repo")
540 b = self.make_branch_with_multiple_chk_nodes()
541 src_repo = b.repository
542 src_repo.lock_read()
543@@ -293,6 +300,9 @@
544 for a parent inventory of a new revision is missing.
545 """
546 repo = self.make_repository('damaged-repo')
547+ if isinstance(repo, RemoteRepository):
548+ raise TestNotApplicable(
549+ "Unable to obtain CHKInventory from remote repo")
550 b = self.make_branch_with_multiple_chk_nodes()
551 b.lock_read()
552 self.addCleanup(b.unlock)
553
554=== modified file 'bzrlib/tests/per_repository_vf/test_add_inventory_by_delta.py'
555--- bzrlib/tests/per_repository_vf/test_add_inventory_by_delta.py 2011-05-02 22:39:15 +0000
556+++ bzrlib/tests/per_repository_vf/test_add_inventory_by_delta.py 2011-12-14 21:28:31 +0000
557@@ -109,4 +109,4 @@
558 else:
559 repo_delta.commit_write_group()
560 self.assertEqual(add_validator, delta_validator)
561- self.assertEqual(new_inv, inv)
562+ self.assertEqual(list(new_inv.iter_entries()), list(inv.iter_entries()))
563
564=== modified file 'bzrlib/tests/test_remote.py'
565--- bzrlib/tests/test_remote.py 2011-12-14 12:20:36 +0000
566+++ bzrlib/tests/test_remote.py 2011-12-14 21:28:31 +0000
567@@ -69,6 +69,7 @@
568 from bzrlib.smart.repository import (
569 SmartServerRepositoryGetParentMap,
570 SmartServerRepositoryGetStream_1_19,
571+ _stream_to_byte_stream,
572 )
573 from bzrlib.symbol_versioning import deprecated_in
574 from bzrlib.tests import (
575@@ -4233,3 +4234,43 @@
576 'Repository.unlock', ('quack/', 'token', 'False'),
577 'success', ('ok', ))
578 repo.pack(['hinta', 'hintb'])
579+
580+
581+class TestRepositoryIterInventories(TestRemoteRepository):
582+ """Test Repository.iter_inventories."""
583+
584+ def _serialize_inv_delta(self, old_name, new_name, delta):
585+ serializer = inventory_delta.InventoryDeltaSerializer(True, False)
586+ return "".join(serializer.delta_to_lines(old_name, new_name, delta))
587+
588+ def test_single_empty(self):
589+ transport_path = 'quack'
590+ repo, client = self.setup_fake_client_and_repository(transport_path)
591+ fmt = bzrdir.format_registry.get('2a')().repository_format
592+ repo._format = fmt
593+ stream = [('inventory-deltas', [
594+ versionedfile.FulltextContentFactory('somerevid', None, None,
595+ self._serialize_inv_delta('null:', 'somerevid', []))])]
596+ client.add_expected_call(
597+ 'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
598+ 'success', ('ok', ),
599+ _stream_to_byte_stream(stream, fmt))
600+ ret = list(repo.iter_inventories(["somerevid"]))
601+ self.assertLength(1, ret)
602+ inv = ret[0]
603+ self.assertEquals("somerevid", inv.revision_id)
604+
605+ def test_empty(self):
606+ transport_path = 'quack'
607+ repo, client = self.setup_fake_client_and_repository(transport_path)
608+ ret = list(repo.iter_inventories([]))
609+ self.assertEquals(ret, [])
610+
611+ def test_missing(self):
612+ transport_path = 'quack'
613+ repo, client = self.setup_fake_client_and_repository(transport_path)
614+ client.add_expected_call(
615+ 'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
616+ 'success', ('ok', ), iter([]))
617+ self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(
618+ ["somerevid"]))
619
620=== modified file 'bzrlib/tests/test_smart.py'
621--- bzrlib/tests/test_smart.py 2011-12-14 12:20:36 +0000
622+++ bzrlib/tests/test_smart.py 2011-12-14 21:28:31 +0000
623@@ -32,6 +32,7 @@
624 bzrdir,
625 errors,
626 gpg,
627+ inventory_delta,
628 tests,
629 transport,
630 urlutils,
631@@ -2565,6 +2566,8 @@
632 smart_repo.SmartServerRepositoryAbortWriteGroup)
633 self.assertHandlerEqual('VersionedFileRepository.get_serializer_format',
634 smart_repo.SmartServerRepositoryGetSerializerFormat)
635+ self.assertHandlerEqual('VersionedFileRepository.get_inventories',
636+ smart_repo.SmartServerRepositoryGetInventories)
637 self.assertHandlerEqual('Transport.is_readonly',
638 smart_req.SmartServerIsReadonly)
639
640@@ -2630,3 +2633,48 @@
641 smart_req.SuccessfulSmartServerResponse(('ok', ), ),
642 request.do_body(''))
643
644+
645+class TestSmartServerRepositoryGetInventories(tests.TestCaseWithTransport):
646+
647+ def _get_serialized_inventory_delta(self, repository, base_revid, revid):
648+ base_inv = repository.revision_tree(base_revid).inventory
649+ inv = repository.revision_tree(revid).inventory
650+ inv_delta = inv._make_delta(base_inv)
651+ serializer = inventory_delta.InventoryDeltaSerializer(True, False)
652+ return "".join(serializer.delta_to_lines(base_revid, revid, inv_delta))
653+
654+ def test_single(self):
655+ backing = self.get_transport()
656+ request = smart_repo.SmartServerRepositoryGetInventories(backing)
657+ t = self.make_branch_and_tree('.', format='2a')
658+ self.addCleanup(t.lock_write().unlock)
659+ self.build_tree_contents([("file", "somecontents")])
660+ t.add(["file"], ["thefileid"])
661+ t.commit(rev_id='somerev', message="add file")
662+ self.assertIs(None, request.execute('', 'unordered'))
663+ response = request.do_body("somerev\n")
664+ self.assertTrue(response.is_successful())
665+ self.assertEquals(response.args, ("ok", ))
666+ stream = [('inventory-deltas', [
667+ versionedfile.FulltextContentFactory('somerev', None, None,
668+ self._get_serialized_inventory_delta(
669+ t.branch.repository, 'null:', 'somerev'))])]
670+ fmt = bzrdir.format_registry.get('2a')().repository_format
671+ self.assertEquals(
672+ "".join(response.body_stream),
673+ "".join(smart_repo._stream_to_byte_stream(stream, fmt)))
674+
675+ def test_empty(self):
676+ backing = self.get_transport()
677+ request = smart_repo.SmartServerRepositoryGetInventories(backing)
678+ t = self.make_branch_and_tree('.', format='2a')
679+ self.addCleanup(t.lock_write().unlock)
680+ self.build_tree_contents([("file", "somecontents")])
681+ t.add(["file"], ["thefileid"])
682+ t.commit(rev_id='somerev', message="add file")
683+ self.assertIs(None, request.execute('', 'unordered'))
684+ response = request.do_body("")
685+ self.assertTrue(response.is_successful())
686+ self.assertEquals(response.args, ("ok", ))
687+ self.assertEquals("".join(response.body_stream),
688+ "Bazaar pack format 1 (introduced in 0.18)\nB54\n\nBazaar repository format 2a (needs bzr 1.16 or later)\nE")
689
690=== modified file 'doc/en/release-notes/bzr-2.5.txt'
691--- doc/en/release-notes/bzr-2.5.txt 2011-12-14 20:39:58 +0000
692+++ doc/en/release-notes/bzr-2.5.txt 2011-12-14 21:28:31 +0000
693@@ -95,6 +95,10 @@
694
695 * New HPSS call ``BzrDir.checkout_metadir``. (Jelmer Vernooij, #894459)
696
697+* New HPSS call ``VersionedFileRepository.get_inventories``,
698+ speeding up various commands including ``bzr export``,
699+ ``bzr checkout`` and ``bzr cat``. (Jelmer Vernooij, #608640)
700+
701 Testing
702 *******
703
704@@ -156,6 +160,9 @@
705 * Plugins can now register additional "location aliases".
706 (Jelmer Vernooij)
707
708+* ``bzr status`` no longer shows shelves if files are specified.
709+ (Francis Devereux)
710+
711 * Revision specifiers will now only browse as much history as they
712 need to, rather than grabbing the whole history unnecessarily in some
713 cases. (Jelmer Vernooij)
714@@ -281,8 +288,14 @@
715 ``Repository.get_revision_signature_text``.
716 (Jelmer Vernooij)
717
718+* Add HPSS call for ``Branch.get_checkout_format``. (Jelmer Vernooij, #894459)
719+
720 * Add HPSS call for ``Repository.pack``. (Jelmer Vernooij, #894461)
721
722+* Add HPSS calls for ``Repository.iter_files_bytes``, speeding up
723+ several commands including ``bzr export`` and ``bzr co --lightweight``.
724+ (Jelmer Vernooij, #608640)
725+
726 * Custom HPSS error handlers can now be installed in the smart server client
727 using the ``error_translators`` and ``no_context_error_translators``
728 registries. (Jelmer Vernooij)