Merge lp:~spiv/bzr/stacking-friendly-revision-history-verb into lp:~bzr/bzr/trunk-old

Proposed by Andrew Bennetts
Status: Merged
Merged at revision: not available
Proposed branch: lp:~spiv/bzr/stacking-friendly-revision-history-verb
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 655 lines
To merge this branch: bzr merge lp:~spiv/bzr/stacking-friendly-revision-history-verb
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+7443@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

Looks conceptually fine. Some thoughts
 - rather than using a special revision marker, use the presence/absence
of null: at the start of the history. This is unambiguous and doesn't
require taking up another special revid.
 - update the version numbers.

 review +1

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

Robert Collins wrote:
> Review: Approve
> Looks conceptually fine. Some thoughts
> - rather than using a special revision marker, use the presence/absence
> of null: at the start of the history. This is unambiguous and doesn't
> require taking up another special revid.

There's no special revids here. There's an explicit slot in the protocol
(and Python API) for saying “yep found it” vs. “ran out of history in this
repo”. So it is unambiguous already, so I'll land that as is.

> - update the version numbers.

Oops, done. Thanks.

Revision history for this message
Robert Collins (lifeless) wrote :

Ah, I see, I misread.

I guess what I was trying to get at is that that isn't really needed
with null: being unique anyhow.

Still, it looks ok - go ahead.

