Merge lp:~jelmer/brz/clone into lp:brz/3.1

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: no longer in the source branch.
Merge reported by: The Breezy Bot
Merged at revision: not available
Proposed branch: lp:~jelmer/brz/clone
Merge into: lp:brz/3.1
Diff against target: 948 lines (+305/-83)
20 files modified
breezy/branch.py (+3/-3)
breezy/builtins.py (+33/-0)
breezy/bzr/branch.py (+3/-3)
breezy/bzr/bzrdir.py (+29/-14)
breezy/bzr/remote.py (+23/-4)
breezy/bzr/smart/bzrdir.py (+10/-3)
breezy/controldir.py (+12/-0)
breezy/git/dir.py (+25/-9)
breezy/git/tests/test_blackbox.py (+3/-3)
breezy/git/tests/test_remote.py (+2/-0)
breezy/info.py (+1/-1)
breezy/plugins/weave_fmt/bzrdir.py (+1/-1)
breezy/tests/blackbox/__init__.py (+1/-0)
breezy/tests/blackbox/test_clone.py (+70/-0)
breezy/tests/blackbox/test_switch.py (+1/-1)
breezy/tests/per_bzrdir/test_bzrdir.py (+1/-1)
breezy/tests/per_controldir/test_controldir.py (+62/-40)
breezy/tests/per_controldir_colo/test_supported.py (+5/-0)
breezy/tests/test_smart.py (+13/-0)
doc/en/release-notes/brz-3.1.txt (+7/-0)
To merge this branch: bzr merge lp:~jelmer/brz/clone
Reviewer Review Type Date Requested Status
Jelmer Vernooij Approve
Review via email: mp+379369@code.launchpad.net

Commit message

Add a 'brz clone' command.

Description of the change

