Merge lp:~jelmer/brz/push-non-mainline-to-git into lp:brz/3.0

Proposed by Jelmer Vernooij
Status: Superseded
Proposed branch: lp:~jelmer/brz/push-non-mainline-to-git
Merge into: lp:brz/3.0
Diff against target: 1006 lines (+389/-115)
25 files modified
breezy/__init__.py (+1/-1)
breezy/directory_service.py (+2/-2)
breezy/git/branch.py (+72/-36)
breezy/git/tests/test_blackbox.py (+16/-0)
breezy/git/tests/test_urls.py (+23/-4)
breezy/git/urls.py (+39/-12)
breezy/location.py (+33/-2)
breezy/plugins/propose/github.py (+18/-13)
breezy/plugins/propose/gitlabs.py (+23/-18)
breezy/plugins/propose/launchpad.py (+3/-3)
breezy/plugins/propose/propose.py (+10/-3)
breezy/tests/per_branch/test_branch.py (+3/-1)
breezy/tests/per_branch/test_sprout.py (+4/-1)
breezy/tests/test_http_response.py (+5/-2)
breezy/tests/test_location.py (+26/-0)
breezy/tests/test_transport.py (+7/-5)
breezy/transport/remote.py (+7/-3)
breezy/urlutils.py (+2/-2)
brz (+1/-1)
doc/en/index.txt (+1/-1)
doc/en/release-notes/brz-3.0.txt (+4/-0)
doc/en/release-notes/brz-3.1.txt (+62/-0)
doc/en/whats-new/template.txt (+3/-3)
doc/en/whats-new/whats-new-in-3.1.txt (+22/-0)
tools/time_graph.py (+2/-2)
To merge this branch: bzr merge lp:~jelmer/brz/push-non-mainline-to-git
Reviewer Review Type Date Requested Status
Breezy developers Pending
Review via email: mp+368028@code.launchpad.net

This proposal supersedes a proposal from 2019-04-11.

This proposal has been superseded by a proposal from 2019-05-29.

Commit message

Fix pushing non-mainline revisions from bzr to git.

Description of the change