-Rob

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2009-06-15 15:20:24 +0000
3+++ NEWS 2009-06-16 02:35:31 +0000
4@@ -26,6 +26,14 @@
5 rather than requiring all command names be known a-priori.
6 (Robert Collins)
7
8+Improvements
9+************
10+
11+* Resolving a revno to a revision id on a branch accessed via ``bzr://``
12+ or ``bzr+ssh://`` is now much faster and involves no VFS operations.
13+ This speeds up commands like ``bzr pull -r 123``. (Andrew Bennetts)
14+
15+
16 bzr 1.16rc1 "It's yesterday in California" 2009-06-11
17 #####################################################
18 :Codename: yesterday-in-california
19
20=== modified file 'bzrlib/branch.py'
21--- bzrlib/branch.py 2009-06-10 03:56:49 +0000
22+++ bzrlib/branch.py 2009-06-16 02:35:31 +0000
23@@ -91,6 +91,7 @@
24 self._revision_history_cache = None
25 self._revision_id_to_revno_cache = None
26 self._partial_revision_id_to_revno_cache = {}
27+ self._partial_revision_history_cache = []
28 self._last_revision_info_cache = None
29 self._merge_sorted_revisions_cache = None
30 self._open_hook()
31@@ -125,6 +126,27 @@
32 raise errors.UnstackableRepositoryFormat(self.repository._format,
33 self.repository.base)
34
35+ def _extend_partial_history(self, stop_index=None, stop_revision=None):
36+ """Extend the partial history to include a given index
37+
38+ If a stop_index is supplied, stop when that index has been reached.
39+ If a stop_revision is supplied, stop when that revision is
40+ encountered. Otherwise, stop when the beginning of history is
41+ reached.
42+
43+ :param stop_index: The index which should be present. When it is
44+ present, history extension will stop.
45+ :param stop_revision: The revision id which should be present. When
46+ it is encountered, history extension will stop.
47+ """
48+ if len(self._partial_revision_history_cache) == 0:
49+ self._partial_revision_history_cache = [self.last_revision()]
50+ repository._iter_for_revno(
51+ self.repository, self._partial_revision_history_cache,
52+ stop_index=stop_index, stop_revision=stop_revision)
53+ if self._partial_revision_history_cache[-1] == _mod_revision.NULL_REVISION:
54+ self._partial_revision_history_cache.pop()
55+
56 @staticmethod
57 def open(base, _unsupported=False, possible_transports=None):
58 """Open the branch rooted at base.
59@@ -698,6 +720,8 @@
60 self._revision_id_to_revno_cache = None
61 self._last_revision_info_cache = None
62 self._merge_sorted_revisions_cache = None
63+ self._partial_revision_history_cache = []
64+ self._partial_revision_id_to_revno_cache = {}
65
66 def _gen_revision_history(self):
67 """Return sequence of revision hashes on to this branch.
68@@ -831,15 +855,20 @@
69 except ValueError:
70 raise errors.NoSuchRevision(self, revision_id)
71
72+ @needs_read_lock
73 def get_rev_id(self, revno, history=None):
74 """Find the revision id of the specified revno."""
75 if revno == 0:
76 return _mod_revision.NULL_REVISION
77- if history is None:
78- history = self.revision_history()
79- if revno <= 0 or revno > len(history):
80+ last_revno, last_revid = self.last_revision_info()
81+ if revno == last_revno:
82+ return last_revid
83+ if revno <= 0 or revno > last_revno:
84 raise errors.NoSuchRevision(self, revno)
85- return history[revno - 1]
86+ distance_from_last = last_revno - revno
87+ if len(self._partial_revision_history_cache) <= distance_from_last:
88+ self._extend_partial_history(revno)
89+ return self._partial_revision_history_cache[revno]
90
91 @needs_write_lock
92 def pull(self, source, overwrite=False, stop_revision=None,
93@@ -2376,13 +2405,11 @@
94 self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
95 super(BzrBranch8, self).__init__(*args, **kwargs)
96 self._last_revision_info_cache = None
97- self._partial_revision_history_cache = []
98 self._reference_info = None
99
100 def _clear_cached_state(self):
101 super(BzrBranch8, self)._clear_cached_state()
102 self._last_revision_info_cache = None
103- self._partial_revision_history_cache = []
104 self._reference_info = None
105
106 def _last_revision_info(self):
107@@ -2444,35 +2471,6 @@
108 self._extend_partial_history(stop_index=last_revno-1)
109 return list(reversed(self._partial_revision_history_cache))
110
111- def _extend_partial_history(self, stop_index=None, stop_revision=None):
112- """Extend the partial history to include a given index
113-
114- If a stop_index is supplied, stop when that index has been reached.
115- If a stop_revision is supplied, stop when that revision is
116- encountered. Otherwise, stop when the beginning of history is
117- reached.
118-
119- :param stop_index: The index which should be present. When it is
120- present, history extension will stop.
121- :param revision_id: The revision id which should be present. When
122- it is encountered, history extension will stop.
123- """
124- repo = self.repository
125- if len(self._partial_revision_history_cache) == 0:
126- iterator = repo.iter_reverse_revision_history(self.last_revision())
127- else:
128- start_revision = self._partial_revision_history_cache[-1]
129- iterator = repo.iter_reverse_revision_history(start_revision)
130- #skip the last revision in the list
131- next_revision = iterator.next()
132- for revision_id in iterator:
133- self._partial_revision_history_cache.append(revision_id)
134- if (stop_index is not None and
135- len(self._partial_revision_history_cache) > stop_index):
136- break
137- if revision_id == stop_revision:
138- break
139-
140 def _write_revision_history(self, history):
141 """Factored out of set_revision_history.
142
143
144=== modified file 'bzrlib/builtins.py'
145--- bzrlib/builtins.py 2009-06-11 06:54:33 +0000
146+++ bzrlib/builtins.py 2009-06-16 02:35:31 +0000
147@@ -946,29 +946,36 @@
148 if branch_to.get_parent() is None or remember:
149 branch_to.set_parent(branch_from.base)
150
151- if revision is not None:
152- revision_id = revision.as_revision_id(branch_from)
153-
154- branch_to.lock_write()
155+ if branch_from is not branch_to:
156+ branch_from.lock_read()
157 try:
158- if tree_to is not None:
159- view_info = _get_view_info_for_change_reporter(tree_to)
160- change_reporter = delta._ChangeReporter(
161- unversioned_filter=tree_to.is_ignored, view_info=view_info)
162- result = tree_to.pull(branch_from, overwrite, revision_id,
163- change_reporter,
164- possible_transports=possible_transports,
165- local=local)
166- else:
167- result = branch_to.pull(branch_from, overwrite, revision_id,
168- local=local)
169-
170- result.report(self.outf)
171- if verbose and result.old_revid != result.new_revid:
172- log.show_branch_change(branch_to, self.outf, result.old_revno,
173- result.old_revid)
174+ if revision is not None:
175+ revision_id = revision.as_revision_id(branch_from)
176+
177+ branch_to.lock_write()
178+ try:
179+ if tree_to is not None:
180+ view_info = _get_view_info_for_change_reporter(tree_to)
181+ change_reporter = delta._ChangeReporter(
182+ unversioned_filter=tree_to.is_ignored,
183+ view_info=view_info)
184+ result = tree_to.pull(
185+ branch_from, overwrite, revision_id, change_reporter,
186+ possible_transports=possible_transports, local=local)
187+ else:
188+ result = branch_to.pull(
189+ branch_from, overwrite, revision_id, local=local)
190+
191+ result.report(self.outf)
192+ if verbose and result.old_revid != result.new_revid:
193+ log.show_branch_change(
194+ branch_to, self.outf, result.old_revno,
195+ result.old_revid)
196+ finally:
197+ branch_to.unlock()
198 finally:
199- branch_to.unlock()
200+ if branch_from is not branch_to:
201+ branch_from.unlock()
202
203
204 class cmd_push(Command):
205
206=== modified file 'bzrlib/remote.py'
207--- bzrlib/remote.py 2009-06-11 09:11:21 +0000
208+++ bzrlib/remote.py 2009-06-16 02:35:31 +0000
209@@ -28,12 +28,10 @@
210 errors,
211 graph,
212 lockdir,
213- pack,
214 repository,
215 revision,
216 revision as _mod_revision,
217 symbol_versioning,
218- urlutils,
219 )
220 from bzrlib.branch import BranchReferenceFormat
221 from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
222@@ -675,6 +673,37 @@
223 return self._real_repository.get_missing_parent_inventories(
224 check_for_missing_texts=check_for_missing_texts)
225
226+ def _get_rev_id_for_revno_vfs(self, revno, known_pair):
227+ self._ensure_real()
228+ return self._real_repository.get_rev_id_for_revno(
229+ revno, known_pair)
230+
231+ def get_rev_id_for_revno(self, revno, known_pair):
232+ """See Repository.get_rev_id_for_revno."""
233+ path = self.bzrdir._path_for_remote_call(self._client)
234+ try:
235+ if self._client._medium._is_remote_before((1, 16)):
236+ return self._get_rev_id_for_revno_vfs(revno, known_pair)
237+ response = self._call(
238+ 'Repository.get_rev_id_for_revno', path, revno, known_pair)
239+ except errors.UnknownSmartMethod:
240+ self._client._medium._remember_remote_is_before((1, 16))
241+ return self._get_rev_id_for_revno_vfs(revno, known_pair)
242+ if response[0] == 'ok':
243+ return True, response[1]
244+ elif response[0] == 'history-incomplete':
245+ known_pair = response[1:3]
246+ for fallback in self._fallback_repositories:
247+ found, result = fallback.get_rev_id_for_revno(revno, known_pair)
248+ if found:
249+ return True, result
250+ else:
251+ known_pair = result
252+ # Not found in any fallbacks
253+ return False, known_pair
254+ else:
255+ raise errors.UnexpectedSmartServerResponse(response)
256+
257 def _ensure_real(self):
258 """Ensure that there is a _real_repository set.
259
260@@ -1919,11 +1948,6 @@
261 # We intentionally don't call the parent class's __init__, because it
262 # will try to assign to self.tags, which is a property in this subclass.
263 # And the parent's __init__ doesn't do much anyway.
264- self._revision_id_to_revno_cache = None
265- self._partial_revision_id_to_revno_cache = {}
266- self._revision_history_cache = None
267- self._last_revision_info_cache = None
268- self._merge_sorted_revisions_cache = None
269 self.bzrdir = remote_bzrdir
270 if _client is not None:
271 self._client = _client
272@@ -1943,6 +1967,7 @@
273 else:
274 self._real_branch = None
275 # Fill out expected attributes of branch for bzrlib API users.
276+ self._clear_cached_state()
277 self.base = self.bzrdir.root_transport.base
278 self._control_files = None
279 self._lock_mode = None
280@@ -2229,6 +2254,15 @@
281 raise NotImplementedError(self.dont_leave_lock_in_place)
282 self._leave_lock = False
283
284+ def get_rev_id(self, revno, history=None):
285+ last_revision_info = self.last_revision_info()
286+ ok, result = self.repository.get_rev_id_for_revno(
287+ revno, last_revision_info)
288+ if ok:
289+ return result
290+ missing_parent = result[1]
291+ raise errors.RevisionNotPresent(missing_parent, self.repository)
292+
293 def _last_revision_info(self):
294 response = self._call('Branch.last_revision_info', self._remote_path())
295 if response[0] != 'ok':
296
297=== modified file 'bzrlib/repository.py'
298--- bzrlib/repository.py 2009-06-12 01:11:00 +0000
299+++ bzrlib/repository.py 2009-06-16 02:35:31 +0000
300@@ -2234,6 +2234,41 @@
301 """
302 return self.get_revision(revision_id).inventory_sha1
303
304+ def get_rev_id_for_revno(self, revno, known_pair):
305+ """Return the revision id of a revno, given a later (revno, revid)
306+ pair in the same history.
307+
308+ :return: if found (True, revid). If the available history ran out
309+ before reaching the revno, then this returns
310+ (False, (closest_revno, closest_revid)).
311+ """
312+ known_revno, known_revid = known_pair
313+ partial_history = [known_revid]
314+ distance_from_known = known_revno - revno
315+ if distance_from_known < 0:
316+ raise ValueError(
317+ 'requested revno (%d) is later than given known revno (%d)'
318+ % (revno, known_revno))
319+ try:
320+ _iter_for_revno(
321+ self, partial_history, stop_index=distance_from_known)
322+ except errors.RevisionNotPresent, err:
323+ if err.revision_id == known_revid:
324+ # The start revision (known_revid) wasn't found.
325+ raise
326+ # This is a stacked repository with no fallbacks, or a there's a
327+ # left-hand ghost. Either way, even though the revision named in
328+ # the error isn't in this repo, we know it's the next step in this
329+ # left-hand history.
330+ partial_history.append(err.revision_id)
331+ if len(partial_history) <= distance_from_known:
332+ # Didn't find enough history to get a revid for the revno.
333+ earliest_revno = known_revno - len(partial_history) + 1
334+ return (False, (earliest_revno, partial_history[-1]))
335+ if len(partial_history) - 1 > distance_from_known:
336+ raise AssertionError('_iter_for_revno returned too much history')
337+ return (True, partial_history[-1])
338+
339 def iter_reverse_revision_history(self, revision_id):
340 """Iterate backwards through revision ids in the lefthand history
341
342@@ -4417,3 +4452,35 @@
343 yield versionedfile.FulltextContentFactory(
344 key, parent_keys, None, as_bytes)
345
346+
347+def _iter_for_revno(repo, partial_history_cache, stop_index=None,
348+ stop_revision=None):
349+ """Extend the partial history to include a given index
350+
351+ If a stop_index is supplied, stop when that index has been reached.
352+ If a stop_revision is supplied, stop when that revision is
353+ encountered. Otherwise, stop when the beginning of history is
354+ reached.
355+
356+ :param stop_index: The index which should be present. When it is
357+ present, history extension will stop.
358+ :param stop_revision: The revision id which should be present. When
359+ it is encountered, history extension will stop.
360+ """
361+ start_revision = partial_history_cache[-1]
362+ iterator = repo.iter_reverse_revision_history(start_revision)
363+ try:
364+ #skip the last revision in the list
365+ iterator.next()
366+ while True:
367+ if (stop_index is not None and
368+ len(partial_history_cache) > stop_index):
369+ break
370+ if partial_history_cache[-1] == stop_revision:
371+ break
372+ revision_id = iterator.next()
373+ partial_history_cache.append(revision_id)
374+ except StopIteration:
375+ # No more history
376+ return
377+
378
379=== modified file 'bzrlib/smart/repository.py'
380--- bzrlib/smart/repository.py 2009-06-10 03:56:49 +0000
381+++ bzrlib/smart/repository.py 2009-06-16 02:35:31 +0000
382@@ -19,7 +19,6 @@
383 import bz2
384 import os
385 import Queue
386-import struct
387 import sys
388 import tarfile
389 import tempfile
390@@ -284,6 +283,31 @@
391 return SuccessfulSmartServerResponse(('ok', ), '\n'.join(lines))
392
393
394+class SmartServerRepositoryGetRevIdForRevno(SmartServerRepositoryReadLocked):
395+
396+ def do_readlocked_repository_request(self, repository, revno,
397+ known_pair):
398+ """Find the revid for a given revno, given a known revno/revid pair.
399+
400+ New in 1.16.
401+ """
402+ try:
403+ found_flag, result = repository.get_rev_id_for_revno(revno, known_pair)
404+ except errors.RevisionNotPresent, err:
405+ if err.revision_id != known_pair[1]:
406+ raise AssertionError(
407+ 'get_rev_id_for_revno raised RevisionNotPresent for '
408+ 'non-initial revision: ' + err.revision_id)
409+ return FailedSmartServerResponse(
410+ ('nosuchrevision', err.revision_id))
411+ if found_flag:
412+ return SuccessfulSmartServerResponse(('ok', result))
413+ else:
414+ earliest_revno, earliest_revid = result
415+ return SuccessfulSmartServerResponse(
416+ ('history-incomplete', earliest_revno, earliest_revid))
417+
418+
419 class SmartServerRequestHasRevision(SmartServerRepositoryRequest):
420
421 def do_repository_request(self, repository, revision_id):
422
423=== modified file 'bzrlib/smart/request.py'
424--- bzrlib/smart/request.py 2009-06-12 01:48:43 +0000
425+++ bzrlib/smart/request.py 2009-06-16 02:35:31 +0000
426@@ -555,6 +555,9 @@
427 request_handlers.register_lazy(
428 'Repository.unlock', 'bzrlib.smart.repository', 'SmartServerRepositoryUnlock')
429 request_handlers.register_lazy(
430+ 'Repository.get_rev_id_for_revno', 'bzrlib.smart.repository',
431+ 'SmartServerRepositoryGetRevIdForRevno')
432+request_handlers.register_lazy(
433 'Repository.get_stream', 'bzrlib.smart.repository',
434 'SmartServerRepositoryGetStream')
435 request_handlers.register_lazy(
436
437=== modified file 'bzrlib/tests/blackbox/test_pull.py'
438--- bzrlib/tests/blackbox/test_pull.py 2009-06-11 07:43:56 +0000
439+++ bzrlib/tests/blackbox/test_pull.py 2009-06-16 02:35:32 +0000
440@@ -368,8 +368,12 @@
441 See <https://launchpad.net/bugs/380314>
442 """
443 self.setup_smart_server_with_call_log()
444+ # Make a stacked-on branch with two commits so that the
445+ # revision-history can't be determined just by looking at the parent
446+ # field in the revision in the stacked repo.
447 parent = self.make_branch_and_tree('parent', format='1.9')
448 parent.commit(message='first commit')
449+ parent.commit(message='second commit')
450 local = parent.bzrdir.sprout('local').open_workingtree()
451 local.commit(message='local commit')
452 local.branch.create_clone_on_transport(
453@@ -383,7 +387,7 @@
454 # being too low. If rpc_count increases, more network roundtrips have
455 # become necessary for this use case. Please do not adjust this number
456 # upwards without agreement from bzr's network support maintainers.
457- self.assertLength(43, self.hpss_calls)
458+ self.assertLength(18, self.hpss_calls)
459 remote = Branch.open('stacked')
460 self.assertEndsWith(remote.get_stacked_on_url(), '/parent')
461
462
463=== modified file 'bzrlib/tests/per_repository_reference/__init__.py'
464--- bzrlib/tests/per_repository_reference/__init__.py 2009-06-10 03:56:49 +0000
465+++ bzrlib/tests/per_repository_reference/__init__.py 2009-06-16 02:35:32 +0000
466@@ -98,6 +98,7 @@
467 'bzrlib.tests.per_repository_reference.test_check',
468 'bzrlib.tests.per_repository_reference.test_default_stacking',
469 'bzrlib.tests.per_repository_reference.test_fetch',
470+ 'bzrlib.tests.per_repository_reference.test_get_rev_id_for_revno',
471 'bzrlib.tests.per_repository_reference.test_initialize',
472 'bzrlib.tests.per_repository_reference.test_unlock',
473 ]
474
475=== added file 'bzrlib/tests/per_repository_reference/test_get_rev_id_for_revno.py'
476--- bzrlib/tests/per_repository_reference/test_get_rev_id_for_revno.py 1970-01-01 00:00:00 +0000
477+++ bzrlib/tests/per_repository_reference/test_get_rev_id_for_revno.py 2009-06-16 02:35:32 +0000
478@@ -0,0 +1,46 @@
479+# Copyright (C) 2009 Canonical Ltd
480+#
481+# This program is free software; you can redistribute it and/or modify
482+# it under the terms of the GNU General Public License as published by
483+# the Free Software Foundation; either version 2 of the License, or
484+# (at your option) any later version.
485+#
486+# This program is distributed in the hope that it will be useful,
487+# but WITHOUT ANY WARRANTY; without even the implied warranty of
488+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
489+# GNU General Public License for more details.
490+#
491+# You should have received a copy of the GNU General Public License
492+# along with this program; if not, write to the Free Software
493+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
494+
495+"""Tests for get_rev_id_for_revno on a repository with external references."""
496+
497+from bzrlib import errors
498+from bzrlib.tests.per_repository_reference import (
499+ TestCaseWithExternalReferenceRepository,
500+ )
501+
502+class TestGetRevIdForRevno(TestCaseWithExternalReferenceRepository):
503+
504+ def test_uses_fallback(self):
505+ tree = self.make_branch_and_tree('base')
506+ base = tree.branch.repository
507+ revid = tree.commit('one')
508+ revid2 = tree.commit('two')
509+ spare_tree = tree.bzrdir.sprout('spare').open_workingtree()
510+ revid3 = spare_tree.commit('three')
511+ branch = spare_tree.branch.create_clone_on_transport(
512+ self.get_transport('referring'), stacked_on=self.get_url('base'))
513+ repo = branch.repository
514+ # Sanity check: now repo has 'revid3', and base has 'revid' + 'revid2'
515+ self.assertEqual(set([revid3]),
516+ set(repo.bzrdir.open_repository().all_revision_ids()))
517+ self.assertEqual(set([revid2, revid]),
518+ set(base.bzrdir.open_repository().all_revision_ids()))
519+ # get_rev_id_for_revno will find revno 1 == 'revid', even though
520+ # that revision can only be found in the fallback.
521+ repo.lock_read()
522+ self.addCleanup(repo.unlock)
523+ self.assertEqual(
524+ (True, revid), repo.get_rev_id_for_revno(1, (3, revid3)))
525
526=== modified file 'bzrlib/tests/test_remote.py'
527--- bzrlib/tests/test_remote.py 2009-06-12 01:48:43 +0000
528+++ bzrlib/tests/test_remote.py 2009-06-16 02:35:32 +0000
529@@ -2018,6 +2018,65 @@
530 self.assertEqual(('AnUnexpectedError',), e.error_tuple)
531
532
533+class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
534+
535+ def test_ok(self):
536+ repo, client = self.setup_fake_client_and_repository('quack')
537+ client.add_expected_call(
538+ 'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
539+ 'success', ('ok', 'rev-five'))
540+ result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
541+ self.assertEqual((True, 'rev-five'), result)
542+ client.finished_test()
543+
544+ def test_history_incomplete(self):
545+ repo, client = self.setup_fake_client_and_repository('quack')
546+ client.add_expected_call(
547+ 'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
548+ 'success', ('history-incomplete', 10, 'rev-ten'))
549+ result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
550+ self.assertEqual((False, (10, 'rev-ten')), result)
551+ client.finished_test()
552+
553+ def test_history_incomplete_with_fallback(self):
554+ """A 'history-incomplete' response causes the fallback repository to be
555+ queried too, if one is set.
556+ """
557+ # Make a repo with a fallback repo, both using a FakeClient.
558+ format = remote.response_tuple_to_repo_format(
559+ ('yes', 'no', 'yes', 'fake-network-name'))
560+ repo, client = self.setup_fake_client_and_repository('quack')
561+ repo._format = format
562+ fallback_repo, ignored = self.setup_fake_client_and_repository(
563+ 'fallback')
564+ fallback_repo._client = client
565+ repo.add_fallback_repository(fallback_repo)
566+ # First the client should ask the primary repo
567+ client.add_expected_call(
568+ 'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
569+ 'success', ('history-incomplete', 2, 'rev-two'))
570+ # Then it should ask the fallback, using revno/revid from the
571+ # history-incomplete response as the known revno/revid.
572+ client.add_expected_call(
573+ 'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
574+ 'success', ('ok', 'rev-one'))
575+ result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
576+ self.assertEqual((True, 'rev-one'), result)
577+ client.finished_test()
578+
579+ def test_nosuchrevision(self):
580+ # 'nosuchrevision' is returned when the known-revid is not found in the
581+ # remote repo. The client translates that response to NoSuchRevision.
582+ repo, client = self.setup_fake_client_and_repository('quack')
583+ client.add_expected_call(
584+ 'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
585+ 'error', ('nosuchrevision', 'rev-foo'))
586+ self.assertRaises(
587+ errors.NoSuchRevision,
588+ repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
589+ client.finished_test()
590+
591+
592 class TestRepositoryIsShared(TestRemoteRepository):
593
594 def test_is_shared(self):
595
596=== modified file 'bzrlib/tests/test_smart.py'
597--- bzrlib/tests/test_smart.py 2009-06-12 01:48:43 +0000
598+++ bzrlib/tests/test_smart.py 2009-06-16 02:35:32 +0000
599@@ -1161,6 +1161,47 @@
600 request.execute('', 'missingrevision'))
601
602
603+class TestSmartServerRepositoryGetRevIdForRevno(tests.TestCaseWithMemoryTransport):
604+
605+ def test_revno_found(self):
606+ backing = self.get_transport()
607+ request = smart.repository.SmartServerRepositoryGetRevIdForRevno(backing)
608+ tree = self.make_branch_and_memory_tree('.')
609+ tree.lock_write()
610+ tree.add('')
611+ rev1_id_utf8 = u'\xc8'.encode('utf-8')
612+ rev2_id_utf8 = u'\xc9'.encode('utf-8')
613+ tree.commit('1st commit', rev_id=rev1_id_utf8)
614+ tree.commit('2nd commit', rev_id=rev2_id_utf8)
615+ tree.unlock()
616+
617+ self.assertEqual(SmartServerResponse(('ok', rev1_id_utf8)),
618+ request.execute('', 1, (2, rev2_id_utf8)))
619+
620+ def test_known_revid_missing(self):
621+ backing = self.get_transport()
622+ request = smart.repository.SmartServerRepositoryGetRevIdForRevno(backing)
623+ repo = self.make_repository('.')
624+ self.assertEqual(
625+ FailedSmartServerResponse(('nosuchrevision', 'ghost')),
626+ request.execute('', 1, (2, 'ghost')))
627+
628+ def test_history_incomplete(self):
629+ backing = self.get_transport()
630+ request = smart.repository.SmartServerRepositoryGetRevIdForRevno(backing)
631+ parent = self.make_branch_and_memory_tree('parent', format='1.9')
632+ r1 = parent.commit(message='first commit')
633+ r2 = parent.commit(message='second commit')
634+ local = self.make_branch_and_memory_tree('local', format='1.9')
635+ local.branch.pull(parent.branch)
636+ local.set_parent_ids([r2])
637+ r3 = local.commit(message='local commit')
638+ local.branch.create_clone_on_transport(
639+ self.get_transport('stacked'), stacked_on=self.get_url('parent'))
640+ self.assertEqual(
641+ SmartServerResponse(('history-incomplete', 2, r2)),
642+ request.execute('stacked', 1, (3, r3)))
643+
644 class TestSmartServerRepositoryGetStream(tests.TestCaseWithMemoryTransport):
645
646 def make_two_commit_repo(self):
647@@ -1576,6 +1617,8 @@
648 smart.repository.SmartServerRepositoryGatherStats)
649 self.assertHandlerEqual('Repository.get_parent_map',
650 smart.repository.SmartServerRepositoryGetParentMap)
651+ self.assertHandlerEqual('Repository.get_rev_id_for_revno',
652+ smart.repository.SmartServerRepositoryGetRevIdForRevno)
653 self.assertHandlerEqual('Repository.get_revision_graph',
654 smart.repository.SmartServerRepositoryGetRevisionGraph)
655 self.assertHandlerEqual('Repository.get_stream',