Add a 'brz clone' command.

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) :
review: Approve
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote :
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote :
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote :
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'breezy/branch.py'
2--- breezy/branch.py 2020-02-18 03:11:01 +0000
3+++ breezy/branch.py 2020-03-22 21:30:11 +0000
4@@ -1194,8 +1194,8 @@
5 if revno < 1 or revno > self.revno():
6 raise errors.InvalidRevisionNumber(revno)
7
8- def clone(self, to_controldir, revision_id=None, repository_policy=None,
9- tag_selector=None):
10+ def clone(self, to_controldir, revision_id=None, name=None,
11+ repository_policy=None, tag_selector=None):
12 """Clone this branch into to_controldir preserving all semantic values.
13
14 Most API users will want 'create_clone_on_transport', which creates a
15@@ -1204,7 +1204,7 @@
16 revision_id: if not None, the revision history in the new branch will
17 be truncated to end with revision_id.
18 """
19- result = to_controldir.create_branch()
20+ result = to_controldir.create_branch(name=name)
21 with self.lock_read(), result.lock_write():
22 if repository_policy is not None:
23 repository_policy.configure_branch(result)
24
25=== modified file 'breezy/builtins.py'
26--- breezy/builtins.py 2020-01-31 02:34:57 +0000
27+++ breezy/builtins.py 2020-03-22 21:30:11 +0000
28@@ -1442,6 +1442,7 @@
29 parameter, as in "branch foo/bar -r 5".
30 """
31
32+ aliase = ['sprout']
33 _see_also = ['checkout']
34 takes_args = ['from_location', 'to_location?']
35 takes_options = ['revision',
36@@ -1704,6 +1705,38 @@
37 accelerator_tree, hardlink)
38
39
40+class cmd_clone(Command):
41+ __doc__ = """Clone a control directory.
42+ """
43+
44+ takes_args = ['from_location', 'to_location?']
45+ takes_options = ['revision',
46+ Option('no-recurse-nested',
47+ help='Do not recursively check out nested trees.'),
48+ ]
49+
50+ def run(self, from_location, to_location=None, revision=None, no_recurse_nested=False):
51+ accelerator_tree, br_from = controldir.ControlDir.open_tree_or_branch(
52+ from_location)
53+ if no_recurse_nested:
54+ recurse = 'none'
55+ else:
56+ recurse = 'down'
57+ revision = _get_one_revision('branch', revision)
58+ self.enter_context(br_from.lock_read())
59+ if revision is not None:
60+ revision_id = revision.as_revision_id(br_from)
61+ else:
62+ # FIXME - wt.last_revision, fallback to branch, fall back to
63+ # None or perhaps NULL_REVISION to mean copy nothing
64+ # RBC 20060209
65+ revision_id = br_from.last_revision()
66+ if to_location is None:
67+ to_location = urlutils.derive_to_location(from_location)
68+ target_controldir = br_from.controldir.clone(to_location, revision_id=revision_id)
69+ note(gettext('Created new control directory.'))
70+
71+
72 class cmd_renames(Command):
73 __doc__ = """Show list of renamed files.
74 """
75
76=== modified file 'breezy/bzr/branch.py'
77--- breezy/bzr/branch.py 2020-02-18 03:11:01 +0000
78+++ breezy/bzr/branch.py 2020-03-22 21:30:11 +0000
79@@ -1032,10 +1032,10 @@
80
81 def _make_reference_clone_function(format, a_branch):
82 """Create a clone() routine for a branch dynamically."""
83- def clone(to_bzrdir, revision_id=None,
84- repository_policy=None, tag_selector=None):
85+ def clone(to_bzrdir, revision_id=None, repository_policy=None, name=None,
86+ tag_selector=None):
87 """See Branch.clone()."""
88- return format.initialize(to_bzrdir, target_branch=a_branch)
89+ return format.initialize(to_bzrdir, target_branch=a_branch, name=name)
90 # cannot obey revision_id limits when cloning a reference ...
91 # FIXME RBC 20060210 either nuke revision_id for clone, or
92 # emit some sort of warning/error to the caller ?!
93
94=== modified file 'breezy/bzr/bzrdir.py'
95--- breezy/bzr/bzrdir.py 2020-02-18 03:11:01 +0000
96+++ breezy/bzr/bzrdir.py 2020-03-22 21:30:11 +0000
97@@ -174,17 +174,18 @@
98 local_repo = self.find_repository()
99 except errors.NoRepositoryPresent:
100 local_repo = None
101+ local_branches = self.get_branches()
102 try:
103- local_branch = self.open_branch()
104- except errors.NotBranchError:
105- local_branch = None
106+ local_active_branch = local_branches['']
107+ except KeyError:
108+ pass
109 else:
110 # enable fallbacks when branch is not a branch reference
111- if local_branch.repository.has_same_location(local_repo):
112- local_repo = local_branch.repository
113+ if local_active_branch.repository.has_same_location(local_repo):
114+ local_repo = local_active_branch.repository
115 if preserve_stacking:
116 try:
117- stacked_on = local_branch.get_stacked_on_url()
118+ stacked_on = local_active_branch.get_stacked_on_url()
119 except (_mod_branch.UnstackableBranchFormat,
120 errors.UnstackableRepositoryFormat,
121 errors.NotStacked):
122@@ -233,11 +234,11 @@
123 # 1 if there is a branch present
124 # make sure its content is available in the target repository
125 # clone it.
126- if local_branch is not None:
127+ for name, local_branch in local_branches.items():
128 local_branch.clone(
129- result, revision_id=revision_id,
130+ result, revision_id=(None if name != '' else revision_id),
131 repository_policy=repository_policy,
132- tag_selector=tag_selector)
133+ name=name, tag_selector=tag_selector)
134 try:
135 # Cheaper to check if the target is not local, than to try making
136 # the tree and fail.
137@@ -1016,6 +1017,18 @@
138 pass
139 return self.transport.clone('checkout')
140
141+ def branch_names(self):
142+ """See ControlDir.branch_names."""
143+ ret = []
144+ try:
145+ self.get_branch_reference()
146+ except errors.NotBranchError:
147+ pass
148+ else:
149+ ret.append("")
150+ ret.extend(self._read_branch_list())
151+ return ret
152+
153 def get_branches(self):
154 """See ControlDir.get_branches."""
155 ret = {}
156@@ -1314,11 +1327,13 @@
157 remote_dir_format = RemoteBzrDirFormat()
158 remote_dir_format._network_name = self.network_name()
159 self._supply_sub_formats_to(remote_dir_format)
160- return remote_dir_format.initialize_on_transport_ex(transport,
161- use_existing_dir=use_existing_dir, create_prefix=create_prefix,
162- force_new_repo=force_new_repo, stacked_on=stacked_on,
163- stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
164- make_working_trees=make_working_trees, shared_repo=shared_repo)
165+ return remote_dir_format.initialize_on_transport_ex(
166+ transport, use_existing_dir=use_existing_dir,
167+ create_prefix=create_prefix, force_new_repo=force_new_repo,
168+ stacked_on=stacked_on, stack_on_pwd=stack_on_pwd,
169+ repo_format_name=repo_format_name,
170+ make_working_trees=make_working_trees,
171+ shared_repo=shared_repo)
172 # XXX: Refactor the create_prefix/no_create_prefix code into a
173 # common helper function
174 # The destination may not exist - if so make it according to policy.
175
176=== modified file 'breezy/bzr/remote.py'
177--- breezy/bzr/remote.py 2020-02-19 04:50:09 +0000
178+++ breezy/bzr/remote.py 2020-03-22 21:30:11 +0000
179@@ -707,6 +707,23 @@
180 b = self.open_branch(name=name)
181 return b._format
182
183+ def branch_names(self):
184+ path = self._path_for_remote_call(self._client)
185+ try:
186+ response, handler = self._call_expecting_body(
187+ b'BzrDir.get_branches', path)
188+ except errors.UnknownSmartMethod:
189+ self._ensure_real()
190+ return self._real_bzrdir.branch_names()
191+ if response[0] != b"success":
192+ raise errors.UnexpectedSmartServerResponse(response)
193+ body = bencode.bdecode(handler.read_body_bytes())
194+ ret = []
195+ for name, value in viewitems(body):
196+ name = name.decode('utf-8')
197+ ret.append(name)
198+ return ret
199+
200 def get_branches(self, possible_transports=None, ignore_fallbacks=False):
201 path = self._path_for_remote_call(self._client)
202 try:
203@@ -721,9 +738,10 @@
204 ret = {}
205 for name, value in viewitems(body):
206 name = name.decode('utf-8')
207- ret[name] = self._open_branch(name, value[0], value[1],
208- possible_transports=possible_transports,
209- ignore_fallbacks=ignore_fallbacks)
210+ ret[name] = self._open_branch(
211+ name, value[0].decode('ascii'), value[1],
212+ possible_transports=possible_transports,
213+ ignore_fallbacks=ignore_fallbacks)
214 return ret
215
216 def set_branch_reference(self, target_branch, name=None):
217@@ -792,8 +810,9 @@
218 if kind == 'ref':
219 # a branch reference, use the existing BranchReference logic.
220 format = BranchReferenceFormat()
221+ ref_loc = urlutils.join(self.user_url, location_or_format.decode('utf-8'))
222 return format.open(self, name=name, _found=True,
223- location=location_or_format.decode('utf-8'),
224+ location=ref_loc,
225 ignore_fallbacks=ignore_fallbacks,
226 possible_transports=possible_transports)
227 branch_format_name = location_or_format
228
229=== modified file 'breezy/bzr/smart/bzrdir.py'
230--- breezy/bzr/smart/bzrdir.py 2018-11-11 04:08:32 +0000
231+++ breezy/bzr/smart/bzrdir.py 2020-03-22 21:30:11 +0000
232@@ -443,12 +443,19 @@
233 The body is a bencoded dictionary, with values similar to the return
234 value of the open branch request.
235 """
236- branches = self._bzrdir.get_branches()
237+ branch_names = self._bzrdir.branch_names()
238 ret = {}
239- for name, b in branches.items():
240+ for name in branch_names:
241 if name is None:
242 name = b""
243- ret[name.encode('utf-8')] = (b"branch", b._format.network_name())
244+ branch_ref = self._bzrdir.get_branch_reference(name=name)
245+ if branch_ref is not None:
246+ branch_ref = urlutils.relative_url(self._bzrdir.user_url, branch_ref)
247+ value = (b"ref", branch_ref.encode('utf-8'))
248+ else:
249+ b = self._bzrdir.open_branch(name=name, ignore_fallbacks=True)
250+ value = (b"branch", b._format.network_name())
251+ ret[name.encode('utf-8')] = value
252 return SuccessfulSmartServerResponse(
253 (b"success", ), bencode.bencode(ret))
254
255
256=== modified file 'breezy/controldir.py'
257--- breezy/controldir.py 2020-02-18 03:11:01 +0000
258+++ breezy/controldir.py 2020-03-22 21:30:11 +0000
259@@ -129,6 +129,18 @@
260 """
261 return list(self.get_branches().values())
262
263+ def branch_names(self):
264+ """List all branch names in this control directory.
265+
266+ :return: List of branch names
267+ """
268+ try:
269+ self.get_branch_reference()
270+ except (errors.NotBranchError, errors.NoRepositoryPresent):
271+ return []
272+ else:
273+ return [""]
274+
275 def get_branches(self):
276 """Get all branches in this control directory, as a dictionary.
277
278
279=== modified file 'breezy/git/dir.py'
280--- breezy/git/dir.py 2020-02-19 04:50:09 +0000
281+++ breezy/git/dir.py 2020-03-22 21:30:11 +0000
282@@ -228,6 +228,7 @@
283 """See ControlDir.clone_on_transport."""
284 from ..repository import InterRepository
285 from .mapping import default_mapping
286+ from ..transport.local import LocalTransport
287 if stacked_on is not None:
288 raise _mod_branch.UnstackableBranchFormat(
289 self._format, self.user_url)
290@@ -253,18 +254,19 @@
291 mapping=default_mapping)
292 for name, val in viewitems(refs):
293 target_git_repo.refs[name] = val
294- result_dir = self.__class__(transport, target_git_repo, format)
295+ result_dir = LocalGitDir(transport, target_git_repo, format)
296 if revision_id is not None:
297 result_dir.open_branch().set_last_revision(revision_id)
298- try:
299- # Cheaper to check if the target is not local, than to try making
300- # the tree and fail.
301- result_dir.root_transport.local_abspath('.')
302+ if not no_tree and isinstance(result_dir.root_transport, LocalTransport):
303 if result_dir.open_repository().make_working_trees():
304- self.open_workingtree().clone(
305- result_dir, revision_id=revision_id)
306- except (brz_errors.NoWorkingTree, brz_errors.NotLocalUrl):
307- pass
308+ try:
309+ local_wt = self.open_workingtree()
310+ except brz_errors.NoWorkingTree:
311+ pass
312+ except brz_errors.NotLocalUrl:
313+ result_dir.create_workingtree(revision_id=revision_id)
314+ else:
315+ local_wt.clone(result_dir, revision_id=revision_id)
316
317 return result_dir
318
319@@ -299,6 +301,20 @@
320 """
321 return UseExistingRepository(self.find_repository())
322
323+ def branch_names(self):
324+ from .refs import ref_to_branch_name
325+ ret = []
326+ for ref in self.get_refs_container().keys():
327+ try:
328+ branch_name = ref_to_branch_name(ref)
329+ except UnicodeDecodeError:
330+ trace.warning("Ignoring branch %r with unicode error ref", ref)
331+ continue
332+ except ValueError:
333+ continue
334+ ret.append(branch_name)
335+ return ret
336+
337 def get_branches(self):
338 from .refs import ref_to_branch_name
339 ret = {}
340
341=== modified file 'breezy/git/tests/test_blackbox.py'
342--- breezy/git/tests/test_blackbox.py 2020-03-21 20:56:33 +0000
343+++ breezy/git/tests/test_blackbox.py 2020-03-22 21:30:11 +0000
344@@ -349,7 +349,7 @@
345 self.run_bzr(["git-import", "--colocated", "a", "b"])
346 self.assertEqual(set([".bzr"]), set(os.listdir("b")))
347 self.assertEqual(set(["abranch", "bbranch"]),
348- set(ControlDir.open("b").get_branches().keys()))
349+ set(ControlDir.open("b").branch_names()))
350
351 def test_git_import_incremental(self):
352 r = GitRepo.init("a", mkdir=True)
353@@ -361,7 +361,7 @@
354 self.run_bzr(["git-import", "--colocated", "a", "b"])
355 self.assertEqual(set([".bzr"]), set(os.listdir("b")))
356 b = ControlDir.open("b")
357- self.assertEqual(["abranch"], list(b.get_branches().keys()))
358+ self.assertEqual(["abranch"], b.branch_names())
359
360 def test_git_import_tags(self):
361 r = GitRepo.init("a", mkdir=True)
362@@ -373,7 +373,7 @@
363 self.run_bzr(["git-import", "--colocated", "a", "b"])
364 self.assertEqual(set([".bzr"]), set(os.listdir("b")))
365 b = ControlDir.open("b")
366- self.assertEqual(["abranch"], list(b.get_branches().keys()))
367+ self.assertEqual(["abranch"], b.branch_names())
368 self.assertEqual(["atag"],
369 list(b.open_branch("abranch").tags.get_tag_dict().keys()))
370
371
372=== modified file 'breezy/git/tests/test_remote.py'
373--- breezy/git/tests/test_remote.py 2020-02-08 18:51:59 +0000
374+++ breezy/git/tests/test_remote.py 2020-03-22 21:30:11 +0000
375@@ -567,6 +567,8 @@
376 self.assertEqual(
377 {'': 'master', 'blah': 'blah', 'master': 'master'},
378 {n: b.name for (n, b) in remote.get_branches().items()})
379+ self.assertEqual(
380+ set(['', 'blah', 'master']), set(remote.branch_names()))
381
382 def test_remove_tag(self):
383 c1 = self.remote_real.do_commit(
384
385=== modified file 'breezy/info.py'
386--- breezy/info.py 2019-11-19 18:10:28 +0000
387+++ breezy/info.py 2020-03-22 21:30:11 +0000
388@@ -457,7 +457,7 @@
389 extra = []
390 if repository.make_working_trees():
391 extra.append('trees')
392- if len(control.get_branches()) > 0:
393+ if len(control.branch_names()) > 0:
394 extra.append('colocated branches')
395 if extra:
396 phrase += ' with ' + " and ".join(extra)
397
398=== modified file 'breezy/plugins/weave_fmt/bzrdir.py'
399--- breezy/plugins/weave_fmt/bzrdir.py 2020-02-18 03:16:21 +0000
400+++ breezy/plugins/weave_fmt/bzrdir.py 2020-03-22 21:30:11 +0000
401@@ -842,7 +842,7 @@
402
403 def get_branch_transport(self, branch_format, name=None):
404 """See BzrDir.get_branch_transport()."""
405- if name is not None:
406+ if name:
407 raise errors.NoColocatedBranchSupport(self)
408 if branch_format is None:
409 return self.transport
410
411=== modified file 'breezy/tests/blackbox/__init__.py'
412--- breezy/tests/blackbox/__init__.py 2019-06-15 17:06:40 +0000
413+++ breezy/tests/blackbox/__init__.py 2020-03-22 21:30:11 +0000
414@@ -51,6 +51,7 @@
415 'test_check',
416 'test_checkout',
417 'test_clean_tree',
418+ 'test_clone',
419 'test_command_encoding',
420 'test_commit',
421 'test_config',
422
423=== added file 'breezy/tests/blackbox/test_clone.py'
424--- breezy/tests/blackbox/test_clone.py 1970-01-01 00:00:00 +0000
425+++ breezy/tests/blackbox/test_clone.py 2020-03-22 21:30:11 +0000
426@@ -0,0 +1,70 @@
427+# Copyright (C) 2006-2012, 2016 Canonical Ltd
428+#
429+# This program is free software; you can redistribute it and/or modify
430+# it under the terms of the GNU General Public License as published by
431+# the Free Software Foundation; either version 2 of the License, or
432+# (at your option) any later version.
433+#
434+# This program is distributed in the hope that it will be useful,
435+# but WITHOUT ANY WARRANTY; without even the implied warranty of
436+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
437+# GNU General Public License for more details.
438+#
439+# You should have received a copy of the GNU General Public License
440+# along with this program; if not, write to the Free Software
441+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
442+
443+
444+"""Black-box tests for brz branch."""
445+
446+import os
447+
448+from breezy import (
449+ branch,
450+ controldir,
451+ tests,
452+ )
453+from breezy.urlutils import local_path_to_url
454+
455+
456+class TestClone(tests.TestCaseWithTransport):
457+
458+ def example_dir(self, path='.', format=None):
459+ tree = self.make_branch_and_tree(path, format=format)
460+ self.build_tree_contents([(path + '/hello', b'foo')])
461+ tree.add('hello')
462+ tree.commit(message='setup')
463+ self.build_tree_contents([(path + '/goodbye', b'baz')])
464+ tree.add('goodbye')
465+ tree.commit(message='setup')
466+ return tree
467+
468+ def test_clone(self):
469+ """Branch from one branch to another."""
470+ self.example_dir('a')
471+ self.run_bzr('clone a b')
472+ b = branch.Branch.open('b')
473+ self.run_bzr('clone a c -r 1')
474+ # previously was erroneously created by branching
475+ self.assertFalse(b._transport.has('branch-name'))
476+ b.controldir.open_workingtree().commit(message='foo', allow_pointless=True)
477+
478+ def test_clone_no_to_location(self):
479+ """The to_location is derived from the source branch name."""
480+ os.mkdir("something")
481+ a = self.example_dir('something/a').branch
482+ self.run_bzr('clone something/a')
483+ b = branch.Branch.open('a')
484+ self.assertEqual(b.last_revision_info(), a.last_revision_info())
485+
486+ def test_from_colocated(self):
487+ """Branch from a colocated branch into a regular branch."""
488+ os.mkdir('b')
489+ tree = self.example_dir('b/a')
490+ tree.controldir.create_branch(name='somecolo')
491+ out, err = self.run_bzr('clone %s' % local_path_to_url('b/a'))
492+ self.assertEqual('', out)
493+ self.assertEqual('Created new control directory.\n', err)
494+ self.assertPathExists('a')
495+ target = controldir.ControlDir.open('a')
496+ self.assertEqual(['', 'somecolo'], target.branch_names())
497
498=== modified file 'breezy/tests/blackbox/test_switch.py'
499--- breezy/tests/blackbox/test_switch.py 2019-09-01 15:33:59 +0000
500+++ breezy/tests/blackbox/test_switch.py 2020-03-22 21:30:11 +0000
501@@ -207,7 +207,7 @@
502 self.run_bzr(['switch', '-b', 'anotherbranch'])
503 self.assertEqual(
504 {'', 'anotherbranch'},
505- set(tree.branch.controldir.get_branches().keys()))
506+ set(tree.branch.controldir.branch_names()))
507
508 def test_switch_into_unrelated_colocated(self):
509 # Create a new colocated branch from an existing non-colocated branch.
510
511=== modified file 'breezy/tests/per_bzrdir/test_bzrdir.py'
512--- breezy/tests/per_bzrdir/test_bzrdir.py 2018-11-11 04:08:32 +0000
513+++ breezy/tests/per_bzrdir/test_bzrdir.py 2020-03-22 21:30:11 +0000
514@@ -688,4 +688,4 @@
515 target_branch = repo.controldir.create_branch(name='foo')
516 repo.controldir.set_branch_reference(target_branch)
517 self.assertEqual({"", 'foo'},
518- set(repo.controldir.get_branches().keys()))
519+ set(repo.controldir.branch_names()))
520
521=== modified file 'breezy/tests/per_controldir/test_controldir.py'
522--- breezy/tests/per_controldir/test_controldir.py 2019-05-29 03:22:34 +0000
523+++ breezy/tests/per_controldir/test_controldir.py 2020-03-22 21:30:11 +0000
524@@ -199,7 +199,7 @@
525 "control directories without working tree")
526 self.assertRaises(errors.NoWorkingTree, dir.open_workingtree)
527
528- def test_clone_bzrdir_repository_under_shared(self):
529+ def test_clone_controldir_repository_under_shared(self):
530 tree = self.make_branch_and_tree('commit_tree')
531 self.build_tree(
532 ['foo'], transport=tree.controldir.transport.clone('..'))
533@@ -221,7 +221,7 @@
534 self.assertNotEqual(dir.transport.base, target.transport.base)
535 self.assertRaises(errors.NoRepositoryPresent, target.open_repository)
536
537- def test_clone_bzrdir_repository_branch_both_under_shared(self):
538+ def test_clone_controldir_repository_branch_both_under_shared(self):
539 # Create a shared repository
540 try:
541 shared_repo = self.make_repository('shared', shared=True)
542@@ -249,7 +249,7 @@
543 dir.create_branch()
544 # Clone 'source' to 'target', also inside the shared repository.
545 target = dir.clone(self.get_url('shared/target'))
546- # 'source', 'target', and the shared repo all have distinct bzrdirs.
547+ # 'source', 'target', and the shared repo all have distinct controldirs.
548 self.assertNotEqual(dir.transport.base, target.transport.base)
549 self.assertNotEqual(
550 dir.transport.base, shared_repo.controldir.transport.base)
551@@ -258,7 +258,7 @@
552 # 'commit_tree' branch.
553 self.assertTrue(shared_repo.has_revision(rev1))
554
555- def test_clone_bzrdir_repository_branch_only_source_under_shared(self):
556+ def test_clone_controldir_repository_branch_only_source_under_shared(self):
557 try:
558 shared_repo = self.make_repository('shared', shared=True)
559 except errors.IncompatibleFormat:
560@@ -291,7 +291,7 @@
561 self.assertFalse(branch.repository.make_working_trees())
562 self.assertTrue(branch.repository.is_shared())
563
564- def test_clone_bzrdir_repository_revision(self):
565+ def test_clone_controldir_repository_revision(self):
566 # test for revision limiting, [smoke test, not corner case checks].
567 # make a repository with some revisions,
568 # and clone it with a revision limit.
569@@ -310,7 +310,7 @@
570 dir.clone(self.get_url('target'), revision_id=rev2)
571 raise TestSkipped('revision limiting not strict yet')
572
573- def test_clone_bzrdir_branch_and_repo_fixed_user_id(self):
574+ def test_clone_controldir_branch_and_repo_fixed_user_id(self):
575 # Bug #430868 is about an email containing '.sig'
576 self.overrideEnv('BRZ_EMAIL', 'murphy@host.sighup.org')
577 tree = self.make_branch_and_tree('commit_tree')
578@@ -330,7 +330,7 @@
579 tree_repo.get_signature_text(rev1),
580 target.repository.get_signature_text(rev1))
581
582- def test_clone_bzrdir_branch_and_repo_into_shared_repo(self):
583+ def test_clone_controldir_branch_and_repo_into_shared_repo(self):
584 # by default cloning into a shared repo uses the shared repo.
585 tree = self.make_branch_and_tree('commit_tree')
586 self.build_tree(['commit_tree/foo'])
587@@ -354,7 +354,7 @@
588 self.assertEqual(source.last_revision(),
589 target.open_branch().last_revision())
590
591- def test_clone_bzrdir_branch_revision(self):
592+ def test_clone_controldir_branch_revision(self):
593 # test for revision limiting, [smoke test, not corner case checks].
594 # make a branch with some revisions,
595 # and clone it with a revision limit.
596@@ -371,6 +371,23 @@
597 target = dir.clone(self.get_url('target'), revision_id=rev1)
598 self.assertEqual(rev1, target.open_branch().last_revision())
599
600+ def test_clone_controldir_with_colocated(self):
601+ if not self.bzrdir_format.colocated_branches:
602+ raise TestNotApplicable(
603+ 'format does not supported colocated branches')
604+ tree = self.make_branch_and_tree('commit_tree')
605+ self.build_tree(['commit_tree/foo'])
606+ tree.add('foo')
607+ rev1 = tree.commit('revision 1')
608+ rev2 = tree.commit('revision 2', allow_pointless=True)
609+ rev3 = tree.commit('revision 2', allow_pointless=True)
610+ dir = tree.branch.controldir
611+ colo = dir.create_branch(name='colo')
612+ colo.pull(tree.branch, stop_revision=rev1)
613+ target = dir.clone(self.get_url('target'), revision_id=rev2)
614+ self.assertEqual(rev2, target.open_branch().last_revision())
615+ self.assertEqual(rev1, target.open_branch(name='colo').last_revision())
616+
617 def test_clone_on_transport_preserves_repo_format(self):
618 if self.bzrdir_format == controldir.format_registry.make_controldir('default'):
619 format = 'knit'
620@@ -381,8 +398,8 @@
621 a_dir = breezy.branch.Branch.open_from_transport(
622 self.get_transport('source')).controldir
623 target_transport = self.get_transport('target')
624- target_bzrdir = a_dir.clone_on_transport(target_transport)
625- target_repo = target_bzrdir.open_repository()
626+ target_controldir = a_dir.clone_on_transport(target_transport)
627+ target_repo = target_controldir.open_repository()
628 source_branch = breezy.branch.Branch.open(
629 self.get_vfs_only_url('source'))
630 if isinstance(target_repo, RemoteRepository):
631@@ -390,7 +407,7 @@
632 target_repo = target_repo._real_repository
633 self.assertEqual(target_repo._format, source_branch.repository._format)
634
635- def test_clone_bzrdir_tree_revision(self):
636+ def test_clone_controldir_tree_revision(self):
637 # test for revision limiting, [smoke test, not corner case checks].
638 # make a tree with a revision with a last-revision
639 # and clone it with a revision limit.
640@@ -406,7 +423,7 @@
641 self.skipIfNoWorkingTree(target)
642 self.assertEqual([rev1], target.open_workingtree().get_parent_ids())
643
644- def test_clone_bzrdir_into_notrees_repo(self):
645+ def test_clone_controldir_into_notrees_repo(self):
646 """Cloning into a no-trees repo should not create a working tree"""
647 tree = self.make_branch_and_tree('source')
648 self.build_tree(['source/foo'])
649@@ -514,12 +531,12 @@
650 """get_branch_reference should not mask NotBranchErrors."""
651 dir = self.make_controldir('source')
652 if dir.has_branch():
653- # this format does not support branchless bzrdirs.
654+ # this format does not support branchless controldirs.
655 raise TestNotApplicable("format does not support "
656 "branchless control directories")
657 self.assertRaises(errors.NotBranchError, dir.get_branch_reference)
658
659- def test_sprout_bzrdir_empty(self):
660+ def test_sprout_controldir_empty(self):
661 dir = self.make_controldir('source')
662 target = dir.sprout(self.get_url('target'))
663 self.assertNotEqual(dir.control_transport.base,
664@@ -529,7 +546,7 @@
665 target.open_branch()
666 self.openWorkingTreeIfLocal(target)
667
668- def test_sprout_bzrdir_empty_under_shared_repo(self):
669+ def test_sprout_controldir_empty_under_shared_repo(self):
670 # sprouting an empty dir into a repo uses the repo
671 dir = self.make_controldir('source')
672 try:
673@@ -543,13 +560,13 @@
674 try:
675 target.open_workingtree()
676 except errors.NoWorkingTree:
677- # Some bzrdirs can never have working trees.
678+ # Some controldirs can never have working trees.
679 repo = target.find_repository()
680 self.assertFalse(repo.controldir._format.supports_workingtrees)
681
682- def test_sprout_bzrdir_empty_under_shared_repo_force_new(self):
683+ def test_sprout_controldir_empty_under_shared_repo_force_new(self):
684 # the force_new_repo parameter should force use of a new repo in an empty
685- # bzrdir's sprout logic
686+ # controldir's sprout logic
687 dir = self.make_controldir('source')
688 try:
689 self.make_repository('target', shared=True)
690@@ -561,7 +578,7 @@
691 target.open_branch()
692 self.openWorkingTreeIfLocal(target)
693
694- def test_sprout_bzrdir_with_repository_to_shared(self):
695+ def test_sprout_controldir_with_repository_to_shared(self):
696 tree = self.make_branch_and_tree('commit_tree')
697 self.build_tree(['commit_tree/foo'])
698 tree.add('foo')
699@@ -583,7 +600,7 @@
700 target.user_transport.base)
701 self.assertTrue(shared_repo.has_revision(rev1))
702
703- def test_sprout_bzrdir_repository_branch_both_under_shared(self):
704+ def test_sprout_controldir_repository_branch_both_under_shared(self):
705 try:
706 shared_repo = self.make_repository('shared', shared=True)
707 except errors.IncompatibleFormat:
708@@ -609,7 +626,7 @@
709 shared_repo.controldir.transport.base)
710 self.assertTrue(shared_repo.has_revision(rev1))
711
712- def test_sprout_bzrdir_repository_branch_only_source_under_shared(self):
713+ def test_sprout_controldir_repository_branch_only_source_under_shared(self):
714 try:
715 shared_repo = self.make_repository('shared', shared=True)
716 except errors.IncompatibleFormat:
717@@ -639,7 +656,7 @@
718 dir.transport.base,
719 shared_repo.controldir.transport.base)
720 branch = target.open_branch()
721- # The sprouted bzrdir has a branch, so only revisions referenced by
722+ # The sprouted controldir has a branch, so only revisions referenced by
723 # that branch are copied, rather than the whole repository. It's an
724 # empty branch, so none are copied.
725 self.assertEqual([], branch.repository.all_revision_ids())
726@@ -647,7 +664,7 @@
727 self.assertTrue(branch.repository.make_working_trees())
728 self.assertFalse(branch.repository.is_shared())
729
730- def test_sprout_bzrdir_repository_under_shared_force_new_repo(self):
731+ def test_sprout_controldir_repository_under_shared_force_new_repo(self):
732 tree = self.make_branch_and_tree('commit_tree')
733 self.build_tree(['commit_tree/foo'])
734 tree.add('foo')
735@@ -670,7 +687,7 @@
736 target.control_transport.base)
737 self.assertFalse(shared_repo.has_revision(rev1))
738
739- def test_sprout_bzrdir_repository_revision(self):
740+ def test_sprout_controldir_repository_revision(self):
741 # test for revision limiting, [smoke test, not corner case checks].
742 # make a repository with some revisions,
743 # and sprout it with a revision limit.
744@@ -689,7 +706,7 @@
745 self.sproutOrSkip(dir, self.get_url('target'), revision_id=rev2)
746 raise TestSkipped('revision limiting not strict yet')
747
748- def test_sprout_bzrdir_branch_and_repo_shared(self):
749+ def test_sprout_controldir_branch_and_repo_shared(self):
750 # sprouting a branch with a repo into a shared repo uses the shared
751 # repo
752 tree = self.make_branch_and_tree('commit_tree')
753@@ -708,7 +725,7 @@
754 dir.sprout(self.get_url('target/child'))
755 self.assertTrue(shared_repo.has_revision(rev1))
756
757- def test_sprout_bzrdir_branch_and_repo_shared_force_new_repo(self):
758+ def test_sprout_controldir_branch_and_repo_shared_force_new_repo(self):
759 # sprouting a branch with a repo into a shared repo uses the shared
760 # repo
761 tree = self.make_branch_and_tree('commit_tree')
762@@ -729,7 +746,7 @@
763 dir.control_transport.base, target.control_transport.base)
764 self.assertFalse(shared_repo.has_revision(rev1))
765
766- def test_sprout_bzrdir_branch_reference(self):
767+ def test_sprout_controldir_branch_reference(self):
768 # sprouting should create a repository if needed and a sprouted branch.
769 referenced_branch = self.make_branch('referenced')
770 dir = self.make_controldir('source')
771@@ -747,7 +764,7 @@
772 # place
773 target.open_repository()
774
775- def test_sprout_bzrdir_branch_reference_shared(self):
776+ def test_sprout_controldir_branch_reference_shared(self):
777 # sprouting should create a repository if needed and a sprouted branch.
778 referenced_tree = self.make_branch_and_tree('referenced')
779 rev1 = referenced_tree.commit('1', allow_pointless=True)
780@@ -773,7 +790,7 @@
781 # and we want revision '1' in the shared repo
782 self.assertTrue(shared_repo.has_revision(rev1))
783
784- def test_sprout_bzrdir_branch_reference_shared_force_new_repo(self):
785+ def test_sprout_controldir_branch_reference_shared_force_new_repo(self):
786 # sprouting should create a repository if needed and a sprouted branch.
787 referenced_tree = self.make_branch_and_tree('referenced')
788 rev1 = referenced_tree.commit('1', allow_pointless=True)
789@@ -799,7 +816,7 @@
790 # but not the shared one
791 self.assertFalse(shared_repo.has_revision(rev1))
792
793- def test_sprout_bzrdir_branch_revision(self):
794+ def test_sprout_controldir_branch_revision(self):
795 # test for revision limiting, [smoke test, not corner case checks].
796 # make a repository with some revisions,
797 # and sprout it with a revision limit.
798@@ -816,7 +833,7 @@
799 target = dir.sprout(self.get_url('target'), revision_id=rev1)
800 self.assertEqual(rev1, target.open_branch().last_revision())
801
802- def test_sprout_bzrdir_branch_with_tags(self):
803+ def test_sprout_controldir_branch_with_tags(self):
804 # when sprouting a branch all revisions named in the tags are copied
805 # too.
806 builder = self.make_branch_builder('source')
807@@ -835,7 +852,7 @@
808 self.assertEqual(rev2, new_branch.tags.lookup_tag('tag-a'))
809 new_branch.repository.get_revision(rev2)
810
811- def test_sprout_bzrdir_branch_with_absent_tag(self):
812+ def test_sprout_controldir_branch_with_absent_tag(self):
813 # tags referencing absent revisions are copied (and those absent
814 # revisions do not prevent the sprout.)
815 builder = self.make_branch_builder('source')
816@@ -855,7 +872,7 @@
817 new_branch = target.open_branch()
818 self.assertEqual(b'missing-rev', new_branch.tags.lookup_tag('tag-a'))
819
820- def test_sprout_bzrdir_passing_source_branch_with_absent_tag(self):
821+ def test_sprout_controldir_passing_source_branch_with_absent_tag(self):
822 # tags referencing absent revisions are copied (and those absent
823 # revisions do not prevent the sprout.)
824 builder = self.make_branch_builder('source')
825@@ -875,9 +892,9 @@
826 new_branch = target.open_branch()
827 self.assertEqual(b'missing-rev', new_branch.tags.lookup_tag('tag-a'))
828
829- def test_sprout_bzrdir_passing_rev_not_source_branch_copies_tags(self):
830+ def test_sprout_controldir_passing_rev_not_source_branch_copies_tags(self):
831 # dir.sprout(..., revision_id=b'rev1') copies rev1, and all the tags of
832- # the branch at that bzrdir, the ancestry of all of those, but no other
833+ # the branch at that controldir, the ancestry of all of those, but no other
834 # revs (not even the tip of the source branch).
835 builder = self.make_branch_builder('source')
836 base_rev = builder.build_commit(message="Base")
837@@ -927,7 +944,7 @@
838 sorted([base_rev, rev_b1, rev_b2, rev_c1, rev_c2]),
839 sorted(new_branch.repository.all_revision_ids()))
840
841- def test_sprout_bzrdir_tree_branch_reference(self):
842+ def test_sprout_controldir_tree_branch_reference(self):
843 # sprouting should create a repository if needed and a sprouted branch.
844 # the tree state should not be copied.
845 referenced_branch = self.make_branch('referencced')
846@@ -952,7 +969,7 @@
847 result_tree = target.open_workingtree()
848 self.assertFalse(result_tree.has_filename('subdir'))
849
850- def test_sprout_bzrdir_tree_branch_reference_revision(self):
851+ def test_sprout_controldir_tree_branch_reference_revision(self):
852 # sprouting should create a repository if needed and a sprouted branch.
853 # the tree state should not be copied but the revision changed,
854 # and the likewise the new branch should be truncated too
855@@ -982,7 +999,7 @@
856 self.assertEqual([rev1], target.open_workingtree().get_parent_ids())
857 self.assertEqual(rev1, target.open_branch().last_revision())
858
859- def test_sprout_bzrdir_tree_revision(self):
860+ def test_sprout_controldir_tree_revision(self):
861 # test for revision limiting, [smoke test, not corner case checks].
862 # make a tree with a revision with a last-revision
863 # and sprout it with a revision limit.
864@@ -1034,13 +1051,13 @@
865 rev3 = builder.build_commit(message='Rev 3.')
866 builder.finish_series()
867 stack_on = builder.get_branch()
868- # Make a bzrdir with a default stacking policy to stack on that branch.
869+ # Make a controldir with a default stacking policy to stack on that branch.
870 config = self.make_controldir('policy-dir').get_config()
871 try:
872 config.set_default_stack_on(self.get_url('stack-on'))
873 except errors.BzrError:
874 raise TestNotApplicable('Only relevant for stackable formats.')
875- # Sprout the stacked-on branch into the bzrdir.
876+ # Sprout the stacked-on branch into the controldir.
877 sprouted = stack_on.controldir.sprout(
878 self.get_url('policy-dir/sprouted'), revision_id=rev3)
879 # Not all revisions are copied into the sprouted repository.
880@@ -1288,6 +1305,11 @@
881 repo.controldir.create_branch()
882 self.assertEqual([""], list(repo.controldir.get_branches()))
883
884+ def test_branch_names(self):
885+ repo = self.make_repository('branch-1')
886+ repo.controldir.create_branch()
887+ self.assertEqual([""], repo.controldir.branch_names())
888+
889 def test_create_repository(self):
890 # a bzrdir can construct a repository for itself.
891 if not self.bzrdir_format.is_initializable():
892
893=== modified file 'breezy/tests/per_controldir_colo/test_supported.py'
894--- breezy/tests/per_controldir_colo/test_supported.py 2019-04-13 21:37:04 +0000
895+++ breezy/tests/per_controldir_colo/test_supported.py 2020-03-22 21:30:11 +0000
896@@ -184,6 +184,11 @@
897 self.assertEqual(target_branch.base,
898 repo.controldir.get_branches()['foo'].base)
899
900+ def test_branch_names(self):
901+ repo = self.make_repository('branch-1')
902+ target_branch = self.create_branch(repo.controldir, name='foo')
903+ self.assertIn('foo', repo.controldir.branch_names())
904+
905 def test_branch_name_with_slash(self):
906 repo = self.make_repository('branch-1')
907 try:
908
909=== modified file 'breezy/tests/test_smart.py'
910--- breezy/tests/test_smart.py 2020-01-18 02:42:17 +0000
911+++ breezy/tests/test_smart.py 2020-03-22 21:30:11 +0000
912@@ -485,6 +485,19 @@
913 (b"success", ), local_result)
914 self.assertEqual(expected, request.execute(b''))
915
916+ def test_ref(self):
917+ backing = self.get_transport()
918+ dir = self.make_controldir('foo')
919+ b = self.make_branch('bar')
920+ dir.set_branch_reference(b)
921+ request_class = smart_dir.SmartServerBzrDirRequestGetBranches
922+ request = request_class(backing)
923+ local_result = bencode.bencode(
924+ {b"": (b"ref", b'../bar/')})
925+ expected = smart_req.SuccessfulSmartServerResponse(
926+ (b"success", ), local_result)
927+ self.assertEqual(expected, request.execute(b'foo'))
928+
929 def test_empty(self):
930 backing = self.get_transport()
931 self.make_controldir('.')
932
933=== modified file 'doc/en/release-notes/brz-3.1.txt'
934--- doc/en/release-notes/brz-3.1.txt 2020-03-21 20:56:33 +0000
935+++ doc/en/release-notes/brz-3.1.txt 2020-03-22 21:30:11 +0000
936@@ -73,6 +73,13 @@
937 * When pushing to Git repositories, symrefs are now followed.
938 (Jelmer Vernooij, #1800393)
939
940+ * New ``brz clone`` command, which clones everything under
941+ a control directory. I.e. all colocated branches, like
942+ ``git clone``. (Jelmer Vernooij, #831939)
943+
944+ * ``brz sprout`` is now an alias for ``brz branch``.
945+ (Jelmer Vernooij)
946+
947 Improvements
948 ************
949

Subscribers

People subscribed via source and target branches