Merge lp:~jelmer/brz/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/merge-3.1
Merge into: lp:brz
Diff against target: 452 lines (+129/-41)
13 files modified
breezy/git/branch.py (+20/-18)
breezy/git/dir.py (+16/-5)
breezy/git/remote.py (+4/-2)
breezy/git/repository.py (+7/-0)
breezy/git/tests/test_remote.py (+10/-0)
breezy/git/transform.py (+1/-1)
breezy/plugins/github/hoster.py (+2/-2)
breezy/plugins/gitlab/hoster.py (+55/-9)
breezy/plugins/propose/cmds.py (+1/-1)
breezy/propose.py (+5/-1)
breezy/tests/per_branch/test_http.py (+1/-1)
breezy/tests/test_propose.py (+6/-0)
breezy/transport/http/urllib.py (+1/-1)
To merge this branch: bzr merge lp:~jelmer/brz/merge-3.1
Reviewer Review Type Date Requested Status
Jelmer Vernooij Approve
Review via email: mp+400593@code.launchpad.net

Commit message

Merge lp:brz/3.1.

Description of the change

Merge lp:brz/3.1.

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'breezy/git/branch.py'
--- breezy/git/branch.py 2021-03-22 21:07:08 +0000
+++ breezy/git/branch.py 2021-04-03 12:59:06 +0000
@@ -151,7 +151,7 @@
151 else:151 else:
152 conflicts.append(152 conflicts.append(
153 (tag_name,153 (tag_name,
154 self.repository.lookup_foreign_revision_id(peeled),154 self.source.branch.repository.lookup_foreign_revision_id(peeled),
155 self.target.branch.repository.lookup_foreign_revision_id(155 self.target.branch.repository.lookup_foreign_revision_id(
156 old_refs[ref_name])))156 old_refs[ref_name])))
157 return ret157 return ret
@@ -258,7 +258,7 @@
258 return updates, conflicts258 return updates, conflicts
259259
260 def _merge_to(self, to_tags, source_tag_refs, overwrite=False,260 def _merge_to(self, to_tags, source_tag_refs, overwrite=False,
261 selector=None):261 selector=None, ignore_master=False):
262 unpeeled_map = defaultdict(set)262 unpeeled_map = defaultdict(set)
263 conflicts = []263 conflicts = []
264 updates = {}264 updates = {}
@@ -497,8 +497,9 @@
497 if getattr(self.repository, '_git', None):497 if getattr(self.repository, '_git', None):
498 cs = self.repository._git.get_config_stack()498 cs = self.repository._git.get_config_stack()
499 try:499 try:
500 return cs.get((b"branch", self.name.encode('utf-8')),500 return cs.get(
501 b"nick").decode("utf-8")501 (b"branch", self.name.encode('utf-8')),
502 b"nick").decode("utf-8")
502 except KeyError:503 except KeyError:
503 pass504 pass
504 return self.name or u"HEAD"505 return self.name or u"HEAD"
@@ -517,6 +518,9 @@
517 return "<%s(%r, %r)>" % (self.__class__.__name__, self.repository.base,518 return "<%s(%r, %r)>" % (self.__class__.__name__, self.repository.base,
518 self.name)519 self.name)
519520
521 def set_last_revision(self, revid):
522 raise NotImplementedError(self.set_last_revision)
523
520 def generate_revision_history(self, revid, last_rev=None,524 def generate_revision_history(self, revid, last_rev=None,
521 other_branch=None):525 other_branch=None):
522 if last_rev is not None:526 if last_rev is not None:
@@ -592,7 +596,7 @@
592 try:596 try:
593 ref = cs.get((b"branch", remote), b"merge")597 ref = cs.get((b"branch", remote), b"merge")
594 except KeyError:598 except KeyError:
595 ref = self.ref599 ref = b'HEAD'
596600
597 return git_url_to_bzr_url(location.decode('utf-8'), ref=ref)601 return git_url_to_bzr_url(location.decode('utf-8'), ref=ref)
598602
@@ -601,11 +605,6 @@
601 cs = self.repository._git.get_config_stack()605 cs = self.repository._git.get_config_stack()
602 return self._get_related_merge_branch(cs)606 return self._get_related_merge_branch(cs)
603607
604 def _write_git_config(self, cs):
605 f = BytesIO()
606 cs.write_to_file(f)
607 self.repository._git._put_named_file('config', f.getvalue())
608
609 def set_parent(self, location):608 def set_parent(self, location):
610 cs = self.repository._git.get_config()609 cs = self.repository._git.get_config()
611 remote = self._get_origin(cs)610 remote = self._get_origin(cs)
@@ -613,14 +612,17 @@
613 target_url, branch, ref = bzr_url_to_git_url(location)612 target_url, branch, ref = bzr_url_to_git_url(location)
614 location = urlutils.relative_url(this_url, target_url)613 location = urlutils.relative_url(this_url, target_url)
615 cs.set((b"remote", remote), b"url", location)614 cs.set((b"remote", remote), b"url", location)
616 if branch:615 cs.set((b"remote", remote), b'fetch',
617 cs.set((b"branch", remote), b"merge", branch_name_to_ref(branch))616 b'+refs/heads/*:refs/remotes/%s/*' % remote)
618 elif ref:617 if self.name:
619 cs.set((b"branch", remote), b"merge", ref)618 if branch:
620 else:619 cs.set((b"branch", self.name.encode()), b"merge", branch_name_to_ref(branch))
621 # TODO(jelmer): Maybe unset rather than setting to HEAD?620 elif ref:
622 cs.set((b"branch", remote), b"merge", b'HEAD')621 cs.set((b"branch", self.name.encode()), b"merge", ref)
623 self._write_git_config(cs)622 else:
623 # TODO(jelmer): Maybe unset rather than setting to HEAD?
624 cs.set((b"branch", self.name.encode()), b"merge", b'HEAD')
625 self.repository._write_git_config(cs)
624626
625 def break_lock(self):627 def break_lock(self):
626 raise NotImplementedError(self.break_lock)628 raise NotImplementedError(self.break_lock)
627629
=== modified file 'breezy/git/dir.py'
--- breezy/git/dir.py 2020-07-28 02:11:05 +0000
+++ breezy/git/dir.py 2021-04-03 12:59:06 +0000
@@ -233,13 +233,13 @@
233 from ..repository import InterRepository233 from ..repository import InterRepository
234 from .mapping import default_mapping234 from .mapping import default_mapping
235 from ..transport.local import LocalTransport235 from ..transport.local import LocalTransport
236 if stacked_on is not None:236 from .refs import is_peeled
237 raise _mod_branch.UnstackableBranchFormat(
238 self._format, self.user_url)
239 if no_tree:237 if no_tree:
240 format = BareLocalGitControlDirFormat()238 format = BareLocalGitControlDirFormat()
241 else:239 else:
242 format = LocalGitControlDirFormat()240 format = LocalGitControlDirFormat()
241 if stacked_on is not None:
242 raise _mod_branch.UnstackableBranchFormat(format, self.user_url)
243 (target_repo, target_controldir, stacking,243 (target_repo, target_controldir, stacking,
244 repo_policy) = format.initialize_on_transport_ex(244 repo_policy) = format.initialize_on_transport_ex(
245 transport, use_existing_dir=use_existing_dir,245 transport, use_existing_dir=use_existing_dir,
@@ -257,10 +257,21 @@
257 (pack_hint, _, refs) = interrepo.fetch_objects(determine_wants,257 (pack_hint, _, refs) = interrepo.fetch_objects(determine_wants,
258 mapping=default_mapping)258 mapping=default_mapping)
259 for name, val in refs.items():259 for name, val in refs.items():
260 target_git_repo.refs[name] = val260 if is_peeled(name):
261 continue
262 if val in target_git_repo.object_store:
263 target_git_repo.refs[name] = val
261 result_dir = LocalGitDir(transport, target_git_repo, format)264 result_dir = LocalGitDir(transport, target_git_repo, format)
265 result_branch = result_dir.open_branch()
266 try:
267 parent = self.open_branch().get_parent()
268 except brz_errors.InaccessibleParent:
269 pass
270 else:
271 if parent:
272 result_branch.set_parent(parent)
262 if revision_id is not None:273 if revision_id is not None:
263 result_dir.open_branch().set_last_revision(revision_id)274 result_branch.set_last_revision(revision_id)
264 if not no_tree and isinstance(result_dir.root_transport, LocalTransport):275 if not no_tree and isinstance(result_dir.root_transport, LocalTransport):
265 if result_dir.open_repository().make_working_trees():276 if result_dir.open_repository().make_working_trees():
266 try:277 try:
267278
=== modified file 'breezy/git/remote.py'
--- breezy/git/remote.py 2021-03-22 21:07:08 +0000
+++ breezy/git/remote.py 2021-04-03 12:59:06 +0000
@@ -201,6 +201,8 @@
201 return PermissionDenied(url, message)201 return PermissionDenied(url, message)
202 if message.endswith(' does not appear to be a git repository'):202 if message.endswith(' does not appear to be a git repository'):
203 return NotBranchError(url, message)203 return NotBranchError(url, message)
204 if message == 'A repository for this project does not exist yet.':
205 return NotBranchError(url, message)
204 if message == 'pre-receive hook declined':206 if message == 'pre-receive hook declined':
205 return PermissionDenied(url, message)207 return PermissionDenied(url, message)
206 if re.match('(.+) is not a valid repository name',208 if re.match('(.+) is not a valid repository name',
@@ -384,7 +386,7 @@
384386
385 def progress(self, text):387 def progress(self, text):
386 text = text.rstrip(b"\r\n")388 text = text.rstrip(b"\r\n")
387 text = text.decode('utf-8')389 text = text.decode('utf-8', 'surrogateescape')
388 if text.lower().startswith('error: '):390 if text.lower().startswith('error: '):
389 trace.show_error('git: %s', text[len(b'error: '):])391 trace.show_error('git: %s', text[len(b'error: '):])
390 else:392 else:
@@ -499,7 +501,7 @@
499 raise AlreadyBranchError(self.user_url)501 raise AlreadyBranchError(self.user_url)
500 ref_chain, unused_sha = self.get_refs_container().follow(502 ref_chain, unused_sha = self.get_refs_container().follow(
501 self._get_selected_ref(name))503 self._get_selected_ref(name))
502 if ref_chain and ref_chain[0] == b'HEAD':504 if ref_chain and ref_chain[0] == b'HEAD' and len(ref_chain) > 1:
503 refname = ref_chain[1]505 refname = ref_chain[1]
504 repo = self.open_repository()506 repo = self.open_repository()
505 return RemoteGitBranch(self, repo, refname)507 return RemoteGitBranch(self, repo, refname)
506508
=== modified file 'breezy/git/repository.py'
--- breezy/git/repository.py 2021-01-10 00:25:52 +0000
+++ breezy/git/repository.py 2021-04-03 12:59:06 +0000
@@ -17,6 +17,8 @@
1717
18"""An adapter between a Git Repository and a Bazaar Branch"""18"""An adapter between a Git Repository and a Bazaar Branch"""
1919
20from io import BytesIO
21
20from .. import (22from .. import (
21 check,23 check,
22 errors,24 errors,
@@ -258,6 +260,11 @@
258 self.start_write_group()260 self.start_write_group()
259 return builder261 return builder
260262
263 def _write_git_config(self, cs):
264 f = BytesIO()
265 cs.write_to_file(f)
266 self._git._put_named_file('config', f.getvalue())
267
261 def get_file_graph(self):268 def get_file_graph(self):
262 return _mod_graph.Graph(GitFileParentProvider(269 return _mod_graph.Graph(GitFileParentProvider(
263 self._file_change_scanner))270 self._file_change_scanner))
264271
=== modified file 'breezy/git/tests/test_remote.py'
--- breezy/git/tests/test_remote.py 2020-08-10 15:00:17 +0000
+++ breezy/git/tests/test_remote.py 2021-04-03 12:59:06 +0000
@@ -205,6 +205,16 @@
205 [b'=======',205 [b'=======',
206 b'You are not allowed to push code to this project.', b'', b'======'])))206 b'You are not allowed to push code to this project.', b'', b'======'])))
207207
208 def test_notbrancherror_yet(self):
209 self.assertEqual(
210 NotBranchError('http://', 'A repository for this project does not exist yet.'),
211 parse_git_hangup(
212 'http://',
213 HangupException(
214 [b'=======',
215 b'',
216 b'A repository for this project does not exist yet.', b'', b'======'])))
217
208218
209class TestRemoteGitBranchFormat(TestCase):219class TestRemoteGitBranchFormat(TestCase):
210220
211221
=== modified file 'breezy/git/transform.py'
--- breezy/git/transform.py 2021-03-22 21:07:08 +0000
+++ breezy/git/transform.py 2021-04-03 12:59:06 +0000
@@ -1497,7 +1497,7 @@
14971497
1498 def __init__(self, tree, pb=None, case_sensitive=True):1498 def __init__(self, tree, pb=None, case_sensitive=True):
1499 tree.lock_read()1499 tree.lock_read()
1500 limbodir = osutils.mkdtemp(prefix='bzr-limbo-')1500 limbodir = osutils.mkdtemp(prefix='git-limbo-')
1501 DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)1501 DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
15021502
1503 def canonical_path(self, path):1503 def canonical_path(self, path):
15041504
=== modified file 'breezy/plugins/github/hoster.py'
--- breezy/plugins/github/hoster.py 2021-03-22 21:07:08 +0000
+++ breezy/plugins/github/hoster.py 2021-04-03 12:59:06 +0000
@@ -281,11 +281,11 @@
281 headers=headers, body=body, retries=3)281 headers=headers, body=body, retries=3)
282 except UnexpectedHttpStatus as e:282 except UnexpectedHttpStatus as e:
283 if e.code == 401:283 if e.code == 401:
284 raise GitHubLoginRequired(self)284 raise GitHubLoginRequired(self.base_url)
285 else:285 else:
286 raise286 raise
287 if response.status == 401:287 if response.status == 401:
288 raise GitHubLoginRequired(self)288 raise GitHubLoginRequired(self.base_url)
289 return response289 return response
290290
291 def _get_repo(self, owner, repo):291 def _get_repo(self, owner, repo):
292292
=== modified file 'breezy/plugins/gitlab/hoster.py'
--- breezy/plugins/gitlab/hoster.py 2021-03-22 21:07:08 +0000
+++ breezy/plugins/gitlab/hoster.py 2021-04-03 12:59:06 +0000
@@ -42,6 +42,7 @@
42 PrerequisiteBranchUnsupported,42 PrerequisiteBranchUnsupported,
43 SourceNotDerivedFromTarget,43 SourceNotDerivedFromTarget,
44 UnsupportedHoster,44 UnsupportedHoster,
45 HosterLoginRequired,
45 )46 )
4647
4748
@@ -76,13 +77,24 @@
76 self.url = url77 self.url = url
7778
7879
80class GitLabError(errors.BzrError):
81
82 _fmt = "GitLab error: %(error)s"
83
84 def __init__(self, error, full_response):
85 errors.BzrError.__init__(self)
86 self.error = error
87 self.full_response = full_response
88
89
79class GitLabUnprocessable(errors.BzrError):90class GitLabUnprocessable(errors.BzrError):
8091
81 _fmt = "GitLab can not process request: %(error)s."92 _fmt = "GitLab can not process request: %(error)s."
8293
83 def __init__(self, error):94 def __init__(self, error, full_response):
84 errors.BzrError.__init__(self)95 errors.BzrError.__init__(self)
85 self.error = error96 self.error = error
97 self.full_response = full_response
8698
8799
88class DifferentGitLabInstances(errors.BzrError):100class DifferentGitLabInstances(errors.BzrError):
@@ -95,9 +107,9 @@
95 self.target_host = target_host107 self.target_host = target_host
96108
97109
98class GitLabLoginMissing(errors.BzrError):110class GitLabLoginMissing(HosterLoginRequired):
99111
100 _fmt = ("Please log into GitLab")112 _fmt = ("Please log into GitLab instance at %(hoster)s")
101113
102114
103class GitlabLoginError(errors.BzrError):115class GitlabLoginError(errors.BzrError):
@@ -416,8 +428,24 @@
416 _unexpected_status(path, response)428 _unexpected_status(path, response)
417429
418 def create_project(self, project_name):430 def create_project(self, project_name):
419 fields = {'name': project_name}431 if project_name.endswith('.git'):
432 project_name = project_name[:-4]
433 if '/' in project_name:
434 namespace, path = project_name.rsplit('/', 1)
435 else:
436 namespace = None
437 path = project_name
438 fields = {
439 'path': path,
440 'name': path.replace('-', '_'),
441 'namespace_path': namespace,
442 }
420 response = self._api_request('POST', 'projects', fields=fields)443 response = self._api_request('POST', 'projects', fields=fields)
444 if response.status == 400:
445 ret = json.loads(response.data)
446 if ret.get("message", {}).get("path") == ["has already been taken"]:
447 raise errors.AlreadyControlDirError(project_name)
448 raise
421 if response.status == 403:449 if response.status == 403:
422 raise errors.PermissionDenied(response.text)450 raise errors.PermissionDenied(response.text)
423 if response.status not in (200, 201):451 if response.status not in (200, 201):
@@ -556,13 +584,15 @@
556 if labels:584 if labels:
557 fields['labels'] = labels585 fields['labels'] = labels
558 response = self._api_request('POST', path, fields=fields)586 response = self._api_request('POST', path, fields=fields)
587 if response.status == 400:
588 raise GitLabError(data.get('message'), data)
559 if response.status == 403:589 if response.status == 403:
560 raise errors.PermissionDenied(response.text)590 raise errors.PermissionDenied(response.text)
561 if response.status == 409:591 if response.status == 409:
562 raise GitLabConflict(json.loads(response.data).get('message'))592 raise GitLabConflict(json.loads(response.data).get('message'))
563 if response.status == 422:593 if response.status == 422:
564 data = json.loads(response.data)594 data = json.loads(response.data)
565 raise GitLabUnprocessable(data['error'])595 raise GitLabUnprocessable(data.get('error'), data)
566 if response.status != 201:596 if response.status != 201:
567 _unexpected_status(path, response)597 _unexpected_status(path, response)
568 return json.loads(response.data)598 return json.loads(response.data)
@@ -659,13 +689,18 @@
659 return self.base_hostname == host689 return self.base_hostname == host
660690
661 def check(self):691 def check(self):
662 response = self._api_request('GET', 'user')692 try:
693 response = self._api_request('GET', 'user')
694 except errors.UnexpectedHttpStatus as e:
695 if e.code == 401:
696 raise GitLabLoginMissing(self.base_url)
697 raise
663 if response.status == 200:698 if response.status == 200:
664 self._current_user = json.loads(response.data)699 self._current_user = json.loads(response.data)
665 return700 return
666 if response == 401:701 if response.status == 401:
667 if json.loads(response.data) == {"message": "401 Unauthorized"}:702 if json.loads(response.data) == {"message": "401 Unauthorized"}:
668 raise GitLabLoginMissing()703 raise GitLabLoginMissing(self.base_url)
669 else:704 else:
670 raise GitlabLoginError(response.text)705 raise GitlabLoginError(response.text)
671 raise UnsupportedHoster(self.base_url)706 raise UnsupportedHoster(self.base_url)
@@ -681,7 +716,17 @@
681 credentials = get_credentials_by_url(transport.base)716 credentials = get_credentials_by_url(transport.base)
682 if credentials is not None:717 if credentials is not None:
683 return cls(transport, credentials.get('private_token'))718 return cls(transport, credentials.get('private_token'))
684 raise UnsupportedHoster(url)719 try:
720 resp = transport.request(
721 'GET', 'https://%s/api/v4/projects/%s' % (host, urlutils.quote(str(project), '')))
722 except errors.UnexpectedHttpStatus as e:
723 raise UnsupportedHoster(url)
724 else:
725 if not resp.getheader('X-Gitlab-Feature-Category'):
726 raise UnsupportedHoster(url)
727 if resp.status in (200, 401):
728 raise GitLabLoginMissing('https://%s/' % host)
729 raise UnsupportedHoster(url)
685730
686 @classmethod731 @classmethod
687 def iter_instances(cls):732 def iter_instances(cls):
@@ -806,6 +851,7 @@
806 "Source project is not a fork of the target project"]:851 "Source project is not a fork of the target project"]:
807 raise SourceNotDerivedFromTarget(852 raise SourceNotDerivedFromTarget(
808 self.source_branch, self.target_branch)853 self.source_branch, self.target_branch)
854 raise
809 return GitLabMergeProposal(self.gl, merge_request)855 return GitLabMergeProposal(self.gl, merge_request)
810856
811857
812858
=== modified file 'breezy/plugins/propose/cmds.py'
--- breezy/plugins/propose/cmds.py 2020-11-18 02:15:43 +0000
+++ breezy/plugins/propose/cmds.py 2021-04-03 12:59:06 +0000
@@ -300,7 +300,7 @@
300 for l in description.splitlines()])300 for l in description.splitlines()])
301 self.outf.write('\n')301 self.outf.write('\n')
302 except _mod_propose.HosterLoginRequired as e:302 except _mod_propose.HosterLoginRequired as e:
303 warning('Skipping %r, login required.', instance)303 warning('Skipping %s, login required.', instance)
304304
305305
306class cmd_land_merge_proposal(Command):306class cmd_land_merge_proposal(Command):
307307
=== modified file 'breezy/propose.py'
--- breezy/propose.py 2021-03-22 21:07:08 +0000
+++ breezy/propose.py 2021-04-03 12:59:06 +0000
@@ -388,7 +388,11 @@
388388
389def determine_title(description):389def determine_title(description):
390 """Determine the title for a merge proposal based on full description."""390 """Determine the title for a merge proposal based on full description."""
391 firstline = description.splitlines()[0]391 for firstline in description.splitlines():
392 if firstline.strip():
393 break
394 else:
395 raise ValueError
392 try:396 try:
393 i = firstline.index('. ')397 i = firstline.index('. ')
394 except ValueError:398 except ValueError:
395399
=== modified file 'breezy/tests/per_branch/test_http.py'
--- breezy/tests/per_branch/test_http.py 2018-11-11 04:08:32 +0000
+++ breezy/tests/per_branch/test_http.py 2021-04-03 12:59:06 +0000
@@ -76,4 +76,4 @@
76 # from, even if that branch has an invalid parent.76 # from, even if that branch has an invalid parent.
77 branch_b = self.get_branch_with_invalid_parent()77 branch_b = self.get_branch_with_invalid_parent()
78 branch_c = branch_b.controldir.sprout('c').open_branch()78 branch_c = branch_b.controldir.sprout('c').open_branch()
79 self.assertEqual(branch_b.user_url, branch_c.get_parent())79 self.assertEqual(branch_b.base, branch_c.get_parent())
8080
=== modified file 'breezy/tests/test_propose.py'
--- breezy/tests/test_propose.py 2020-10-22 22:14:42 +0000
+++ breezy/tests/test_propose.py 2021-04-03 12:59:06 +0000
@@ -127,3 +127,9 @@
127127
128And here are some more details.128And here are some more details.
129"""))129"""))
130 self.assertEqual('Release version 5.1', determine_title("""\
131
132Release version 5.1
133
134And here are some more details.
135"""))
130136
=== modified file 'breezy/transport/http/urllib.py'
--- breezy/transport/http/urllib.py 2021-01-10 01:22:28 +0000
+++ breezy/transport/http/urllib.py 2021-04-03 12:59:06 +0000
@@ -176,7 +176,7 @@
176 """176 """
177177
178 # Some responses have bodies in which we have no interest178 # Some responses have bodies in which we have no interest
179 _body_ignored_responses = [301, 302, 303, 307, 308, 400, 401, 403, 404, 501]179 _body_ignored_responses = [301, 302, 303, 307, 308, 403, 404, 501]
180180
181 # in finish() below, we may have to discard several MB in the worst181 # in finish() below, we may have to discard several MB in the worst
182 # case. To avoid buffering that much, we read and discard by chunks182 # case. To avoid buffering that much, we read and discard by chunks

Subscribers

People subscribed via source and target branches