Merge lp:~spiv/bzr/sprout-does-not-reopen-repo into lp:bzr

Proposed by Andrew Bennetts
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: no longer in the source branch.
Merged at revision: 5570
Proposed branch: lp:~spiv/bzr/sprout-does-not-reopen-repo
Merge into: lp:bzr
Diff against target: 550 lines (+133/-47)
10 files modified
bzrlib/branch.py (+45/-22)
bzrlib/bzrdir.py (+7/-3)
bzrlib/controldir.py (+26/-5)
bzrlib/remote.py (+27/-11)
bzrlib/tests/blackbox/test_branch.py (+1/-1)
bzrlib/tests/test_branch.py (+1/-1)
bzrlib/tests/test_bzrdir.py (+7/-0)
bzrlib/tests/test_foreign.py (+4/-2)
doc/en/release-notes/bzr-2.3.txt (+12/-0)
doc/en/whats-new/whats-new-in-2.3.txt (+3/-2)
To merge this branch: bzr merge lp:~spiv/bzr/sprout-does-not-reopen-repo
Reviewer Review Type Date Requested Status
Vincent Ladeuil Approve
John A Meinel Needs Information
Review via email: mp+41037@code.launchpad.net

Commit message

Avoid reopening (and relocking) the same branches/repositories in ControlDir.sprout.

Description of the change

This is a step towards bug 309682. It's not quite in a finished state, but I would appreciate feedback.

The idea here is pretty simple: ControlDir.sprout shouldn't open the same repository multiple times. This patch achieves that, and sure enough it causes the worst HPSS ratchet to improve by another notch.

There are a couple of dissatisfying aspects, though:

 1. sprout calls branch.sprout after opening the result repository. So this
    patch adds a new param (repository/found_repository) to Branch.sprout,
    Branch.create_branch, BranchFormat.open, etc. This touches a lot of
    signatures. Perhaps something more radical is called for, like a single
    API to acquire or create both a branch and a repo?

 2. For some reason, when stacking, the same fallback is added twice, which
    confuses _unstack. (Maybe this only happens for RemoteBranches?) I'm
    not sure exactly why this happens, but the hack to simply ignore
    duplicate _activate_fallback_location calls doesn't feel right. This
    is an XXX in the patch.

 3. It's at least theoretically possible that the repository object we've
    already acquired locally doesn't have the same location as the one
    suggested by the server (e.g. if the server has a bug, or there's a race
    with another client changing something, or perhaps it can even happen
    legitimately?). What ought to happen in this case? I suppose this is
    already an issue, which the code currently ignores because of the
    independent open_repository calls. This is an XXX and AssertionError
    in the patch.

Comments on these or any other parts of this patch are very welcome.

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

Sorry about the delay on this. In general, I really like the idea of this patch.

I would at least mutter a warning about the double fallback, we don't have to display it on the screen. It would be nice to fix it, but you don't have to get everything right on the first attempt.

I really don't like the proliferation of "no, really, use this object" parameters. I think the problem is that BzrDir.sprout() wants to be the master, but it still defers some of the work to Branch/Repo/WorkingTree. I don't have a better answer, though. I know you mentioned this, too.

I'd personally really like to see a:
  X.open_write_locked(create=True)

Or something along those lines.

I think part of the confusion is also that if you got something which is a Branch, and you think there is a tree there, the standard use is:
 branch.bzrdir.open_workingtree()

However, that *reopens* the Branch and Repository.

We also have a lot of code that does stuff like:
  a) If there is a WT, do X
  b) If not, but there is a Branch, do Y
  c) If not, but there is a Repository, do Z

It would be nice to simplify a lot of that if we could. That is some of what "BzrDir.open_containing_tree_or_branch" is supposed to be. Though many times I want "open_containing_tree_or_direct_branch" (don't recurse for a branch, but you can recurse for a Tree).

This is potentially disruptive to bzr-svn, because it adds a new parameter that BzrDir will be passing down the stack. As such, adding a cleaner new api may be an easier way to get what we want. It would let us add a compatability shim for objects that don't implement the new interface, and could be cleaner overall.

Then again, the practical side of me says, you've done the work, it works, even if it isn't perfect, it is better than what we have.

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

I would almost agree with John about the practical aspect (it works, let's land).

On the other hand, you have already spent a good amount of time on this which will be necessary again if we want to provide a cleaner solution.

But...

267 registry,
268 + repository,
269 revision as _mod_revision,

seems weird. I'd like jelmer's feedback here, but I'm pretty sure we pull controldir out of bzrdir to make sure it was isolated from bzr specifics and having to reintroduce repository here sounds like a step backward.

Also,

10 + # This fallback is already configured. This probably only
11 + # happens because BzrDir.sprout is a horrible mess. To avoid
12 + # confusing _unstack we don't add this a second time.
13 + # XXX: log a warning, maybe?
14 + return

is a sign that you're close to nailing the issue and I think you should ;)

On the overall, we have a 'repository' attribute on the branch object, so having to provide it should only occur when we are creating this object. Do we have that many different ways to create branches that you end up modifying so many signatures ? Doesn't it reveal a deeper issue ?

review: Needs Information
Revision history for this message
Andrew Bennetts (spiv) wrote :
Download full text (3.3 KiB)

Vincent Ladeuil wrote:
> Review: Needs Information
> I would almost agree with John about the practical aspect (it works, let's land).
>
> On the other hand, you have already spent a good amount of time on this which will be necessary again if we want to provide a cleaner solution.
>
> But...
>
> 267 registry,
> 268 + repository,
> 269 revision as _mod_revision,
>
> seems weird. I'd like jelmer's feedback here, but I'm pretty sure we pull
> controldir out of bzrdir to make sure it was isolated from bzr specifics and
> having to reintroduce repository here sounds like a step backward.

That seems to be a leftover from an earlier version of this patch. It's
not actually used. I've removed that import (and gardened a couple of
others noticed by pyflakes). Thanks for spotting that.

> Also,
>
> 10 + # This fallback is already configured. This probably only
> 11 + # happens because BzrDir.sprout is a horrible mess. To avoid
> 12 + # confusing _unstack we don't add this a second time.
> 13 + # XXX: log a warning, maybe?
> 14 + return
>
> is a sign that you're close to nailing the issue and I think you should ;)

If I thought I was close I'd have nailed it already!

> On the overall, we have a 'repository' attribute on the branch object, so
> having to provide it should only occur when we are creating this object. Do we
> have that many different ways to create branches that you end up modifying so
> many signatures ? Doesn't it reveal a deeper issue ?

I agree, I think there's a deeper issue here, as my cover letter tried
to say. I'm not sure what a good way solution is.

Regarding the number of ways to create branches, here's one way to count
that, based on this diff:

 * 8 definitions of BranchFormat.initialize
 * 4 definitions of ControlDir.create_branch
 * 1 definition of Branch.sprout

That doesn't count definitions in test code.

Plus there's open methods, which create branch objects, which are also
affected:

 * 3 definitions of BranchFormat.open

So yes, we do have 16 different ways to create a branch object, hence
why I touch so many methods. I don't see an obvious solution to that.

A possible approach might be to have the low-level
BranchFormat.open/initialize methods be designed to return Branches
without the repository object being set, so returning only partially
initialized Branch objects. Then have higher-level APIs (along the
lines of the existing open_containg_tree_or_branch? or something new
here too?) that assemble these into fully initialized objects with
.repository attributes filled in. There's the related consideration
that parts of the code want to open and/or create some combination of
branch/repo/tree in one logical step (and ideally one network
roundtrip), but the current design opens each of the bzrdir, repository,
and branch separately. I think this is part of the cause of the double
add_fallback ugliness that's an XXX for that matter.

So my suspicion is a fairly fundamental redesign of how we open/create
these things would be help a lot... but getting it right may take some
effort. And it's likely to be way more disruptive to bzr-svn and
friends than a new keyword param! Until someone has a concrete ...

Read more...

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

I've only reviewed this for possible issues with foreign VCS implementations, and it seems fine in that regard (bearing in mind the new repository= parameter).

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

Thanks, Jelmer !

Ok, so my main concerns are addressed, just a few remarks, possibly tweaks:

379 + if not isinstance(repository, RemoteRepository):
380 + raise AssertionError('xxx %r' % (repository,))

replacing this 'xxx' with something meaningful can only help people confronted with the raised assertion ;)

406 + raise AssertionError(
407 + 'diff %r: %r vs. (%r + %r)' %
408 + (url_diff, repository.user_url, medium.base, repo_path))

 ditto (less sure here, the tb may be enough)

And finally, you summary in your reply above would be nice to keep in devnotes IMHO. Let's not redo this the next time we want to address the deeper issue.

review: Approve
Revision history for this message
Andrew Bennetts (spiv) 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/branch.py'
2--- bzrlib/branch.py 2010-11-18 00:22:24 +0000
3+++ bzrlib/branch.py 2010-12-15 00:41:26 +0000
4@@ -105,6 +105,13 @@
5
6 def _activate_fallback_location(self, url):
7 """Activate the branch/repository from url as a fallback repository."""
8+ for existing_fallback_repo in self.repository._fallback_repositories:
9+ if existing_fallback_repo.user_url == url:
10+ # This fallback is already configured. This probably only
11+ # happens because BzrDir.sprout is a horrible mess. To avoid
12+ # confusing _unstack we don't add this a second time.
13+ mutter('duplicate activation of fallback %r on %r', url, self)
14+ return
15 repo = self._get_fallback_repository(url)
16 if repo.has_same_location(self.repository):
17 raise errors.UnstackableLocationError(self.user_url, url)
18@@ -809,7 +816,8 @@
19 old_repository = self.repository
20 if len(old_repository._fallback_repositories) != 1:
21 raise AssertionError("can't cope with fallback repositories "
22- "of %r" % (self.repository,))
23+ "of %r (fallbacks: %r)" % (old_repository,
24+ old_repository._fallback_repositories))
25 # Open the new repository object.
26 # Repositories don't offer an interface to remove fallback
27 # repositories today; take the conceptually simpler option and just
28@@ -1266,7 +1274,8 @@
29 return result
30
31 @needs_read_lock
32- def sprout(self, to_bzrdir, revision_id=None, repository_policy=None):
33+ def sprout(self, to_bzrdir, revision_id=None, repository_policy=None,
34+ repository=None):
35 """Create a new line of development from the branch, into to_bzrdir.
36
37 to_bzrdir controls the branch format.
38@@ -1277,7 +1286,7 @@
39 if (repository_policy is not None and
40 repository_policy.requires_stacking()):
41 to_bzrdir._format.require_stacking(_skip_repo=True)
42- result = to_bzrdir.create_branch()
43+ result = to_bzrdir.create_branch(repository=repository)
44 result.lock_write()
45 try:
46 if repository_policy is not None:
47@@ -1634,7 +1643,8 @@
48 hook(params)
49
50 def _initialize_helper(self, a_bzrdir, utf8_files, name=None,
51- lock_type='metadir', set_format=True):
52+ repository=None, lock_type='metadir',
53+ set_format=True):
54 """Initialize a branch in a bzrdir, with specified files
55
56 :param a_bzrdir: The bzrdir to initialize the branch in
57@@ -1674,11 +1684,12 @@
58 finally:
59 if lock_taken:
60 control_files.unlock()
61- branch = self.open(a_bzrdir, name, _found=True)
62+ branch = self.open(a_bzrdir, name, _found=True,
63+ found_repository=repository)
64 self._run_post_branch_init_hooks(a_bzrdir, name, branch)
65 return branch
66
67- def initialize(self, a_bzrdir, name=None):
68+ def initialize(self, a_bzrdir, name=None, repository=None):
69 """Create a branch of this format in a_bzrdir.
70
71 :param name: Name of the colocated branch to create.
72@@ -1718,7 +1729,8 @@
73 """
74 raise NotImplementedError(self.network_name)
75
76- def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):
77+ def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False,
78+ found_repository=None):
79 """Return the branch object for a_bzrdir
80
81 :param a_bzrdir: A BzrDir that contains a branch.
82@@ -2018,8 +2030,11 @@
83 """See BranchFormat.get_format_description()."""
84 return "Branch format 4"
85
86- def initialize(self, a_bzrdir, name=None):
87+ def initialize(self, a_bzrdir, name=None, repository=None):
88 """Create a branch of this format in a_bzrdir."""
89+ if repository is not None:
90+ raise NotImplementedError(
91+ "initialize(repository=<not None>) on %r" % (self,))
92 utf8_files = [('revision-history', ''),
93 ('branch-name', ''),
94 ]
95@@ -2034,16 +2049,19 @@
96 """The network name for this format is the control dirs disk label."""
97 return self._matchingbzrdir.get_format_string()
98
99- def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):
100+ def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False,
101+ found_repository=None):
102 """See BranchFormat.open()."""
103 if not _found:
104 # we are being called directly and must probe.
105 raise NotImplementedError
106+ if found_repository is None:
107+ found_repository = a_bzrdir.open_repository()
108 return BzrBranch(_format=self,
109 _control_files=a_bzrdir._control_files,
110 a_bzrdir=a_bzrdir,
111 name=name,
112- _repository=a_bzrdir.open_repository())
113+ _repository=found_repository)
114
115 def __str__(self):
116 return "Bazaar-NG branch format 4"
117@@ -2063,7 +2081,8 @@
118 """
119 return self.get_format_string()
120
121- def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):
122+ def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False,
123+ found_repository=None):
124 """See BranchFormat.open()."""
125 if not _found:
126 format = BranchFormat.find_format(a_bzrdir, name=name)
127@@ -2074,11 +2093,13 @@
128 try:
129 control_files = lockable_files.LockableFiles(transport, 'lock',
130 lockdir.LockDir)
131+ if found_repository is None:
132+ found_repository = a_bzrdir.find_repository()
133 return self._branch_class()(_format=self,
134 _control_files=control_files,
135 name=name,
136 a_bzrdir=a_bzrdir,
137- _repository=a_bzrdir.find_repository(),
138+ _repository=found_repository,
139 ignore_fallbacks=ignore_fallbacks)
140 except errors.NoSuchFile:
141 raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
142@@ -2116,12 +2137,12 @@
143 """See BranchFormat.get_format_description()."""
144 return "Branch format 5"
145
146- def initialize(self, a_bzrdir, name=None):
147+ def initialize(self, a_bzrdir, name=None, repository=None):
148 """Create a branch of this format in a_bzrdir."""
149 utf8_files = [('revision-history', ''),
150 ('branch-name', ''),
151 ]
152- return self._initialize_helper(a_bzrdir, utf8_files, name)
153+ return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
154
155 def supports_tags(self):
156 return False
157@@ -2149,13 +2170,13 @@
158 """See BranchFormat.get_format_description()."""
159 return "Branch format 6"
160
161- def initialize(self, a_bzrdir, name=None):
162+ def initialize(self, a_bzrdir, name=None, repository=None):
163 """Create a branch of this format in a_bzrdir."""
164 utf8_files = [('last-revision', '0 null:\n'),
165 ('branch.conf', ''),
166 ('tags', ''),
167 ]
168- return self._initialize_helper(a_bzrdir, utf8_files, name)
169+ return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
170
171 def make_tags(self, branch):
172 """See bzrlib.branch.BranchFormat.make_tags()."""
173@@ -2179,14 +2200,14 @@
174 """See BranchFormat.get_format_description()."""
175 return "Branch format 8"
176
177- def initialize(self, a_bzrdir, name=None):
178+ def initialize(self, a_bzrdir, name=None, repository=None):
179 """Create a branch of this format in a_bzrdir."""
180 utf8_files = [('last-revision', '0 null:\n'),
181 ('branch.conf', ''),
182 ('tags', ''),
183 ('references', '')
184 ]
185- return self._initialize_helper(a_bzrdir, utf8_files, name)
186+ return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
187
188 def __init__(self):
189 super(BzrBranchFormat8, self).__init__()
190@@ -2215,13 +2236,13 @@
191 This format was introduced in bzr 1.6.
192 """
193
194- def initialize(self, a_bzrdir, name=None):
195+ def initialize(self, a_bzrdir, name=None, repository=None):
196 """Create a branch of this format in a_bzrdir."""
197 utf8_files = [('last-revision', '0 null:\n'),
198 ('branch.conf', ''),
199 ('tags', ''),
200 ]
201- return self._initialize_helper(a_bzrdir, utf8_files, name)
202+ return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
203
204 def _branch_class(self):
205 return BzrBranch7
206@@ -2269,7 +2290,8 @@
207 transport = a_bzrdir.get_branch_transport(None, name=name)
208 location = transport.put_bytes('location', to_branch.base)
209
210- def initialize(self, a_bzrdir, name=None, target_branch=None):
211+ def initialize(self, a_bzrdir, name=None, target_branch=None,
212+ repository=None):
213 """Create a branch of this format in a_bzrdir."""
214 if target_branch is None:
215 # this format does not implement branch itself, thus the implicit
216@@ -2303,7 +2325,8 @@
217 return clone
218
219 def open(self, a_bzrdir, name=None, _found=False, location=None,
220- possible_transports=None, ignore_fallbacks=False):
221+ possible_transports=None, ignore_fallbacks=False,
222+ found_repository=None):
223 """Return the branch that the branch reference in a_bzrdir points at.
224
225 :param a_bzrdir: A BzrDir that contains a branch.
226
227=== modified file 'bzrlib/bzrdir.py'
228--- bzrlib/bzrdir.py 2010-12-07 09:06:39 +0000
229+++ bzrlib/bzrdir.py 2010-12-15 00:41:26 +0000
230@@ -1027,8 +1027,11 @@
231 tree.clone(result)
232 return result
233
234- def create_branch(self, name=None):
235+ def create_branch(self, name=None, repository=None):
236 """See BzrDir.create_branch."""
237+ if repository is not None:
238+ raise NotImplementedError(
239+ "create_branch(repository=<not None>) on %r" % (self,))
240 return self._format.get_branch_format().initialize(self, name=name)
241
242 def destroy_branch(self, name=None):
243@@ -1264,9 +1267,10 @@
244 """See BzrDir.can_convert_format()."""
245 return True
246
247- def create_branch(self, name=None):
248+ def create_branch(self, name=None, repository=None):
249 """See BzrDir.create_branch."""
250- return self._format.get_branch_format().initialize(self, name=name)
251+ return self._format.get_branch_format().initialize(self, name=name,
252+ repository=repository)
253
254 def destroy_branch(self, name=None):
255 """See BzrDir.create_branch."""
256
257=== modified file 'bzrlib/controldir.py'
258--- bzrlib/controldir.py 2010-09-10 09:46:15 +0000
259+++ bzrlib/controldir.py 2010-12-15 00:41:26 +0000
260@@ -27,11 +27,10 @@
261 import textwrap
262
263 from bzrlib import (
264+ cleanup,
265 errors,
266 graph,
267- registry,
268 revision as _mod_revision,
269- symbol_versioning,
270 urlutils,
271 )
272 from bzrlib.push import (
273@@ -47,6 +46,8 @@
274
275 """)
276
277+from bzrlib import registry
278+
279
280 class ControlComponent(object):
281 """Abstract base class for control directory components.
282@@ -143,7 +144,7 @@
283 """Destroy the repository in this ControlDir."""
284 raise NotImplementedError(self.destroy_repository)
285
286- def create_branch(self, name=None):
287+ def create_branch(self, name=None, repository=None):
288 """Create a branch in this ControlDir.
289
290 :param name: Name of the colocated branch to create, None for
291@@ -364,6 +365,19 @@
292 :param create_tree_if_local: If true, a working-tree will be created
293 when working locally.
294 """
295+ operation = cleanup.OperationWithCleanups(self._sprout)
296+ return operation.run(url, revision_id=revision_id,
297+ force_new_repo=force_new_repo, recurse=recurse,
298+ possible_transports=possible_transports,
299+ accelerator_tree=accelerator_tree, hardlink=hardlink,
300+ stacked=stacked, source_branch=source_branch,
301+ create_tree_if_local=create_tree_if_local)
302+
303+ def _sprout(self, op, url, revision_id=None, force_new_repo=False,
304+ recurse='down', possible_transports=None,
305+ accelerator_tree=None, hardlink=False, stacked=False,
306+ source_branch=None, create_tree_if_local=True):
307+ add_cleanup = op.add_cleanup
308 target_transport = get_transport(url, possible_transports)
309 target_transport.ensure_base()
310 cloning_format = self.cloning_metadir(stacked)
311@@ -373,6 +387,7 @@
312 # even if the origin was stacked
313 stacked_branch_url = None
314 if source_branch is not None:
315+ add_cleanup(source_branch.lock_read().unlock)
316 if stacked:
317 stacked_branch_url = self.root_transport.base
318 source_repository = source_branch.repository
319@@ -388,9 +403,14 @@
320 source_repository = self.open_repository()
321 except errors.NoRepositoryPresent:
322 source_repository = None
323+ else:
324+ add_cleanup(source_repository.lock_read().unlock)
325+ else:
326+ add_cleanup(source_branch.lock_read().unlock)
327 repository_policy = result.determine_repository_policy(
328 force_new_repo, stacked_branch_url, require_stacking=stacked)
329 result_repo, is_new_repo = repository_policy.acquire_repository()
330+ add_cleanup(result_repo.lock_write().unlock)
331 is_stacked = stacked or (len(result_repo._fallback_repositories) != 0)
332 if is_new_repo and revision_id is not None and not is_stacked:
333 fetch_spec = graph.PendingAncestryResult(
334@@ -412,7 +432,8 @@
335 result_branch = result.create_branch()
336 else:
337 result_branch = source_branch.sprout(result,
338- revision_id=revision_id, repository_policy=repository_policy)
339+ revision_id=revision_id, repository_policy=repository_policy,
340+ repository=result_repo)
341 mutter("created new branch %r" % (result_branch,))
342
343 # Create/update the result working tree
344@@ -420,7 +441,7 @@
345 isinstance(target_transport, local.LocalTransport) and
346 (result_repo is None or result_repo.make_working_trees())):
347 wt = result.create_workingtree(accelerator_tree=accelerator_tree,
348- hardlink=hardlink)
349+ hardlink=hardlink, from_branch=result_branch)
350 wt.lock_write()
351 try:
352 if wt.path2id('') is None:
353
354=== modified file 'bzrlib/remote.py'
355--- bzrlib/remote.py 2010-12-02 10:41:05 +0000
356+++ bzrlib/remote.py 2010-12-15 00:41:26 +0000
357@@ -33,6 +33,7 @@
358 revision as _mod_revision,
359 static_tuple,
360 symbol_versioning,
361+ urlutils,
362 )
363 from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
364 from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
365@@ -246,14 +247,17 @@
366 self._ensure_real()
367 self._real_bzrdir.destroy_repository()
368
369- def create_branch(self, name=None):
370+ def create_branch(self, name=None, repository=None):
371 # as per meta1 formats - just delegate to the format object which may
372 # be parameterised.
373 real_branch = self._format.get_branch_format().initialize(self,
374- name=name)
375+ name=name, repository=repository)
376 if not isinstance(real_branch, RemoteBranch):
377- result = RemoteBranch(self, self.find_repository(), real_branch,
378- name=name)
379+ if not isinstance(repository, RemoteRepository):
380+ raise AssertionError(
381+ 'need a RemoteRepository to use with RemoteBranch, got %r'
382+ % (repository,))
383+ result = RemoteBranch(self, repository, real_branch, name=name)
384 else:
385 result = real_branch
386 # BzrDir.clone_on_transport() uses the result of create_branch but does
387@@ -2093,7 +2097,7 @@
388 name=name)
389 return result
390
391- def initialize(self, a_bzrdir, name=None):
392+ def initialize(self, a_bzrdir, name=None, repository=None):
393 # 1) get the network name to use.
394 if self._custom_format:
395 network_name = self._custom_format.network_name()
396@@ -2127,13 +2131,25 @@
397 # Turn the response into a RemoteRepository object.
398 format = RemoteBranchFormat(network_name=response[1])
399 repo_format = response_tuple_to_repo_format(response[3:])
400- if response[2] == '':
401- repo_bzrdir = a_bzrdir
402+ repo_path = response[2]
403+ if repository is not None:
404+ remote_repo_url = urlutils.join(medium.base, repo_path)
405+ url_diff = urlutils.relative_url(repository.user_url,
406+ remote_repo_url)
407+ if url_diff != '.':
408+ raise AssertionError(
409+ 'repository.user_url %r does not match URL from server '
410+ 'response (%r + %r)'
411+ % (repository.user_url, medium.base, repo_path))
412+ remote_repo = repository
413 else:
414- repo_bzrdir = RemoteBzrDir(
415- a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
416- a_bzrdir._client)
417- remote_repo = RemoteRepository(repo_bzrdir, repo_format)
418+ if repo_path == '':
419+ repo_bzrdir = a_bzrdir
420+ else:
421+ repo_bzrdir = RemoteBzrDir(
422+ a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
423+ a_bzrdir._client)
424+ remote_repo = RemoteRepository(repo_bzrdir, repo_format)
425 remote_branch = RemoteBranch(a_bzrdir, remote_repo,
426 format=format, setup_stacking=False, name=name)
427 # XXX: We know this is a new branch, so it must have revno 0, revid
428
429=== modified file 'bzrlib/tests/blackbox/test_branch.py'
430--- bzrlib/tests/blackbox/test_branch.py 2010-12-02 10:41:05 +0000
431+++ bzrlib/tests/blackbox/test_branch.py 2010-12-15 00:41:26 +0000
432@@ -414,7 +414,7 @@
433 # being too low. If rpc_count increases, more network roundtrips have
434 # become necessary for this use case. Please do not adjust this number
435 # upwards without agreement from bzr's network support maintainers.
436- self.assertLength(37, self.hpss_calls)
437+ self.assertLength(36, self.hpss_calls)
438
439 def test_branch_from_trivial_branch_streaming_acceptance(self):
440 self.setup_smart_server_with_call_log()
441
442=== modified file 'bzrlib/tests/test_branch.py'
443--- bzrlib/tests/test_branch.py 2010-12-07 09:06:39 +0000
444+++ bzrlib/tests/test_branch.py 2010-12-15 00:41:26 +0000
445@@ -113,7 +113,7 @@
446 """See BzrBranchFormat.get_format_string()."""
447 return "Sample branch format."
448
449- def initialize(self, a_bzrdir, name=None):
450+ def initialize(self, a_bzrdir, name=None, repository=None):
451 """Format 4 branches cannot be created."""
452 t = a_bzrdir.get_branch_transport(self, name=name)
453 t.put_bytes('format', self.get_format_string())
454
455=== modified file 'bzrlib/tests/test_bzrdir.py'
456--- bzrlib/tests/test_bzrdir.py 2010-10-15 14:21:03 +0000
457+++ bzrlib/tests/test_bzrdir.py 2010-12-15 00:41:26 +0000
458@@ -29,6 +29,7 @@
459 controldir,
460 errors,
461 help_topics,
462+ lock,
463 repository,
464 osutils,
465 remote,
466@@ -1349,6 +1350,12 @@
467 def set_parent(self, parent):
468 self._parent = parent
469
470+ def lock_read(self):
471+ return lock.LogicalLockResult(self.unlock)
472+
473+ def unlock(self):
474+ return
475+
476
477 class TestBzrDirSprout(TestCaseWithMemoryTransport):
478
479
480=== modified file 'bzrlib/tests/test_foreign.py'
481--- bzrlib/tests/test_foreign.py 2010-08-02 18:36:31 +0000
482+++ bzrlib/tests/test_foreign.py 2010-12-15 00:41:26 +0000
483@@ -173,17 +173,19 @@
484 super(DummyForeignVcsBranchFormat, self).__init__()
485 self._matchingbzrdir = DummyForeignVcsDirFormat()
486
487- def open(self, a_bzrdir, name=None, _found=False):
488+ def open(self, a_bzrdir, name=None, _found=False, found_repository=None):
489 if not _found:
490 raise NotImplementedError
491 try:
492 transport = a_bzrdir.get_branch_transport(None, name=name)
493 control_files = lockable_files.LockableFiles(transport, 'lock',
494 lockdir.LockDir)
495+ if found_repository is None:
496+ found_repository = a_bzrdir.find_repository()
497 return DummyForeignVcsBranch(_format=self,
498 _control_files=control_files,
499 a_bzrdir=a_bzrdir,
500- _repository=a_bzrdir.find_repository())
501+ _repository=found_repository)
502 except errors.NoSuchFile:
503 raise errors.NotBranchError(path=transport.base)
504
505
506=== modified file 'doc/en/release-notes/bzr-2.3.txt'
507--- doc/en/release-notes/bzr-2.3.txt 2010-12-14 23:00:51 +0000
508+++ doc/en/release-notes/bzr-2.3.txt 2010-12-15 00:41:26 +0000
509@@ -31,6 +31,10 @@
510 missing inventories. This removes at least one network roundtrip when
511 pushing to a stacked branch. (Andrew Bennetts)
512
513+* ``ControlDir.sprout`` no longer opens the target repository more than
514+ once. This avoids some unnecessary IO, and removes a network roundtrip
515+ when doing ``bzr branch`` to a smart server URL. (Andrew Bennetts)
516+
517 * ``bzr resolve`` now accepts ``--take-this`` and ``--take-other`` actions
518 for text conflicts. This *replace* the whole file with the content
519 designated by the action. This will *ignore* all differences that would
520@@ -53,6 +57,14 @@
521 .. Changes that may require updates in plugins or other code that uses
522 bzrlib.
523
524+* ``Branch.sprout``, ``BranchFormat.initalize`` and
525+ ``ControlDir.create_branch`` now take an optional ``repository`` keyword
526+ argument, and ``BranchFormat.open`` now takes an optional
527+ ``found_repository`` keyword argument. These provide the repository
528+ object for new branch object to use (for cases when the caller has
529+ already opened that repository). Implementations of these APIs will
530+ need to be updated to accept these arguments. (Andrew Bennetts)
531+
532 Internals
533 *********
534
535
536=== modified file 'doc/en/whats-new/whats-new-in-2.3.txt'
537--- doc/en/whats-new/whats-new-in-2.3.txt 2010-12-14 09:33:53 +0000
538+++ doc/en/whats-new/whats-new-in-2.3.txt 2010-12-15 00:41:26 +0000
539@@ -69,8 +69,9 @@
540 * ``bzr send`` uses less memory.
541 (John Arbash Meinel, #614576)
542
543-* Fetches involving stacked branches and branches with tags are now faster.
544- Some redundant network reads were removed. (Andrew Bennetts)
545+* Fetches involving stacked branches and branches with tags now do slightly less
546+ I/O, and so does branching from an existing branch. This also improves the
547+ network performance of these operations. (Andrew Bennetts)
548
549 * Inventory entries now consume less memory (on 32-bit Ubuntu file entries
550 have dropped from 68 bytes to 40, and directory entries from 120 bytes