Merge lp:~jelmer/bzr/colo-urls into lp:bzr

Proposed by Jelmer Vernooij on 2010-03-07
Status: Rejected
Rejected by: Jelmer Vernooij on 2011-08-15
Proposed branch: lp:~jelmer/bzr/colo-urls
Merge into: lp:bzr
Prerequisite: lp:~jelmer/bzr/use-branch-open
Diff against target: 778 lines (+247/-89)
13 files modified
bzrlib/branch.py (+45/-11)
bzrlib/builtins.py (+38/-35)
bzrlib/bzrdir.py (+29/-15)
bzrlib/errors.py (+7/-0)
bzrlib/reconcile.py (+8/-11)
bzrlib/remote.py (+8/-5)
bzrlib/switch.py (+1/-1)
bzrlib/tests/per_branch/test_branch.py (+2/-1)
bzrlib/tests/per_bzrdir_colo/test_unsupported.py (+21/-9)
bzrlib/tests/test_branch.py (+37/-0)
bzrlib/tests/test_remote.py (+1/-1)
bzrlib/tests/test_urlutils.py (+24/-0)
bzrlib/urlutils.py (+26/-0)
To merge this branch: bzr merge lp:~jelmer/bzr/colo-urls
Reviewer Review Type Date Requested Status
Robert Collins (community) 2010-04-29 Needs Fixing on 2010-05-14
Andrew Bennetts 2010-03-07 Needs Information on 2010-03-31
Review via email: mp+20860@code.launchpad.net

Description of the Change

Add support for addressing colocated branches using comma's.

To post a comment you must log in.
Jelmer Vernooij (jelmer) wrote :

This is ready but I'd be interested to hear how we should handle comma's in URLs. I think it's reasonable to require that colocated branch names never can contain a comma in a URL.

Andrew Bennetts (spiv) wrote :

My understanding of STD 66 is that commas should bind to path segments, rather than delimit paths entirely, but I'm happy to be corrected if I'm wrong. Also, I don't see any problem with having commas or even slashes in a branch name, that's what percent-encoding is for.

So, if the plan is that ",foo" in a URL indicates a branch name of "foo", then I'd expect URLs to be parsed like:

 * http://host/path,branchname
   * branch location: http://host/path
   * branch name: branchname
 * http://host/path,branch%2Cname
   * branch location: http://host/path
   * branch name: branch,name
 * http://host/path,branchname/README.txt
   * branch location: http://host/path
   * branch name: branchname
   * path inside branch: README.txt [e.g. I want to be able to use this with bzr log, bzr cat, and bzr annotate]

I wasn't at Strasbourg. Did we decide against a key/value syntax (like ",branch=name"), or is it still intended that we might support e.g. ",revid=AAAA..." at some point?

(What happened to the promised update to doc/developers/colocated-branches.txt?)

review: Needs Information
lp:~jelmer/bzr/colo-urls updated on 2010-04-17
5070. By Jelmer Vernooij on 2010-04-03

Add branch name argument in a couple more places.

5071. By Jelmer Vernooij on 2010-04-10

Fix two test failures.

5072. By Jelmer Vernooij on 2010-04-10

Merge updated colo spec.

5073. By Jelmer Vernooij on 2010-04-11

merge colocated branch tests.

5074. By Jelmer Vernooij on 2010-04-16

Merge more-colo.

5075. By Jelmer Vernooij on 2010-04-17

merge more-colo

5076. By Jelmer Vernooij on 2010-04-17

Clarify parsing of subsegments.

5077. By Jelmer Vernooij on 2010-04-17

Remove unused error.

5078. By Jelmer Vernooij on 2010-04-17

merge urlutils subsegment improvements.

5079. By Jelmer Vernooij on 2010-04-17

use new subsegments urlutils functions.

Martin Pool (mbp) wrote :

I'm still a bit concerned that this is passing the branch name around so many different places, rather than currying that into an object. I guess the problem here is that the places you're updating here are "places a branch could be" not necessarily an existing open branch. Aside from that it looks pretty good, and perhaps it's easier to bring it in and then change it later.

- this_branch._format.set_reference(this_branch.bzrdir, other_branch)
+ this_branch._format.set_reference(this_branch.bzrdir, None,
+ other_branch)

Inserting parameters into the middle is a somewhat noxious way to change the api in Python. It's ok to have an api bump but perhaps we should add to the end?

Robert Collins (lifeless) wrote :

actually this is clearly bogus: other_branch *is a branch object*, there is
no need for the branch name to be there - its known already by the branch
object.

I need to go through this with a fine comb I think, this makes me worry
other changes that aren't needed are being done.

Vincent Ladeuil (vila) wrote :

@Robert: Did you unpack your comb ? :-)

Jelmer Vernooij (jelmer) wrote :

Rob, we're setting a branch reference in a local bzrdir to another branch. The name of the local colocated branch is specified by the new parameter.

Robert Collins (lifeless) wrote :

