Merge lp:~jelmer/brz/push-non-mainline-to-git into lp:brz/3.0
- push-non-mainline-to-git
- Merge into 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 |
Related bugs: |
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): |