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 |
Related bugs: |
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
1 | === modified file 'breezy/git/branch.py' | |||
2 | --- breezy/git/branch.py 2021-03-22 21:07:08 +0000 | |||
3 | +++ breezy/git/branch.py 2021-04-03 12:59:06 +0000 | |||
4 | @@ -151,7 +151,7 @@ | |||
5 | 151 | else: | 151 | else: |
6 | 152 | conflicts.append( | 152 | conflicts.append( |
7 | 153 | (tag_name, | 153 | (tag_name, |
9 | 154 | self.repository.lookup_foreign_revision_id(peeled), | 154 | self.source.branch.repository.lookup_foreign_revision_id(peeled), |
10 | 155 | self.target.branch.repository.lookup_foreign_revision_id( | 155 | self.target.branch.repository.lookup_foreign_revision_id( |
11 | 156 | old_refs[ref_name]))) | 156 | old_refs[ref_name]))) |
12 | 157 | return ret | 157 | return ret |
13 | @@ -258,7 +258,7 @@ | |||
14 | 258 | return updates, conflicts | 258 | return updates, conflicts |
15 | 259 | 259 | ||
16 | 260 | def _merge_to(self, to_tags, source_tag_refs, overwrite=False, | 260 | def _merge_to(self, to_tags, source_tag_refs, overwrite=False, |
18 | 261 | selector=None): | 261 | selector=None, ignore_master=False): |
19 | 262 | unpeeled_map = defaultdict(set) | 262 | unpeeled_map = defaultdict(set) |
20 | 263 | conflicts = [] | 263 | conflicts = [] |
21 | 264 | updates = {} | 264 | updates = {} |
22 | @@ -497,8 +497,9 @@ | |||
23 | 497 | if getattr(self.repository, '_git', None): | 497 | if getattr(self.repository, '_git', None): |
24 | 498 | cs = self.repository._git.get_config_stack() | 498 | cs = self.repository._git.get_config_stack() |
25 | 499 | try: | 499 | try: |
28 | 500 | return cs.get((b"branch", self.name.encode('utf-8')), | 500 | return cs.get( |
29 | 501 | b"nick").decode("utf-8") | 501 | (b"branch", self.name.encode('utf-8')), |
30 | 502 | b"nick").decode("utf-8") | ||
31 | 502 | except KeyError: | 503 | except KeyError: |
32 | 503 | pass | 504 | pass |
33 | 504 | return self.name or u"HEAD" | 505 | return self.name or u"HEAD" |
34 | @@ -517,6 +518,9 @@ | |||
35 | 517 | return "<%s(%r, %r)>" % (self.__class__.__name__, self.repository.base, | 518 | return "<%s(%r, %r)>" % (self.__class__.__name__, self.repository.base, |
36 | 518 | self.name) | 519 | self.name) |
37 | 519 | 520 | ||
38 | 521 | def set_last_revision(self, revid): | ||
39 | 522 | raise NotImplementedError(self.set_last_revision) | ||
40 | 523 | |||
41 | 520 | def generate_revision_history(self, revid, last_rev=None, | 524 | def generate_revision_history(self, revid, last_rev=None, |
42 | 521 | other_branch=None): | 525 | other_branch=None): |
43 | 522 | if last_rev is not None: | 526 | if last_rev is not None: |
44 | @@ -592,7 +596,7 @@ | |||
45 | 592 | try: | 596 | try: |
46 | 593 | ref = cs.get((b"branch", remote), b"merge") | 597 | ref = cs.get((b"branch", remote), b"merge") |
47 | 594 | except KeyError: | 598 | except KeyError: |
49 | 595 | ref = self.ref | 599 | ref = b'HEAD' |
50 | 596 | 600 | ||
51 | 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) |
52 | 598 | 602 | ||
53 | @@ -601,11 +605,6 @@ | |||
54 | 601 | cs = self.repository._git.get_config_stack() | 605 | cs = self.repository._git.get_config_stack() |
55 | 602 | return self._get_related_merge_branch(cs) | 606 | return self._get_related_merge_branch(cs) |
56 | 603 | 607 | ||
57 | 604 | def _write_git_config(self, cs): | ||
58 | 605 | f = BytesIO() | ||
59 | 606 | cs.write_to_file(f) | ||
60 | 607 | self.repository._git._put_named_file('config', f.getvalue()) | ||
61 | 608 | |||
62 | 609 | def set_parent(self, location): | 608 | def set_parent(self, location): |
63 | 610 | cs = self.repository._git.get_config() | 609 | cs = self.repository._git.get_config() |
64 | 611 | remote = self._get_origin(cs) | 610 | remote = self._get_origin(cs) |
65 | @@ -613,14 +612,17 @@ | |||
66 | 613 | target_url, branch, ref = bzr_url_to_git_url(location) | 612 | target_url, branch, ref = bzr_url_to_git_url(location) |
67 | 614 | location = urlutils.relative_url(this_url, target_url) | 613 | location = urlutils.relative_url(this_url, target_url) |
68 | 615 | cs.set((b"remote", remote), b"url", location) | 614 | cs.set((b"remote", remote), b"url", location) |
77 | 616 | if branch: | 615 | cs.set((b"remote", remote), b'fetch', |
78 | 617 | cs.set((b"branch", remote), b"merge", branch_name_to_ref(branch)) | 616 | b'+refs/heads/*:refs/remotes/%s/*' % remote) |
79 | 618 | elif ref: | 617 | if self.name: |
80 | 619 | cs.set((b"branch", remote), b"merge", ref) | 618 | if branch: |
81 | 620 | else: | 619 | cs.set((b"branch", self.name.encode()), b"merge", branch_name_to_ref(branch)) |
82 | 621 | # TODO(jelmer): Maybe unset rather than setting to HEAD? | 620 | elif ref: |
83 | 622 | cs.set((b"branch", remote), b"merge", b'HEAD') | 621 | cs.set((b"branch", self.name.encode()), b"merge", ref) |
84 | 623 | self._write_git_config(cs) | 622 | else: |
85 | 623 | # TODO(jelmer): Maybe unset rather than setting to HEAD? | ||
86 | 624 | cs.set((b"branch", self.name.encode()), b"merge", b'HEAD') | ||
87 | 625 | self.repository._write_git_config(cs) | ||
88 | 624 | 626 | ||
89 | 625 | def break_lock(self): | 627 | def break_lock(self): |
90 | 626 | raise NotImplementedError(self.break_lock) | 628 | raise NotImplementedError(self.break_lock) |
91 | 627 | 629 | ||
92 | === modified file 'breezy/git/dir.py' | |||
93 | --- breezy/git/dir.py 2020-07-28 02:11:05 +0000 | |||
94 | +++ breezy/git/dir.py 2021-04-03 12:59:06 +0000 | |||
95 | @@ -233,13 +233,13 @@ | |||
96 | 233 | from ..repository import InterRepository | 233 | from ..repository import InterRepository |
97 | 234 | from .mapping import default_mapping | 234 | from .mapping import default_mapping |
98 | 235 | from ..transport.local import LocalTransport | 235 | from ..transport.local import LocalTransport |
102 | 236 | if stacked_on is not None: | 236 | from .refs import is_peeled |
100 | 237 | raise _mod_branch.UnstackableBranchFormat( | ||
101 | 238 | self._format, self.user_url) | ||
103 | 239 | if no_tree: | 237 | if no_tree: |
104 | 240 | format = BareLocalGitControlDirFormat() | 238 | format = BareLocalGitControlDirFormat() |
105 | 241 | else: | 239 | else: |
106 | 242 | format = LocalGitControlDirFormat() | 240 | format = LocalGitControlDirFormat() |
107 | 241 | if stacked_on is not None: | ||
108 | 242 | raise _mod_branch.UnstackableBranchFormat(format, self.user_url) | ||
109 | 243 | (target_repo, target_controldir, stacking, | 243 | (target_repo, target_controldir, stacking, |
110 | 244 | repo_policy) = format.initialize_on_transport_ex( | 244 | repo_policy) = format.initialize_on_transport_ex( |
111 | 245 | transport, use_existing_dir=use_existing_dir, | 245 | transport, use_existing_dir=use_existing_dir, |
112 | @@ -257,10 +257,21 @@ | |||
113 | 257 | (pack_hint, _, refs) = interrepo.fetch_objects(determine_wants, | 257 | (pack_hint, _, refs) = interrepo.fetch_objects(determine_wants, |
114 | 258 | mapping=default_mapping) | 258 | mapping=default_mapping) |
115 | 259 | for name, val in refs.items(): | 259 | for name, val in refs.items(): |
117 | 260 | target_git_repo.refs[name] = val | 260 | if is_peeled(name): |
118 | 261 | continue | ||
119 | 262 | if val in target_git_repo.object_store: | ||
120 | 263 | target_git_repo.refs[name] = val | ||
121 | 261 | result_dir = LocalGitDir(transport, target_git_repo, format) | 264 | result_dir = LocalGitDir(transport, target_git_repo, format) |
122 | 265 | result_branch = result_dir.open_branch() | ||
123 | 266 | try: | ||
124 | 267 | parent = self.open_branch().get_parent() | ||
125 | 268 | except brz_errors.InaccessibleParent: | ||
126 | 269 | pass | ||
127 | 270 | else: | ||
128 | 271 | if parent: | ||
129 | 272 | result_branch.set_parent(parent) | ||
130 | 262 | if revision_id is not None: | 273 | if revision_id is not None: |
132 | 263 | result_dir.open_branch().set_last_revision(revision_id) | 274 | result_branch.set_last_revision(revision_id) |
133 | 264 | if not no_tree and isinstance(result_dir.root_transport, LocalTransport): | 275 | if not no_tree and isinstance(result_dir.root_transport, LocalTransport): |
134 | 265 | if result_dir.open_repository().make_working_trees(): | 276 | if result_dir.open_repository().make_working_trees(): |
135 | 266 | try: | 277 | try: |
136 | 267 | 278 | ||
137 | === modified file 'breezy/git/remote.py' | |||
138 | --- breezy/git/remote.py 2021-03-22 21:07:08 +0000 | |||
139 | +++ breezy/git/remote.py 2021-04-03 12:59:06 +0000 | |||
140 | @@ -201,6 +201,8 @@ | |||
141 | 201 | return PermissionDenied(url, message) | 201 | return PermissionDenied(url, message) |
142 | 202 | if message.endswith(' does not appear to be a git repository'): | 202 | if message.endswith(' does not appear to be a git repository'): |
143 | 203 | return NotBranchError(url, message) | 203 | return NotBranchError(url, message) |
144 | 204 | if message == 'A repository for this project does not exist yet.': | ||
145 | 205 | return NotBranchError(url, message) | ||
146 | 204 | if message == 'pre-receive hook declined': | 206 | if message == 'pre-receive hook declined': |
147 | 205 | return PermissionDenied(url, message) | 207 | return PermissionDenied(url, message) |
148 | 206 | if re.match('(.+) is not a valid repository name', | 208 | if re.match('(.+) is not a valid repository name', |
149 | @@ -384,7 +386,7 @@ | |||
150 | 384 | 386 | ||
151 | 385 | def progress(self, text): | 387 | def progress(self, text): |
152 | 386 | text = text.rstrip(b"\r\n") | 388 | text = text.rstrip(b"\r\n") |
154 | 387 | text = text.decode('utf-8') | 389 | text = text.decode('utf-8', 'surrogateescape') |
155 | 388 | if text.lower().startswith('error: '): | 390 | if text.lower().startswith('error: '): |
156 | 389 | trace.show_error('git: %s', text[len(b'error: '):]) | 391 | trace.show_error('git: %s', text[len(b'error: '):]) |
157 | 390 | else: | 392 | else: |
158 | @@ -499,7 +501,7 @@ | |||
159 | 499 | raise AlreadyBranchError(self.user_url) | 501 | raise AlreadyBranchError(self.user_url) |
160 | 500 | ref_chain, unused_sha = self.get_refs_container().follow( | 502 | ref_chain, unused_sha = self.get_refs_container().follow( |
161 | 501 | self._get_selected_ref(name)) | 503 | self._get_selected_ref(name)) |
163 | 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: |
164 | 503 | refname = ref_chain[1] | 505 | refname = ref_chain[1] |
165 | 504 | repo = self.open_repository() | 506 | repo = self.open_repository() |
166 | 505 | return RemoteGitBranch(self, repo, refname) | 507 | return RemoteGitBranch(self, repo, refname) |
167 | 506 | 508 | ||
168 | === modified file 'breezy/git/repository.py' | |||
169 | --- breezy/git/repository.py 2021-01-10 00:25:52 +0000 | |||
170 | +++ breezy/git/repository.py 2021-04-03 12:59:06 +0000 | |||
171 | @@ -17,6 +17,8 @@ | |||
172 | 17 | 17 | ||
173 | 18 | """An adapter between a Git Repository and a Bazaar Branch""" | 18 | """An adapter between a Git Repository and a Bazaar Branch""" |
174 | 19 | 19 | ||
175 | 20 | from io import BytesIO | ||
176 | 21 | |||
177 | 20 | from .. import ( | 22 | from .. import ( |
178 | 21 | check, | 23 | check, |
179 | 22 | errors, | 24 | errors, |
180 | @@ -258,6 +260,11 @@ | |||
181 | 258 | self.start_write_group() | 260 | self.start_write_group() |
182 | 259 | return builder | 261 | return builder |
183 | 260 | 262 | ||
184 | 263 | def _write_git_config(self, cs): | ||
185 | 264 | f = BytesIO() | ||
186 | 265 | cs.write_to_file(f) | ||
187 | 266 | self._git._put_named_file('config', f.getvalue()) | ||
188 | 267 | |||
189 | 261 | def get_file_graph(self): | 268 | def get_file_graph(self): |
190 | 262 | return _mod_graph.Graph(GitFileParentProvider( | 269 | return _mod_graph.Graph(GitFileParentProvider( |
191 | 263 | self._file_change_scanner)) | 270 | self._file_change_scanner)) |
192 | 264 | 271 | ||
193 | === modified file 'breezy/git/tests/test_remote.py' | |||
194 | --- breezy/git/tests/test_remote.py 2020-08-10 15:00:17 +0000 | |||
195 | +++ breezy/git/tests/test_remote.py 2021-04-03 12:59:06 +0000 | |||
196 | @@ -205,6 +205,16 @@ | |||
197 | 205 | [b'=======', | 205 | [b'=======', |
198 | 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'======']))) |
199 | 207 | 207 | ||
200 | 208 | def test_notbrancherror_yet(self): | ||
201 | 209 | self.assertEqual( | ||
202 | 210 | NotBranchError('http://', 'A repository for this project does not exist yet.'), | ||
203 | 211 | parse_git_hangup( | ||
204 | 212 | 'http://', | ||
205 | 213 | HangupException( | ||
206 | 214 | [b'=======', | ||
207 | 215 | b'', | ||
208 | 216 | b'A repository for this project does not exist yet.', b'', b'======']))) | ||
209 | 217 | |||
210 | 208 | 218 | ||
211 | 209 | class TestRemoteGitBranchFormat(TestCase): | 219 | class TestRemoteGitBranchFormat(TestCase): |
212 | 210 | 220 | ||
213 | 211 | 221 | ||
214 | === modified file 'breezy/git/transform.py' | |||
215 | --- breezy/git/transform.py 2021-03-22 21:07:08 +0000 | |||
216 | +++ breezy/git/transform.py 2021-04-03 12:59:06 +0000 | |||
217 | @@ -1497,7 +1497,7 @@ | |||
218 | 1497 | 1497 | ||
219 | 1498 | def __init__(self, tree, pb=None, case_sensitive=True): | 1498 | def __init__(self, tree, pb=None, case_sensitive=True): |
220 | 1499 | tree.lock_read() | 1499 | tree.lock_read() |
222 | 1500 | limbodir = osutils.mkdtemp(prefix='bzr-limbo-') | 1500 | limbodir = osutils.mkdtemp(prefix='git-limbo-') |
223 | 1501 | DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive) | 1501 | DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive) |
224 | 1502 | 1502 | ||
225 | 1503 | def canonical_path(self, path): | 1503 | def canonical_path(self, path): |
226 | 1504 | 1504 | ||
227 | === modified file 'breezy/plugins/github/hoster.py' | |||
228 | --- breezy/plugins/github/hoster.py 2021-03-22 21:07:08 +0000 | |||
229 | +++ breezy/plugins/github/hoster.py 2021-04-03 12:59:06 +0000 | |||
230 | @@ -281,11 +281,11 @@ | |||
231 | 281 | headers=headers, body=body, retries=3) | 281 | headers=headers, body=body, retries=3) |
232 | 282 | except UnexpectedHttpStatus as e: | 282 | except UnexpectedHttpStatus as e: |
233 | 283 | if e.code == 401: | 283 | if e.code == 401: |
235 | 284 | raise GitHubLoginRequired(self) | 284 | raise GitHubLoginRequired(self.base_url) |
236 | 285 | else: | 285 | else: |
237 | 286 | raise | 286 | raise |
238 | 287 | if response.status == 401: | 287 | if response.status == 401: |
240 | 288 | raise GitHubLoginRequired(self) | 288 | raise GitHubLoginRequired(self.base_url) |
241 | 289 | return response | 289 | return response |
242 | 290 | 290 | ||
243 | 291 | def _get_repo(self, owner, repo): | 291 | def _get_repo(self, owner, repo): |
244 | 292 | 292 | ||
245 | === modified file 'breezy/plugins/gitlab/hoster.py' | |||
246 | --- breezy/plugins/gitlab/hoster.py 2021-03-22 21:07:08 +0000 | |||
247 | +++ breezy/plugins/gitlab/hoster.py 2021-04-03 12:59:06 +0000 | |||
248 | @@ -42,6 +42,7 @@ | |||
249 | 42 | PrerequisiteBranchUnsupported, | 42 | PrerequisiteBranchUnsupported, |
250 | 43 | SourceNotDerivedFromTarget, | 43 | SourceNotDerivedFromTarget, |
251 | 44 | UnsupportedHoster, | 44 | UnsupportedHoster, |
252 | 45 | HosterLoginRequired, | ||
253 | 45 | ) | 46 | ) |
254 | 46 | 47 | ||
255 | 47 | 48 | ||
256 | @@ -76,13 +77,24 @@ | |||
257 | 76 | self.url = url | 77 | self.url = url |
258 | 77 | 78 | ||
259 | 78 | 79 | ||
260 | 80 | class GitLabError(errors.BzrError): | ||
261 | 81 | |||
262 | 82 | _fmt = "GitLab error: %(error)s" | ||
263 | 83 | |||
264 | 84 | def __init__(self, error, full_response): | ||
265 | 85 | errors.BzrError.__init__(self) | ||
266 | 86 | self.error = error | ||
267 | 87 | self.full_response = full_response | ||
268 | 88 | |||
269 | 89 | |||
270 | 79 | class GitLabUnprocessable(errors.BzrError): | 90 | class GitLabUnprocessable(errors.BzrError): |
271 | 80 | 91 | ||
272 | 81 | _fmt = "GitLab can not process request: %(error)s." | 92 | _fmt = "GitLab can not process request: %(error)s." |
273 | 82 | 93 | ||
275 | 83 | def __init__(self, error): | 94 | def __init__(self, error, full_response): |
276 | 84 | errors.BzrError.__init__(self) | 95 | errors.BzrError.__init__(self) |
277 | 85 | self.error = error | 96 | self.error = error |
278 | 97 | self.full_response = full_response | ||
279 | 86 | 98 | ||
280 | 87 | 99 | ||
281 | 88 | class DifferentGitLabInstances(errors.BzrError): | 100 | class DifferentGitLabInstances(errors.BzrError): |
282 | @@ -95,9 +107,9 @@ | |||
283 | 95 | self.target_host = target_host | 107 | self.target_host = target_host |
284 | 96 | 108 | ||
285 | 97 | 109 | ||
287 | 98 | class GitLabLoginMissing(errors.BzrError): | 110 | class GitLabLoginMissing(HosterLoginRequired): |
288 | 99 | 111 | ||
290 | 100 | _fmt = ("Please log into GitLab") | 112 | _fmt = ("Please log into GitLab instance at %(hoster)s") |
291 | 101 | 113 | ||
292 | 102 | 114 | ||
293 | 103 | class GitlabLoginError(errors.BzrError): | 115 | class GitlabLoginError(errors.BzrError): |
294 | @@ -416,8 +428,24 @@ | |||
295 | 416 | _unexpected_status(path, response) | 428 | _unexpected_status(path, response) |
296 | 417 | 429 | ||
297 | 418 | def create_project(self, project_name): | 430 | def create_project(self, project_name): |
299 | 419 | fields = {'name': project_name} | 431 | if project_name.endswith('.git'): |
300 | 432 | project_name = project_name[:-4] | ||
301 | 433 | if '/' in project_name: | ||
302 | 434 | namespace, path = project_name.rsplit('/', 1) | ||
303 | 435 | else: | ||
304 | 436 | namespace = None | ||
305 | 437 | path = project_name | ||
306 | 438 | fields = { | ||
307 | 439 | 'path': path, | ||
308 | 440 | 'name': path.replace('-', '_'), | ||
309 | 441 | 'namespace_path': namespace, | ||
310 | 442 | } | ||
311 | 420 | response = self._api_request('POST', 'projects', fields=fields) | 443 | response = self._api_request('POST', 'projects', fields=fields) |
312 | 444 | if response.status == 400: | ||
313 | 445 | ret = json.loads(response.data) | ||
314 | 446 | if ret.get("message", {}).get("path") == ["has already been taken"]: | ||
315 | 447 | raise errors.AlreadyControlDirError(project_name) | ||
316 | 448 | raise | ||
317 | 421 | if response.status == 403: | 449 | if response.status == 403: |
318 | 422 | raise errors.PermissionDenied(response.text) | 450 | raise errors.PermissionDenied(response.text) |
319 | 423 | if response.status not in (200, 201): | 451 | if response.status not in (200, 201): |
320 | @@ -556,13 +584,15 @@ | |||
321 | 556 | if labels: | 584 | if labels: |
322 | 557 | fields['labels'] = labels | 585 | fields['labels'] = labels |
323 | 558 | response = self._api_request('POST', path, fields=fields) | 586 | response = self._api_request('POST', path, fields=fields) |
324 | 587 | if response.status == 400: | ||
325 | 588 | raise GitLabError(data.get('message'), data) | ||
326 | 559 | if response.status == 403: | 589 | if response.status == 403: |
327 | 560 | raise errors.PermissionDenied(response.text) | 590 | raise errors.PermissionDenied(response.text) |
328 | 561 | if response.status == 409: | 591 | if response.status == 409: |
329 | 562 | raise GitLabConflict(json.loads(response.data).get('message')) | 592 | raise GitLabConflict(json.loads(response.data).get('message')) |
330 | 563 | if response.status == 422: | 593 | if response.status == 422: |
331 | 564 | data = json.loads(response.data) | 594 | data = json.loads(response.data) |
333 | 565 | raise GitLabUnprocessable(data['error']) | 595 | raise GitLabUnprocessable(data.get('error'), data) |
334 | 566 | if response.status != 201: | 596 | if response.status != 201: |
335 | 567 | _unexpected_status(path, response) | 597 | _unexpected_status(path, response) |
336 | 568 | return json.loads(response.data) | 598 | return json.loads(response.data) |
337 | @@ -659,13 +689,18 @@ | |||
338 | 659 | return self.base_hostname == host | 689 | return self.base_hostname == host |
339 | 660 | 690 | ||
340 | 661 | def check(self): | 691 | def check(self): |
342 | 662 | response = self._api_request('GET', 'user') | 692 | try: |
343 | 693 | response = self._api_request('GET', 'user') | ||
344 | 694 | except errors.UnexpectedHttpStatus as e: | ||
345 | 695 | if e.code == 401: | ||
346 | 696 | raise GitLabLoginMissing(self.base_url) | ||
347 | 697 | raise | ||
348 | 663 | if response.status == 200: | 698 | if response.status == 200: |
349 | 664 | self._current_user = json.loads(response.data) | 699 | self._current_user = json.loads(response.data) |
350 | 665 | return | 700 | return |
352 | 666 | if response == 401: | 701 | if response.status == 401: |
353 | 667 | if json.loads(response.data) == {"message": "401 Unauthorized"}: | 702 | if json.loads(response.data) == {"message": "401 Unauthorized"}: |
355 | 668 | raise GitLabLoginMissing() | 703 | raise GitLabLoginMissing(self.base_url) |
356 | 669 | else: | 704 | else: |
357 | 670 | raise GitlabLoginError(response.text) | 705 | raise GitlabLoginError(response.text) |
358 | 671 | raise UnsupportedHoster(self.base_url) | 706 | raise UnsupportedHoster(self.base_url) |
359 | @@ -681,7 +716,17 @@ | |||
360 | 681 | credentials = get_credentials_by_url(transport.base) | 716 | credentials = get_credentials_by_url(transport.base) |
361 | 682 | if credentials is not None: | 717 | if credentials is not None: |
362 | 683 | return cls(transport, credentials.get('private_token')) | 718 | return cls(transport, credentials.get('private_token')) |
364 | 684 | raise UnsupportedHoster(url) | 719 | try: |
365 | 720 | resp = transport.request( | ||
366 | 721 | 'GET', 'https://%s/api/v4/projects/%s' % (host, urlutils.quote(str(project), ''))) | ||
367 | 722 | except errors.UnexpectedHttpStatus as e: | ||
368 | 723 | raise UnsupportedHoster(url) | ||
369 | 724 | else: | ||
370 | 725 | if not resp.getheader('X-Gitlab-Feature-Category'): | ||
371 | 726 | raise UnsupportedHoster(url) | ||
372 | 727 | if resp.status in (200, 401): | ||
373 | 728 | raise GitLabLoginMissing('https://%s/' % host) | ||
374 | 729 | raise UnsupportedHoster(url) | ||
375 | 685 | 730 | ||
376 | 686 | @classmethod | 731 | @classmethod |
377 | 687 | def iter_instances(cls): | 732 | def iter_instances(cls): |
378 | @@ -806,6 +851,7 @@ | |||
379 | 806 | "Source project is not a fork of the target project"]: | 851 | "Source project is not a fork of the target project"]: |
380 | 807 | raise SourceNotDerivedFromTarget( | 852 | raise SourceNotDerivedFromTarget( |
381 | 808 | self.source_branch, self.target_branch) | 853 | self.source_branch, self.target_branch) |
382 | 854 | raise | ||
383 | 809 | return GitLabMergeProposal(self.gl, merge_request) | 855 | return GitLabMergeProposal(self.gl, merge_request) |
384 | 810 | 856 | ||
385 | 811 | 857 | ||
386 | 812 | 858 | ||
387 | === modified file 'breezy/plugins/propose/cmds.py' | |||
388 | --- breezy/plugins/propose/cmds.py 2020-11-18 02:15:43 +0000 | |||
389 | +++ breezy/plugins/propose/cmds.py 2021-04-03 12:59:06 +0000 | |||
390 | @@ -300,7 +300,7 @@ | |||
391 | 300 | for l in description.splitlines()]) | 300 | for l in description.splitlines()]) |
392 | 301 | self.outf.write('\n') | 301 | self.outf.write('\n') |
393 | 302 | except _mod_propose.HosterLoginRequired as e: | 302 | except _mod_propose.HosterLoginRequired as e: |
395 | 303 | warning('Skipping %r, login required.', instance) | 303 | warning('Skipping %s, login required.', instance) |
396 | 304 | 304 | ||
397 | 305 | 305 | ||
398 | 306 | class cmd_land_merge_proposal(Command): | 306 | class cmd_land_merge_proposal(Command): |
399 | 307 | 307 | ||
400 | === modified file 'breezy/propose.py' | |||
401 | --- breezy/propose.py 2021-03-22 21:07:08 +0000 | |||
402 | +++ breezy/propose.py 2021-04-03 12:59:06 +0000 | |||
403 | @@ -388,7 +388,11 @@ | |||
404 | 388 | 388 | ||
405 | 389 | def determine_title(description): | 389 | def determine_title(description): |
406 | 390 | """Determine the title for a merge proposal based on full description.""" | 390 | """Determine the title for a merge proposal based on full description.""" |
408 | 391 | firstline = description.splitlines()[0] | 391 | for firstline in description.splitlines(): |
409 | 392 | if firstline.strip(): | ||
410 | 393 | break | ||
411 | 394 | else: | ||
412 | 395 | raise ValueError | ||
413 | 392 | try: | 396 | try: |
414 | 393 | i = firstline.index('. ') | 397 | i = firstline.index('. ') |
415 | 394 | except ValueError: | 398 | except ValueError: |
416 | 395 | 399 | ||
417 | === modified file 'breezy/tests/per_branch/test_http.py' | |||
418 | --- breezy/tests/per_branch/test_http.py 2018-11-11 04:08:32 +0000 | |||
419 | +++ breezy/tests/per_branch/test_http.py 2021-04-03 12:59:06 +0000 | |||
420 | @@ -76,4 +76,4 @@ | |||
421 | 76 | # from, even if that branch has an invalid parent. | 76 | # from, even if that branch has an invalid parent. |
422 | 77 | branch_b = self.get_branch_with_invalid_parent() | 77 | branch_b = self.get_branch_with_invalid_parent() |
423 | 78 | branch_c = branch_b.controldir.sprout('c').open_branch() | 78 | branch_c = branch_b.controldir.sprout('c').open_branch() |
425 | 79 | self.assertEqual(branch_b.user_url, branch_c.get_parent()) | 79 | self.assertEqual(branch_b.base, branch_c.get_parent()) |
426 | 80 | 80 | ||
427 | === modified file 'breezy/tests/test_propose.py' | |||
428 | --- breezy/tests/test_propose.py 2020-10-22 22:14:42 +0000 | |||
429 | +++ breezy/tests/test_propose.py 2021-04-03 12:59:06 +0000 | |||
430 | @@ -127,3 +127,9 @@ | |||
431 | 127 | 127 | ||
432 | 128 | And here are some more details. | 128 | And here are some more details. |
433 | 129 | """)) | 129 | """)) |
434 | 130 | self.assertEqual('Release version 5.1', determine_title("""\ | ||
435 | 131 | |||
436 | 132 | Release version 5.1 | ||
437 | 133 | |||
438 | 134 | And here are some more details. | ||
439 | 135 | """)) | ||
440 | 130 | 136 | ||
441 | === modified file 'breezy/transport/http/urllib.py' | |||
442 | --- breezy/transport/http/urllib.py 2021-01-10 01:22:28 +0000 | |||
443 | +++ breezy/transport/http/urllib.py 2021-04-03 12:59:06 +0000 | |||
444 | @@ -176,7 +176,7 @@ | |||
445 | 176 | """ | 176 | """ |
446 | 177 | 177 | ||
447 | 178 | # Some responses have bodies in which we have no interest | 178 | # Some responses have bodies in which we have no interest |
449 | 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] |
450 | 180 | 180 | ||
451 | 181 | # in finish() below, we may have to discard several MB in the worst | 181 | # in finish() below, we may have to discard several MB in the worst |
452 | 182 | # case. To avoid buffering that much, we read and discard by chunks | 182 | # case. To avoid buffering that much, we read and discard by chunks |