Am looking at this, iz not forgotten.

Robert Collins (lifeless) wrote :

Oh, and @spiv - yes, , should be per segment in the path, not whole-string - thats the point of this ;)

Robert Collins (lifeless) wrote :

Ok, some notes:
 - if we have to edit the url before passing to transport, transport isn't supporting segment parameters properly - need some tests on that layer

 e.g. opening foo and foo,name=bar should get two equal transports. (except for parameters we decide transports should support - e.g. passive ftp might be one (though we have a syntax for that already)

So - further discussion:
 - Transport is our 'access a url' abstraction; it should take care of getting parameters parsed, exposing the parsed parameters to BzrDir.open_from_transport, and so on.
 - BzrDir should get the default branch from the transport parameters.
 - other code shouldn't need to change for this part of the conversion
 - we should end up being able to open '/foo/bar,name=gam' as a result ! (without changing all the higher layers).

review: Needs Fixing

Unmerged revisions

5079. By Jelmer Vernooij on 2010-04-17

use new subsegments urlutils functions.

5078. By Jelmer Vernooij on 2010-04-17

merge urlutils subsegment improvements.

5077. By Jelmer Vernooij on 2010-04-17

Remove unused error.

5076. By Jelmer Vernooij on 2010-04-17

Clarify parsing of subsegments.

5075. By Jelmer Vernooij on 2010-04-17

merge more-colo

5074. By Jelmer Vernooij on 2010-04-16

Merge more-colo.

5073. By Jelmer Vernooij on 2010-04-11

merge colocated branch tests.

5072. By Jelmer Vernooij on 2010-04-10

Merge updated colo spec.

5071. By Jelmer Vernooij on 2010-04-10

Fix two test failures.

5070. By Jelmer Vernooij on 2010-04-03

Add branch name argument in a couple more places.

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-04-10 18:03:54 +0000
3+++ bzrlib/branch.py 2010-04-17 17:41:17 +0000
4@@ -63,6 +63,34 @@
5 BZR_BRANCH_FORMAT_6 = "Bazaar Branch Format 6 (bzr 0.15)\n"
6
7
8+def split_colocated_name(url):
9+ """Extract the colocated branch name from a URL.
10+
11+ :param url: The original URL
12+ :return: Tuple with new URL and colocated branch name (None if not specified)
13+ """
14+ if url is None:
15+ return (None, None)
16+ (base, subsegments) = urlutils.split_subsegments(url)
17+ if len(subsegments) == 0:
18+ return (url, None)
19+ if len(subsegments) > 1:
20+ raise errors.InvalidURL(url)
21+ return (base, subsegments[0])
22+
23+
24+def join_colocated_name(base, branch_name):
25+ """Combine a location and a colocated branch name URL.
26+
27+ :param base: Base location
28+ :param branch_name: Co-located branch name
29+ :return: A URL that addresses a colocated branch
30+ """
31+ if branch_name is None:
32+ return base
33+ return urlutils.join_subsegments(base, branch_name)
34+
35+
36 # TODO: Maybe include checks for common corruption of newlines, etc?
37
38 # TODO: Some operations like log might retrieve the same revisions
39@@ -159,15 +187,16 @@
40 return [('revision-existence', revid), ('lefthand-distance', revid)]
41
42 @staticmethod
43- def open(base, _unsupported=False, possible_transports=None):
44+ def open(url, _unsupported=False, possible_transports=None):
45 """Open the branch rooted at base.
46
47 For instance, if the branch is at URL/.bzr/branch,
48 Branch.open(URL) -> a Branch instance.
49 """
50+ (base, branch_name) = split_colocated_name(url)
51 control = bzrdir.BzrDir.open(base, _unsupported,
52 possible_transports=possible_transports)
53- return control.open_branch(unsupported=_unsupported)
54+ return control.open_branch(name=branch_name, unsupported=_unsupported)
55
56 @staticmethod
57 def open_from_transport(transport, name=None, _unsupported=False):
58@@ -187,9 +216,10 @@
59 format, UnknownFormatError or UnsupportedFormatError are raised.
60 If there is one, it is returned, along with the unused portion of url.
61 """
62- control, relpath = bzrdir.BzrDir.open_containing(url,
63+ (base, branch_name) = split_colocated_name(url)
64+ control, relpath = bzrdir.BzrDir.open_containing(base,
65 possible_transports)
66- return control.open_branch(), relpath
67+ return control.open_branch(name=branch_name), relpath
68
69 def _push_should_merge_tags(self):
70 """Should _basic_push merge this branch's tags into the target?
71@@ -1335,6 +1365,8 @@
72 """
73 # XXX: Fix the bzrdir API to allow getting the branch back from the
74 # clone call. Or something. 20090224 RBC/spiv.
75+ # XXX: Should this perhaps clone colocated branches as well,
76+ # rather than just the default branch? 20100319 JRV
77 if revision_id is None:
78 revision_id = self.last_revision()
79 dir_to = self.bzrdir.clone_on_transport(to_transport,
80@@ -1510,7 +1542,7 @@
81 """Return the current default format."""
82 return klass._default_format
83
84- def get_reference(self, a_bzrdir):
85+ def get_reference(self, a_bzrdir, branch_name=None):
86 """Get the target reference of the branch in a_bzrdir.
87
88 format probing must have been completed before calling
89@@ -1518,12 +1550,13 @@
90 in a_bzrdir is correct.
91
92 :param a_bzrdir: The bzrdir to get the branch data from.
93+ :param branch_name: Name of the colocated branch to fetch
94 :return: None if the branch is not a reference branch.
95 """
96 return None
97
98 @classmethod
99- def set_reference(self, a_bzrdir, to_branch):
100+ def set_reference(self, a_bzrdir, branch_name, to_branch):
101 """Set the target reference of the branch in a_bzrdir.
102
103 format probing must have been completed before calling
104@@ -1531,6 +1564,7 @@
105 in a_bzrdir is correct.
106
107 :param a_bzrdir: The bzrdir to set the branch reference for.
108+ :param branch_name: Name of colocated branch to set, None for default
109 :param to_branch: branch that the checkout is to reference
110 """
111 raise NotImplementedError(self.set_reference)
112@@ -2048,14 +2082,14 @@
113 """See BranchFormat.get_format_description()."""
114 return "Checkout reference format 1"
115
116- def get_reference(self, a_bzrdir):
117+ def get_reference(self, a_bzrdir, branch_name=None):
118 """See BranchFormat.get_reference()."""
119- transport = a_bzrdir.get_branch_transport(None)
120+ transport = a_bzrdir.get_branch_transport(branch_name)
121 return transport.get_bytes('location')
122
123- def set_reference(self, a_bzrdir, to_branch):
124+ def set_reference(self, a_bzrdir, branch_name, to_branch):
125 """See BranchFormat.set_reference()."""
126- transport = a_bzrdir.get_branch_transport(None)
127+ transport = a_bzrdir.get_branch_transport(branch_name)
128 location = transport.put_bytes('location', to_branch.base)
129
130 def initialize(self, a_bzrdir, name=None, target_branch=None):
131@@ -2110,7 +2144,7 @@
132 raise AssertionError("wrong format %r found for %r" %
133 (format, self))
134 if location is None:
135- location = self.get_reference(a_bzrdir)
136+ location = self.get_reference(a_bzrdir, name)
137 real_bzrdir = bzrdir.BzrDir.open(
138 location, possible_transports=possible_transports)
139 result = real_bzrdir.open_branch(name=name,
140
141=== modified file 'bzrlib/builtins.py'
142--- bzrlib/builtins.py 2010-04-15 15:03:15 +0000
143+++ bzrlib/builtins.py 2010-04-17 17:41:17 +0000
144@@ -52,7 +52,11 @@
145 urlutils,
146 views,
147 )
148-from bzrlib.branch import Branch
149+from bzrlib.branch import (
150+ Branch,
151+ join_colocated_name,
152+ split_colocated_name,
153+ )
154 from bzrlib.conflicts import ConflictList
155 from bzrlib.transport import memory
156 from bzrlib.revisionspec import RevisionSpec, RevisionInfo
157@@ -1230,23 +1234,24 @@
158 revision_id = br_from.last_revision()
159 if to_location is None:
160 to_location = urlutils.derive_to_location(from_location)
161- to_transport = transport.get_transport(to_location)
162+ to_base, to_branch_name = split_colocated_name(to_location)
163+ to_transport = transport.get_transport(to_base)
164 try:
165 to_transport.mkdir('.')
166 except errors.FileExists:
167 if not use_existing_dir:
168 raise errors.BzrCommandError('Target directory "%s" '
169- 'already exists.' % to_location)
170+ 'already exists.' % to_base)
171 else:
172 try:
173 bzrdir.BzrDir.open_from_transport(to_transport)
174 except errors.NotBranchError:
175 pass
176 else:
177- raise errors.AlreadyBranchError(to_location)
178+ raise errors.AlreadyBranchError(to_base)
179 except errors.NoSuchFile:
180 raise errors.BzrCommandError('Parent of "%s" does not exist.'
181- % to_location)
182+ % to_base)
183 try:
184 # preserve whatever source format we have.
185 dir = br_from.bzrdir.sprout(to_transport.base, revision_id,
186@@ -1256,7 +1261,7 @@
187 force_new_repo=standalone,
188 create_tree_if_local=not no_tree,
189 source_branch=br_from)
190- branch = dir.open_branch()
191+ branch = dir.open_branch(to_branch_name)
192 except errors.NoSuchRevision:
193 to_transport.delete_tree('.')
194 msg = "The branch %s has no revision %s." % (from_location,
195@@ -1731,7 +1736,8 @@
196 if location is None:
197 location = u'.'
198
199- to_transport = transport.get_transport(location)
200+ (base, branch_name) = split_colocated_name(location)
201+ to_transport = transport.get_transport(base)
202
203 # The path has to exist to initialize a
204 # branch inside of it.
205@@ -1746,7 +1752,7 @@
206 " does not exist."
207 "\nYou may supply --create-prefix to create all"
208 " leading parent directories."
209- % location)
210+ % base)
211 to_transport.create_prefix()
212
213 try:
214@@ -1754,17 +1760,18 @@
215 except errors.NotBranchError:
216 # really a NotBzrDir error...
217 create_branch = bzrdir.BzrDir.create_branch_convenience
218- branch = create_branch(to_transport.base, format=format,
219- possible_transports=[to_transport])
220+ branch = create_branch(
221+ join_colocated_name(to_transport.base, branch_name),
222+ format=format, possible_transports=[to_transport])
223 a_bzrdir = branch.bzrdir
224 else:
225 from bzrlib.transport.local import LocalTransport
226- if a_bzrdir.has_branch():
227+ if a_bzrdir.has_branch(branch_name):
228 if (isinstance(to_transport, LocalTransport)
229 and not a_bzrdir.has_workingtree()):
230 raise errors.BranchExistsWithoutWorkingTree(location)
231 raise errors.AlreadyBranchError(location)
232- branch = a_bzrdir.create_branch()
233+ branch = a_bzrdir.create_branch(branch_name)
234 a_bzrdir.create_workingtree()
235 if append_revisions_only:
236 try:
237@@ -2365,8 +2372,7 @@
238 location = revision[0].get_branch()
239 else:
240 location = '.'
241- dir, relpath = bzrdir.BzrDir.open_containing(location)
242- b = dir.open_branch()
243+ b, relpath = Branch.open_containing(location)
244 b.lock_read()
245 self.add_cleanup(b.unlock)
246 rev1, rev2 = _get_revision_range(revision, b, self.name())
247@@ -4732,13 +4738,8 @@
248 revision=None, force=False, local=False):
249 if location is None:
250 location = u'.'
251- control, relpath = bzrdir.BzrDir.open_containing(location)
252- try:
253- tree = control.open_workingtree()
254- b = tree.branch
255- except (errors.NoWorkingTree, errors.NotLocalUrl):
256- tree = None
257- b = control.open_branch()
258+ tree, b, relpath = bzrdir.BzrDir.open_containing_tree_or_branch(
259+ location)
260
261 if tree is not None:
262 tree.lock_write()
263@@ -5543,51 +5544,53 @@
264 from bzrlib import switch
265 tree_location = '.'
266 revision = _get_one_revision('switch', revision)
267- control_dir = bzrdir.BzrDir.open_containing(tree_location)[0]
268+ tree_base, branch_name = split_colocated_name(tree_location)
269+ control_dir = bzrdir.BzrDir.open_containing(tree_base)[0]
270 if to_location is None:
271 if revision is None:
272 raise errors.BzrCommandError('You must supply either a'
273 ' revision or a location')
274 to_location = '.'
275 try:
276- branch = control_dir.open_branch()
277+ branch = control_dir.open_branch(branch_name)
278 had_explicit_nick = branch.get_config().has_explicit_nickname()
279 except errors.NotBranchError:
280 branch = None
281 had_explicit_nick = False
282+ to_base, to_branch_name = split_colocated_name(to_location)
283 if create_branch:
284 if branch is None:
285 raise errors.BzrCommandError('cannot create branch without'
286 ' source branch')
287- to_location = directory_service.directories.dereference(
288- to_location)
289- if '/' not in to_location and '\\' not in to_location:
290+ to_base = directory_service.directories.dereference(
291+ to_base)
292+ if '/' not in to_base and '\\' not in to_base:
293 # This path is meant to be relative to the existing branch
294 this_url = self._get_branch_location(control_dir)
295- to_location = urlutils.join(this_url, '..', to_location)
296- to_branch = branch.bzrdir.sprout(to_location,
297- possible_transports=[branch.bzrdir.root_transport],
298- source_branch=branch).open_branch()
299+ to_base = urlutils.join(this_url, '..', to_base)
300+ to_branch = branch.bzrdir.sprout(to_base,
301+ possible_transports=[branch.bzrdir.root_transport],
302+ source_branch=branch).open_branch(to_branch_name)
303 else:
304 try:
305 to_branch = Branch.open(to_location)
306 except errors.NotBranchError:
307- this_url = self._get_branch_location(control_dir)
308+ this_url = self._get_branch_location(control_dir, branch_name)
309 to_branch = Branch.open(
310 urlutils.join(this_url, '..', to_location))
311 if revision is not None:
312 revision = revision.as_revision_id(to_branch)
313 switch.switch(control_dir, to_branch, force, revision_id=revision)
314 if had_explicit_nick:
315- branch = control_dir.open_branch() #get the new branch!
316+ branch = control_dir.open_branch(to_branch_name) #get the new branch!
317 branch.nick = to_branch.nick
318 note('Switched to branch: %s',
319 urlutils.unescape_for_display(to_branch.base, 'utf-8'))
320
321- def _get_branch_location(self, control_dir):
322+ def _get_branch_location(self, control_dir, branch_name):
323 """Return location of branch for this control dir."""
324 try:
325- this_branch = control_dir.open_branch()
326+ this_branch = control_dir.open_branch(branch_name)
327 # This may be a heavy checkout, where we want the master branch
328 master_location = this_branch.get_bound_location()
329 if master_location is not None:
330@@ -5597,7 +5600,7 @@
331 except errors.NotBranchError:
332 format = control_dir.find_branch_format()
333 if getattr(format, 'get_reference', None) is not None:
334- return format.get_reference(control_dir)
335+ return format.get_reference(control_dir, branch_name)
336 else:
337 return control_dir.root_transport.base
338
339
340=== modified file 'bzrlib/bzrdir.py'
341--- bzrlib/bzrdir.py 2010-04-15 15:03:15 +0000
342+++ bzrlib/bzrdir.py 2010-04-17 17:41:17 +0000
343@@ -220,7 +220,7 @@
344 except errors.NoRepositoryPresent:
345 local_repo = None
346 try:
347- local_branch = self.open_branch()
348+ local_branch = self.open_branch() # XXX Deal with colocated branches
349 except errors.NotBranchError:
350 local_branch = None
351 else:
352@@ -704,13 +704,18 @@
353 raise errors.NoRepositoryPresent(self)
354 return found_repo
355
356- def get_branch_reference(self):
357+ def get_branch_reference(self, name=None):
358 """Return the referenced URL for the branch in this bzrdir.
359
360+ :param name: Optional colocated branch name
361 :raises NotBranchError: If there is no Branch.
362+ :raises NoColocatedBranchSupport: If a branch name was specified
363+ but colocated branches are not supported.
364 :return: The URL the branch in this bzrdir references if it is a
365 reference branch, or None for regular branches.
366 """
367+ if name is not None:
368+ raise errors.NoColocatedBranchSupport(self)
369 return None
370
371 def get_branch_transport(self, branch_format, name=None):
372@@ -951,9 +956,11 @@
373 raise errors.NotBranchError(path=url)
374 a_transport = new_t
375
376- def _get_tree_branch(self):
377+ def _get_tree_branch(self, name=None):
378 """Return the branch and tree, if any, for this bzrdir.
379
380+ :param name: Name of colocated branch to open.
381+
382 Return None for tree if not present or inaccessible.
383 Raise NotBranchError if no branch is present.
384 :return: (tree, branch)
385@@ -962,9 +969,12 @@
386 tree = self.open_workingtree()
387 except (errors.NoWorkingTree, errors.NotLocalUrl):
388 tree = None
389- branch = self.open_branch()
390+ branch = self.open_branch(name=name)
391 else:
392- branch = tree.branch
393+ if name is not None:
394+ branch = self.open_branch(name=name)
395+ else:
396+ branch = tree.branch
397 return tree, branch
398
399 @classmethod
400@@ -976,8 +986,10 @@
401 raised
402 :return: (tree, branch)
403 """
404+ from bzrlib.branch import split_colocated_name
405+ (location, branch_name) = split_colocated_name(location)
406 bzrdir = klass.open(location)
407- return bzrdir._get_tree_branch()
408+ return bzrdir._get_tree_branch(branch_name)
409
410 @classmethod
411 def open_containing_tree_or_branch(klass, location):
412@@ -989,8 +1001,10 @@
413 raised
414 relpath is the portion of the path that is contained by the branch.
415 """
416+ from bzrlib.branch import split_colocated_name
417+ (location, branch_name) = split_colocated_name(location)
418 bzrdir, relpath = klass.open_containing(location)
419- tree, branch = bzrdir._get_tree_branch()
420+ tree, branch = bzrdir._get_tree_branch(branch_name)
421 return tree, branch, relpath
422
423 @classmethod
424@@ -1261,13 +1275,13 @@
425 return result
426
427 def push_branch(self, source, revision_id=None, overwrite=False,
428- remember=False, create_prefix=False):
429+ remember=False, create_prefix=False, branch_name=None):
430 """Push the source branch into this BzrDir."""
431 br_to = None
432 # If we can open a branch, use its direct repository, otherwise see
433 # if there is a repository without a branch.
434 try:
435- br_to = self.open_branch()
436+ br_to = self.open_branch(branch_name)
437 except errors.NotBranchError:
438 # Didn't find a branch, can we find a repository?
439 repository_to = self.find_repository()
440@@ -1652,13 +1666,13 @@
441 def destroy_workingtree_metadata(self):
442 self.transport.delete_tree('checkout')
443
444- def find_branch_format(self):
445+ def find_branch_format(self, name=None):
446 """Find the branch 'format' for this bzrdir.
447
448 This might be a synthetic object for e.g. RemoteBranch and SVN.
449 """
450 from bzrlib.branch import BranchFormat
451- return BranchFormat.find_format(self)
452+ return BranchFormat.find_format(self, name=name)
453
454 def _get_mkdir_mode(self):
455 """Figure out the mode to use when creating a bzrdir subdir."""
456@@ -1666,11 +1680,11 @@
457 lockable_files.TransportLock)
458 return temp_control._dir_mode
459
460- def get_branch_reference(self):
461+ def get_branch_reference(self, name=None):
462 """See BzrDir.get_branch_reference()."""
463 from bzrlib.branch import BranchFormat
464- format = BranchFormat.find_format(self)
465- return format.get_reference(self)
466+ format = BranchFormat.find_format(self, name=name)
467+ return format.get_reference(self, name=name)
468
469 def get_branch_transport(self, branch_format, name=None):
470 """See BzrDir.get_branch_transport()."""
471@@ -1770,7 +1784,7 @@
472 def open_branch(self, name=None, unsupported=False,
473 ignore_fallbacks=False):
474 """See BzrDir.open_branch."""
475- format = self.find_branch_format()
476+ format = self.find_branch_format(name=name)
477 self._check_supported(format, unsupported)
478 return format.open(self, name=name,
479 _found=True, ignore_fallbacks=ignore_fallbacks)
480
481=== modified file 'bzrlib/errors.py'
482--- bzrlib/errors.py 2010-03-24 14:15:01 +0000
483+++ bzrlib/errors.py 2010-04-17 17:41:17 +0000
484@@ -3134,3 +3134,10 @@
485 def __init__(self, bzrdir):
486 self.bzrdir = bzrdir
487
488+
489+class InvalidColocatedBranchName(BzrError):
490+
491+ _fmt = "%(branch_name)s is an invalid name for a colocated branch."
492+
493+ def __init__(self, branch_name):
494+ self.branch_name = branch_name
495
496=== modified file 'bzrlib/reconcile.py'
497--- bzrlib/reconcile.py 2010-02-17 17:11:16 +0000
498+++ bzrlib/reconcile.py 2010-04-17 17:41:17 +0000
499@@ -80,19 +80,16 @@
500
501 def _reconcile(self):
502 """Helper function for performing reconciliation."""
503- self._reconcile_branch()
504+ self._reconcile_branches()
505 self._reconcile_repository()
506
507- def _reconcile_branch(self):
508- try:
509- self.branch = self.bzrdir.open_branch()
510- except errors.NotBranchError:
511- # Nothing to check here
512- self.fixed_branch_history = None
513- return
514- ui.ui_factory.note('Reconciling branch %s' % self.branch.base)
515- branch_reconciler = self.branch.reconcile(thorough=True)
516- self.fixed_branch_history = branch_reconciler.fixed_history
517+ def _reconcile_branches(self):
518+ self.fixed_branch_history = True
519+ for branch in self.bzrdir.list_branches():
520+ ui.ui_factory.note('Reconciling branch %s' % branch.base)
521+ branch_reconciler = branch.reconcile(thorough=True)
522+ self.fixed_branch_history = (self.fixed_branch_history and
523+ branch_reconciler.fixed_history)
524
525 def _reconcile_repository(self):
526 self.repo = self.bzrdir.find_repository()
527
528=== modified file 'bzrlib/remote.py'
529--- bzrlib/remote.py 2010-03-21 21:39:33 +0000
530+++ bzrlib/remote.py 2010-04-17 17:41:17 +0000
531@@ -271,16 +271,19 @@
532 def create_workingtree(self, revision_id=None, from_branch=None):
533 raise errors.NotLocalUrl(self.transport.base)
534
535- def find_branch_format(self):
536+ def find_branch_format(self, name=None):
537 """Find the branch 'format' for this bzrdir.
538
539 This might be a synthetic object for e.g. RemoteBranch and SVN.
540 """
541- b = self.open_branch()
542+ b = self.open_branch(name=name)
543 return b._format
544
545- def get_branch_reference(self):
546+ def get_branch_reference(self, name=None):
547 """See BzrDir.get_branch_reference()."""
548+ if name is not None:
549+ # XXX JRV20100304: Support opening colocated branches
550+ raise errors.NoColocatedBranchSupport(self)
551 response = self._get_branch_reference()
552 if response[0] == 'ref':
553 return response[1]
554@@ -317,9 +320,9 @@
555 raise errors.UnexpectedSmartServerResponse(response)
556 return response
557
558- def _get_tree_branch(self):
559+ def _get_tree_branch(self, name=None):
560 """See BzrDir._get_tree_branch()."""
561- return None, self.open_branch()
562+ return None, self.open_branch(name=name)
563
564 def open_branch(self, name=None, unsupported=False,
565 ignore_fallbacks=False):
566
567=== modified file 'bzrlib/switch.py'
568--- bzrlib/switch.py 2010-02-17 17:11:16 +0000
569+++ bzrlib/switch.py 2010-04-17 17:41:17 +0000
570@@ -70,7 +70,7 @@
571 branch_format = control.find_branch_format()
572 if branch_format.get_reference(control) is not None:
573 # Lightweight checkout: update the branch reference
574- branch_format.set_reference(control, to_branch)
575+ branch_format.set_reference(control, None, to_branch)
576 else:
577 b = control.open_branch()
578 bound_branch = b.get_bound_location()
579
580=== modified file 'bzrlib/tests/per_branch/test_branch.py'
581--- bzrlib/tests/per_branch/test_branch.py 2010-03-02 22:25:58 +0000
582+++ bzrlib/tests/per_branch/test_branch.py 2010-04-17 17:41:17 +0000
583@@ -668,7 +668,8 @@
584 this_branch = self.make_branch('this')
585 other_branch = self.make_branch('other')
586 try:
587- this_branch._format.set_reference(this_branch.bzrdir, other_branch)
588+ this_branch._format.set_reference(this_branch.bzrdir, None,
589+ other_branch)
590 except NotImplementedError:
591 # that's ok
592 pass
593
594=== modified file 'bzrlib/tests/per_bzrdir_colo/test_unsupported.py'
595--- bzrlib/tests/per_bzrdir_colo/test_unsupported.py 2010-04-11 19:40:23 +0000
596+++ bzrlib/tests/per_bzrdir_colo/test_unsupported.py 2010-04-17 17:41:17 +0000
597@@ -35,15 +35,7 @@
598
599 class TestNoColocatedSupport(TestCaseWithBzrDir):
600
601- def test_destroy_colocated_branch(self):
602- branch = self.make_branch('branch')
603- # Colocated branches should not be supported *or*
604- # destroy_branch should not be supported at all
605- self.assertRaises(
606- (errors.NoColocatedBranchSupport, errors.UnsupportedOperation),
607- branch.bzrdir.destroy_branch, 'colo')
608-
609- def test_create_colo_branch(self):
610+ def make_bzrdir_with_repo(self):
611 # a bzrdir can construct a branch and repository for itself.
612 if not self.bzrdir_format.is_supported():
613 # unsupported formats are not loopback testable
614@@ -53,7 +45,27 @@
615 t = get_transport(self.get_url())
616 made_control = self.bzrdir_format.initialize(t.base)
617 made_repo = made_control.create_repository()
618+ return made_control
619+
620+ def test_destroy_colocated_branch(self):
621+ branch = self.make_branch('branch')
622+ # Colocated branches should not be supported *or*
623+ # destroy_branch should not be supported at all
624+ self.assertRaises(
625+ (errors.NoColocatedBranchSupport, errors.UnsupportedOperation),
626+ branch.bzrdir.destroy_branch, 'colo')
627+
628+ def test_create_colo_branch(self):
629+ made_control = self.make_bzrdir_with_repo()
630 self.assertRaises(errors.NoColocatedBranchSupport,
631 made_control.create_branch, "colo")
632
633+ def test_branch_transport(self):
634+ made_control = self.make_bzrdir_with_repo()
635+ self.assertRaises(errors.NoColocatedBranchSupport,
636+ made_control.get_branch_transport, None, "colo")
637
638+ def test_get_branch_reference(self):
639+ made_control = self.make_bzrdir_with_repo()
640+ self.assertRaises(errors.NoColocatedBranchSupport,
641+ made_control.get_branch_reference, "colo")
642
643=== modified file 'bzrlib/tests/test_branch.py'
644--- bzrlib/tests/test_branch.py 2010-03-11 13:14:37 +0000
645+++ bzrlib/tests/test_branch.py 2010-04-17 17:41:17 +0000
646@@ -577,3 +577,40 @@
647 self.assertEqual(['lock_write', 'func called', 'unlock'], self._calls)
648
649
650+class TestExtractColocatedName(tests.TestCase):
651+
652+ def test_no_colocated(self):
653+ self.assertEquals(("/some/path", None),
654+ _mod_branch.split_colocated_name("/some/path"))
655+
656+ def test_colocated(self):
657+ self.assertEquals(("/some/path", "tip"),
658+ _mod_branch.split_colocated_name("/some/path,tip"))
659+
660+ def test_colocated_uses_right_comma(self):
661+ self.assertEquals(("/some,dir/path", "tip"),
662+ _mod_branch.split_colocated_name("/some,dir/path,tip"))
663+
664+ def test_colocated_allows_slashes(self):
665+ self.assertEquals(("/somedir/path", "heads%2Ftip"),
666+ _mod_branch.split_colocated_name("/somedir/path,heads%2Ftip"))
667+
668+ def test_none(self):
669+ self.assertEquals((None, None),
670+ _mod_branch.split_colocated_name(None))
671+
672+
673+class TestJoinColocatedName(tests.TestCase):
674+
675+ def test_default(self):
676+ self.assertEquals("/somedir/path",
677+ _mod_branch.join_colocated_name("/somedir/path", None))
678+
679+ def test_colocated(self):
680+ self.assertEquals("/somedir/path,brrr",
681+ _mod_branch.join_colocated_name("/somedir/path", "brrr"))
682+
683+ def test_invalid_branch_name(self):
684+ self.assertRaises(errors.InvalidColocatedBranchName,
685+ _mod_branch.join_colocated_name, "/somedir/path", "brr,brr,brr")
686+
687
688=== modified file 'bzrlib/tests/test_remote.py'
689--- bzrlib/tests/test_remote.py 2010-02-23 07:43:11 +0000
690+++ bzrlib/tests/test_remote.py 2010-04-17 17:41:17 +0000
691@@ -584,7 +584,7 @@
692 # _get_tree_branch is a form of open_branch, but it should only ask for
693 # branch opening, not any other network requests.
694 calls = []
695- def open_branch():
696+ def open_branch(name=None):
697 calls.append("Called")
698 return "a-branch"
699 transport = MemoryTransport()
700
701=== modified file 'bzrlib/tests/test_urlutils.py'
702--- bzrlib/tests/test_urlutils.py 2010-02-17 17:11:16 +0000
703+++ bzrlib/tests/test_urlutils.py 2010-04-17 17:41:17 +0000
704@@ -270,6 +270,17 @@
705 self.assertRaises(InvalidURLJoin, urlutils.joinpath, '/', '..')
706 self.assertRaises(InvalidURLJoin, urlutils.joinpath, '/', '/..')
707
708+ def test_join_subsegments(self):
709+ join_subsegments = urlutils.join_subsegments
710+ self.assertEquals("/somedir/path",
711+ join_subsegments("/somedir/path"))
712+ self.assertEquals("/somedir/path,brrr",
713+ join_subsegments("/somedir/path", "brrr"))
714+ self.assertRaises(InvalidURLJoin,
715+ join_subsegments, "/somedir/path", "brr,brr,brr")
716+ self.assertEquals("/somedir/path,bla,bar",
717+ join_subsegments("/somedir/path", "bla", "bar"))
718+
719 def test_function_type(self):
720 if sys.platform == 'win32':
721 self.assertEqual(urlutils._win32_local_path_to_url, urlutils.local_path_to_url)
722@@ -412,6 +423,19 @@
723 self.assertEqual(('path/..', 'foo'), split('path/../foo'))
724 self.assertEqual(('../path', 'foo'), split('../path/foo'))
725
726+ def test_split_subsegments(self):
727+ split_subsegments = urlutils.split_subsegments
728+ self.assertEquals(("/some/path", []),
729+ split_subsegments("/some/path"))
730+ self.assertEquals(("/some/path", ["tip"]),
731+ split_subsegments("/some/path,tip"))
732+ self.assertEquals(("/some,dir/path", ["tip"]),
733+ split_subsegments("/some,dir/path,tip"))
734+ self.assertEquals(("/somedir/path", ["heads%2Ftip"]),
735+ split_subsegments("/somedir/path,heads%2Ftip"))
736+ self.assertEquals(("/somedir/path", ["heads%2Ftip", "bar"]),
737+ split_subsegments("/somedir/path,heads%2Ftip,bar"))
738+
739 def test_win32_strip_local_trailing_slash(self):
740 strip = urlutils._win32_strip_local_trailing_slash
741 self.assertEqual('file://', strip('file://'))
742
743=== modified file 'bzrlib/urlutils.py'
744--- bzrlib/urlutils.py 2010-02-17 17:11:16 +0000
745+++ bzrlib/urlutils.py 2010-04-17 17:41:17 +0000
746@@ -469,6 +469,32 @@
747 return url_base + head, tail
748
749
750+def split_subsegments(url):
751+ """Split the subsegment of the last segment of a URL.
752+
753+ :param url: A relative or absolute URL
754+ :return: (url, subsegments)
755+ """
756+ (parent_url, child_dir) = split(url)
757+ subsegments = child_dir.split(",")
758+ if len(subsegments) == 1:
759+ return (url, [])
760+ return (join(parent_url, subsegments[0]), subsegments[1:])
761+
762+
763+def join_subsegments(base, *subsegments):
764+ """Create a new URL by adding subsegments to an existing one.
765+
766+ """
767+ if not subsegments:
768+ return base
769+ for subsegment in subsegments:
770+ if "," in subsegment:
771+ raise errors.InvalidURLJoin(", exists in subsegments",
772+ base, subsegments)
773+ return ",".join((base,) + subsegments)
774+
775+
776 def _win32_strip_local_trailing_slash(url):
777 """Strip slashes after the drive letter"""
778 if len(url) > WIN32_MIN_ABS_FILEURL_LENGTH: