Merge lp:~jelmer/brz/trunk-merge-3.1 into lp:brz

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/trunk-merge-3.1
Merge into: lp:brz
Diff against target: 1947 lines (+738/-158)
45 files modified
.gitignore (+11/-0)
breezy/__main__.py (+79/-0)
breezy/branch.py (+3/-3)
breezy/builtins.py (+37/-2)
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/config.py (+3/-3)
breezy/controldir.py (+14/-2)
breezy/git/__init__.py (+1/-1)
breezy/git/branch.py (+1/-1)
breezy/git/dir.py (+25/-9)
breezy/git/mapping.py (+11/-4)
breezy/git/object_store.py (+22/-7)
breezy/git/remote.py (+19/-5)
breezy/git/server.py (+10/-3)
breezy/git/tests/test_blackbox.py (+3/-3)
breezy/git/tests/test_remote.py (+2/-0)
breezy/git/transportgit.py (+54/-4)
breezy/git/tree.py (+3/-1)
breezy/git/workingtree.py (+6/-2)
breezy/info.py (+1/-1)
breezy/plugins/darcs/__init__.py (+5/-3)
breezy/plugins/fastimport/tests/test_head_tracking.py (+1/-1)
breezy/plugins/propose/__init__.py (+8/-0)
breezy/plugins/propose/gitlabs.py (+28/-8)
breezy/plugins/propose/tests/__init__.py (+26/-0)
breezy/plugins/propose/tests/test_gitlab.py (+52/-0)
breezy/plugins/weave_fmt/bzrdir.py (+1/-1)
breezy/tests/blackbox/__init__.py (+1/-0)
breezy/tests/blackbox/test_branch.py (+11/-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/per_workingtree/test_changes_from.py (+18/-0)
breezy/tests/test_bzrdir.py (+14/-0)
breezy/tests/test_config.py (+16/-1)
breezy/tests/test_smart.py (+13/-0)
breezy/transport/http/__init__.py (+5/-1)
brz (+2/-24)
doc/en/release-notes/brz-3.1.txt (+24/-0)
doc/en/user-guide/setting_up_email.txt (+4/-2)
To merge this branch: bzr merge lp:~jelmer/brz/trunk-merge-3.1
Reviewer Review Type Date Requested Status
Jelmer Vernooij Approve
Review via email: mp+383481@code.launchpad.net

Commit message

Merge the 3.1 branch.

Description of the change

Merge the 3.1 branch.

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 :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.gitignore'
2--- .gitignore 2019-10-13 14:45:08 +0000
3+++ .gitignore 2020-05-06 02:56:30 +0000
4@@ -1,1 +1,12 @@
5 __pycache__
6+*.pyc
7+build/
8+*_pyx.so
9+*_pyx.c
10+*_pyx.h
11+*_pyx_api.h
12+*_pyx.cpython-*.so
13+*_c.cpython-*.so
14+*_c.so
15+*_pyx.cpython-*.c
16+*~
17
18=== added file 'breezy/__main__.py'
19--- breezy/__main__.py 1970-01-01 00:00:00 +0000
20+++ breezy/__main__.py 2020-05-06 02:56:30 +0000
21@@ -0,0 +1,79 @@
22+#! /usr/bin/env python3
23+
24+# Copyright (C) 2005-2013, 2016, 2017 Canonical Ltd
25+# Copyright (C) 2018-2020 Breezy Developers
26+#
27+# This program is free software; you can redistribute it and/or modify
28+# it under the terms of the GNU General Public License as published by
29+# the Free Software Foundation; either version 2 of the License, or
30+# (at your option) any later version.
31+#
32+# This program is distributed in the hope that it will be useful,
33+# but WITHOUT ANY WARRANTY; without even the implied warranty of
34+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35+# GNU General Public License for more details.
36+#
37+# You should have received a copy of the GNU General Public License
38+# along with this program; if not, write to the Free Software
39+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
40+
41+from __future__ import absolute_import
42+
43+"""Breezy -- a free distributed version-control tool"""
44+
45+import os
46+import sys
47+import warnings
48+
49+
50+profiling = False
51+if '--profile-imports' in sys.argv:
52+ import profile_imports
53+ profile_imports.install()
54+ profiling = True
55+
56+
57+if os.name == "posix":
58+ import locale
59+ try:
60+ locale.setlocale(locale.LC_ALL, '')
61+ except locale.Error as e:
62+ sys.stderr.write(
63+ 'brz: warning: %s\n'
64+ ' bzr could not set the application locale.\n'
65+ ' Although this should be no problem for bzr itself, it might\n'
66+ ' cause problems with some plugins. To investigate the issue,\n'
67+ ' look at the output of the locale(1p) tool.\n' % e)
68+ # Use better default than ascii with posix filesystems that deal in bytes
69+ # natively even when the C locale or no locale at all is given. Note that
70+ # we need an immortal string for the hack, hence the lack of a hyphen.
71+ sys._brz_default_fs_enc = "utf8"
72+
73+
74+def main():
75+ import breezy.breakin
76+ breezy.breakin.hook_debugger_to_signal()
77+
78+ import breezy.commands
79+ import breezy.trace
80+
81+ with breezy.initialize():
82+ exit_val = breezy.commands.main()
83+ if profiling:
84+ profile_imports.log_stack_info(sys.stderr)
85+
86+ # By this point we really have completed everything we want to do, and
87+ # there's no point doing any additional cleanup. Abruptly exiting here
88+ # stops any background threads getting into trouble as code is unloaded,
89+ # and it may also be slightly faster, through avoiding gc of objects that
90+ # are just about to be discarded anyhow. This does mean that atexit hooks
91+ # won't run but we don't use them. Also file buffers won't be flushed,
92+ # but our policy is to always close files from a finally block. -- mbp 20070215
93+ exitfunc = getattr(sys, "exitfunc", None)
94+ if exitfunc is not None:
95+ exitfunc()
96+ os._exit(exit_val)
97+
98+
99+if __name__ == '__main__':
100+ main()
101
102=== modified file 'breezy/branch.py'
103--- breezy/branch.py 2020-02-21 03:58:42 +0000
104+++ breezy/branch.py 2020-05-06 02:56:30 +0000
105@@ -1188,8 +1188,8 @@
106 if revno < 1 or revno > self.revno():
107 raise errors.InvalidRevisionNumber(revno)
108
109- def clone(self, to_controldir, revision_id=None, repository_policy=None,
110- tag_selector=None):
111+ def clone(self, to_controldir, revision_id=None, name=None,
112+ repository_policy=None, tag_selector=None):
113 """Clone this branch into to_controldir preserving all semantic values.
114
115 Most API users will want 'create_clone_on_transport', which creates a
116@@ -1198,7 +1198,7 @@
117 revision_id: if not None, the revision history in the new branch will
118 be truncated to end with revision_id.
119 """
120- result = to_controldir.create_branch()
121+ result = to_controldir.create_branch(name=name)
122 with self.lock_read(), result.lock_write():
123 if repository_policy is not None:
124 repository_policy.configure_branch(result)
125
126=== modified file 'breezy/builtins.py'
127--- breezy/builtins.py 2020-02-18 01:57:45 +0000
128+++ breezy/builtins.py 2020-05-06 02:56:30 +0000
129@@ -1434,6 +1434,7 @@
130 parameter, as in "branch foo/bar -r 5".
131 """
132
133+ aliase = ['sprout']
134 _see_also = ['checkout']
135 takes_args = ['from_location', 'to_location?']
136 takes_options = ['revision',
137@@ -1461,15 +1462,17 @@
138 help="Bind new branch to from location."),
139 Option('no-recurse-nested',
140 help='Do not recursively check out nested trees.'),
141+ Option('colocated-branch', short_name='b',
142+ type=str, help='Name of colocated branch to sprout.'),
143 ]
144
145 def run(self, from_location, to_location=None, revision=None,
146 hardlink=False, stacked=False, standalone=False, no_tree=False,
147 use_existing_dir=False, switch=False, bind=False,
148- files_from=None, no_recurse_nested=False):
149+ files_from=None, no_recurse_nested=False, colocated_branch=None):
150 from breezy import switch as _mod_switch
151 accelerator_tree, br_from = controldir.ControlDir.open_tree_or_branch(
152- from_location)
153+ from_location, name=colocated_branch)
154 if no_recurse_nested:
155 recurse = 'none'
156 else:
157@@ -1695,6 +1698,38 @@
158 accelerator_tree, hardlink)
159
160
161+class cmd_clone(Command):
162+ __doc__ = """Clone a control directory.
163+ """
164+
165+ takes_args = ['from_location', 'to_location?']
166+ takes_options = ['revision',
167+ Option('no-recurse-nested',
168+ help='Do not recursively check out nested trees.'),
169+ ]
170+
171+ def run(self, from_location, to_location=None, revision=None, no_recurse_nested=False):
172+ accelerator_tree, br_from = controldir.ControlDir.open_tree_or_branch(
173+ from_location)
174+ if no_recurse_nested:
175+ recurse = 'none'
176+ else:
177+ recurse = 'down'
178+ revision = _get_one_revision('branch', revision)
179+ self.enter_context(br_from.lock_read())
180+ if revision is not None:
181+ revision_id = revision.as_revision_id(br_from)
182+ else:
183+ # FIXME - wt.last_revision, fallback to branch, fall back to
184+ # None or perhaps NULL_REVISION to mean copy nothing
185+ # RBC 20060209
186+ revision_id = br_from.last_revision()
187+ if to_location is None:
188+ to_location = urlutils.derive_to_location(from_location)
189+ target_controldir = br_from.controldir.clone(to_location, revision_id=revision_id)
190+ note(gettext('Created new control directory.'))
191+
192+
193 class cmd_renames(Command):
194 __doc__ = """Show list of renamed files.
195 """
196
197=== modified file 'breezy/bzr/branch.py'
198--- breezy/bzr/branch.py 2020-02-21 03:58:42 +0000
199+++ breezy/bzr/branch.py 2020-05-06 02:56:30 +0000
200@@ -1023,10 +1023,10 @@
201
202 def _make_reference_clone_function(format, a_branch):
203 """Create a clone() routine for a branch dynamically."""
204- def clone(to_bzrdir, revision_id=None,
205- repository_policy=None, tag_selector=None):
206+ def clone(to_bzrdir, revision_id=None, repository_policy=None, name=None,
207+ tag_selector=None):
208 """See Branch.clone()."""
209- return format.initialize(to_bzrdir, target_branch=a_branch)
210+ return format.initialize(to_bzrdir, target_branch=a_branch, name=name)
211 # cannot obey revision_id limits when cloning a reference ...
212 # FIXME RBC 20060210 either nuke revision_id for clone, or
213 # emit some sort of warning/error to the caller ?!
214
215=== modified file 'breezy/bzr/bzrdir.py'
216--- breezy/bzr/bzrdir.py 2020-02-21 03:58:42 +0000
217+++ breezy/bzr/bzrdir.py 2020-05-06 02:56:30 +0000
218@@ -172,17 +172,18 @@
219 local_repo = self.find_repository()
220 except errors.NoRepositoryPresent:
221 local_repo = None
222+ local_branches = self.get_branches()
223 try:
224- local_branch = self.open_branch()
225- except errors.NotBranchError:
226- local_branch = None
227+ local_active_branch = local_branches['']
228+ except KeyError:
229+ pass
230 else:
231 # enable fallbacks when branch is not a branch reference
232- if local_branch.repository.has_same_location(local_repo):
233- local_repo = local_branch.repository
234+ if local_active_branch.repository.has_same_location(local_repo):
235+ local_repo = local_active_branch.repository
236 if preserve_stacking:
237 try:
238- stacked_on = local_branch.get_stacked_on_url()
239+ stacked_on = local_active_branch.get_stacked_on_url()
240 except (_mod_branch.UnstackableBranchFormat,
241 errors.UnstackableRepositoryFormat,
242 errors.NotStacked):
243@@ -231,11 +232,11 @@
244 # 1 if there is a branch present
245 # make sure its content is available in the target repository
246 # clone it.
247- if local_branch is not None:
248+ for name, local_branch in local_branches.items():
249 local_branch.clone(
250- result, revision_id=revision_id,
251+ result, revision_id=(None if name != '' else revision_id),
252 repository_policy=repository_policy,
253- tag_selector=tag_selector)
254+ name=name, tag_selector=tag_selector)
255 try:
256 # Cheaper to check if the target is not local, than to try making
257 # the tree and fail.
258@@ -1014,6 +1015,18 @@
259 pass
260 return self.transport.clone('checkout')
261
262+ def branch_names(self):
263+ """See ControlDir.branch_names."""
264+ ret = []
265+ try:
266+ self.get_branch_reference()
267+ except errors.NotBranchError:
268+ pass
269+ else:
270+ ret.append("")
271+ ret.extend(self._read_branch_list())
272+ return ret
273+
274 def get_branches(self):
275 """See ControlDir.get_branches."""
276 ret = {}
277@@ -1312,11 +1325,13 @@
278 remote_dir_format = RemoteBzrDirFormat()
279 remote_dir_format._network_name = self.network_name()
280 self._supply_sub_formats_to(remote_dir_format)
281- return remote_dir_format.initialize_on_transport_ex(transport,
282- use_existing_dir=use_existing_dir, create_prefix=create_prefix,
283- force_new_repo=force_new_repo, stacked_on=stacked_on,
284- stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
285- make_working_trees=make_working_trees, shared_repo=shared_repo)
286+ return remote_dir_format.initialize_on_transport_ex(
287+ transport, use_existing_dir=use_existing_dir,
288+ create_prefix=create_prefix, force_new_repo=force_new_repo,
289+ stacked_on=stacked_on, stack_on_pwd=stack_on_pwd,
290+ repo_format_name=repo_format_name,
291+ make_working_trees=make_working_trees,
292+ shared_repo=shared_repo)
293 # XXX: Refactor the create_prefix/no_create_prefix code into a
294 # common helper function
295 # The destination may not exist - if so make it according to policy.
296
297=== modified file 'breezy/bzr/remote.py'
298--- breezy/bzr/remote.py 2020-02-21 03:58:42 +0000
299+++ breezy/bzr/remote.py 2020-05-06 02:56:30 +0000
300@@ -698,6 +698,23 @@
301 b = self.open_branch(name=name)
302 return b._format
303
304+ def branch_names(self):
305+ path = self._path_for_remote_call(self._client)
306+ try:
307+ response, handler = self._call_expecting_body(
308+ b'BzrDir.get_branches', path)
309+ except errors.UnknownSmartMethod:
310+ self._ensure_real()
311+ return self._real_bzrdir.branch_names()
312+ if response[0] != b"success":
313+ raise errors.UnexpectedSmartServerResponse(response)
314+ body = bencode.bdecode(handler.read_body_bytes())
315+ ret = []
316+ for name, value in body.items():
317+ name = name.decode('utf-8')
318+ ret.append(name)
319+ return ret
320+
321 def get_branches(self, possible_transports=None, ignore_fallbacks=False):
322 path = self._path_for_remote_call(self._client)
323 try:
324@@ -712,9 +729,10 @@
325 ret = {}
326 for name, value in body.items():
327 name = name.decode('utf-8')
328- ret[name] = self._open_branch(name, value[0], value[1],
329- possible_transports=possible_transports,
330- ignore_fallbacks=ignore_fallbacks)
331+ ret[name] = self._open_branch(
332+ name, value[0].decode('ascii'), value[1],
333+ possible_transports=possible_transports,
334+ ignore_fallbacks=ignore_fallbacks)
335 return ret
336
337 def set_branch_reference(self, target_branch, name=None):
338@@ -783,8 +801,9 @@
339 if kind == 'ref':
340 # a branch reference, use the existing BranchReference logic.
341 format = BranchReferenceFormat()
342+ ref_loc = urlutils.join(self.user_url, location_or_format.decode('utf-8'))
343 return format.open(self, name=name, _found=True,
344- location=location_or_format.decode('utf-8'),
345+ location=ref_loc,
346 ignore_fallbacks=ignore_fallbacks,
347 possible_transports=possible_transports)
348 branch_format_name = location_or_format
349
350=== modified file 'breezy/bzr/smart/bzrdir.py'
351--- breezy/bzr/smart/bzrdir.py 2020-02-18 01:57:45 +0000
352+++ breezy/bzr/smart/bzrdir.py 2020-05-06 02:56:30 +0000
353@@ -440,12 +440,19 @@
354 The body is a bencoded dictionary, with values similar to the return
355 value of the open branch request.
356 """
357- branches = self._bzrdir.get_branches()
358+ branch_names = self._bzrdir.branch_names()
359 ret = {}
360- for name, b in branches.items():
361+ for name in branch_names:
362 if name is None:
363 name = b""
364- ret[name.encode('utf-8')] = (b"branch", b._format.network_name())
365+ branch_ref = self._bzrdir.get_branch_reference(name=name)
366+ if branch_ref is not None:
367+ branch_ref = urlutils.relative_url(self._bzrdir.user_url, branch_ref)
368+ value = (b"ref", branch_ref.encode('utf-8'))
369+ else:
370+ b = self._bzrdir.open_branch(name=name, ignore_fallbacks=True)
371+ value = (b"branch", b._format.network_name())
372+ ret[name.encode('utf-8')] = value
373 return SuccessfulSmartServerResponse(
374 (b"success", ), bencode.bencode(ret))
375
376
377=== modified file 'breezy/config.py'
378--- breezy/config.py 2020-02-18 01:57:45 +0000
379+++ breezy/config.py 2020-05-06 02:56:30 +0000
380@@ -530,12 +530,12 @@
381
382 Something similar to 'Martin Pool <mbp@sourcefrog.net>'
383
384- $BRZ_EMAIL can be set to override this, then
385+ $BRZ_EMAIL or $BZR_EMAIL can be set to override this, then
386 the concrete policy type is checked, and finally
387 $EMAIL is examined.
388 If no username can be found, NoWhoami exception is raised.
389 """
390- v = os.environ.get('BRZ_EMAIL')
391+ v = os.environ.get('BRZ_EMAIL') or os.environ.get('BZR_EMAIL')
392 if v:
393 return v
394 v = self._get_user_id()
395@@ -2514,7 +2514,7 @@
396 Option('editor',
397 help='The command called to launch an editor to enter a message.'))
398 option_registry.register(
399- Option('email', override_from_env=['BRZ_EMAIL'],
400+ Option('email', override_from_env=['BRZ_EMAIL', 'BZR_EMAIL'],
401 default=bedding.default_email, help='The users identity'))
402 option_registry.register(
403 Option('gpg_signing_key',
404
405=== modified file 'breezy/controldir.py'
406--- breezy/controldir.py 2020-02-21 03:58:42 +0000
407+++ breezy/controldir.py 2020-05-06 02:56:30 +0000
408@@ -127,6 +127,18 @@
409 """
410 return list(self.get_branches().values())
411
412+ def branch_names(self):
413+ """List all branch names in this control directory.
414+
415+ :return: List of branch names
416+ """
417+ try:
418+ self.get_branch_reference()
419+ except (errors.NotBranchError, errors.NoRepositoryPresent):
420+ return []
421+ else:
422+ return [""]
423+
424 def get_branches(self):
425 """Get all branches in this control directory, as a dictionary.
426
427@@ -794,7 +806,7 @@
428 a_transport = new_t
429
430 @classmethod
431- def open_tree_or_branch(klass, location):
432+ def open_tree_or_branch(klass, location, name=None):
433 """Return the branch and working tree at a location.
434
435 If there is no tree at the location, tree will be None.
436@@ -803,7 +815,7 @@
437 :return: (tree, branch)
438 """
439 controldir = klass.open(location)
440- return controldir._get_tree_branch()
441+ return controldir._get_tree_branch(name=name)
442
443 @classmethod
444 def open_containing_tree_or_branch(klass, location,
445
446=== modified file 'breezy/git/__init__.py'
447--- breezy/git/__init__.py 2020-02-18 01:57:45 +0000
448+++ breezy/git/__init__.py 2020-05-06 02:56:30 +0000
449@@ -147,7 +147,7 @@
450 def is_github_url(url):
451 (scheme, user, password, host, port,
452 path) = urlutils.parse_url(url)
453- return host == "github.com"
454+ return host in ("github.com", "gopkg.in")
455
456
457 class RemoteGitProber(Prober):
458
459=== modified file 'breezy/git/branch.py'
460--- breezy/git/branch.py 2020-02-21 03:58:42 +0000
461+++ breezy/git/branch.py 2020-05-06 02:56:30 +0000
462@@ -1198,7 +1198,7 @@
463 return refs
464 self.target.repository.send_pack(
465 get_changed_refs,
466- self.source.repository._git.object_store.generate_pack_data)
467+ self.source.repository._git.generate_pack_data)
468 return result
469
470
471
472=== modified file 'breezy/git/dir.py'
473--- breezy/git/dir.py 2020-02-21 03:58:42 +0000
474+++ breezy/git/dir.py 2020-05-06 02:56:30 +0000
475@@ -223,6 +223,7 @@
476 """See ControlDir.clone_on_transport."""
477 from ..repository import InterRepository
478 from .mapping import default_mapping
479+ from ..transport.local import LocalTransport
480 if stacked_on is not None:
481 raise _mod_branch.UnstackableBranchFormat(
482 self._format, self.user_url)
483@@ -248,18 +249,19 @@
484 mapping=default_mapping)
485 for name, val in refs.items():
486 target_git_repo.refs[name] = val
487- result_dir = self.__class__(transport, target_git_repo, format)
488+ result_dir = LocalGitDir(transport, target_git_repo, format)
489 if revision_id is not None:
490 result_dir.open_branch().set_last_revision(revision_id)
491- try:
492- # Cheaper to check if the target is not local, than to try making
493- # the tree and fail.
494- result_dir.root_transport.local_abspath('.')
495+ if not no_tree and isinstance(result_dir.root_transport, LocalTransport):
496 if result_dir.open_repository().make_working_trees():
497- self.open_workingtree().clone(
498- result_dir, revision_id=revision_id)
499- except (brz_errors.NoWorkingTree, brz_errors.NotLocalUrl):
500- pass
501+ try:
502+ local_wt = self.open_workingtree()
503+ except brz_errors.NoWorkingTree:
504+ pass
505+ except brz_errors.NotLocalUrl:
506+ result_dir.create_workingtree(revision_id=revision_id)
507+ else:
508+ local_wt.clone(result_dir, revision_id=revision_id)
509
510 return result_dir
511
512@@ -294,6 +296,20 @@
513 """
514 return UseExistingRepository(self.find_repository())
515
516+ def branch_names(self):
517+ from .refs import ref_to_branch_name
518+ ret = []
519+ for ref in self.get_refs_container().keys():
520+ try:
521+ branch_name = ref_to_branch_name(ref)
522+ except UnicodeDecodeError:
523+ trace.warning("Ignoring branch %r with unicode error ref", ref)
524+ continue
525+ except ValueError:
526+ continue
527+ ret.append(branch_name)
528+ return ret
529+
530 def get_branches(self):
531 from .refs import ref_to_branch_name
532 ret = {}
533
534=== modified file 'breezy/git/mapping.py'
535--- breezy/git/mapping.py 2020-02-18 01:57:45 +0000
536+++ breezy/git/mapping.py 2020-05-06 02:56:30 +0000
537@@ -324,7 +324,9 @@
538 commit.author_timezone = commit.commit_timezone
539 if u'git-gpg-signature' in rev.properties:
540 commit.gpgsig = rev.properties[u'git-gpg-signature'].encode(
541- 'ascii')
542+ 'utf-8')
543+ if u'git-gpg-signature-b64' in rev.properties:
544+ commit.gpgsig = base64.b64decode(rev.properties[u'git-gpg-signature-b64'])
545 commit.message = self._encode_commit_message(rev, rev.message,
546 encoding)
547 if not isinstance(commit.message, bytes):
548@@ -337,7 +339,8 @@
549 mapping_properties = set(
550 [u'author', u'author-timezone', u'author-timezone-neg-utc',
551 u'commit-timezone-neg-utc', u'git-implicit-encoding',
552- u'git-gpg-signature', u'git-explicit-encoding',
553+ u'git-gpg-signature', u'git-gpg-signature-b64',
554+ u'git-explicit-encoding',
555 u'author-timestamp', u'file-modes'])
556 for k, v in rev.properties.items():
557 if k not in mapping_properties:
558@@ -419,8 +422,12 @@
559 if commit._commit_timezone_neg_utc:
560 rev.properties[u'commit-timezone-neg-utc'] = ""
561 if commit.gpgsig:
562- rev.properties[u'git-gpg-signature'] = commit.gpgsig.decode(
563- 'ascii')
564+ try:
565+ rev.properties[u'git-gpg-signature'] = commit.gpgsig.decode(
566+ 'utf-8')
567+ except UnicodeDecodeError:
568+ rev.properties[u'git-gpg-signature-b64'] = base64.b64encode(
569+ commit.gpgsig)
570 if commit.mergetag:
571 for i, tag in enumerate(commit.mergetag):
572 rev.properties[u'git-mergetag-%d' % i] = tag.as_raw_string()
573
574=== modified file 'breezy/git/object_store.py'
575--- breezy/git/object_store.py 2020-02-18 01:57:45 +0000
576+++ breezy/git/object_store.py 2020-05-06 02:56:30 +0000
577@@ -132,7 +132,7 @@
578 self._cache[tree.get_revision_id()] = tree
579
580
581-def _find_missing_bzr_revids(graph, want, have):
582+def _find_missing_bzr_revids(graph, want, have, shallow=None):
583 """Find the revisions that have to be pushed.
584
585 :param get_parent_map: Function that returns the parents for a sequence
586@@ -142,13 +142,17 @@
587 :return: Set of revisions to fetch
588 """
589 handled = set(have)
590+ if shallow:
591+ # Shallows themselves still need to be fetched, but let's exclude their
592+ # parents.
593+ for ps in graph.get_parent_map(shallow).values():
594+ handled.update(ps)
595+ handled.add(NULL_REVISION)
596 todo = set()
597 for rev in want:
598 extra_todo = graph.find_unique_ancestors(rev, handled)
599 todo.update(extra_todo)
600 handled.update(extra_todo)
601- if NULL_REVISION in todo:
602- todo.remove(NULL_REVISION)
603 return todo
604
605
606@@ -755,13 +759,15 @@
607 else:
608 raise KeyError(sha)
609
610- def generate_lossy_pack_data(self, have, want, progress=None,
611+ def generate_lossy_pack_data(self, have, want, shallow=None,
612+ progress=None,
613 get_tagged=None, ofs_delta=False):
614 return pack_objects_to_data(
615- self.generate_pack_contents(have, want, progress, get_tagged,
616+ self.generate_pack_contents(have, want, progress=progress,
617+ shallow=shallow, get_tagged=get_tagged,
618 lossy=True))
619
620- def generate_pack_contents(self, have, want, progress=None,
621+ def generate_pack_contents(self, have, want, shallow=None, progress=None,
622 ofs_delta=False, get_tagged=None, lossy=False):
623 """Iterate over the contents of a pack file.
624
625@@ -790,9 +796,18 @@
626 pending.add(type_data[0])
627 except KeyError:
628 pass
629+ shallows = set()
630+ for commit_sha in shallow or set():
631+ try:
632+ for (type, type_data) in ret[commit_sha]:
633+ if type != "commit":
634+ raise AssertionError("Type was %s, not commit" % type)
635+ shallows.add(type_data[0])
636+ except KeyError:
637+ pass
638
639 graph = self.repository.get_graph()
640- todo = _find_missing_bzr_revids(graph, pending, processed)
641+ todo = _find_missing_bzr_revids(graph, pending, processed, shallow)
642 ret = PackTupleIterable(self)
643 with ui.ui_factory.nested_progress_bar() as pb:
644 for i, revid in enumerate(graph.iter_topo_order(todo)):
645
646=== modified file 'breezy/git/remote.py'
647--- breezy/git/remote.py 2020-03-07 14:46:08 +0000
648+++ breezy/git/remote.py 2020-05-06 02:56:30 +0000
649@@ -606,10 +606,24 @@
650 ret[tag_name_to_ref(tagname)] = new_sha
651 return ret
652 with source_store.lock_read():
653- if lossy:
654- generate_pack_data = source_store.generate_lossy_pack_data
655- else:
656- generate_pack_data = source_store.generate_pack_data
657+ def generate_pack_data(have, want, progress=None,
658+ ofs_delta=True):
659+ git_repo = getattr(source.repository, '_git', None)
660+ if git_repo:
661+ shallow = git_repo.get_shallow()
662+ else:
663+ shallow = None
664+ if lossy:
665+ return source_store.generate_lossy_pack_data(
666+ have, want, shallow=shallow,
667+ progress=progress, ofs_delta=ofs_delta)
668+ elif shallow:
669+ return source_store.generate_pack_data(
670+ have, want, shallow=shallow,
671+ progress=progress, ofs_delta=ofs_delta)
672+ else:
673+ return source_store.generate_pack_data(
674+ have, want, progress=progress, ofs_delta=ofs_delta)
675 new_refs = self.send_pack(get_changed_refs, generate_pack_data)
676 push_result.new_revid = repo.lookup_foreign_revision_id(
677 new_refs[actual_refname])
678@@ -712,7 +726,7 @@
679 raise NotGitRepository()
680 elif response.status != 200:
681 raise GitProtocolError("unexpected http resp %d for %s" %
682- (response.code, url))
683+ (response.status, url))
684
685 # TODO: Optimization available by adding `preload_content=False` to the
686 # request and just passing the `read` method on instead of going via
687
688=== modified file 'breezy/git/server.py'
689--- breezy/git/server.py 2020-02-18 01:57:45 +0000
690+++ breezy/git/server.py 2020-05-06 02:56:30 +0000
691@@ -90,12 +90,19 @@
692 have = self.object_store.find_common_revisions(graph_walker)
693 if wants is None:
694 return
695+ shallows = getattr(graph_walker, 'shallow', frozenset())
696 if isinstance(self.object_store, BazaarObjectStore):
697 return self.object_store.generate_pack_contents(
698- have, wants, progress, get_tagged=get_tagged, lossy=True)
699+ have, wants, shallow=shallows,
700+ progress=progress, get_tagged=get_tagged, lossy=True)
701 else:
702- return self.object_store.generate_pack_contents(
703- have, wants, progress)
704+ if shallows:
705+ return self.object_store.generate_pack_contents(
706+ have, wants, shallow=shallows, progress=progress)
707+ else:
708+ return self.object_store.generate_pack_contents(
709+ have, wants, progress=progress)
710+
711
712
713 class BzrTCPGitServer(TCPGitServer):
714
715=== modified file 'breezy/git/tests/test_blackbox.py'
716--- breezy/git/tests/test_blackbox.py 2020-03-22 01:35:14 +0000
717+++ breezy/git/tests/test_blackbox.py 2020-05-06 02:56:30 +0000
718@@ -347,7 +347,7 @@
719 self.run_bzr(["git-import", "--colocated", "a", "b"])
720 self.assertEqual(set([".bzr"]), set(os.listdir("b")))
721 self.assertEqual(set(["abranch", "bbranch"]),
722- set(ControlDir.open("b").get_branches().keys()))
723+ set(ControlDir.open("b").branch_names()))
724
725 def test_git_import_incremental(self):
726 r = GitRepo.init("a", mkdir=True)
727@@ -359,7 +359,7 @@
728 self.run_bzr(["git-import", "--colocated", "a", "b"])
729 self.assertEqual(set([".bzr"]), set(os.listdir("b")))
730 b = ControlDir.open("b")
731- self.assertEqual(["abranch"], list(b.get_branches().keys()))
732+ self.assertEqual(["abranch"], b.branch_names())
733
734 def test_git_import_tags(self):
735 r = GitRepo.init("a", mkdir=True)
736@@ -371,7 +371,7 @@
737 self.run_bzr(["git-import", "--colocated", "a", "b"])
738 self.assertEqual(set([".bzr"]), set(os.listdir("b")))
739 b = ControlDir.open("b")
740- self.assertEqual(["abranch"], list(b.get_branches().keys()))
741+ self.assertEqual(["abranch"], b.branch_names())
742 self.assertEqual(["atag"],
743 list(b.open_branch("abranch").tags.get_tag_dict().keys()))
744
745
746=== modified file 'breezy/git/tests/test_remote.py'
747--- breezy/git/tests/test_remote.py 2020-02-18 01:57:45 +0000
748+++ breezy/git/tests/test_remote.py 2020-05-06 02:56:30 +0000
749@@ -565,6 +565,8 @@
750 self.assertEqual(
751 {'': 'master', 'blah': 'blah', 'master': 'master'},
752 {n: b.name for (n, b) in remote.get_branches().items()})
753+ self.assertEqual(
754+ set(['', 'blah', 'master']), set(remote.branch_names()))
755
756 def test_remove_tag(self):
757 c1 = self.remote_real.do_commit(
758
759=== modified file 'breezy/git/transportgit.py'
760--- breezy/git/transportgit.py 2020-02-18 01:57:45 +0000
761+++ breezy/git/transportgit.py 2020-05-06 02:56:30 +0000
762@@ -424,8 +424,10 @@
763 _mod_transport.get_transport_from_path(commondir)
764 else:
765 self._commontransport = self._controltransport
766- object_store = TransportObjectStore(
767- self._commontransport.clone(OBJECTDIR))
768+ config = self.get_config()
769+ object_store = TransportObjectStore.from_config(
770+ self._commontransport.clone(OBJECTDIR),
771+ config)
772 if refs_text is not None:
773 refs_container = InfoRefsContainer(BytesIO(refs_text))
774 try:
775@@ -512,6 +514,25 @@
776 backends.extend(StackedConfig.default_backends())
777 return StackedConfig(backends, writable=writable)
778
779+ # Here for compatibility with dulwich < 0.19.17
780+ def generate_pack_data(self, have, want, progress=None, ofs_delta=None):
781+ """Generate pack data objects for a set of wants/haves.
782+
783+ Args:
784+ have: List of SHA1s of objects that should not be sent
785+ want: List of SHA1s of objects that should be sent
786+ ofs_delta: Whether OFS deltas can be included
787+ progress: Optional progress reporting method
788+ """
789+ shallow = self.get_shallow()
790+ if shallow:
791+ return self.object_store.generate_pack_data(
792+ have, want, shallow=shallow,
793+ progress=progress, ofs_delta=ofs_delta)
794+ else:
795+ return self.object_store.generate_pack_data(
796+ have, want, progress=progress, ofs_delta=ofs_delta)
797+
798 def __repr__(self):
799 return "<%s for %r>" % (self.__class__.__name__, self.transport)
800
801@@ -544,16 +565,38 @@
802 class TransportObjectStore(PackBasedObjectStore):
803 """Git-style object store that exists on disk."""
804
805- def __init__(self, transport):
806+ def __init__(self, transport,
807+ loose_compression_level=-1, pack_compression_level=-1):
808 """Open an object store.
809
810 :param transport: Transport to open data from
811 """
812 super(TransportObjectStore, self).__init__()
813+ self.pack_compression_level = pack_compression_level
814+ self.loose_compression_level = loose_compression_level
815 self.transport = transport
816 self.pack_transport = self.transport.clone(PACKDIR)
817 self._alternates = None
818
819+ @classmethod
820+ def from_config(cls, path, config):
821+ try:
822+ default_compression_level = int(config.get(
823+ (b'core', ), b'compression').decode())
824+ except KeyError:
825+ default_compression_level = -1
826+ try:
827+ loose_compression_level = int(config.get(
828+ (b'core', ), b'looseCompression').decode())
829+ except KeyError:
830+ loose_compression_level = default_compression_level
831+ try:
832+ pack_compression_level = int(config.get(
833+ (b'core', ), 'packCompression').decode())
834+ except KeyError:
835+ pack_compression_level = default_compression_level
836+ return cls(path, loose_compression_level, pack_compression_level)
837+
838 def __eq__(self, other):
839 if not isinstance(other, TransportObjectStore):
840 return False
841@@ -686,7 +729,14 @@
842 path = urlutils.quote_from_bytes(osutils.pathjoin(dir, file))
843 if self.transport.has(path):
844 return # Already there, no need to write again
845- self.transport.put_bytes(path, obj.as_legacy_object())
846+ # Backwards compatibility with Dulwich < 0.20, which doesn't support
847+ # the compression_level parameter.
848+ if self.loose_compression_level not in (-1, None):
849+ raw_string = obj.as_legacy_object(
850+ compression_level=self.loose_compression_level)
851+ else:
852+ raw_string = obj.as_legacy_object()
853+ self.transport.put_bytes(path, raw_string)
854
855 def move_in_pack(self, f):
856 """Move a specific file containing a pack into the pack directory.
857
858=== modified file 'breezy/git/tree.py'
859--- breezy/git/tree.py 2020-02-18 01:57:45 +0000
860+++ breezy/git/tree.py 2020-05-06 02:56:30 +0000
861@@ -1670,9 +1670,11 @@
862 e, osutils._fs_enc)
863 if stat.S_ISDIR(st.st_mode):
864 blob = Tree()
865- else:
866+ elif stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
867 blob = blob_from_path_and_stat(
868 target.abspath(e).encode(osutils._fs_enc), st)
869+ else:
870+ continue
871 store.add_object(blob)
872 np = np.encode('utf-8')
873 blobs[np] = (blob.id, cleanup_mode(st.st_mode))
874
875=== modified file 'breezy/git/workingtree.py'
876--- breezy/git/workingtree.py 2020-02-21 03:58:42 +0000
877+++ breezy/git/workingtree.py 2020-05-06 02:56:30 +0000
878@@ -124,7 +124,7 @@
879 index_path = os.path.join(self.basedir, relpath.decode('utf-8'), '.git', 'index')
880 else:
881 index_path = self.control_transport.local_abspath(
882- posixpath.join('modules', info[1], 'index'))
883+ posixpath.join('modules', info[1].decode('utf-8'), 'index'))
884 return Index(index_path)
885
886 def lock_read(self):
887@@ -814,7 +814,11 @@
888 ie = self._get_file_ie(name, path, value, dir_ids[parent])
889 yield (posixpath.relpath(path, from_dir), "V", ie.kind, ie)
890 else:
891- ie = fk_entries[kind]()
892+ try:
893+ ie = fk_entries[kind]()
894+ except KeyError:
895+ # unsupported kind
896+ continue
897 yield (posixpath.relpath(path, from_dir),
898 ("I" if self.is_ignored(path) else "?"), kind, ie)
899
900
901=== modified file 'breezy/info.py'
902--- breezy/info.py 2020-02-18 01:57:45 +0000
903+++ breezy/info.py 2020-05-06 02:56:30 +0000
904@@ -455,7 +455,7 @@
905 extra = []
906 if repository.make_working_trees():
907 extra.append('trees')
908- if len(control.get_branches()) > 0:
909+ if len(control.branch_names()) > 0:
910 extra.append('colocated branches')
911 if extra:
912 phrase += ' with ' + " and ".join(extra)
913
914=== modified file 'breezy/plugins/darcs/__init__.py'
915--- breezy/plugins/darcs/__init__.py 2020-02-18 01:57:45 +0000
916+++ breezy/plugins/darcs/__init__.py 2020-05-06 02:56:30 +0000
917@@ -57,11 +57,11 @@
918
919 def open(self, transport, _found=False):
920 """Open this directory."""
921- raise DarcsUnsupportedError(self)
922+ raise DarcsUnsupportedError()
923
924 def check_support_status(self, allow_unsupported, recommend_upgrade=True,
925 basedir=None):
926- raise DarcsUnsupportedError(self)
927+ raise DarcsUnsupportedError()
928
929 def open(self, transport):
930 # Raise NotBranchError if there is nothing there
931@@ -73,11 +73,13 @@
932
933 @classmethod
934 def priority(klass, transport):
935+ if 'darcs' in transport.base:
936+ return 90
937 return 100
938
939 @classmethod
940 def probe_transport(klass, transport):
941- if transport.has('_darcs'):
942+ if transport.has('_darcs/format'):
943 return DarcsDirFormat()
944 raise errors.NotBranchError(path=transport.base)
945
946
947=== modified file 'breezy/plugins/fastimport/tests/test_head_tracking.py'
948--- breezy/plugins/fastimport/tests/test_head_tracking.py 2020-02-18 01:57:45 +0000
949+++ breezy/plugins/fastimport/tests/test_head_tracking.py 2020-05-06 02:56:30 +0000
950@@ -24,7 +24,7 @@
951
952 import testtools
953
954-from .reftracker import (
955+from fastimport.reftracker import (
956 RefTracker,
957 )
958
959
960=== modified file 'breezy/plugins/propose/__init__.py'
961--- breezy/plugins/propose/__init__.py 2020-02-18 01:57:45 +0000
962+++ breezy/plugins/propose/__init__.py 2020-05-06 02:56:30 +0000
963@@ -40,3 +40,11 @@
964 hosters.register_lazy(
965 "gitlab", "breezy.plugins.propose.gitlabs",
966 "GitLab")
967+
968+
969+def test_suite():
970+ from unittest import TestSuite
971+ from .tests import test_suite
972+ result = TestSuite()
973+ result.addTest(test_suite())
974+ return result
975
976=== modified file 'breezy/plugins/propose/gitlabs.py'
977--- breezy/plugins/propose/gitlabs.py 2020-03-22 01:35:14 +0000
978+++ breezy/plugins/propose/gitlabs.py 2020-05-06 02:56:30 +0000
979@@ -166,9 +166,15 @@
980 raise NotGitLabUrl(url)
981 path = path.strip('/')
982 parts = path.split('/')
983+ if len(parts) < 2:
984+ raise NotMergeRequestUrl(host, url)
985 if parts[-2] != 'merge_requests':
986 raise NotMergeRequestUrl(host, url)
987- return host, '/'.join(parts[:-2]), int(parts[-1])
988+ if parts[-3] == '-':
989+ project_name = '/'.join(parts[:-3])
990+ else:
991+ project_name = '/'.join(parts[:-2])
992+ return host, project_name, int(parts[-1])
993
994
995 class GitLabMergeProposal(MergeProposal):
996@@ -280,6 +286,10 @@
997 def base_url(self):
998 return self.transport.base
999
1000+ @property
1001+ def base_hostname(self):
1002+ return urlutils.parse_url(self.base_url)[3]
1003+
1004 def _api_request(self, method, path, fields=None, body=None):
1005 return self.transport.request(
1006 method, urlutils.join(self.base_url, 'api', 'v4', path),
1007@@ -374,6 +384,15 @@
1008 parameters['owner_id'] = urlutils.quote(owner, '')
1009 return self._list_paged(path, parameters, per_page=DEFAULT_PAGE_SIZE)
1010
1011+ def _get_merge_request(self, project, merge_id):
1012+ path = 'projects/%s/merge_requests/%d' % (urlutils.quote(str(project), ''), merge_id)
1013+ response = self._api_request('GET', path)
1014+ if response.status == 403:
1015+ raise errors.PermissionDenied(response.text)
1016+ if response.status != 200:
1017+ raise errors.InvalidHttpResponse(path, response.text)
1018+ return json.loads(response.data)
1019+
1020 def _list_projects(self, owner):
1021 path = 'users/%s/projects' % urlutils.quote(str(owner), '')
1022 parameters = {}
1023@@ -390,7 +409,7 @@
1024 def _create_mergerequest(
1025 self, title, source_project_id, target_project_id,
1026 source_branch_name, target_branch_name, description,
1027- labels=None):
1028+ labels=None, allow_collaboration=False):
1029 path = 'projects/%s/merge_requests' % source_project_id
1030 fields = {
1031 'title': title,
1032@@ -398,6 +417,7 @@
1033 'target_branch': target_branch_name,
1034 'target_project_id': target_project_id,
1035 'description': description,
1036+ 'allow_collaboration': allow_collaboration,
1037 }
1038 if labels:
1039 fields['labels'] = labels
1040@@ -484,7 +504,7 @@
1041 (host, project, branch_name) = parse_gitlab_branch_url(branch)
1042 except NotGitLabUrl:
1043 return False
1044- return (self.base_url == ('https://%s' % host))
1045+ return self.base_hostname == host
1046
1047 def check(self):
1048 response = self._api_request('GET', 'user')
1049@@ -539,15 +559,15 @@
1050 except NotGitLabUrl:
1051 raise UnsupportedHoster(url)
1052 except NotMergeRequestUrl as e:
1053- if self.base_url == ('https://%s' % e.host):
1054+ if self.base_hostname == e.host:
1055 raise
1056 else:
1057 raise UnsupportedHoster(url)
1058- if self.base_url != ('https://%s' % host):
1059+ if self.base_hostname != host:
1060 raise UnsupportedHoster(url)
1061 project = self._get_project(project)
1062- mr = project.mergerequests.get(merge_id)
1063- return GitLabMergeProposal(mr)
1064+ mr = self._get_merge_request(project['path_with_namespace'], merge_id)
1065+ return GitLabMergeProposal(self, mr)
1066
1067 def delete_project(self, project):
1068 path = 'projects/%s' % urlutils.quote(str(project), '')
1069@@ -623,7 +643,7 @@
1070 try:
1071 merge_request = self.gl._create_mergerequest(**kwargs)
1072 except MergeRequestExists:
1073- raise ProposalExists(self.source_branch.user_url)
1074+ raise MergeProposalExists(self.source_branch.user_url)
1075 return GitLabMergeProposal(self.gl, merge_request)
1076
1077
1078
1079=== added directory 'breezy/plugins/propose/tests'
1080=== added file 'breezy/plugins/propose/tests/__init__.py'
1081--- breezy/plugins/propose/tests/__init__.py 1970-01-01 00:00:00 +0000
1082+++ breezy/plugins/propose/tests/__init__.py 2020-05-06 02:56:30 +0000
1083@@ -0,0 +1,26 @@
1084+# Copyright (C) 2020 Jelmer Vernooij <jelmer@jelmer.uk>
1085+#
1086+# This program is free software; you can redistribute it and/or modify
1087+# it under the terms of the GNU General Public License as published by
1088+# the Free Software Foundation; either version 2 of the License, or
1089+# (at your option) any later version.
1090+#
1091+# This program is distributed in the hope that it will be useful,
1092+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1093+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1094+# GNU General Public License for more details.
1095+#
1096+# You should have received a copy of the GNU General Public License
1097+# along with this program; if not, write to the Free Software
1098+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1099+
1100+from unittest import TestLoader, TestSuite
1101+
1102+
1103+def test_suite():
1104+ result = TestSuite()
1105+ from . import test_gitlab
1106+
1107+ loader = TestLoader()
1108+ result.addTests(loader.loadTestsFromModule(test_gitlab))
1109+ return result
1110
1111=== added file 'breezy/plugins/propose/tests/test_gitlab.py'
1112--- breezy/plugins/propose/tests/test_gitlab.py 1970-01-01 00:00:00 +0000
1113+++ breezy/plugins/propose/tests/test_gitlab.py 2020-05-06 02:56:30 +0000
1114@@ -0,0 +1,52 @@
1115+# Copyright (C) 2020 Jelmer Vernooij <jelmer@jelmer.uk>
1116+#
1117+# This program is free software; you can redistribute it and/or modify
1118+# it under the terms of the GNU General Public License as published by
1119+# the Free Software Foundation; either version 2 of the License, or
1120+# (at your option) any later version.
1121+#
1122+# This program is distributed in the hope that it will be useful,
1123+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1124+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1125+# GNU General Public License for more details.
1126+#
1127+# You should have received a copy of the GNU General Public License
1128+# along with this program; if not, write to the Free Software
1129+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1130+
1131+from breezy.tests import TestCase
1132+
1133+from breezy.plugins.propose.gitlabs import (
1134+ parse_gitlab_merge_request_url,
1135+ NotMergeRequestUrl,
1136+ NotGitLabUrl,
1137+ )
1138+
1139+
1140+class ParseGitLabMergeRequestUrlTests(TestCase):
1141+
1142+ def test_invalid(self):
1143+ self.assertRaises(
1144+ NotMergeRequestUrl, parse_gitlab_merge_request_url,
1145+ "https://salsa.debian.org/")
1146+ self.assertRaises(
1147+ NotGitLabUrl, parse_gitlab_merge_request_url,
1148+ "bzr://salsa.debian.org/")
1149+ self.assertRaises(
1150+ NotGitLabUrl, parse_gitlab_merge_request_url,
1151+ "https:///salsa.debian.org/")
1152+ self.assertRaises(
1153+ NotMergeRequestUrl, parse_gitlab_merge_request_url,
1154+ "https://salsa.debian.org/jelmer/salsa")
1155+
1156+ def test_old_style(self):
1157+ self.assertEqual(
1158+ ('salsa.debian.org', 'jelmer/salsa', 4),
1159+ parse_gitlab_merge_request_url(
1160+ 'https://salsa.debian.org/jelmer/salsa/merge_requests/4'))
1161+
1162+ def test_new_style(self):
1163+ self.assertEqual(
1164+ ('salsa.debian.org', 'jelmer/salsa', 4),
1165+ parse_gitlab_merge_request_url(
1166+ 'https://salsa.debian.org/jelmer/salsa/-/merge_requests/4'))
1167
1168=== modified file 'breezy/plugins/weave_fmt/bzrdir.py'
1169--- breezy/plugins/weave_fmt/bzrdir.py 2020-02-21 03:58:42 +0000
1170+++ breezy/plugins/weave_fmt/bzrdir.py 2020-05-06 02:56:30 +0000
1171@@ -840,7 +840,7 @@
1172
1173 def get_branch_transport(self, branch_format, name=None):
1174 """See BzrDir.get_branch_transport()."""
1175- if name is not None:
1176+ if name:
1177 raise errors.NoColocatedBranchSupport(self)
1178 if branch_format is None:
1179 return self.transport
1180
1181=== modified file 'breezy/tests/blackbox/__init__.py'
1182--- breezy/tests/blackbox/__init__.py 2019-06-15 17:06:40 +0000
1183+++ breezy/tests/blackbox/__init__.py 2020-05-06 02:56:30 +0000
1184@@ -51,6 +51,7 @@
1185 'test_check',
1186 'test_checkout',
1187 'test_clean_tree',
1188+ 'test_clone',
1189 'test_command_encoding',
1190 'test_commit',
1191 'test_config',
1192
1193=== modified file 'breezy/tests/blackbox/test_branch.py'
1194--- breezy/tests/blackbox/test_branch.py 2020-01-19 14:49:03 +0000
1195+++ breezy/tests/blackbox/test_branch.py 2020-05-06 02:56:30 +0000
1196@@ -107,6 +107,17 @@
1197 self.assertEqual('Branched 0 revisions.\n', err)
1198 self.assertPathExists('a')
1199
1200+ def test_from_name(self):
1201+ """Branch from a colocated branch into a regular branch."""
1202+ os.mkdir('b')
1203+ tree = self.example_branch('b/a', format='development-colo')
1204+ tree.controldir.create_branch(name='somecolo')
1205+ out, err = self.run_bzr('branch -b somecolo %s' %
1206+ local_path_to_url('b/a'))
1207+ self.assertEqual('', out)
1208+ self.assertEqual('Branched 0 revisions.\n', err)
1209+ self.assertPathExists('a')
1210+
1211 def test_branch_broken_pack(self):
1212 """branching with a corrupted pack file."""
1213 self.example_branch('a')
1214
1215=== added file 'breezy/tests/blackbox/test_clone.py'
1216--- breezy/tests/blackbox/test_clone.py 1970-01-01 00:00:00 +0000
1217+++ breezy/tests/blackbox/test_clone.py 2020-05-06 02:56:30 +0000
1218@@ -0,0 +1,70 @@
1219+# Copyright (C) 2006-2012, 2016 Canonical Ltd
1220+#
1221+# This program is free software; you can redistribute it and/or modify
1222+# it under the terms of the GNU General Public License as published by
1223+# the Free Software Foundation; either version 2 of the License, or
1224+# (at your option) any later version.
1225+#
1226+# This program is distributed in the hope that it will be useful,
1227+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1228+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1229+# GNU General Public License for more details.
1230+#
1231+# You should have received a copy of the GNU General Public License
1232+# along with this program; if not, write to the Free Software
1233+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1234+
1235+
1236+"""Black-box tests for brz branch."""
1237+
1238+import os
1239+
1240+from breezy import (
1241+ branch,
1242+ controldir,
1243+ tests,
1244+ )
1245+from breezy.urlutils import local_path_to_url
1246+
1247+
1248+class TestClone(tests.TestCaseWithTransport):
1249+
1250+ def example_dir(self, path='.', format=None):
1251+ tree = self.make_branch_and_tree(path, format=format)
1252+ self.build_tree_contents([(path + '/hello', b'foo')])
1253+ tree.add('hello')
1254+ tree.commit(message='setup')
1255+ self.build_tree_contents([(path + '/goodbye', b'baz')])
1256+ tree.add('goodbye')
1257+ tree.commit(message='setup')
1258+ return tree
1259+
1260+ def test_clone(self):
1261+ """Branch from one branch to another."""
1262+ self.example_dir('a')
1263+ self.run_bzr('clone a b')
1264+ b = branch.Branch.open('b')
1265+ self.run_bzr('clone a c -r 1')
1266+ # previously was erroneously created by branching
1267+ self.assertFalse(b._transport.has('branch-name'))
1268+ b.controldir.open_workingtree().commit(message='foo', allow_pointless=True)
1269+
1270+ def test_clone_no_to_location(self):
1271+ """The to_location is derived from the source branch name."""
1272+ os.mkdir("something")
1273+ a = self.example_dir('something/a').branch
1274+ self.run_bzr('clone something/a')
1275+ b = branch.Branch.open('a')
1276+ self.assertEqual(b.last_revision_info(), a.last_revision_info())
1277+
1278+ def test_from_colocated(self):
1279+ """Branch from a colocated branch into a regular branch."""
1280+ os.mkdir('b')
1281+ tree = self.example_dir('b/a')
1282+ tree.controldir.create_branch(name='somecolo')
1283+ out, err = self.run_bzr('clone %s' % local_path_to_url('b/a'))
1284+ self.assertEqual('', out)
1285+ self.assertEqual('Created new control directory.\n', err)
1286+ self.assertPathExists('a')
1287+ target = controldir.ControlDir.open('a')
1288+ self.assertEqual(['', 'somecolo'], target.branch_names())
1289
1290=== modified file 'breezy/tests/blackbox/test_switch.py'
1291--- breezy/tests/blackbox/test_switch.py 2019-09-01 15:33:59 +0000
1292+++ breezy/tests/blackbox/test_switch.py 2020-05-06 02:56:30 +0000
1293@@ -207,7 +207,7 @@
1294 self.run_bzr(['switch', '-b', 'anotherbranch'])
1295 self.assertEqual(
1296 {'', 'anotherbranch'},
1297- set(tree.branch.controldir.get_branches().keys()))
1298+ set(tree.branch.controldir.branch_names()))
1299
1300 def test_switch_into_unrelated_colocated(self):
1301 # Create a new colocated branch from an existing non-colocated branch.
1302
1303=== modified file 'breezy/tests/per_bzrdir/test_bzrdir.py'
1304--- breezy/tests/per_bzrdir/test_bzrdir.py 2018-11-11 04:08:32 +0000
1305+++ breezy/tests/per_bzrdir/test_bzrdir.py 2020-05-06 02:56:30 +0000
1306@@ -688,4 +688,4 @@
1307 target_branch = repo.controldir.create_branch(name='foo')
1308 repo.controldir.set_branch_reference(target_branch)
1309 self.assertEqual({"", 'foo'},
1310- set(repo.controldir.get_branches().keys()))
1311+ set(repo.controldir.branch_names()))
1312
1313=== modified file 'breezy/tests/per_controldir/test_controldir.py'
1314--- breezy/tests/per_controldir/test_controldir.py 2019-05-29 03:22:34 +0000
1315+++ breezy/tests/per_controldir/test_controldir.py 2020-05-06 02:56:30 +0000
1316@@ -199,7 +199,7 @@
1317 "control directories without working tree")
1318 self.assertRaises(errors.NoWorkingTree, dir.open_workingtree)
1319
1320- def test_clone_bzrdir_repository_under_shared(self):
1321+ def test_clone_controldir_repository_under_shared(self):
1322 tree = self.make_branch_and_tree('commit_tree')
1323 self.build_tree(
1324 ['foo'], transport=tree.controldir.transport.clone('..'))
1325@@ -221,7 +221,7 @@
1326 self.assertNotEqual(dir.transport.base, target.transport.base)
1327 self.assertRaises(errors.NoRepositoryPresent, target.open_repository)
1328
1329- def test_clone_bzrdir_repository_branch_both_under_shared(self):
1330+ def test_clone_controldir_repository_branch_both_under_shared(self):
1331 # Create a shared repository
1332 try:
1333 shared_repo = self.make_repository('shared', shared=True)
1334@@ -249,7 +249,7 @@
1335 dir.create_branch()
1336 # Clone 'source' to 'target', also inside the shared repository.
1337 target = dir.clone(self.get_url('shared/target'))
1338- # 'source', 'target', and the shared repo all have distinct bzrdirs.
1339+ # 'source', 'target', and the shared repo all have distinct controldirs.
1340 self.assertNotEqual(dir.transport.base, target.transport.base)
1341 self.assertNotEqual(
1342 dir.transport.base, shared_repo.controldir.transport.base)
1343@@ -258,7 +258,7 @@
1344 # 'commit_tree' branch.
1345 self.assertTrue(shared_repo.has_revision(rev1))
1346
1347- def test_clone_bzrdir_repository_branch_only_source_under_shared(self):
1348+ def test_clone_controldir_repository_branch_only_source_under_shared(self):
1349 try:
1350 shared_repo = self.make_repository('shared', shared=True)
1351 except errors.IncompatibleFormat:
1352@@ -291,7 +291,7 @@
1353 self.assertFalse(branch.repository.make_working_trees())
1354 self.assertTrue(branch.repository.is_shared())
1355
1356- def test_clone_bzrdir_repository_revision(self):
1357+ def test_clone_controldir_repository_revision(self):
1358 # test for revision limiting, [smoke test, not corner case checks].
1359 # make a repository with some revisions,
1360 # and clone it with a revision limit.
1361@@ -310,7 +310,7 @@
1362 dir.clone(self.get_url('target'), revision_id=rev2)
1363 raise TestSkipped('revision limiting not strict yet')
1364
1365- def test_clone_bzrdir_branch_and_repo_fixed_user_id(self):
1366+ def test_clone_controldir_branch_and_repo_fixed_user_id(self):
1367 # Bug #430868 is about an email containing '.sig'
1368 self.overrideEnv('BRZ_EMAIL', 'murphy@host.sighup.org')
1369 tree = self.make_branch_and_tree('commit_tree')
1370@@ -330,7 +330,7 @@
1371 tree_repo.get_signature_text(rev1),
1372 target.repository.get_signature_text(rev1))
1373
1374- def test_clone_bzrdir_branch_and_repo_into_shared_repo(self):
1375+ def test_clone_controldir_branch_and_repo_into_shared_repo(self):
1376 # by default cloning into a shared repo uses the shared repo.
1377 tree = self.make_branch_and_tree('commit_tree')
1378 self.build_tree(['commit_tree/foo'])
1379@@ -354,7 +354,7 @@
1380 self.assertEqual(source.last_revision(),
1381 target.open_branch().last_revision())
1382
1383- def test_clone_bzrdir_branch_revision(self):
1384+ def test_clone_controldir_branch_revision(self):
1385 # test for revision limiting, [smoke test, not corner case checks].
1386 # make a branch with some revisions,
1387 # and clone it with a revision limit.
1388@@ -371,6 +371,23 @@
1389 target = dir.clone(self.get_url('target'), revision_id=rev1)
1390 self.assertEqual(rev1, target.open_branch().last_revision())
1391
1392+ def test_clone_controldir_with_colocated(self):
1393+ if not self.bzrdir_format.colocated_branches:
1394+ raise TestNotApplicable(
1395+ 'format does not supported colocated branches')
1396+ tree = self.make_branch_and_tree('commit_tree')
1397+ self.build_tree(['commit_tree/foo'])
1398+ tree.add('foo')
1399+ rev1 = tree.commit('revision 1')
1400+ rev2 = tree.commit('revision 2', allow_pointless=True)
1401+ rev3 = tree.commit('revision 2', allow_pointless=True)
1402+ dir = tree.branch.controldir
1403+ colo = dir.create_branch(name='colo')
1404+ colo.pull(tree.branch, stop_revision=rev1)
1405+ target = dir.clone(self.get_url('target'), revision_id=rev2)
1406+ self.assertEqual(rev2, target.open_branch().last_revision())
1407+ self.assertEqual(rev1, target.open_branch(name='colo').last_revision())
1408+
1409 def test_clone_on_transport_preserves_repo_format(self):
1410 if self.bzrdir_format == controldir.format_registry.make_controldir('default'):
1411 format = 'knit'
1412@@ -381,8 +398,8 @@
1413 a_dir = breezy.branch.Branch.open_from_transport(
1414 self.get_transport('source')).controldir
1415 target_transport = self.get_transport('target')
1416- target_bzrdir = a_dir.clone_on_transport(target_transport)
1417- target_repo = target_bzrdir.open_repository()
1418+ target_controldir = a_dir.clone_on_transport(target_transport)
1419+ target_repo = target_controldir.open_repository()
1420 source_branch = breezy.branch.Branch.open(
1421 self.get_vfs_only_url('source'))
1422 if isinstance(target_repo, RemoteRepository):
1423@@ -390,7 +407,7 @@
1424 target_repo = target_repo._real_repository
1425 self.assertEqual(target_repo._format, source_branch.repository._format)
1426
1427- def test_clone_bzrdir_tree_revision(self):
1428+ def test_clone_controldir_tree_revision(self):
1429 # test for revision limiting, [smoke test, not corner case checks].
1430 # make a tree with a revision with a last-revision
1431 # and clone it with a revision limit.
1432@@ -406,7 +423,7 @@
1433 self.skipIfNoWorkingTree(target)
1434 self.assertEqual([rev1], target.open_workingtree().get_parent_ids())
1435
1436- def test_clone_bzrdir_into_notrees_repo(self):
1437+ def test_clone_controldir_into_notrees_repo(self):
1438 """Cloning into a no-trees repo should not create a working tree"""
1439 tree = self.make_branch_and_tree('source')
1440 self.build_tree(['source/foo'])
1441@@ -514,12 +531,12 @@
1442 """get_branch_reference should not mask NotBranchErrors."""
1443 dir = self.make_controldir('source')
1444 if dir.has_branch():
1445- # this format does not support branchless bzrdirs.
1446+ # this format does not support branchless controldirs.
1447 raise TestNotApplicable("format does not support "
1448 "branchless control directories")
1449 self.assertRaises(errors.NotBranchError, dir.get_branch_reference)
1450
1451- def test_sprout_bzrdir_empty(self):
1452+ def test_sprout_controldir_empty(self):
1453 dir = self.make_controldir('source')
1454 target = dir.sprout(self.get_url('target'))
1455 self.assertNotEqual(dir.control_transport.base,
1456@@ -529,7 +546,7 @@
1457 target.open_branch()
1458 self.openWorkingTreeIfLocal(target)
1459
1460- def test_sprout_bzrdir_empty_under_shared_repo(self):
1461+ def test_sprout_controldir_empty_under_shared_repo(self):
1462 # sprouting an empty dir into a repo uses the repo
1463 dir = self.make_controldir('source')
1464 try:
1465@@ -543,13 +560,13 @@
1466 try:
1467 target.open_workingtree()
1468 except errors.NoWorkingTree:
1469- # Some bzrdirs can never have working trees.
1470+ # Some controldirs can never have working trees.
1471 repo = target.find_repository()
1472 self.assertFalse(repo.controldir._format.supports_workingtrees)
1473
1474- def test_sprout_bzrdir_empty_under_shared_repo_force_new(self):
1475+ def test_sprout_controldir_empty_under_shared_repo_force_new(self):
1476 # the force_new_repo parameter should force use of a new repo in an empty
1477- # bzrdir's sprout logic
1478+ # controldir's sprout logic
1479 dir = self.make_controldir('source')
1480 try:
1481 self.make_repository('target', shared=True)
1482@@ -561,7 +578,7 @@
1483 target.open_branch()
1484 self.openWorkingTreeIfLocal(target)
1485
1486- def test_sprout_bzrdir_with_repository_to_shared(self):
1487+ def test_sprout_controldir_with_repository_to_shared(self):
1488 tree = self.make_branch_and_tree('commit_tree')
1489 self.build_tree(['commit_tree/foo'])
1490 tree.add('foo')
1491@@ -583,7 +600,7 @@
1492 target.user_transport.base)
1493 self.assertTrue(shared_repo.has_revision(rev1))
1494
1495- def test_sprout_bzrdir_repository_branch_both_under_shared(self):
1496+ def test_sprout_controldir_repository_branch_both_under_shared(self):
1497 try:
1498 shared_repo = self.make_repository('shared', shared=True)
1499 except errors.IncompatibleFormat:
1500@@ -609,7 +626,7 @@
1501 shared_repo.controldir.transport.base)
1502 self.assertTrue(shared_repo.has_revision(rev1))
1503
1504- def test_sprout_bzrdir_repository_branch_only_source_under_shared(self):
1505+ def test_sprout_controldir_repository_branch_only_source_under_shared(self):
1506 try:
1507 shared_repo = self.make_repository('shared', shared=True)
1508 except errors.IncompatibleFormat:
1509@@ -639,7 +656,7 @@
1510 dir.transport.base,
1511 shared_repo.controldir.transport.base)
1512 branch = target.open_branch()
1513- # The sprouted bzrdir has a branch, so only revisions referenced by
1514+ # The sprouted controldir has a branch, so only revisions referenced by
1515 # that branch are copied, rather than the whole repository. It's an
1516 # empty branch, so none are copied.
1517 self.assertEqual([], branch.repository.all_revision_ids())
1518@@ -647,7 +664,7 @@
1519 self.assertTrue(branch.repository.make_working_trees())
1520 self.assertFalse(branch.repository.is_shared())
1521
1522- def test_sprout_bzrdir_repository_under_shared_force_new_repo(self):
1523+ def test_sprout_controldir_repository_under_shared_force_new_repo(self):
1524 tree = self.make_branch_and_tree('commit_tree')
1525 self.build_tree(['commit_tree/foo'])
1526 tree.add('foo')
1527@@ -670,7 +687,7 @@
1528 target.control_transport.base)
1529 self.assertFalse(shared_repo.has_revision(rev1))
1530
1531- def test_sprout_bzrdir_repository_revision(self):
1532+ def test_sprout_controldir_repository_revision(self):
1533 # test for revision limiting, [smoke test, not corner case checks].
1534 # make a repository with some revisions,
1535 # and sprout it with a revision limit.
1536@@ -689,7 +706,7 @@
1537 self.sproutOrSkip(dir, self.get_url('target'), revision_id=rev2)
1538 raise TestSkipped('revision limiting not strict yet')
1539
1540- def test_sprout_bzrdir_branch_and_repo_shared(self):
1541+ def test_sprout_controldir_branch_and_repo_shared(self):
1542 # sprouting a branch with a repo into a shared repo uses the shared
1543 # repo
1544 tree = self.make_branch_and_tree('commit_tree')
1545@@ -708,7 +725,7 @@
1546 dir.sprout(self.get_url('target/child'))
1547 self.assertTrue(shared_repo.has_revision(rev1))
1548
1549- def test_sprout_bzrdir_branch_and_repo_shared_force_new_repo(self):
1550+ def test_sprout_controldir_branch_and_repo_shared_force_new_repo(self):
1551 # sprouting a branch with a repo into a shared repo uses the shared
1552 # repo
1553 tree = self.make_branch_and_tree('commit_tree')
1554@@ -729,7 +746,7 @@
1555 dir.control_transport.base, target.control_transport.base)
1556 self.assertFalse(shared_repo.has_revision(rev1))
1557
1558- def test_sprout_bzrdir_branch_reference(self):
1559+ def test_sprout_controldir_branch_reference(self):
1560 # sprouting should create a repository if needed and a sprouted branch.
1561 referenced_branch = self.make_branch('referenced')
1562 dir = self.make_controldir('source')
1563@@ -747,7 +764,7 @@
1564 # place
1565 target.open_repository()
1566
1567- def test_sprout_bzrdir_branch_reference_shared(self):
1568+ def test_sprout_controldir_branch_reference_shared(self):
1569 # sprouting should create a repository if needed and a sprouted branch.
1570 referenced_tree = self.make_branch_and_tree('referenced')
1571 rev1 = referenced_tree.commit('1', allow_pointless=True)
1572@@ -773,7 +790,7 @@
1573 # and we want revision '1' in the shared repo
1574 self.assertTrue(shared_repo.has_revision(rev1))
1575
1576- def test_sprout_bzrdir_branch_reference_shared_force_new_repo(self):
1577+ def test_sprout_controldir_branch_reference_shared_force_new_repo(self):
1578 # sprouting should create a repository if needed and a sprouted branch.
1579 referenced_tree = self.make_branch_and_tree('referenced')
1580 rev1 = referenced_tree.commit('1', allow_pointless=True)
1581@@ -799,7 +816,7 @@
1582 # but not the shared one
1583 self.assertFalse(shared_repo.has_revision(rev1))
1584
1585- def test_sprout_bzrdir_branch_revision(self):
1586+ def test_sprout_controldir_branch_revision(self):
1587 # test for revision limiting, [smoke test, not corner case checks].
1588 # make a repository with some revisions,
1589 # and sprout it with a revision limit.
1590@@ -816,7 +833,7 @@
1591 target = dir.sprout(self.get_url('target'), revision_id=rev1)
1592 self.assertEqual(rev1, target.open_branch().last_revision())
1593
1594- def test_sprout_bzrdir_branch_with_tags(self):
1595+ def test_sprout_controldir_branch_with_tags(self):
1596 # when sprouting a branch all revisions named in the tags are copied
1597 # too.
1598 builder = self.make_branch_builder('source')
1599@@ -835,7 +852,7 @@
1600 self.assertEqual(rev2, new_branch.tags.lookup_tag('tag-a'))
1601 new_branch.repository.get_revision(rev2)
1602
1603- def test_sprout_bzrdir_branch_with_absent_tag(self):
1604+ def test_sprout_controldir_branch_with_absent_tag(self):
1605 # tags referencing absent revisions are copied (and those absent
1606 # revisions do not prevent the sprout.)
1607 builder = self.make_branch_builder('source')
1608@@ -855,7 +872,7 @@
1609 new_branch = target.open_branch()
1610 self.assertEqual(b'missing-rev', new_branch.tags.lookup_tag('tag-a'))
1611
1612- def test_sprout_bzrdir_passing_source_branch_with_absent_tag(self):
1613+ def test_sprout_controldir_passing_source_branch_with_absent_tag(self):
1614 # tags referencing absent revisions are copied (and those absent
1615 # revisions do not prevent the sprout.)
1616 builder = self.make_branch_builder('source')
1617@@ -875,9 +892,9 @@
1618 new_branch = target.open_branch()
1619 self.assertEqual(b'missing-rev', new_branch.tags.lookup_tag('tag-a'))
1620
1621- def test_sprout_bzrdir_passing_rev_not_source_branch_copies_tags(self):
1622+ def test_sprout_controldir_passing_rev_not_source_branch_copies_tags(self):
1623 # dir.sprout(..., revision_id=b'rev1') copies rev1, and all the tags of
1624- # the branch at that bzrdir, the ancestry of all of those, but no other
1625+ # the branch at that controldir, the ancestry of all of those, but no other
1626 # revs (not even the tip of the source branch).
1627 builder = self.make_branch_builder('source')
1628 base_rev = builder.build_commit(message="Base")
1629@@ -927,7 +944,7 @@
1630 sorted([base_rev, rev_b1, rev_b2, rev_c1, rev_c2]),
1631 sorted(new_branch.repository.all_revision_ids()))
1632
1633- def test_sprout_bzrdir_tree_branch_reference(self):
1634+ def test_sprout_controldir_tree_branch_reference(self):
1635 # sprouting should create a repository if needed and a sprouted branch.
1636 # the tree state should not be copied.
1637 referenced_branch = self.make_branch('referencced')
1638@@ -952,7 +969,7 @@
1639 result_tree = target.open_workingtree()
1640 self.assertFalse(result_tree.has_filename('subdir'))
1641
1642- def test_sprout_bzrdir_tree_branch_reference_revision(self):
1643+ def test_sprout_controldir_tree_branch_reference_revision(self):
1644 # sprouting should create a repository if needed and a sprouted branch.
1645 # the tree state should not be copied but the revision changed,
1646 # and the likewise the new branch should be truncated too
1647@@ -982,7 +999,7 @@
1648 self.assertEqual([rev1], target.open_workingtree().get_parent_ids())
1649 self.assertEqual(rev1, target.open_branch().last_revision())
1650
1651- def test_sprout_bzrdir_tree_revision(self):
1652+ def test_sprout_controldir_tree_revision(self):
1653 # test for revision limiting, [smoke test, not corner case checks].
1654 # make a tree with a revision with a last-revision
1655 # and sprout it with a revision limit.
1656@@ -1034,13 +1051,13 @@
1657 rev3 = builder.build_commit(message='Rev 3.')
1658 builder.finish_series()
1659 stack_on = builder.get_branch()
1660- # Make a bzrdir with a default stacking policy to stack on that branch.
1661+ # Make a controldir with a default stacking policy to stack on that branch.
1662 config = self.make_controldir('policy-dir').get_config()
1663 try:
1664 config.set_default_stack_on(self.get_url('stack-on'))
1665 except errors.BzrError:
1666 raise TestNotApplicable('Only relevant for stackable formats.')
1667- # Sprout the stacked-on branch into the bzrdir.
1668+ # Sprout the stacked-on branch into the controldir.
1669 sprouted = stack_on.controldir.sprout(
1670 self.get_url('policy-dir/sprouted'), revision_id=rev3)
1671 # Not all revisions are copied into the sprouted repository.
1672@@ -1288,6 +1305,11 @@
1673 repo.controldir.create_branch()
1674 self.assertEqual([""], list(repo.controldir.get_branches()))
1675
1676+ def test_branch_names(self):
1677+ repo = self.make_repository('branch-1')
1678+ repo.controldir.create_branch()
1679+ self.assertEqual([""], repo.controldir.branch_names())
1680+
1681 def test_create_repository(self):
1682 # a bzrdir can construct a repository for itself.
1683 if not self.bzrdir_format.is_initializable():
1684
1685=== modified file 'breezy/tests/per_controldir_colo/test_supported.py'
1686--- breezy/tests/per_controldir_colo/test_supported.py 2019-04-13 21:37:04 +0000
1687+++ breezy/tests/per_controldir_colo/test_supported.py 2020-05-06 02:56:30 +0000
1688@@ -184,6 +184,11 @@
1689 self.assertEqual(target_branch.base,
1690 repo.controldir.get_branches()['foo'].base)
1691
1692+ def test_branch_names(self):
1693+ repo = self.make_repository('branch-1')
1694+ target_branch = self.create_branch(repo.controldir, name='foo')
1695+ self.assertIn('foo', repo.controldir.branch_names())
1696+
1697 def test_branch_name_with_slash(self):
1698 repo = self.make_repository('branch-1')
1699 try:
1700
1701=== modified file 'breezy/tests/per_workingtree/test_changes_from.py'
1702--- breezy/tests/per_workingtree/test_changes_from.py 2019-06-30 12:59:05 +0000
1703+++ breezy/tests/per_workingtree/test_changes_from.py 2020-05-06 02:56:30 +0000
1704@@ -16,6 +16,8 @@
1705
1706 """Test Tree.changes_from() for WorkingTree specific scenarios."""
1707
1708+import socket
1709+
1710 from breezy import revision
1711 from breezy.tests.per_workingtree import TestCaseWithWorkingTree
1712
1713@@ -54,3 +56,19 @@
1714 self.assertEqual([], d.renamed)
1715 self.assertEqual([], d.copied)
1716 self.assertEqual([], d.modified)
1717+
1718+ def test_socket(self):
1719+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1720+ s.bind('tree/socketpath')
1721+ s.listen(1)
1722+ empty_tree = self.tree.branch.repository.revision_tree(
1723+ revision.NULL_REVISION)
1724+ d = self.tree.changes_from(
1725+ empty_tree, specific_files=['socketpath'],
1726+ want_unversioned=True)
1727+ self.assertEqual([], d.added)
1728+ self.assertEqual([], d.removed)
1729+ self.assertEqual([], d.renamed)
1730+ self.assertEqual([], d.copied)
1731+ self.assertEqual([], d.modified)
1732+ self.assertIn([x.path[1] for x in d.unversioned], [['socketpath'], []])
1733
1734=== modified file 'breezy/tests/test_bzrdir.py'
1735--- breezy/tests/test_bzrdir.py 2020-01-19 03:22:04 +0000
1736+++ breezy/tests/test_bzrdir.py 2020-05-06 02:56:30 +0000
1737@@ -821,6 +821,20 @@
1738 self.assertEqual(os.path.realpath('topdir/foo'),
1739 self.local_branch_path(branch))
1740
1741+ def test_open_tree_or_branch_named(self):
1742+ tree = self.make_branch_and_tree('topdir')
1743+ self.assertRaises(
1744+ NotBranchError,
1745+ bzrdir.BzrDir.open_tree_or_branch, 'topdir', name='missing')
1746+ tree.branch.controldir.create_branch('named')
1747+ tree, branch = bzrdir.BzrDir.open_tree_or_branch('topdir', name='named')
1748+ self.assertEqual(os.path.realpath('topdir'),
1749+ os.path.realpath(tree.basedir))
1750+ self.assertEqual(os.path.realpath('topdir'),
1751+ self.local_branch_path(branch))
1752+ self.assertEqual(branch.name, 'named')
1753+ self.assertIs(tree.controldir, branch.controldir)
1754+
1755 def test_open_from_transport(self):
1756 # transport pointing at bzrdir should give a bzrdir with root transport
1757 # set to the given transport
1758
1759=== modified file 'breezy/tests/test_config.py'
1760--- breezy/tests/test_config.py 2020-02-07 02:14:30 +0000
1761+++ breezy/tests/test_config.py 2020-05-06 02:56:30 +0000
1762@@ -1481,6 +1481,13 @@
1763 self.assertEqual("Robert Collins <robertc@example.org>",
1764 my_config.username())
1765
1766+ def test_BRZ_EMAIL_OVERRIDES(self):
1767+ self.overrideEnv('BZR_EMAIL', "Robert Collins <robertc@example.org>")
1768+ branch = FakeBranch()
1769+ my_config = config.BranchConfig(branch)
1770+ self.assertEqual("Robert Collins <robertc@example.org>",
1771+ my_config.username())
1772+
1773 def test_get_user_option_global(self):
1774 my_config = self.get_branch_config(global_config=sample_config_text)
1775 self.assertEqual('something',
1776@@ -4645,8 +4652,16 @@
1777
1778 def test_default_email_uses_BRZ_EMAIL(self):
1779 conf = config.MemoryStack(b'email=jelmer@debian.org')
1780- # BRZ_EMAIL takes precedence over EMAIL
1781+ # BRZ_EMAIL takes precedence over BZR_EMAIL and EMAIL
1782 self.overrideEnv('BRZ_EMAIL', 'jelmer@samba.org')
1783+ self.overrideEnv('BZR_EMAIL', 'jelmer@jelmer.uk')
1784+ self.overrideEnv('EMAIL', 'jelmer@apache.org')
1785+ self.assertEqual('jelmer@samba.org', conf.get('email'))
1786+
1787+ def test_default_email_uses_BZR_EMAIL(self):
1788+ conf = config.MemoryStack(b'email=jelmer@debian.org')
1789+ # BZR_EMAIL takes precedence over EMAIL
1790+ self.overrideEnv('BZR_EMAIL', 'jelmer@samba.org')
1791 self.overrideEnv('EMAIL', 'jelmer@apache.org')
1792 self.assertEqual('jelmer@samba.org', conf.get('email'))
1793
1794
1795=== modified file 'breezy/tests/test_smart.py'
1796--- breezy/tests/test_smart.py 2020-01-18 02:42:17 +0000
1797+++ breezy/tests/test_smart.py 2020-05-06 02:56:30 +0000
1798@@ -485,6 +485,19 @@
1799 (b"success", ), local_result)
1800 self.assertEqual(expected, request.execute(b''))
1801
1802+ def test_ref(self):
1803+ backing = self.get_transport()
1804+ dir = self.make_controldir('foo')
1805+ b = self.make_branch('bar')
1806+ dir.set_branch_reference(b)
1807+ request_class = smart_dir.SmartServerBzrDirRequestGetBranches
1808+ request = request_class(backing)
1809+ local_result = bencode.bencode(
1810+ {b"": (b"ref", b'../bar/')})
1811+ expected = smart_req.SuccessfulSmartServerResponse(
1812+ (b"success", ), local_result)
1813+ self.assertEqual(expected, request.execute(b'foo'))
1814+
1815 def test_empty(self):
1816 backing = self.get_transport()
1817 self.make_controldir('.')
1818
1819=== modified file 'breezy/transport/http/__init__.py'
1820--- breezy/transport/http/__init__.py 2020-02-18 01:57:45 +0000
1821+++ breezy/transport/http/__init__.py 2020-05-06 02:56:30 +0000
1822@@ -1811,10 +1811,14 @@
1823 report_activity=self._report_activity, ca_certs=ca_certs)
1824
1825 def request(self, method, url, fields=None, headers=None, **urlopen_kw):
1826+ body = urlopen_kw.pop('body', None)
1827 if fields is not None:
1828 data = urlencode(fields).encode()
1829+ if body is not None:
1830+ raise ValueError(
1831+ 'body and fields are mutually exclusive')
1832 else:
1833- data = urlopen_kw.pop('body', None)
1834+ data = body
1835 if headers is None:
1836 headers = {}
1837 request = Request(method, url, data, headers)
1838
1839=== modified file 'brz'
1840--- brz 2020-02-18 01:57:45 +0000
1841+++ brz 2020-05-06 02:56:30 +0000
1842@@ -78,30 +78,8 @@
1843 breezy._format_version_tuple(_script_version),
1844 __file__))
1845
1846-
1847-import breezy.breakin
1848-breezy.breakin.hook_debugger_to_signal()
1849-
1850-import breezy.commands
1851-import breezy.trace
1852-
1853-
1854 if __name__ == '__main__':
1855- with breezy.initialize():
1856- exit_val = breezy.commands.main()
1857- if profiling:
1858- profile_imports.log_stack_info(sys.stderr)
1859-
1860- # By this point we really have completed everything we want to do, and
1861- # there's no point doing any additional cleanup. Abruptly exiting here
1862- # stops any background threads getting into trouble as code is unloaded,
1863- # and it may also be slightly faster, through avoiding gc of objects that
1864- # are just about to be discarded anyhow. This does mean that atexit hooks
1865- # won't run but we don't use them. Also file buffers won't be flushed,
1866- # but our policy is to always close files from a finally block. -- mbp 20070215
1867- exitfunc = getattr(sys, "exitfunc", None)
1868- if exitfunc is not None:
1869- exitfunc()
1870- os._exit(exit_val)
1871+ from breezy.__main__ import main
1872+ main()
1873 else:
1874 raise ImportError("The brz script cannot be imported.")
1875
1876=== modified file 'doc/en/release-notes/brz-3.1.txt'
1877--- doc/en/release-notes/brz-3.1.txt 2020-03-21 20:56:33 +0000
1878+++ doc/en/release-notes/brz-3.1.txt 2020-05-06 02:56:30 +0000
1879@@ -73,6 +73,20 @@
1880 * When pushing to Git repositories, symrefs are now followed.
1881 (Jelmer Vernooij, #1800393)
1882
1883+ * New ``brz clone`` command, which clones everything under
1884+ a control directory. I.e. all colocated branches, like
1885+ ``git clone``. (Jelmer Vernooij, #831939)
1886+
1887+ * ``brz sprout`` is now an alias for ``brz branch``.
1888+ (Jelmer Vernooij)
1889+
1890+ * ``brz branch`` now accepts a ``-b`` flag with the
1891+ name of the colocated branch to sprout.
1892+ (Jelmer Vernooij, #1869977)
1893+
1894+ * Add a ``breezy.__main__`` module so that
1895+ ``python3 -m breezy`` works. (Jelmer Vernooij)
1896+
1897 Improvements
1898 ************
1899
1900@@ -117,6 +131,10 @@
1901 * ``.git/config`` is now consulted to determine the users' identity
1902 for commits, and the gpg_signing_key. (Jelmer Vernooij)
1903
1904+ * Ignore special files (fifos, block/character devices, sockets)
1905+ when finding changes in Git working trees. (Jelmer Vernooij, #1857244)
1906+
1907+
1908 Bug Fixes
1909 *********
1910
1911@@ -154,6 +172,12 @@
1912 * Fix ``setup_ui=False`` when initializing Breezy.
1913 (Jelmer Vernooij, #1852647)
1914
1915+* Fix backwards compatibility with Bazaar by supporting the
1916+ $BZR_EMAIL variable. (Jelmer Vernooij, #1869178)
1917+
1918+* Cope with non-ascii characters in Git signatures.
1919+ (Jelmer Vernooij, #1869533)
1920+
1921 Documentation
1922 *************
1923
1924
1925=== modified file 'doc/en/user-guide/setting_up_email.txt'
1926--- doc/en/user-guide/setting_up_email.txt 2017-07-30 16:59:50 +0000
1927+++ doc/en/user-guide/setting_up_email.txt 2020-05-06 02:56:30 +0000
1928@@ -30,7 +30,9 @@
1929
1930 The order of precedence is
1931
1932- 1. If the ``BZR_EMAIL`` environment variable is set.
1933+ 1. If the ``BRZ_EMAIL`` environment variable is set.
1934+ #. If the ``BZR_EMAIL`` environment variable is set, for backwards
1935+ compatibility with Bazaar.
1936 #. If an email is set for your current branch in the ``locations.conf``
1937 file.
1938 #. If an email is set four your current branch in the
1939@@ -90,7 +92,7 @@
1940
1941 Setting email via environment variable
1942 --------------------------------------
1943-The final method Breezy will use is checking for the ``BZR_EMAIL``
1944+The final method Breezy will use is checking for the ``BRZ_EMAIL``
1945 and ``EMAIL`` environment variables. Generally, you would use this
1946 method to override the email in a script context. If you would like
1947 to set a general default, then please see the ini methods above.

Subscribers

People subscribed via source and target branches