Fix pushing non-mainline revisions from bzr to git.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'breezy/__init__.py'
2--- breezy/__init__.py 2019-03-06 11:06:58 +0000
3+++ breezy/__init__.py 2019-05-29 03:52:43 +0000
4@@ -50,7 +50,7 @@
5 # Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a
6 # releaselevel of 'dev' for unreleased under-development code.
7
8-version_info = (3, 0, 0, 'final', 0)
9+version_info = (3, 1, 0, 'dev', 1)
10
11
12 def _format_version_tuple(version_info):
13
14=== modified file 'breezy/directory_service.py'
15--- breezy/directory_service.py 2019-02-09 03:23:20 +0000
16+++ breezy/directory_service.py 2019-05-29 03:52:43 +0000
17@@ -185,8 +185,8 @@
18
19 def look_up(self, name, url, purpose=None):
20 dir = _mod_controldir.ControlDir.open_containing('.')[0]
21- return urlutils.join_segment_parameters(dir.user_url,
22- {"branch": urlutils.escape(name)})
23+ return urlutils.join_segment_parameters(
24+ dir.user_url, {"branch": urlutils.escape(name)})
25
26
27 directories.register('co:', ColocatedDirectory,
28
29=== modified file 'breezy/git/branch.py'
30--- breezy/git/branch.py 2019-01-19 17:13:53 +0000
31+++ breezy/git/branch.py 2019-05-29 03:52:43 +0000
32@@ -76,7 +76,10 @@
33 from .unpeel_map import (
34 UnpeelMap,
35 )
36-from .urls import git_url_to_bzr_url
37+from .urls import (
38+ git_url_to_bzr_url,
39+ bzr_url_to_git_url,
40+ )
41
42
43 class GitPullResult(branch.PullResult):
44@@ -464,49 +467,76 @@
45 # Git doesn't do stacking (yet...)
46 raise branch.UnstackableBranchFormat(self._format, self.base)
47
48+ def _get_push_origin(self, cs):
49+ """Get the name for the push origin.
50+
51+ The exact behaviour is documented in the git-config(1) manpage.
52+ """
53+ try:
54+ return cs.get((b'branch', self.name.encode('utf-8')), b'pushRemote')
55+ except KeyError:
56+ try:
57+ return cs.get((b'branch', ), b'remote')
58+ except KeyError:
59+ try:
60+ return cs.get((b'branch', self.name.encode('utf-8')), b'remote')
61+ except KeyError:
62+ return b'origin'
63+
64+ def _get_origin(self, cs):
65+ try:
66+ return cs.get((b'branch', self.name.encode('utf-8')), b'remote')
67+ except KeyError:
68+ return b'origin'
69+
70+ def _get_related_push_branch(self, cs):
71+ remote = self._get_push_origin(cs)
72+ try:
73+ location = cs.get((b"remote", remote), b"url")
74+ except KeyError:
75+ return None
76+
77+ return git_url_to_bzr_url(location.decode('utf-8'), ref=self.ref)
78+
79+ def _get_related_merge_branch(self, cs):
80+ remote = self._get_origin(cs)
81+ try:
82+ location = cs.get((b"remote", remote), b"url")
83+ except KeyError:
84+ return None
85+
86+ try:
87+ ref = cs.get((b"branch", remote), b"merge")
88+ except KeyError:
89+ ref = self.ref
90+
91+ return git_url_to_bzr_url(location.decode('utf-8'), ref=ref)
92+
93 def _get_parent_location(self):
94 """See Branch.get_parent()."""
95- # FIXME: Set "origin" url from .git/config ?
96 cs = self.repository._git.get_config_stack()
97- try:
98- location = cs.get((b"remote", b'origin'), b"url")
99- except KeyError:
100- return None
101-
102- params = {}
103- try:
104- ref = cs.get((b"remote", b"origin"), b"merge")
105- except KeyError:
106- pass
107- else:
108- if ref != b'HEAD':
109- try:
110- params['branch'] = urlutils.escape(ref_to_branch_name(ref))
111- except ValueError:
112- params['ref'] = urlutils.quote_from_bytes(ref)
113-
114- url = git_url_to_bzr_url(location.decode('utf-8'))
115- return urlutils.join_segment_parameters(url, params)
116+ return self._get_related_merge_branch(cs)
117+
118+ def _write_git_config(self, cs):
119+ f = BytesIO()
120+ cs.write_to_file(f)
121+ self.repository._git._put_named_file('config', f.getvalue())
122
123 def set_parent(self, location):
124- # FIXME: Set "origin" url in .git/config ?
125 cs = self.repository._git.get_config()
126+ remote = self._get_origin(cs)
127 this_url = urlutils.split_segment_parameters(self.user_url)[0]
128- target_url, target_params = urlutils.split_segment_parameters(location)
129+ target_url, branch, ref = bzr_url_to_git_url(location)
130 location = urlutils.relative_url(this_url, target_url)
131- cs.set((b"remote", b"origin"), b"url", location)
132- if 'branch' in target_params:
133- cs.set((b"remote", b"origin"), b"merge",
134- branch_name_to_ref(target_params['branch']))
135- elif 'ref' in target_params:
136- cs.set((b"remote", b"origin"), b"merge",
137- target_params['ref'])
138+ cs.set((b"remote", remote), b"url", location)
139+ if branch:
140+ cs.set((b"branch", remote), b"merge", branch_name_to_ref(branch))
141+ elif ref:
142+ cs.set((b"branch", remote), b"merge", ref)
143 else:
144 # TODO(jelmer): Maybe unset rather than setting to HEAD?
145- cs.set((b"remote", b"origin"), b"merge", 'HEAD')
146- f = BytesIO()
147- cs.write_to_file(f)
148- self.repository._git._put_named_file('config', f.getvalue())
149+ cs.set((b"branch", remote), b"merge", b'HEAD')
150+ self._write_git_config(cs)
151
152 def break_lock(self):
153 raise NotImplementedError(self.break_lock)
154@@ -720,7 +750,10 @@
155 def get_push_location(self):
156 """See Branch.get_push_location."""
157 push_loc = self.get_config_stack().get('push_location')
158- return push_loc
159+ if push_loc is not None:
160+ return push_loc
161+ cs = self.repository._git.get_config_stack()
162+ return self._get_related_push_branch(cs)
163
164 def set_push_location(self, location):
165 """See Branch.set_push_location."""
166@@ -1221,7 +1254,10 @@
167 if stop_revision is None:
168 (stop_revno, stop_revision) = self.source.last_revision_info()
169 elif stop_revno is None:
170- stop_revno = self.source.revision_id_to_revno(stop_revision)
171+ try:
172+ stop_revno = self.source.revision_id_to_revno(stop_revision)
173+ except errors.NoSuchRevision:
174+ stop_revno = None
175 if not isinstance(stop_revision, bytes):
176 raise TypeError(stop_revision)
177 main_ref = self.target.ref
178
179=== modified file 'breezy/git/tests/test_blackbox.py'
180--- breezy/git/tests/test_blackbox.py 2019-05-28 21:46:53 +0000
181+++ breezy/git/tests/test_blackbox.py 2019-05-29 03:52:43 +0000
182@@ -150,6 +150,22 @@
183 self.assertEqual(b"", output)
184 self.assertTrue(error.endswith(b"Created new branch.\n"))
185
186+ def test_push_lossy_non_mainline(self):
187+ self.run_bzr(['init', '--git', 'bla'])
188+ self.run_bzr(['init', 'foo'])
189+ self.run_bzr(['commit', '--unchanged', '-m', 'bla', 'foo'])
190+ self.run_bzr(['branch', 'foo', 'foo1'])
191+ self.run_bzr(['commit', '--unchanged', '-m', 'bla', 'foo1'])
192+ self.run_bzr(['commit', '--unchanged', '-m', 'bla', 'foo'])
193+ self.run_bzr(['merge', '-d', 'foo', 'foo1'])
194+ self.run_bzr(['commit', '--unchanged', '-m', 'merge', 'foo'])
195+ output, error = self.run_bzr(['push', '--lossy', '-r1.1.1', '-d', 'foo', 'bla'])
196+ self.assertEqual(b"", output)
197+ self.assertEqual(
198+ b'Pushing from a Bazaar to a Git repository. For better '
199+ b'performance, push into a Bazaar repository.\n'
200+ b'Pushed up to revision 2.\n', error)
201+
202 def test_log(self):
203 # Smoke test for "bzr log" in a git repository.
204 self.simple_commit()
205
206=== modified file 'breezy/git/tests/test_urls.py'
207--- breezy/git/tests/test_urls.py 2018-11-11 04:08:32 +0000
208+++ breezy/git/tests/test_urls.py 2019-05-29 03:52:43 +0000
209@@ -42,7 +42,26 @@
210 ('git+ssh://user@foo/bar/path'))
211
212 def test_path(self):
213- self.assertEqual(
214- git_url_to_bzr_url(
215- '/bar/path'),
216- ('/bar/path'))
217+ self.assertEqual(git_url_to_bzr_url('/bar/path'), ('/bar/path'))
218+
219+ def test_with_ref(self):
220+ self.assertEqual(
221+ git_url_to_bzr_url('foo:bar/path', ref=b'HEAD'),
222+ 'git+ssh://foo/bar/path')
223+ self.assertEqual(
224+ git_url_to_bzr_url('foo:bar/path', ref=b'refs/heads/blah'),
225+ 'git+ssh://foo/bar/path,branch=blah')
226+ self.assertEqual(
227+ git_url_to_bzr_url('foo:bar/path', ref=b'refs/tags/blah'),
228+ 'git+ssh://foo/bar/path,ref=refs%2Ftags%2Fblah')
229+
230+ def test_with_branch(self):
231+ self.assertEqual(
232+ git_url_to_bzr_url('foo:bar/path', branch=''),
233+ 'git+ssh://foo/bar/path')
234+ self.assertEqual(
235+ git_url_to_bzr_url('foo:bar/path', branch='foo/blah'),
236+ 'git+ssh://foo/bar/path,branch=foo%2Fblah')
237+ self.assertEqual(
238+ git_url_to_bzr_url('foo:bar/path', branch='blah'),
239+ 'git+ssh://foo/bar/path,branch=blah')
240
241=== modified file 'breezy/git/urls.py'
242--- breezy/git/urls.py 2018-11-11 14:23:06 +0000
243+++ breezy/git/urls.py 2019-05-29 03:52:43 +0000
244@@ -18,7 +18,10 @@
245
246 from __future__ import absolute_import
247
248-from breezy.urlutils import URL, quote
249+from .. import urlutils
250+from .refs import (
251+ ref_to_branch_name,
252+ )
253
254 from dulwich.client import parse_rsync_url
255
256@@ -26,22 +29,46 @@
257 KNOWN_GIT_SCHEMES = ['git+ssh', 'git', 'http', 'https', 'ftp']
258
259
260-def git_url_to_bzr_url(location):
261- url = URL.from_string(location)
262- if (url.scheme not in KNOWN_GIT_SCHEMES
263- and not url.scheme.startswith('chroot-')):
264+def git_url_to_bzr_url(location, branch=None, ref=None):
265+ if branch is not None and ref is not None:
266+ raise ValueError('only specify one of branch or ref')
267+ url = urlutils.URL.from_string(location)
268+ if (url.scheme not in KNOWN_GIT_SCHEMES and
269+ not url.scheme.startswith('chroot-')):
270 try:
271 (username, host, path) = parse_rsync_url(location)
272 except ValueError:
273 return location
274 else:
275- url = URL(
276+ url = urlutils.URL(
277 scheme='git+ssh',
278- quoted_user=(quote(username) if username else None),
279+ quoted_user=(urlutils.quote(username) if username else None),
280 quoted_password=None,
281- quoted_host=quote(host),
282+ quoted_host=urlutils.quote(host),
283 port=None,
284- quoted_path=quote(path, safe="/~"))
285- return str(url)
286- else:
287- return location
288+ quoted_path=urlutils.quote(path, safe="/~"))
289+ location = str(url)
290+ if ref == b'HEAD':
291+ ref = branch = None
292+ if ref:
293+ try:
294+ branch = ref_to_branch_name(ref)
295+ except ValueError:
296+ branch = None
297+ else:
298+ ref = None
299+ if ref or branch:
300+ params = {}
301+ if ref:
302+ params['ref'] = urlutils.quote_from_bytes(ref, safe='')
303+ if branch:
304+ params['branch'] = urlutils.escape(branch, safe='')
305+ location = urlutils.join_segment_parameters(location, params)
306+ return location
307+
308+
309+def bzr_url_to_git_url(location):
310+ target_url, target_params = urlutils.split_segment_parameters(location)
311+ branch = target_params.get('branch')
312+ ref = target_params.get('ref')
313+ return target_url, branch, ref
314
315=== modified file 'breezy/location.py'
316--- breezy/location.py 2019-02-09 03:23:20 +0000
317+++ breezy/location.py 2019-05-29 03:52:43 +0000
318@@ -19,6 +19,8 @@
319
320 from __future__ import absolute_import
321
322+import re
323+
324 from . import (
325 urlutils,
326 )
327@@ -44,6 +46,28 @@
328 hooks = LocationHooks()
329
330
331+def rcp_location_to_url(location, scheme='ssh'):
332+ """Convert a rcp-style location to a URL.
333+
334+ :param location: Location to convert, e.g. "foo:bar"
335+ :param schenme: URL scheme to return, defaults to "ssh"
336+ :return: A URL, e.g. "ssh://foo/bar"
337+ :raises ValueError: if this is not a RCP-style URL
338+ """
339+ m = re.match('^(?P<user>[^@:/]+@)?(?P<host>[^/:]+):(?P<path>.*)$', location)
340+ if not m:
341+ raise ValueError("Not a RCP URL")
342+ if m.group('path').startswith('//'):
343+ raise ValueError("Not a RCP URL: already looks like a URL")
344+ quoted_user = urlutils.quote(m.group('user')[:-1]) if m.group('user') else None
345+ url = urlutils.URL(
346+ scheme=scheme, quoted_user=quoted_user,
347+ port=None, quoted_password=None,
348+ quoted_host=urlutils.quote(m.group('host')),
349+ quoted_path=urlutils.quote(m.group('path')))
350+ return str(url)
351+
352+
353 def location_to_url(location, purpose=None):
354 """Determine a fully qualified URL from a location string.
355
356@@ -65,8 +89,8 @@
357 location = location.encode('ascii')
358 except UnicodeError:
359 if urlutils.is_url(location):
360- raise urlutils.InvalidURL(path=location,
361- extra='URLs must be properly escaped')
362+ raise urlutils.InvalidURL(
363+ path=location, extra='URLs must be properly escaped')
364 location = urlutils.local_path_to_url(location)
365 else:
366 if PY3:
367@@ -75,6 +99,13 @@
368 if location.startswith("file:") and not location.startswith("file://"):
369 return urlutils.join(urlutils.local_path_to_url("."), location[5:])
370
371+ try:
372+ url = rcp_location_to_url(location, scheme="ssh")
373+ except ValueError:
374+ pass
375+ else:
376+ return url
377+
378 if not urlutils.is_url(location):
379 return urlutils.local_path_to_url(location)
380
381
382=== modified file 'breezy/plugins/propose/github.py'
383--- breezy/plugins/propose/github.py 2019-02-14 04:35:05 +0000
384+++ breezy/plugins/propose/github.py 2019-05-29 03:52:43 +0000
385@@ -132,8 +132,7 @@
386 self._pr.edit(state='closed')
387
388
389-def parse_github_url(branch):
390- url = urlutils.split_segment_parameters(branch.user_url)[0]
391+def parse_github_url(url):
392 (scheme, user, password, host, port, path) = urlutils.parse_url(
393 url)
394 if host != 'github.com':
395@@ -141,6 +140,12 @@
396 (owner, repo_name) = path.strip('/').split('/')
397 if repo_name.endswith('.git'):
398 repo_name = repo_name[:-4]
399+ return owner, repo_name
400+
401+
402+def parse_github_branch_url(branch):
403+ url = urlutils.split_segment_parameters(branch.user_url)[0]
404+ owner, repo_name = parse_github_url(url)
405 return owner, repo_name, branch.name
406
407
408@@ -186,7 +191,7 @@
409 owner=None, revision_id=None, overwrite=False,
410 allow_lossy=True):
411 import github
412- base_owner, base_project, base_branch_name = parse_github_url(base_branch)
413+ base_owner, base_project, base_branch_name = parse_github_branch_url(base_branch)
414 base_repo = self.gh.get_repo('%s/%s' % (base_owner, base_project))
415 if owner is None:
416 owner = self.gh.get_user().login
417@@ -222,14 +227,14 @@
418
419 @convert_github_error
420 def get_push_url(self, branch):
421- owner, project, branch_name = parse_github_url(branch)
422+ owner, project, branch_name = parse_github_branch_url(branch)
423 repo = self.gh.get_repo('%s/%s' % (owner, project))
424 return github_url_to_bzr_url(repo.ssh_url, branch_name)
425
426 @convert_github_error
427 def get_derived_branch(self, base_branch, name, project=None, owner=None):
428 import github
429- base_owner, base_project, base_branch_name = parse_github_url(base_branch)
430+ base_owner, base_project, base_branch_name = parse_github_branch_url(base_branch)
431 base_repo = self.gh.get_repo('%s/%s' % (base_owner, base_project))
432 if owner is None:
433 owner = self.gh.get_user().login
434@@ -249,9 +254,9 @@
435 @convert_github_error
436 def iter_proposals(self, source_branch, target_branch, status='open'):
437 (source_owner, source_repo_name, source_branch_name) = (
438- parse_github_url(source_branch))
439+ parse_github_branch_url(source_branch))
440 (target_owner, target_repo_name, target_branch_name) = (
441- parse_github_url(target_branch))
442+ parse_github_branch_url(target_branch))
443 target_repo = self.gh.get_repo(
444 "%s/%s" % (target_owner, target_repo_name))
445 state = {
446@@ -277,18 +282,18 @@
447
448 def hosts(self, branch):
449 try:
450- parse_github_url(branch)
451+ parse_github_branch_url(branch)
452 except NotGitHubUrl:
453 return False
454 else:
455 return True
456
457 @classmethod
458- def probe(cls, branch):
459+ def probe_from_url(cls, url):
460 try:
461- parse_github_url(branch)
462+ parse_github_url(url)
463 except NotGitHubUrl:
464- raise UnsupportedHoster(branch)
465+ raise UnsupportedHoster(url)
466 return cls()
467
468 @classmethod
469@@ -319,9 +324,9 @@
470 self.source_branch = source_branch
471 self.target_branch = target_branch
472 (self.target_owner, self.target_repo_name, self.target_branch_name) = (
473- parse_github_url(self.target_branch))
474+ parse_github_branch_url(self.target_branch))
475 (self.source_owner, self.source_repo_name, self.source_branch_name) = (
476- parse_github_url(self.source_branch))
477+ parse_github_branch_url(self.source_branch))
478
479 def get_infotext(self):
480 """Determine the initial comment for the merge proposal."""
481
482=== modified file 'breezy/plugins/propose/gitlabs.py'
483--- breezy/plugins/propose/gitlabs.py 2019-02-12 23:38:25 +0000
484+++ breezy/plugins/propose/gitlabs.py 2019-05-29 03:52:43 +0000
485@@ -110,17 +110,22 @@
486 raise GitLabLoginMissing()
487
488
489-def parse_gitlab_url(branch):
490- url = urlutils.split_segment_parameters(branch.user_url)[0]
491+def parse_gitlab_url(url):
492 (scheme, user, password, host, port, path) = urlutils.parse_url(
493 url)
494 if scheme not in ('git+ssh', 'https', 'http'):
495- raise NotGitLabUrl(branch.user_url)
496+ raise NotGitLabUrl(url)
497 if not host:
498- raise NotGitLabUrl(branch.user_url)
499+ raise NotGitLabUrl(url)
500 path = path.strip('/')
501 if path.endswith('.git'):
502 path = path[:-4]
503+ return host, path
504+
505+
506+def parse_gitlab_branch_url(branch):
507+ url = urlutils.split_segment_parameters(branch.user_url)[0]
508+ host, path = parse_gitlab_url(url)
509 return host, path, branch.name
510
511
512@@ -183,7 +188,7 @@
513 self.gl = gl
514
515 def get_push_url(self, branch):
516- (host, project_name, branch_name) = parse_gitlab_url(branch)
517+ (host, project_name, branch_name) = parse_gitlab_branch_url(branch)
518 project = self.gl.projects.get(project_name)
519 return gitlab_url_to_bzr_url(
520 project.ssh_url_to_repo, branch_name)
521@@ -192,7 +197,7 @@
522 owner=None, revision_id=None, overwrite=False,
523 allow_lossy=True):
524 import gitlab
525- (host, base_project, base_branch_name) = parse_gitlab_url(base_branch)
526+ (host, base_project, base_branch_name) = parse_gitlab_branch_url(base_branch)
527 self.gl.auth()
528 try:
529 base_project = self.gl.projects.get(base_project)
530@@ -230,7 +235,7 @@
531
532 def get_derived_branch(self, base_branch, name, project=None, owner=None):
533 import gitlab
534- (host, base_project, base_branch_name) = parse_gitlab_url(base_branch)
535+ (host, base_project, base_branch_name) = parse_gitlab_branch_url(base_branch)
536 self.gl.auth()
537 try:
538 base_project = self.gl.projects.get(base_project)
539@@ -258,9 +263,9 @@
540 def iter_proposals(self, source_branch, target_branch, status):
541 import gitlab
542 (source_host, source_project_name, source_branch_name) = (
543- parse_gitlab_url(source_branch))
544+ parse_gitlab_branch_url(source_branch))
545 (target_host, target_project_name, target_branch_name) = (
546- parse_gitlab_url(target_branch))
547+ parse_gitlab_branch_url(target_branch))
548 if source_host != target_host:
549 raise DifferentGitLabInstances(source_host, target_host)
550 self.gl.auth()
551@@ -281,17 +286,17 @@
552
553 def hosts(self, branch):
554 try:
555- (host, project, branch_name) = parse_gitlab_url(branch)
556+ (host, project, branch_name) = parse_gitlab_branch_url(branch)
557 except NotGitLabUrl:
558 return False
559 return (self.gl.url == ('https://%s' % host))
560
561 @classmethod
562- def probe(cls, branch):
563+ def probe_from_url(cls, url):
564 try:
565- (host, project, branch_name) = parse_gitlab_url(branch)
566+ (host, project) = parse_gitlab_url(url)
567 except NotGitLabUrl:
568- raise UnsupportedHoster(branch)
569+ raise UnsupportedHoster(url)
570 import gitlab
571 import requests.exceptions
572 try:
573@@ -299,12 +304,12 @@
574 gl.auth()
575 except requests.exceptions.SSLError:
576 # Well, I guess it could be..
577- raise UnsupportedHoster(branch)
578+ raise UnsupportedHoster(url)
579 except gitlab.GitlabGetError:
580- raise UnsupportedHoster(branch)
581+ raise UnsupportedHoster(url)
582 except gitlab.GitlabHttpError as e:
583 if e.response_code in (404, 405, 503):
584- raise UnsupportedHoster(branch)
585+ raise UnsupportedHoster(url)
586 else:
587 raise
588 return cls(gl)
589@@ -332,10 +337,10 @@
590 self.gl = gl
591 self.source_branch = source_branch
592 (self.source_host, self.source_project_name, self.source_branch_name) = (
593- parse_gitlab_url(source_branch))
594+ parse_gitlab_branch_url(source_branch))
595 self.target_branch = target_branch
596 (self.target_host, self.target_project_name, self.target_branch_name) = (
597- parse_gitlab_url(target_branch))
598+ parse_gitlab_branch_url(target_branch))
599 if self.source_host != self.target_host:
600 raise DifferentGitLabInstances(self.source_host, self.target_host)
601
602
603=== modified file 'breezy/plugins/propose/launchpad.py'
604--- breezy/plugins/propose/launchpad.py 2019-02-17 02:00:36 +0000
605+++ breezy/plugins/propose/launchpad.py 2019-05-29 03:52:43 +0000
606@@ -175,10 +175,10 @@
607 return plausible_launchpad_url(branch.user_url)
608
609 @classmethod
610- def probe(cls, branch):
611- if plausible_launchpad_url(branch.user_url):
612+ def probe_from_url(cls, url):
613+ if plausible_launchpad_url(url):
614 return Launchpad()
615- raise UnsupportedHoster(branch)
616+ raise UnsupportedHoster(url)
617
618 def _get_lp_git_ref_from_branch(self, branch):
619 url, params = urlutils.split_segment_parameters(branch.user_url)
620
621=== modified file 'breezy/plugins/propose/propose.py'
622--- breezy/plugins/propose/propose.py 2019-02-10 00:54:14 +0000
623+++ breezy/plugins/propose/propose.py 2019-05-29 03:52:43 +0000
624@@ -22,6 +22,7 @@
625 errors,
626 hooks,
627 registry,
628+ urlutils,
629 )
630
631
632@@ -225,9 +226,15 @@
633 raise NotImplementedError(self.hosts)
634
635 @classmethod
636- def probe(cls, branch):
637+ def probe_from_branch(cls, branch):
638 """Create a Hoster object if this hoster knows about a branch."""
639- raise NotImplementedError(cls.probe)
640+ url = urlutils.split_segment_parameters(branch.user_url)[0]
641+ return cls.probe_from_url(url)
642+
643+ @classmethod
644+ def probe_from_url(cls, url):
645+ """Create a Hoster object if this hoster knows about a URL."""
646+ raise NotImplementedError(cls.probe_from_url)
647
648 # TODO(jelmer): Some way of cleaning up old branch proposals/branches
649
650@@ -259,7 +266,7 @@
651 return hoster
652 for name, hoster_cls in hosters.items():
653 try:
654- hoster = hoster_cls.probe(branch)
655+ hoster = hoster_cls.probe_from_branch(branch)
656 except UnsupportedHoster:
657 pass
658 else:
659
660=== modified file 'breezy/tests/per_branch/test_branch.py'
661--- breezy/tests/per_branch/test_branch.py 2018-11-11 04:08:32 +0000
662+++ breezy/tests/per_branch/test_branch.py 2019-05-29 03:52:43 +0000
663@@ -164,7 +164,9 @@
664
665 branch_b = wt_a.branch.controldir.sprout(
666 'b', revision_id=rev1).open_branch()
667- self.assertEqual(wt_a.branch.user_url, branch_b.get_parent())
668+ self.assertEqual(
669+ urlutils.split_segment_parameters(wt_a.branch.user_url)[0],
670+ urlutils.split_segment_parameters(branch_b.get_parent())[0])
671 return branch_b
672
673 def test_clone_branch_nickname(self):
674
675=== modified file 'breezy/tests/per_branch/test_sprout.py'
676--- breezy/tests/per_branch/test_sprout.py 2018-11-11 04:08:32 +0000
677+++ breezy/tests/per_branch/test_sprout.py 2019-05-29 03:52:43 +0000
678@@ -23,6 +23,7 @@
679 osutils,
680 revision as _mod_revision,
681 tests,
682+ urlutils,
683 )
684 from breezy.bzr import (
685 branch as _mod_bzrbranch,
686@@ -43,7 +44,9 @@
687 def test_sprout_branch_parent(self):
688 source = self.make_branch('source')
689 target = source.controldir.sprout(self.get_url('target')).open_branch()
690- self.assertEqual(source.user_url, target.get_parent())
691+ self.assertEqual(
692+ urlutils.split_segment_parameters(source.user_url)[0],
693+ urlutils.split_segment_parameters(target.get_parent())[0])
694
695 def test_sprout_uses_bzrdir_branch_format(self):
696 # branch.sprout(bzrdir) is defined as using the branch format selected
697
698=== modified file 'breezy/tests/test_http_response.py'
699--- breezy/tests/test_http_response.py 2018-11-11 04:08:32 +0000
700+++ breezy/tests/test_http_response.py 2019-05-29 03:52:43 +0000
701@@ -39,9 +39,12 @@
702
703 try:
704 import http.client as http_client
705+except ImportError: # python < 3 without future
706+ import httplib as http_client
707+
708+try:
709 parse_headers = http_client.parse_headers
710-except ImportError: # python < 3
711- import httplib as http_client
712+except AttributeError: # python 2
713 parse_headers = http_client.HTTPMessage
714
715 from .. import (
716
717=== modified file 'breezy/tests/test_location.py'
718--- breezy/tests/test_location.py 2019-02-09 02:59:15 +0000
719+++ breezy/tests/test_location.py 2019-05-29 03:52:43 +0000
720@@ -25,6 +25,7 @@
721 from ..location import (
722 hooks as location_hooks,
723 location_to_url,
724+ rcp_location_to_url,
725 )
726
727
728@@ -75,6 +76,11 @@
729 def test_absolute_file_url(self):
730 self.assertEqual("file:///bar", location_to_url("file:/bar"))
731
732+ def test_rcp_url(self):
733+ self.assertEqual(
734+ "ssh://example.com/srv/git/bar",
735+ location_to_url("example.com:/srv/git/bar"))
736+
737 def test_rewrite_hook(self):
738 self.assertEqual(
739 'http://foo.example.com/blah', location_to_url('http://foo.example.com/blah'))
740@@ -84,3 +90,23 @@
741 location_hooks.install_named_hook('rewrite_url', rewrite_url, 'test')
742 self.assertEqual(
743 'http://bar.example.com/bar', location_to_url('http://foo.example.com/foo'))
744+
745+
746+class RCPLocationTests(tests.TestCase):
747+
748+ def test_without_user(self):
749+ self.assertEqual(
750+ "git+ssh://example.com/srv/git/bar",
751+ rcp_location_to_url("example.com:/srv/git/bar", scheme='git+ssh'))
752+ self.assertEqual(
753+ "ssh://example.com/srv/git/bar",
754+ rcp_location_to_url("example.com:/srv/git/bar"))
755+
756+ def test_with_user(self):
757+ self.assertEqual(
758+ "git+ssh://foo@example.com/srv/git/bar",
759+ rcp_location_to_url("foo@example.com:/srv/git/bar", scheme='git+ssh'))
760+
761+ def test_invalid(self):
762+ self.assertRaises(ValueError, rcp_location_to_url, "http://srv/git/bar")
763+ self.assertRaises(ValueError, rcp_location_to_url, "git/bar")
764
765=== modified file 'breezy/tests/test_transport.py'
766--- breezy/tests/test_transport.py 2018-11-23 23:51:34 +0000
767+++ breezy/tests/test_transport.py 2019-05-29 03:52:43 +0000
768@@ -119,11 +119,13 @@
769 try:
770 transport.get_transport_from_url('ssh://fooserver/foo')
771 except errors.UnsupportedProtocol as e:
772- self.assertEqual('Unsupported protocol'
773- ' for url "ssh://fooserver/foo":'
774- ' bzr supports bzr+ssh to operate over ssh,'
775- ' use "bzr+ssh://fooserver/foo".',
776- str(e))
777+ self.assertEqual(
778+ 'Unsupported protocol'
779+ ' for url "ssh://fooserver/foo":'
780+ ' Use bzr+ssh for Bazaar operations over SSH, '
781+ 'e.g. "bzr+ssh://fooserver/foo". Use git+ssh '
782+ 'for Git operations over SSH, e.g. "git+ssh://fooserver/foo".',
783+ str(e))
784 else:
785 self.fail('Did not raise UnsupportedProtocol')
786
787
788=== modified file 'breezy/transport/remote.py'
789--- breezy/transport/remote.py 2018-11-11 04:08:32 +0000
790+++ breezy/transport/remote.py 2019-05-29 03:52:43 +0000
791@@ -602,11 +602,15 @@
792
793
794 class HintingSSHTransport(transport.Transport):
795- """Simple transport that handles ssh:// and points out bzr+ssh://."""
796+ """Simple transport that handles ssh:// and points out bzr+ssh:// and git+ssh://."""
797+
798+ # TODO(jelmer): Implement support for detecting whether the repository at the
799+ # other end is a git or bzr repository.
800
801 def __init__(self, url):
802- raise errors.UnsupportedProtocol(url,
803- 'bzr supports bzr+ssh to operate over ssh, use "bzr+%s".' % url)
804+ raise errors.UnsupportedProtocol(
805+ url, 'Use bzr+ssh for Bazaar operations over SSH, e.g. "bzr+%s". '
806+ 'Use git+ssh for Git operations over SSH, e.g. "git+%s".' % (url, url))
807
808
809 def get_test_permutations():
810
811=== modified file 'breezy/urlutils.py'
812--- breezy/urlutils.py 2019-02-16 02:57:26 +0000
813+++ breezy/urlutils.py 2019-05-29 03:52:43 +0000
814@@ -162,11 +162,11 @@
815 unquote = urlparse.unquote
816
817
818-def escape(relpath):
819+def escape(relpath, safe='/~'):
820 """Escape relpath to be a valid url."""
821 if not isinstance(relpath, str) and sys.version_info[0] == 2:
822 relpath = relpath.encode('utf-8')
823- return quote(relpath, safe='/~')
824+ return quote(relpath, safe=safe)
825
826
827 def file_relpath(base, path):
828
829=== modified file 'brz'
830--- brz 2018-11-18 12:41:12 +0000
831+++ brz 2019-05-29 03:52:43 +0000
832@@ -25,7 +25,7 @@
833 import warnings
834
835 # update this on each release
836-_script_version = (3, 0, 0)
837+_script_version = (3, 1, 0)
838
839 NEED_VERS = (2, 7)
840
841
842=== modified file 'doc/en/index.txt'
843--- doc/en/index.txt 2018-09-14 09:31:24 +0000
844+++ doc/en/index.txt 2019-05-29 03:52:43 +0000
845@@ -10,7 +10,7 @@
846 .. toctree::
847 :maxdepth: 1
848
849- whats-new/whats-new-in-3.0
850+ whats-new/whats-new-in-3.1
851 user-guide/index
852 tutorials/index
853 quick-reference/index
854
855=== modified file 'doc/en/release-notes/brz-3.0.txt'
856--- doc/en/release-notes/brz-3.0.txt 2019-05-28 23:00:41 +0000
857+++ doc/en/release-notes/brz-3.0.txt 2019-05-29 03:52:43 +0000
858@@ -198,6 +198,10 @@
859 * Plugins can now be registered using the 'entrypoints' mechanism in
860 setuptools. (Jelmer Vernooij, #1802647)
861
862+ * The Breezy UI now handles RCP-style URLs and suggests the
863+ user specify either ``git+ssh`` or ``bzr+ssh``.
864+ (Jelmer Vernooij)
865+
866 Improvements
867 ************
868
869
870=== added file 'doc/en/release-notes/brz-3.1.txt'
871--- doc/en/release-notes/brz-3.1.txt 1970-01-01 00:00:00 +0000
872+++ doc/en/release-notes/brz-3.1.txt 2019-05-29 03:52:43 +0000
873@@ -0,0 +1,62 @@
874+####################
875+Breezy Release Notes
876+####################
877+
878+.. toctree::
879+ :maxdepth: 1
880+
881+brz 3.1.0
882+#########
883+
884+:Codename: Nirvana
885+:3.1.0: NOT RELEASED YET
886+
887+External Compatibility Breaks
888+*****************************
889+
890+.. These may require users to change the way they use Breezy.
891+
892+New Features
893+************
894+
895+.. New commands, options, etc that users may wish to try out.
896+
897+Improvements
898+************
899+
900+.. Improvements to existing commands, especially improved performance
901+ or memory usage, or better results.
902+
903+Bug Fixes
904+*********
905+
906+.. Fixes for situations where brz would previously crash or give incorrect
907+ or undesirable results.
908+
909+Documentation
910+*************
911+
912+.. Improved or updated documentation.
913+
914+API Changes
915+***********
916+
917+.. Changes that may require updates in plugins or other code that uses
918+ breezy.
919+
920+Internals
921+*********
922+
923+.. Major internal changes, unlikely to be visible to users or plugin
924+ developers, but interesting for brz developers.
925+
926+Testing
927+*******
928+
929+.. Fixes and changes that are only relevant to brz's test framework and
930+ suite. This can include new facilities for writing tests, fixes to
931+ spurious test failures and changes to the way things should be tested.
932+
933+
934+..
935+ vim: tw=74 ft=rst ff=unix
936
937=== modified file 'doc/en/whats-new/template.txt'
938--- doc/en/whats-new/template.txt 2011-07-18 15:58:50 +0000
939+++ doc/en/whats-new/template.txt 2019-05-29 03:52:43 +0000
940@@ -1,8 +1,8 @@
941 *************************
942-What's New in Bazaar x.y?
943+What's New in Breezy x.y?
944 *************************
945
946-Bazaar x.y is still under development, and will be released in Month Year.
947+Breezy x.y is still under development, and will be released in Month Year.
948 This document accumulates a high level summary of what's changed. See the
949 :doc:`../release-notes/index` for a full list.
950
951@@ -14,7 +14,7 @@
952 For more detailed information on the changes made, see the the
953 :doc:`../release-notes/index` for:
954
955-* the interim bzr `milestones <https://launchpad.net/bzr/x.y>`_
956+* the interim brz `milestones <https://launchpad.net/bzr/x.y>`_
957 * the plugins you use.
958
959 For a summary of changes made in earlier releases, see:
960
961=== added file 'doc/en/whats-new/whats-new-in-3.1.txt'
962--- doc/en/whats-new/whats-new-in-3.1.txt 1970-01-01 00:00:00 +0000
963+++ doc/en/whats-new/whats-new-in-3.1.txt 2019-05-29 03:52:43 +0000
964@@ -0,0 +1,22 @@
965+*************************
966+What's New in Breezy 3.1?
967+*************************
968+
969+Breezy 3.1 is still under development, and will be released when it is
970+ready. This document accumulates a high level summary of what's changed.
971+See the :doc:`../release-notes/index` for a full list.
972+
973+<topics of interest here>
974+
975+Further information
976+*******************
977+
978+For more detailed information on the changes made, see the the
979+:doc:`../release-notes/index` for:
980+
981+* the interim brz `milestones <https://launchpad.net/brz/3.1>`_
982+* the plugins you use.
983+
984+For a summary of changes made in earlier releases, see:
985+
986+* :doc:`whats-new-in-3.0`
987
988=== modified file 'tools/time_graph.py'
989--- tools/time_graph.py 2019-03-06 10:26:59 +0000
990+++ tools/time_graph.py 2019-05-29 03:52:43 +0000
991@@ -74,13 +74,13 @@
992 graph._counters[1] = 0
993 graph._counters[2] = 0
994
995- begin = time.clock()
996+ begin = osutils.perf_counter()
997 g = graph_klass(parent_map)
998 if opts.lsprof is not None:
999 heads = commands.apply_lsprofiled(opts.lsprof, all_heads_comp, g, comb)
1000 else:
1001 heads = all_heads_comp(g, comb)
1002- end = time.clock()
1003+ end = osutils.perf_counter()
1004 return dict(elapsed=(end - begin), graph=g, heads=heads)
1005
1006 def report(name, g):

Subscribers

People subscribed via source and target branches