##// END OF EJS Templates
api: Add an entry for pr shadow repositories to api functions.
Martin Bornhold -
r893:b7927ff7 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,660 +1,666 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from rhodecode.api import jsonrpc_method, JSONRPCError
24 from rhodecode.api import jsonrpc_method, JSONRPCError
25 from rhodecode.api.utils import (
25 from rhodecode.api.utils import (
26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
28 has_repo_permissions, resolve_ref_or_error)
28 has_repo_permissions, resolve_ref_or_error)
29 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
29 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
30 from rhodecode.lib.base import vcs_operation_context
30 from rhodecode.lib.base import vcs_operation_context
31 from rhodecode.lib.utils2 import str2bool
31 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.model.changeset_status import ChangesetStatusModel
32 from rhodecode.model.changeset_status import ChangesetStatusModel
33 from rhodecode.model.comment import ChangesetCommentsModel
33 from rhodecode.model.comment import ChangesetCommentsModel
34 from rhodecode.model.db import Session, ChangesetStatus
34 from rhodecode.model.db import Session, ChangesetStatus
35 from rhodecode.model.pull_request import PullRequestModel
35 from rhodecode.model.pull_request import PullRequestModel
36 from rhodecode.model.settings import SettingsModel
36 from rhodecode.model.settings import SettingsModel
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 @jsonrpc_method()
41 @jsonrpc_method()
42 def get_pull_request(request, apiuser, repoid, pullrequestid):
42 def get_pull_request(request, apiuser, repoid, pullrequestid):
43 """
43 """
44 Get a pull request based on the given ID.
44 Get a pull request based on the given ID.
45
45
46 :param apiuser: This is filled automatically from the |authtoken|.
46 :param apiuser: This is filled automatically from the |authtoken|.
47 :type apiuser: AuthUser
47 :type apiuser: AuthUser
48 :param repoid: Repository name or repository ID from where the pull
48 :param repoid: Repository name or repository ID from where the pull
49 request was opened.
49 request was opened.
50 :type repoid: str or int
50 :type repoid: str or int
51 :param pullrequestid: ID of the requested pull request.
51 :param pullrequestid: ID of the requested pull request.
52 :type pullrequestid: int
52 :type pullrequestid: int
53
53
54 Example output:
54 Example output:
55
55
56 .. code-block:: bash
56 .. code-block:: bash
57
57
58 "id": <id_given_in_input>,
58 "id": <id_given_in_input>,
59 "result":
59 "result":
60 {
60 {
61 "pull_request_id": "<pull_request_id>",
61 "pull_request_id": "<pull_request_id>",
62 "url": "<url>",
62 "url": "<url>",
63 "title": "<title>",
63 "title": "<title>",
64 "description": "<description>",
64 "description": "<description>",
65 "status" : "<status>",
65 "status" : "<status>",
66 "created_on": "<date_time_created>",
66 "created_on": "<date_time_created>",
67 "updated_on": "<date_time_updated>",
67 "updated_on": "<date_time_updated>",
68 "commit_ids": [
68 "commit_ids": [
69 ...
69 ...
70 "<commit_id>",
70 "<commit_id>",
71 "<commit_id>",
71 "<commit_id>",
72 ...
72 ...
73 ],
73 ],
74 "review_status": "<review_status>",
74 "review_status": "<review_status>",
75 "mergeable": {
75 "mergeable": {
76 "status": "<bool>",
76 "status": "<bool>",
77 "message": "<message>",
77 "message": "<message>",
78 },
78 },
79 "source": {
79 "source": {
80 "clone_url": "<clone_url>",
80 "clone_url": "<clone_url>",
81 "repository": "<repository_name>",
81 "repository": "<repository_name>",
82 "reference":
82 "reference":
83 {
83 {
84 "name": "<name>",
84 "name": "<name>",
85 "type": "<type>",
85 "type": "<type>",
86 "commit_id": "<commit_id>",
86 "commit_id": "<commit_id>",
87 }
87 }
88 },
88 },
89 "target": {
89 "target": {
90 "clone_url": "<clone_url>",
90 "clone_url": "<clone_url>",
91 "repository": "<repository_name>",
91 "repository": "<repository_name>",
92 "reference":
92 "reference":
93 {
93 {
94 "name": "<name>",
94 "name": "<name>",
95 "type": "<type>",
95 "type": "<type>",
96 "commit_id": "<commit_id>",
96 "commit_id": "<commit_id>",
97 }
97 }
98 },
98 },
99 "shadow": {
100 "clone_url": "<clone_url>",
101 },
99 "author": <user_obj>,
102 "author": <user_obj>,
100 "reviewers": [
103 "reviewers": [
101 ...
104 ...
102 {
105 {
103 "user": "<user_obj>",
106 "user": "<user_obj>",
104 "review_status": "<review_status>",
107 "review_status": "<review_status>",
105 }
108 }
106 ...
109 ...
107 ]
110 ]
108 },
111 },
109 "error": null
112 "error": null
110 """
113 """
111 get_repo_or_error(repoid)
114 get_repo_or_error(repoid)
112 pull_request = get_pull_request_or_error(pullrequestid)
115 pull_request = get_pull_request_or_error(pullrequestid)
113 if not PullRequestModel().check_user_read(
116 if not PullRequestModel().check_user_read(
114 pull_request, apiuser, api=True):
117 pull_request, apiuser, api=True):
115 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
118 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
116 data = pull_request.get_api_data()
119 data = pull_request.get_api_data()
117 return data
120 return data
118
121
119
122
120 @jsonrpc_method()
123 @jsonrpc_method()
121 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
124 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
122 """
125 """
123 Get all pull requests from the repository specified in `repoid`.
126 Get all pull requests from the repository specified in `repoid`.
124
127
125 :param apiuser: This is filled automatically from the |authtoken|.
128 :param apiuser: This is filled automatically from the |authtoken|.
126 :type apiuser: AuthUser
129 :type apiuser: AuthUser
127 :param repoid: Repository name or repository ID.
130 :param repoid: Repository name or repository ID.
128 :type repoid: str or int
131 :type repoid: str or int
129 :param status: Only return pull requests with the specified status.
132 :param status: Only return pull requests with the specified status.
130 Valid options are.
133 Valid options are.
131 * ``new`` (default)
134 * ``new`` (default)
132 * ``open``
135 * ``open``
133 * ``closed``
136 * ``closed``
134 :type status: str
137 :type status: str
135
138
136 Example output:
139 Example output:
137
140
138 .. code-block:: bash
141 .. code-block:: bash
139
142
140 "id": <id_given_in_input>,
143 "id": <id_given_in_input>,
141 "result":
144 "result":
142 [
145 [
143 ...
146 ...
144 {
147 {
145 "pull_request_id": "<pull_request_id>",
148 "pull_request_id": "<pull_request_id>",
146 "url": "<url>",
149 "url": "<url>",
147 "title" : "<title>",
150 "title" : "<title>",
148 "description": "<description>",
151 "description": "<description>",
149 "status": "<status>",
152 "status": "<status>",
150 "created_on": "<date_time_created>",
153 "created_on": "<date_time_created>",
151 "updated_on": "<date_time_updated>",
154 "updated_on": "<date_time_updated>",
152 "commit_ids": [
155 "commit_ids": [
153 ...
156 ...
154 "<commit_id>",
157 "<commit_id>",
155 "<commit_id>",
158 "<commit_id>",
156 ...
159 ...
157 ],
160 ],
158 "review_status": "<review_status>",
161 "review_status": "<review_status>",
159 "mergeable": {
162 "mergeable": {
160 "status": "<bool>",
163 "status": "<bool>",
161 "message: "<message>",
164 "message: "<message>",
162 },
165 },
163 "source": {
166 "source": {
164 "clone_url": "<clone_url>",
167 "clone_url": "<clone_url>",
165 "reference":
168 "reference":
166 {
169 {
167 "name": "<name>",
170 "name": "<name>",
168 "type": "<type>",
171 "type": "<type>",
169 "commit_id": "<commit_id>",
172 "commit_id": "<commit_id>",
170 }
173 }
171 },
174 },
172 "target": {
175 "target": {
173 "clone_url": "<clone_url>",
176 "clone_url": "<clone_url>",
174 "reference":
177 "reference":
175 {
178 {
176 "name": "<name>",
179 "name": "<name>",
177 "type": "<type>",
180 "type": "<type>",
178 "commit_id": "<commit_id>",
181 "commit_id": "<commit_id>",
179 }
182 }
180 },
183 },
184 "shadow": {
185 "clone_url": "<clone_url>",
186 },
181 "author": <user_obj>,
187 "author": <user_obj>,
182 "reviewers": [
188 "reviewers": [
183 ...
189 ...
184 {
190 {
185 "user": "<user_obj>",
191 "user": "<user_obj>",
186 "review_status": "<review_status>",
192 "review_status": "<review_status>",
187 }
193 }
188 ...
194 ...
189 ]
195 ]
190 }
196 }
191 ...
197 ...
192 ],
198 ],
193 "error": null
199 "error": null
194
200
195 """
201 """
196 repo = get_repo_or_error(repoid)
202 repo = get_repo_or_error(repoid)
197 if not has_superadmin_permission(apiuser):
203 if not has_superadmin_permission(apiuser):
198 _perms = (
204 _perms = (
199 'repository.admin', 'repository.write', 'repository.read',)
205 'repository.admin', 'repository.write', 'repository.read',)
200 has_repo_permissions(apiuser, repoid, repo, _perms)
206 has_repo_permissions(apiuser, repoid, repo, _perms)
201
207
202 status = Optional.extract(status)
208 status = Optional.extract(status)
203 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
209 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
204 data = [pr.get_api_data() for pr in pull_requests]
210 data = [pr.get_api_data() for pr in pull_requests]
205 return data
211 return data
206
212
207
213
208 @jsonrpc_method()
214 @jsonrpc_method()
209 def merge_pull_request(request, apiuser, repoid, pullrequestid,
215 def merge_pull_request(request, apiuser, repoid, pullrequestid,
210 userid=Optional(OAttr('apiuser'))):
216 userid=Optional(OAttr('apiuser'))):
211 """
217 """
212 Merge the pull request specified by `pullrequestid` into its target
218 Merge the pull request specified by `pullrequestid` into its target
213 repository.
219 repository.
214
220
215 :param apiuser: This is filled automatically from the |authtoken|.
221 :param apiuser: This is filled automatically from the |authtoken|.
216 :type apiuser: AuthUser
222 :type apiuser: AuthUser
217 :param repoid: The Repository name or repository ID of the
223 :param repoid: The Repository name or repository ID of the
218 target repository to which the |pr| is to be merged.
224 target repository to which the |pr| is to be merged.
219 :type repoid: str or int
225 :type repoid: str or int
220 :param pullrequestid: ID of the pull request which shall be merged.
226 :param pullrequestid: ID of the pull request which shall be merged.
221 :type pullrequestid: int
227 :type pullrequestid: int
222 :param userid: Merge the pull request as this user.
228 :param userid: Merge the pull request as this user.
223 :type userid: Optional(str or int)
229 :type userid: Optional(str or int)
224
230
225 Example output:
231 Example output:
226
232
227 .. code-block:: bash
233 .. code-block:: bash
228
234
229 "id": <id_given_in_input>,
235 "id": <id_given_in_input>,
230 "result":
236 "result":
231 {
237 {
232 "executed": "<bool>",
238 "executed": "<bool>",
233 "failure_reason": "<int>",
239 "failure_reason": "<int>",
234 "merge_commit_id": "<merge_commit_id>",
240 "merge_commit_id": "<merge_commit_id>",
235 "possible": "<bool>"
241 "possible": "<bool>"
236 },
242 },
237 "error": null
243 "error": null
238
244
239 """
245 """
240 repo = get_repo_or_error(repoid)
246 repo = get_repo_or_error(repoid)
241 if not isinstance(userid, Optional):
247 if not isinstance(userid, Optional):
242 if (has_superadmin_permission(apiuser) or
248 if (has_superadmin_permission(apiuser) or
243 HasRepoPermissionAnyApi('repository.admin')(
249 HasRepoPermissionAnyApi('repository.admin')(
244 user=apiuser, repo_name=repo.repo_name)):
250 user=apiuser, repo_name=repo.repo_name)):
245 apiuser = get_user_or_error(userid)
251 apiuser = get_user_or_error(userid)
246 else:
252 else:
247 raise JSONRPCError('userid is not the same as your user')
253 raise JSONRPCError('userid is not the same as your user')
248
254
249 pull_request = get_pull_request_or_error(pullrequestid)
255 pull_request = get_pull_request_or_error(pullrequestid)
250 if not PullRequestModel().check_user_merge(
256 if not PullRequestModel().check_user_merge(
251 pull_request, apiuser, api=True):
257 pull_request, apiuser, api=True):
252 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
258 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
253 if pull_request.is_closed():
259 if pull_request.is_closed():
254 raise JSONRPCError(
260 raise JSONRPCError(
255 'pull request `%s` merge failed, pull request is closed' % (
261 'pull request `%s` merge failed, pull request is closed' % (
256 pullrequestid,))
262 pullrequestid,))
257
263
258 target_repo = pull_request.target_repo
264 target_repo = pull_request.target_repo
259 extras = vcs_operation_context(
265 extras = vcs_operation_context(
260 request.environ, repo_name=target_repo.repo_name,
266 request.environ, repo_name=target_repo.repo_name,
261 username=apiuser.username, action='push',
267 username=apiuser.username, action='push',
262 scm=target_repo.repo_type)
268 scm=target_repo.repo_type)
263 data = PullRequestModel().merge(pull_request, apiuser, extras=extras)
269 data = PullRequestModel().merge(pull_request, apiuser, extras=extras)
264 if data.executed:
270 if data.executed:
265 PullRequestModel().close_pull_request(
271 PullRequestModel().close_pull_request(
266 pull_request.pull_request_id, apiuser)
272 pull_request.pull_request_id, apiuser)
267
273
268 Session().commit()
274 Session().commit()
269 return data
275 return data
270
276
271
277
272 @jsonrpc_method()
278 @jsonrpc_method()
273 def close_pull_request(request, apiuser, repoid, pullrequestid,
279 def close_pull_request(request, apiuser, repoid, pullrequestid,
274 userid=Optional(OAttr('apiuser'))):
280 userid=Optional(OAttr('apiuser'))):
275 """
281 """
276 Close the pull request specified by `pullrequestid`.
282 Close the pull request specified by `pullrequestid`.
277
283
278 :param apiuser: This is filled automatically from the |authtoken|.
284 :param apiuser: This is filled automatically from the |authtoken|.
279 :type apiuser: AuthUser
285 :type apiuser: AuthUser
280 :param repoid: Repository name or repository ID to which the pull
286 :param repoid: Repository name or repository ID to which the pull
281 request belongs.
287 request belongs.
282 :type repoid: str or int
288 :type repoid: str or int
283 :param pullrequestid: ID of the pull request to be closed.
289 :param pullrequestid: ID of the pull request to be closed.
284 :type pullrequestid: int
290 :type pullrequestid: int
285 :param userid: Close the pull request as this user.
291 :param userid: Close the pull request as this user.
286 :type userid: Optional(str or int)
292 :type userid: Optional(str or int)
287
293
288 Example output:
294 Example output:
289
295
290 .. code-block:: bash
296 .. code-block:: bash
291
297
292 "id": <id_given_in_input>,
298 "id": <id_given_in_input>,
293 "result":
299 "result":
294 {
300 {
295 "pull_request_id": "<int>",
301 "pull_request_id": "<int>",
296 "closed": "<bool>"
302 "closed": "<bool>"
297 },
303 },
298 "error": null
304 "error": null
299
305
300 """
306 """
301 repo = get_repo_or_error(repoid)
307 repo = get_repo_or_error(repoid)
302 if not isinstance(userid, Optional):
308 if not isinstance(userid, Optional):
303 if (has_superadmin_permission(apiuser) or
309 if (has_superadmin_permission(apiuser) or
304 HasRepoPermissionAnyApi('repository.admin')(
310 HasRepoPermissionAnyApi('repository.admin')(
305 user=apiuser, repo_name=repo.repo_name)):
311 user=apiuser, repo_name=repo.repo_name)):
306 apiuser = get_user_or_error(userid)
312 apiuser = get_user_or_error(userid)
307 else:
313 else:
308 raise JSONRPCError('userid is not the same as your user')
314 raise JSONRPCError('userid is not the same as your user')
309
315
310 pull_request = get_pull_request_or_error(pullrequestid)
316 pull_request = get_pull_request_or_error(pullrequestid)
311 if not PullRequestModel().check_user_update(
317 if not PullRequestModel().check_user_update(
312 pull_request, apiuser, api=True):
318 pull_request, apiuser, api=True):
313 raise JSONRPCError(
319 raise JSONRPCError(
314 'pull request `%s` close failed, no permission to close.' % (
320 'pull request `%s` close failed, no permission to close.' % (
315 pullrequestid,))
321 pullrequestid,))
316 if pull_request.is_closed():
322 if pull_request.is_closed():
317 raise JSONRPCError(
323 raise JSONRPCError(
318 'pull request `%s` is already closed' % (pullrequestid,))
324 'pull request `%s` is already closed' % (pullrequestid,))
319
325
320 PullRequestModel().close_pull_request(
326 PullRequestModel().close_pull_request(
321 pull_request.pull_request_id, apiuser)
327 pull_request.pull_request_id, apiuser)
322 Session().commit()
328 Session().commit()
323 data = {
329 data = {
324 'pull_request_id': pull_request.pull_request_id,
330 'pull_request_id': pull_request.pull_request_id,
325 'closed': True,
331 'closed': True,
326 }
332 }
327 return data
333 return data
328
334
329
335
330 @jsonrpc_method()
336 @jsonrpc_method()
331 def comment_pull_request(request, apiuser, repoid, pullrequestid,
337 def comment_pull_request(request, apiuser, repoid, pullrequestid,
332 message=Optional(None), status=Optional(None),
338 message=Optional(None), status=Optional(None),
333 userid=Optional(OAttr('apiuser'))):
339 userid=Optional(OAttr('apiuser'))):
334 """
340 """
335 Comment on the pull request specified with the `pullrequestid`,
341 Comment on the pull request specified with the `pullrequestid`,
336 in the |repo| specified by the `repoid`, and optionally change the
342 in the |repo| specified by the `repoid`, and optionally change the
337 review status.
343 review status.
338
344
339 :param apiuser: This is filled automatically from the |authtoken|.
345 :param apiuser: This is filled automatically from the |authtoken|.
340 :type apiuser: AuthUser
346 :type apiuser: AuthUser
341 :param repoid: The repository name or repository ID.
347 :param repoid: The repository name or repository ID.
342 :type repoid: str or int
348 :type repoid: str or int
343 :param pullrequestid: The pull request ID.
349 :param pullrequestid: The pull request ID.
344 :type pullrequestid: int
350 :type pullrequestid: int
345 :param message: The text content of the comment.
351 :param message: The text content of the comment.
346 :type message: str
352 :type message: str
347 :param status: (**Optional**) Set the approval status of the pull
353 :param status: (**Optional**) Set the approval status of the pull
348 request. Valid options are:
354 request. Valid options are:
349 * not_reviewed
355 * not_reviewed
350 * approved
356 * approved
351 * rejected
357 * rejected
352 * under_review
358 * under_review
353 :type status: str
359 :type status: str
354 :param userid: Comment on the pull request as this user
360 :param userid: Comment on the pull request as this user
355 :type userid: Optional(str or int)
361 :type userid: Optional(str or int)
356
362
357 Example output:
363 Example output:
358
364
359 .. code-block:: bash
365 .. code-block:: bash
360
366
361 id : <id_given_in_input>
367 id : <id_given_in_input>
362 result :
368 result :
363 {
369 {
364 "pull_request_id": "<Integer>",
370 "pull_request_id": "<Integer>",
365 "comment_id": "<Integer>"
371 "comment_id": "<Integer>"
366 }
372 }
367 error : null
373 error : null
368 """
374 """
369 repo = get_repo_or_error(repoid)
375 repo = get_repo_or_error(repoid)
370 if not isinstance(userid, Optional):
376 if not isinstance(userid, Optional):
371 if (has_superadmin_permission(apiuser) or
377 if (has_superadmin_permission(apiuser) or
372 HasRepoPermissionAnyApi('repository.admin')(
378 HasRepoPermissionAnyApi('repository.admin')(
373 user=apiuser, repo_name=repo.repo_name)):
379 user=apiuser, repo_name=repo.repo_name)):
374 apiuser = get_user_or_error(userid)
380 apiuser = get_user_or_error(userid)
375 else:
381 else:
376 raise JSONRPCError('userid is not the same as your user')
382 raise JSONRPCError('userid is not the same as your user')
377
383
378 pull_request = get_pull_request_or_error(pullrequestid)
384 pull_request = get_pull_request_or_error(pullrequestid)
379 if not PullRequestModel().check_user_read(
385 if not PullRequestModel().check_user_read(
380 pull_request, apiuser, api=True):
386 pull_request, apiuser, api=True):
381 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
387 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
382 message = Optional.extract(message)
388 message = Optional.extract(message)
383 status = Optional.extract(status)
389 status = Optional.extract(status)
384 if not message and not status:
390 if not message and not status:
385 raise JSONRPCError('message and status parameter missing')
391 raise JSONRPCError('message and status parameter missing')
386
392
387 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
393 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
388 status is not None):
394 status is not None):
389 raise JSONRPCError('unknown comment status`%s`' % status)
395 raise JSONRPCError('unknown comment status`%s`' % status)
390
396
391 allowed_to_change_status = PullRequestModel().check_user_change_status(
397 allowed_to_change_status = PullRequestModel().check_user_change_status(
392 pull_request, apiuser)
398 pull_request, apiuser)
393 text = message
399 text = message
394 if status and allowed_to_change_status:
400 if status and allowed_to_change_status:
395 st_message = (('Status change %(transition_icon)s %(status)s')
401 st_message = (('Status change %(transition_icon)s %(status)s')
396 % {'transition_icon': '>',
402 % {'transition_icon': '>',
397 'status': ChangesetStatus.get_status_lbl(status)})
403 'status': ChangesetStatus.get_status_lbl(status)})
398 text = message or st_message
404 text = message or st_message
399
405
400 rc_config = SettingsModel().get_all_settings()
406 rc_config = SettingsModel().get_all_settings()
401 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
407 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
402 comment = ChangesetCommentsModel().create(
408 comment = ChangesetCommentsModel().create(
403 text=text,
409 text=text,
404 repo=pull_request.target_repo.repo_id,
410 repo=pull_request.target_repo.repo_id,
405 user=apiuser.user_id,
411 user=apiuser.user_id,
406 pull_request=pull_request.pull_request_id,
412 pull_request=pull_request.pull_request_id,
407 f_path=None,
413 f_path=None,
408 line_no=None,
414 line_no=None,
409 status_change=(ChangesetStatus.get_status_lbl(status)
415 status_change=(ChangesetStatus.get_status_lbl(status)
410 if status and allowed_to_change_status else None),
416 if status and allowed_to_change_status else None),
411 status_change_type=(status
417 status_change_type=(status
412 if status and allowed_to_change_status else None),
418 if status and allowed_to_change_status else None),
413 closing_pr=False,
419 closing_pr=False,
414 renderer=renderer
420 renderer=renderer
415 )
421 )
416
422
417 if allowed_to_change_status and status:
423 if allowed_to_change_status and status:
418 ChangesetStatusModel().set_status(
424 ChangesetStatusModel().set_status(
419 pull_request.target_repo.repo_id,
425 pull_request.target_repo.repo_id,
420 status,
426 status,
421 apiuser.user_id,
427 apiuser.user_id,
422 comment,
428 comment,
423 pull_request=pull_request.pull_request_id
429 pull_request=pull_request.pull_request_id
424 )
430 )
425 Session().flush()
431 Session().flush()
426
432
427 Session().commit()
433 Session().commit()
428 data = {
434 data = {
429 'pull_request_id': pull_request.pull_request_id,
435 'pull_request_id': pull_request.pull_request_id,
430 'comment_id': comment.comment_id,
436 'comment_id': comment.comment_id,
431 'status': status
437 'status': status
432 }
438 }
433 return data
439 return data
434
440
435
441
436 @jsonrpc_method()
442 @jsonrpc_method()
437 def create_pull_request(
443 def create_pull_request(
438 request, apiuser, source_repo, target_repo, source_ref, target_ref,
444 request, apiuser, source_repo, target_repo, source_ref, target_ref,
439 title, description=Optional(''), reviewers=Optional(None)):
445 title, description=Optional(''), reviewers=Optional(None)):
440 """
446 """
441 Creates a new pull request.
447 Creates a new pull request.
442
448
443 Accepts refs in the following formats:
449 Accepts refs in the following formats:
444
450
445 * branch:<branch_name>:<sha>
451 * branch:<branch_name>:<sha>
446 * branch:<branch_name>
452 * branch:<branch_name>
447 * bookmark:<bookmark_name>:<sha> (Mercurial only)
453 * bookmark:<bookmark_name>:<sha> (Mercurial only)
448 * bookmark:<bookmark_name> (Mercurial only)
454 * bookmark:<bookmark_name> (Mercurial only)
449
455
450 :param apiuser: This is filled automatically from the |authtoken|.
456 :param apiuser: This is filled automatically from the |authtoken|.
451 :type apiuser: AuthUser
457 :type apiuser: AuthUser
452 :param source_repo: Set the source repository name.
458 :param source_repo: Set the source repository name.
453 :type source_repo: str
459 :type source_repo: str
454 :param target_repo: Set the target repository name.
460 :param target_repo: Set the target repository name.
455 :type target_repo: str
461 :type target_repo: str
456 :param source_ref: Set the source ref name.
462 :param source_ref: Set the source ref name.
457 :type source_ref: str
463 :type source_ref: str
458 :param target_ref: Set the target ref name.
464 :param target_ref: Set the target ref name.
459 :type target_ref: str
465 :type target_ref: str
460 :param title: Set the pull request title.
466 :param title: Set the pull request title.
461 :type title: str
467 :type title: str
462 :param description: Set the pull request description.
468 :param description: Set the pull request description.
463 :type description: Optional(str)
469 :type description: Optional(str)
464 :param reviewers: Set the new pull request reviewers list.
470 :param reviewers: Set the new pull request reviewers list.
465 :type reviewers: Optional(list)
471 :type reviewers: Optional(list)
466 Accepts username strings or objects of the format:
472 Accepts username strings or objects of the format:
467 {
473 {
468 'username': 'nick', 'reasons': ['original author']
474 'username': 'nick', 'reasons': ['original author']
469 }
475 }
470 """
476 """
471
477
472 source = get_repo_or_error(source_repo)
478 source = get_repo_or_error(source_repo)
473 target = get_repo_or_error(target_repo)
479 target = get_repo_or_error(target_repo)
474 if not has_superadmin_permission(apiuser):
480 if not has_superadmin_permission(apiuser):
475 _perms = ('repository.admin', 'repository.write', 'repository.read',)
481 _perms = ('repository.admin', 'repository.write', 'repository.read',)
476 has_repo_permissions(apiuser, source_repo, source, _perms)
482 has_repo_permissions(apiuser, source_repo, source, _perms)
477
483
478 full_source_ref = resolve_ref_or_error(source_ref, source)
484 full_source_ref = resolve_ref_or_error(source_ref, source)
479 full_target_ref = resolve_ref_or_error(target_ref, target)
485 full_target_ref = resolve_ref_or_error(target_ref, target)
480 source_commit = get_commit_or_error(full_source_ref, source)
486 source_commit = get_commit_or_error(full_source_ref, source)
481 target_commit = get_commit_or_error(full_target_ref, target)
487 target_commit = get_commit_or_error(full_target_ref, target)
482 source_scm = source.scm_instance()
488 source_scm = source.scm_instance()
483 target_scm = target.scm_instance()
489 target_scm = target.scm_instance()
484
490
485 commit_ranges = target_scm.compare(
491 commit_ranges = target_scm.compare(
486 target_commit.raw_id, source_commit.raw_id, source_scm,
492 target_commit.raw_id, source_commit.raw_id, source_scm,
487 merge=True, pre_load=[])
493 merge=True, pre_load=[])
488
494
489 ancestor = target_scm.get_common_ancestor(
495 ancestor = target_scm.get_common_ancestor(
490 target_commit.raw_id, source_commit.raw_id, source_scm)
496 target_commit.raw_id, source_commit.raw_id, source_scm)
491
497
492 if not commit_ranges:
498 if not commit_ranges:
493 raise JSONRPCError('no commits found')
499 raise JSONRPCError('no commits found')
494
500
495 if not ancestor:
501 if not ancestor:
496 raise JSONRPCError('no common ancestor found')
502 raise JSONRPCError('no common ancestor found')
497
503
498 reviewer_objects = Optional.extract(reviewers) or []
504 reviewer_objects = Optional.extract(reviewers) or []
499 if not isinstance(reviewer_objects, list):
505 if not isinstance(reviewer_objects, list):
500 raise JSONRPCError('reviewers should be specified as a list')
506 raise JSONRPCError('reviewers should be specified as a list')
501
507
502 reviewers_reasons = []
508 reviewers_reasons = []
503 for reviewer_object in reviewer_objects:
509 for reviewer_object in reviewer_objects:
504 reviewer_reasons = []
510 reviewer_reasons = []
505 if isinstance(reviewer_object, (basestring, int)):
511 if isinstance(reviewer_object, (basestring, int)):
506 reviewer_username = reviewer_object
512 reviewer_username = reviewer_object
507 else:
513 else:
508 reviewer_username = reviewer_object['username']
514 reviewer_username = reviewer_object['username']
509 reviewer_reasons = reviewer_object.get('reasons', [])
515 reviewer_reasons = reviewer_object.get('reasons', [])
510
516
511 user = get_user_or_error(reviewer_username)
517 user = get_user_or_error(reviewer_username)
512 reviewers_reasons.append((user.user_id, reviewer_reasons))
518 reviewers_reasons.append((user.user_id, reviewer_reasons))
513
519
514 pull_request_model = PullRequestModel()
520 pull_request_model = PullRequestModel()
515 pull_request = pull_request_model.create(
521 pull_request = pull_request_model.create(
516 created_by=apiuser.user_id,
522 created_by=apiuser.user_id,
517 source_repo=source_repo,
523 source_repo=source_repo,
518 source_ref=full_source_ref,
524 source_ref=full_source_ref,
519 target_repo=target_repo,
525 target_repo=target_repo,
520 target_ref=full_target_ref,
526 target_ref=full_target_ref,
521 revisions=reversed(
527 revisions=reversed(
522 [commit.raw_id for commit in reversed(commit_ranges)]),
528 [commit.raw_id for commit in reversed(commit_ranges)]),
523 reviewers=reviewers_reasons,
529 reviewers=reviewers_reasons,
524 title=title,
530 title=title,
525 description=Optional.extract(description)
531 description=Optional.extract(description)
526 )
532 )
527
533
528 Session().commit()
534 Session().commit()
529 data = {
535 data = {
530 'msg': 'Created new pull request `{}`'.format(title),
536 'msg': 'Created new pull request `{}`'.format(title),
531 'pull_request_id': pull_request.pull_request_id,
537 'pull_request_id': pull_request.pull_request_id,
532 }
538 }
533 return data
539 return data
534
540
535
541
536 @jsonrpc_method()
542 @jsonrpc_method()
537 def update_pull_request(
543 def update_pull_request(
538 request, apiuser, repoid, pullrequestid, title=Optional(''),
544 request, apiuser, repoid, pullrequestid, title=Optional(''),
539 description=Optional(''), reviewers=Optional(None),
545 description=Optional(''), reviewers=Optional(None),
540 update_commits=Optional(None), close_pull_request=Optional(None)):
546 update_commits=Optional(None), close_pull_request=Optional(None)):
541 """
547 """
542 Updates a pull request.
548 Updates a pull request.
543
549
544 :param apiuser: This is filled automatically from the |authtoken|.
550 :param apiuser: This is filled automatically from the |authtoken|.
545 :type apiuser: AuthUser
551 :type apiuser: AuthUser
546 :param repoid: The repository name or repository ID.
552 :param repoid: The repository name or repository ID.
547 :type repoid: str or int
553 :type repoid: str or int
548 :param pullrequestid: The pull request ID.
554 :param pullrequestid: The pull request ID.
549 :type pullrequestid: int
555 :type pullrequestid: int
550 :param title: Set the pull request title.
556 :param title: Set the pull request title.
551 :type title: str
557 :type title: str
552 :param description: Update pull request description.
558 :param description: Update pull request description.
553 :type description: Optional(str)
559 :type description: Optional(str)
554 :param reviewers: Update pull request reviewers list with new value.
560 :param reviewers: Update pull request reviewers list with new value.
555 :type reviewers: Optional(list)
561 :type reviewers: Optional(list)
556 :param update_commits: Trigger update of commits for this pull request
562 :param update_commits: Trigger update of commits for this pull request
557 :type: update_commits: Optional(bool)
563 :type: update_commits: Optional(bool)
558 :param close_pull_request: Close this pull request with rejected state
564 :param close_pull_request: Close this pull request with rejected state
559 :type: close_pull_request: Optional(bool)
565 :type: close_pull_request: Optional(bool)
560
566
561 Example output:
567 Example output:
562
568
563 .. code-block:: bash
569 .. code-block:: bash
564
570
565 id : <id_given_in_input>
571 id : <id_given_in_input>
566 result :
572 result :
567 {
573 {
568 "msg": "Updated pull request `63`",
574 "msg": "Updated pull request `63`",
569 "pull_request": <pull_request_object>,
575 "pull_request": <pull_request_object>,
570 "updated_reviewers": {
576 "updated_reviewers": {
571 "added": [
577 "added": [
572 "username"
578 "username"
573 ],
579 ],
574 "removed": []
580 "removed": []
575 },
581 },
576 "updated_commits": {
582 "updated_commits": {
577 "added": [
583 "added": [
578 "<sha1_hash>"
584 "<sha1_hash>"
579 ],
585 ],
580 "common": [
586 "common": [
581 "<sha1_hash>",
587 "<sha1_hash>",
582 "<sha1_hash>",
588 "<sha1_hash>",
583 ],
589 ],
584 "removed": []
590 "removed": []
585 }
591 }
586 }
592 }
587 error : null
593 error : null
588 """
594 """
589
595
590 repo = get_repo_or_error(repoid)
596 repo = get_repo_or_error(repoid)
591 pull_request = get_pull_request_or_error(pullrequestid)
597 pull_request = get_pull_request_or_error(pullrequestid)
592 if not PullRequestModel().check_user_update(
598 if not PullRequestModel().check_user_update(
593 pull_request, apiuser, api=True):
599 pull_request, apiuser, api=True):
594 raise JSONRPCError(
600 raise JSONRPCError(
595 'pull request `%s` update failed, no permission to update.' % (
601 'pull request `%s` update failed, no permission to update.' % (
596 pullrequestid,))
602 pullrequestid,))
597 if pull_request.is_closed():
603 if pull_request.is_closed():
598 raise JSONRPCError(
604 raise JSONRPCError(
599 'pull request `%s` update failed, pull request is closed' % (
605 'pull request `%s` update failed, pull request is closed' % (
600 pullrequestid,))
606 pullrequestid,))
601
607
602 reviewer_objects = Optional.extract(reviewers) or []
608 reviewer_objects = Optional.extract(reviewers) or []
603 if not isinstance(reviewer_objects, list):
609 if not isinstance(reviewer_objects, list):
604 raise JSONRPCError('reviewers should be specified as a list')
610 raise JSONRPCError('reviewers should be specified as a list')
605
611
606 reviewers_reasons = []
612 reviewers_reasons = []
607 reviewer_ids = set()
613 reviewer_ids = set()
608 for reviewer_object in reviewer_objects:
614 for reviewer_object in reviewer_objects:
609 reviewer_reasons = []
615 reviewer_reasons = []
610 if isinstance(reviewer_object, (int, basestring)):
616 if isinstance(reviewer_object, (int, basestring)):
611 reviewer_username = reviewer_object
617 reviewer_username = reviewer_object
612 else:
618 else:
613 reviewer_username = reviewer_object['username']
619 reviewer_username = reviewer_object['username']
614 reviewer_reasons = reviewer_object.get('reasons', [])
620 reviewer_reasons = reviewer_object.get('reasons', [])
615
621
616 user = get_user_or_error(reviewer_username)
622 user = get_user_or_error(reviewer_username)
617 reviewer_ids.add(user.user_id)
623 reviewer_ids.add(user.user_id)
618 reviewers_reasons.append((user.user_id, reviewer_reasons))
624 reviewers_reasons.append((user.user_id, reviewer_reasons))
619
625
620 title = Optional.extract(title)
626 title = Optional.extract(title)
621 description = Optional.extract(description)
627 description = Optional.extract(description)
622 if title or description:
628 if title or description:
623 PullRequestModel().edit(
629 PullRequestModel().edit(
624 pull_request, title or pull_request.title,
630 pull_request, title or pull_request.title,
625 description or pull_request.description)
631 description or pull_request.description)
626 Session().commit()
632 Session().commit()
627
633
628 commit_changes = {"added": [], "common": [], "removed": []}
634 commit_changes = {"added": [], "common": [], "removed": []}
629 if str2bool(Optional.extract(update_commits)):
635 if str2bool(Optional.extract(update_commits)):
630 if PullRequestModel().has_valid_update_type(pull_request):
636 if PullRequestModel().has_valid_update_type(pull_request):
631 _version, _commit_changes = PullRequestModel().update_commits(
637 _version, _commit_changes = PullRequestModel().update_commits(
632 pull_request)
638 pull_request)
633 commit_changes = _commit_changes or commit_changes
639 commit_changes = _commit_changes or commit_changes
634 Session().commit()
640 Session().commit()
635
641
636 reviewers_changes = {"added": [], "removed": []}
642 reviewers_changes = {"added": [], "removed": []}
637 if reviewer_ids:
643 if reviewer_ids:
638 added_reviewers, removed_reviewers = \
644 added_reviewers, removed_reviewers = \
639 PullRequestModel().update_reviewers(pull_request, reviewers_reasons)
645 PullRequestModel().update_reviewers(pull_request, reviewers_reasons)
640
646
641 reviewers_changes['added'] = sorted(
647 reviewers_changes['added'] = sorted(
642 [get_user_or_error(n).username for n in added_reviewers])
648 [get_user_or_error(n).username for n in added_reviewers])
643 reviewers_changes['removed'] = sorted(
649 reviewers_changes['removed'] = sorted(
644 [get_user_or_error(n).username for n in removed_reviewers])
650 [get_user_or_error(n).username for n in removed_reviewers])
645 Session().commit()
651 Session().commit()
646
652
647 if str2bool(Optional.extract(close_pull_request)):
653 if str2bool(Optional.extract(close_pull_request)):
648 PullRequestModel().close_pull_request_with_comment(
654 PullRequestModel().close_pull_request_with_comment(
649 pull_request, apiuser, repo)
655 pull_request, apiuser, repo)
650 Session().commit()
656 Session().commit()
651
657
652 data = {
658 data = {
653 'msg': 'Updated pull request `{}`'.format(
659 'msg': 'Updated pull request `{}`'.format(
654 pull_request.pull_request_id),
660 pull_request.pull_request_id),
655 'pull_request': pull_request.get_api_data(),
661 'pull_request': pull_request.get_api_data(),
656 'updated_commits': commit_changes,
662 'updated_commits': commit_changes,
657 'updated_reviewers': reviewers_changes
663 'updated_reviewers': reviewers_changes
658 }
664 }
659 return data
665 return data
660
666
@@ -1,509 +1,512 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2016 RhodeCode GmbH
3 # Copyright (C) 2014-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
23 It's implemented with basic auth function
23 It's implemented with basic auth function
24 """
24 """
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import importlib
28 import importlib
29 import re
29 import re
30 from functools import wraps
30 from functools import wraps
31
31
32 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
32 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
33 from webob.exc import (
33 from webob.exc import (
34 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
34 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
35
35
36 import rhodecode
36 import rhodecode
37 from rhodecode.authentication.base import authenticate, VCS_TYPE
37 from rhodecode.authentication.base import authenticate, VCS_TYPE
38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
39 from rhodecode.lib.base import BasicAuth, get_ip_addr, vcs_operation_context
39 from rhodecode.lib.base import BasicAuth, get_ip_addr, vcs_operation_context
40 from rhodecode.lib.exceptions import (
40 from rhodecode.lib.exceptions import (
41 HTTPLockedRC, HTTPRequirementError, UserCreationError,
41 HTTPLockedRC, HTTPRequirementError, UserCreationError,
42 NotAllowedToCreateUserError)
42 NotAllowedToCreateUserError)
43 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
43 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
44 from rhodecode.lib.middleware import appenlight
44 from rhodecode.lib.middleware import appenlight
45 from rhodecode.lib.middleware.utils import scm_app
45 from rhodecode.lib.middleware.utils import scm_app
46 from rhodecode.lib.utils import (
46 from rhodecode.lib.utils import (
47 is_valid_repo, get_rhodecode_realm, get_rhodecode_base_path)
47 is_valid_repo, get_rhodecode_realm, get_rhodecode_base_path)
48 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
48 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
49 from rhodecode.lib.vcs.conf import settings as vcs_settings
49 from rhodecode.lib.vcs.conf import settings as vcs_settings
50 from rhodecode.lib.vcs.backends import base
50 from rhodecode.lib.vcs.backends import base
51 from rhodecode.model import meta
51 from rhodecode.model import meta
52 from rhodecode.model.db import User, Repository
52 from rhodecode.model.db import User, Repository
53 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.scm import ScmModel
54
54
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 def initialize_generator(factory):
59 def initialize_generator(factory):
60 """
60 """
61 Initializes the returned generator by draining its first element.
61 Initializes the returned generator by draining its first element.
62
62
63 This can be used to give a generator an initializer, which is the code
63 This can be used to give a generator an initializer, which is the code
64 up to the first yield statement. This decorator enforces that the first
64 up to the first yield statement. This decorator enforces that the first
65 produced element has the value ``"__init__"`` to make its special
65 produced element has the value ``"__init__"`` to make its special
66 purpose very explicit in the using code.
66 purpose very explicit in the using code.
67 """
67 """
68
68
69 @wraps(factory)
69 @wraps(factory)
70 def wrapper(*args, **kwargs):
70 def wrapper(*args, **kwargs):
71 gen = factory(*args, **kwargs)
71 gen = factory(*args, **kwargs)
72 try:
72 try:
73 init = gen.next()
73 init = gen.next()
74 except StopIteration:
74 except StopIteration:
75 raise ValueError('Generator must yield at least one element.')
75 raise ValueError('Generator must yield at least one element.')
76 if init != "__init__":
76 if init != "__init__":
77 raise ValueError('First yielded element must be "__init__".')
77 raise ValueError('First yielded element must be "__init__".')
78 return gen
78 return gen
79 return wrapper
79 return wrapper
80
80
81
81
82 class SimpleVCS(object):
82 class SimpleVCS(object):
83 """Common functionality for SCM HTTP handlers."""
83 """Common functionality for SCM HTTP handlers."""
84
84
85 SCM = 'unknown'
85 SCM = 'unknown'
86
86
87 acl_repo_name = None
87 acl_repo_name = None
88 url_repo_name = None
88 url_repo_name = None
89 vcs_repo_name = None
89 vcs_repo_name = None
90
90
91 def __init__(self, application, config, registry):
91 def __init__(self, application, config, registry):
92 self.registry = registry
92 self.registry = registry
93 self.application = application
93 self.application = application
94 self.config = config
94 self.config = config
95 # re-populated by specialized middleware
95 # re-populated by specialized middleware
96 self.repo_vcs_config = base.Config()
96 self.repo_vcs_config = base.Config()
97
97
98 # base path of repo locations
98 # base path of repo locations
99 self.basepath = get_rhodecode_base_path()
99 self.basepath = get_rhodecode_base_path()
100 # authenticate this VCS request using authfunc
100 # authenticate this VCS request using authfunc
101 auth_ret_code_detection = \
101 auth_ret_code_detection = \
102 str2bool(self.config.get('auth_ret_code_detection', False))
102 str2bool(self.config.get('auth_ret_code_detection', False))
103 self.authenticate = BasicAuth(
103 self.authenticate = BasicAuth(
104 '', authenticate, registry, config.get('auth_ret_code'),
104 '', authenticate, registry, config.get('auth_ret_code'),
105 auth_ret_code_detection)
105 auth_ret_code_detection)
106 self.ip_addr = '0.0.0.0'
106 self.ip_addr = '0.0.0.0'
107
107
108 def set_repo_names(self, environ):
108 def set_repo_names(self, environ):
109 """
109 """
110 This will populate the attributes acl_repo_name, url_repo_name,
110 This will populate the attributes acl_repo_name, url_repo_name,
111 vcs_repo_name and pr_id on the current instance.
111 vcs_repo_name and pr_id on the current instance.
112 """
112 """
113 # TODO: martinb: Unify generation/suffix of clone url. It is currently
114 # used here in the regex, in PullRequest in get_api_data() and
115 # indirectly in routing configuration.
113 # TODO: martinb: Move to class or module scope.
116 # TODO: martinb: Move to class or module scope.
114 # TODO: martinb: Check if we have to use re.UNICODE.
117 # TODO: martinb: Check if we have to use re.UNICODE.
115 # TODO: martinb: Check which chars are allowed for repo/group names.
118 # TODO: martinb: Check which chars are allowed for repo/group names.
116 # These chars are excluded: '`?=[]\;\'"<>,/~!@#$%^&*()+{}|: '
119 # These chars are excluded: '`?=[]\;\'"<>,/~!@#$%^&*()+{}|: '
117 # Code from: rhodecode/lib/utils.py:repo_name_slug()
120 # Code from: rhodecode/lib/utils.py:repo_name_slug()
118 pr_regex = re.compile(
121 pr_regex = re.compile(
119 '(?P<base_name>(?:[\w-]+)(?:/[\w-]+)*)/' # repo groups
122 '(?P<base_name>(?:[\w-]+)(?:/[\w-]+)*)/' # repo groups
120 '(?P<repo_name>[\w-]+)' # target repo name
123 '(?P<repo_name>[\w-]+)' # target repo name
121 '/pull-request/(?P<pr_id>\d+)/repository') # pr suffix
124 '/pull-request/(?P<pr_id>\d+)/repository') # pr suffix
122
125
123 # Get url repo name from environment.
126 # Get url repo name from environment.
124 self.url_repo_name = self._get_repository_name(environ)
127 self.url_repo_name = self._get_repository_name(environ)
125
128
126 # Check if this is a request to a shadow repository. In case of a
129 # Check if this is a request to a shadow repository. In case of a
127 # shadow repo set vcs_repo_name to the file system path pointing to the
130 # shadow repo set vcs_repo_name to the file system path pointing to the
128 # shadow repo. And set acl_repo_name to the pull request target repo
131 # shadow repo. And set acl_repo_name to the pull request target repo
129 # because we use the target repo for permission checks. Otherwise all
132 # because we use the target repo for permission checks. Otherwise all
130 # names are equal.
133 # names are equal.
131 match = pr_regex.match(self.url_repo_name)
134 match = pr_regex.match(self.url_repo_name)
132 if match:
135 if match:
133 # Get pull request instance.
136 # Get pull request instance.
134 match_dict = match.groupdict()
137 match_dict = match.groupdict()
135 pr_id = match_dict['pr_id']
138 pr_id = match_dict['pr_id']
136 pull_request = PullRequest.get(pr_id)
139 pull_request = PullRequest.get(pr_id)
137
140
138 # Get file system path to shadow repository.
141 # Get file system path to shadow repository.
139 workspace_id = PullRequestModel()._workspace_id(pull_request)
142 workspace_id = PullRequestModel()._workspace_id(pull_request)
140 target_vcs = pull_request.target_repo.scm_instance()
143 target_vcs = pull_request.target_repo.scm_instance()
141 vcs_repo_name = target_vcs._get_shadow_repository_path(
144 vcs_repo_name = target_vcs._get_shadow_repository_path(
142 workspace_id)
145 workspace_id)
143
146
144 # Store names for later usage.
147 # Store names for later usage.
145 self.pr_id = pr_id
148 self.pr_id = pr_id
146 self.vcs_repo_name = vcs_repo_name
149 self.vcs_repo_name = vcs_repo_name
147 self.acl_repo_name = pull_request.target_repo.repo_name
150 self.acl_repo_name = pull_request.target_repo.repo_name
148 else:
151 else:
149 # All names are equal for normal (non shadow) repositories.
152 # All names are equal for normal (non shadow) repositories.
150 self.acl_repo_name = self.url_repo_name
153 self.acl_repo_name = self.url_repo_name
151 self.vcs_repo_name = self.url_repo_name
154 self.vcs_repo_name = self.url_repo_name
152 self.pr_id = None
155 self.pr_id = None
153
156
154 @property
157 @property
155 def repo_name(self):
158 def repo_name(self):
156 # TODO: johbo: Remove, switch to correct repo name attribute
159 # TODO: johbo: Remove, switch to correct repo name attribute
157 return self.acl_repo_name
160 return self.acl_repo_name
158
161
159 @property
162 @property
160 def scm_app(self):
163 def scm_app(self):
161 custom_implementation = self.config.get('vcs.scm_app_implementation')
164 custom_implementation = self.config.get('vcs.scm_app_implementation')
162 if custom_implementation and custom_implementation != 'pyro4':
165 if custom_implementation and custom_implementation != 'pyro4':
163 log.info(
166 log.info(
164 "Using custom implementation of scm_app: %s",
167 "Using custom implementation of scm_app: %s",
165 custom_implementation)
168 custom_implementation)
166 scm_app_impl = importlib.import_module(custom_implementation)
169 scm_app_impl = importlib.import_module(custom_implementation)
167 else:
170 else:
168 scm_app_impl = scm_app
171 scm_app_impl = scm_app
169 return scm_app_impl
172 return scm_app_impl
170
173
171 def _get_by_id(self, repo_name):
174 def _get_by_id(self, repo_name):
172 """
175 """
173 Gets a special pattern _<ID> from clone url and tries to replace it
176 Gets a special pattern _<ID> from clone url and tries to replace it
174 with a repository_name for support of _<ID> non changeable urls
177 with a repository_name for support of _<ID> non changeable urls
175 """
178 """
176
179
177 data = repo_name.split('/')
180 data = repo_name.split('/')
178 if len(data) >= 2:
181 if len(data) >= 2:
179 from rhodecode.model.repo import RepoModel
182 from rhodecode.model.repo import RepoModel
180 by_id_match = RepoModel().get_repo_by_id(repo_name)
183 by_id_match = RepoModel().get_repo_by_id(repo_name)
181 if by_id_match:
184 if by_id_match:
182 data[1] = by_id_match.repo_name
185 data[1] = by_id_match.repo_name
183
186
184 return safe_str('/'.join(data))
187 return safe_str('/'.join(data))
185
188
186 def _invalidate_cache(self, repo_name):
189 def _invalidate_cache(self, repo_name):
187 """
190 """
188 Set's cache for this repository for invalidation on next access
191 Set's cache for this repository for invalidation on next access
189
192
190 :param repo_name: full repo name, also a cache key
193 :param repo_name: full repo name, also a cache key
191 """
194 """
192 ScmModel().mark_for_invalidation(repo_name)
195 ScmModel().mark_for_invalidation(repo_name)
193
196
194 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
197 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
195 db_repo = Repository.get_by_repo_name(repo_name)
198 db_repo = Repository.get_by_repo_name(repo_name)
196 if not db_repo:
199 if not db_repo:
197 log.debug('Repository `%s` not found inside the database.',
200 log.debug('Repository `%s` not found inside the database.',
198 repo_name)
201 repo_name)
199 return False
202 return False
200
203
201 if db_repo.repo_type != scm_type:
204 if db_repo.repo_type != scm_type:
202 log.warning(
205 log.warning(
203 'Repository `%s` have incorrect scm_type, expected %s got %s',
206 'Repository `%s` have incorrect scm_type, expected %s got %s',
204 repo_name, db_repo.repo_type, scm_type)
207 repo_name, db_repo.repo_type, scm_type)
205 return False
208 return False
206
209
207 return is_valid_repo(repo_name, base_path, expect_scm=scm_type)
210 return is_valid_repo(repo_name, base_path, expect_scm=scm_type)
208
211
209 def valid_and_active_user(self, user):
212 def valid_and_active_user(self, user):
210 """
213 """
211 Checks if that user is not empty, and if it's actually object it checks
214 Checks if that user is not empty, and if it's actually object it checks
212 if he's active.
215 if he's active.
213
216
214 :param user: user object or None
217 :param user: user object or None
215 :return: boolean
218 :return: boolean
216 """
219 """
217 if user is None:
220 if user is None:
218 return False
221 return False
219
222
220 elif user.active:
223 elif user.active:
221 return True
224 return True
222
225
223 return False
226 return False
224
227
225 def _check_permission(self, action, user, repo_name, ip_addr=None):
228 def _check_permission(self, action, user, repo_name, ip_addr=None):
226 """
229 """
227 Checks permissions using action (push/pull) user and repository
230 Checks permissions using action (push/pull) user and repository
228 name
231 name
229
232
230 :param action: push or pull action
233 :param action: push or pull action
231 :param user: user instance
234 :param user: user instance
232 :param repo_name: repository name
235 :param repo_name: repository name
233 """
236 """
234 # check IP
237 # check IP
235 inherit = user.inherit_default_permissions
238 inherit = user.inherit_default_permissions
236 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
239 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
237 inherit_from_default=inherit)
240 inherit_from_default=inherit)
238 if ip_allowed:
241 if ip_allowed:
239 log.info('Access for IP:%s allowed', ip_addr)
242 log.info('Access for IP:%s allowed', ip_addr)
240 else:
243 else:
241 return False
244 return False
242
245
243 if action == 'push':
246 if action == 'push':
244 if not HasPermissionAnyMiddleware('repository.write',
247 if not HasPermissionAnyMiddleware('repository.write',
245 'repository.admin')(user,
248 'repository.admin')(user,
246 repo_name):
249 repo_name):
247 return False
250 return False
248
251
249 else:
252 else:
250 # any other action need at least read permission
253 # any other action need at least read permission
251 if not HasPermissionAnyMiddleware('repository.read',
254 if not HasPermissionAnyMiddleware('repository.read',
252 'repository.write',
255 'repository.write',
253 'repository.admin')(user,
256 'repository.admin')(user,
254 repo_name):
257 repo_name):
255 return False
258 return False
256
259
257 return True
260 return True
258
261
259 def _check_ssl(self, environ, start_response):
262 def _check_ssl(self, environ, start_response):
260 """
263 """
261 Checks the SSL check flag and returns False if SSL is not present
264 Checks the SSL check flag and returns False if SSL is not present
262 and required True otherwise
265 and required True otherwise
263 """
266 """
264 org_proto = environ['wsgi._org_proto']
267 org_proto = environ['wsgi._org_proto']
265 # check if we have SSL required ! if not it's a bad request !
268 # check if we have SSL required ! if not it's a bad request !
266 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
269 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
267 if require_ssl and org_proto == 'http':
270 if require_ssl and org_proto == 'http':
268 log.debug('proto is %s and SSL is required BAD REQUEST !',
271 log.debug('proto is %s and SSL is required BAD REQUEST !',
269 org_proto)
272 org_proto)
270 return False
273 return False
271 return True
274 return True
272
275
273 def __call__(self, environ, start_response):
276 def __call__(self, environ, start_response):
274 try:
277 try:
275 return self._handle_request(environ, start_response)
278 return self._handle_request(environ, start_response)
276 except Exception:
279 except Exception:
277 log.exception("Exception while handling request")
280 log.exception("Exception while handling request")
278 appenlight.track_exception(environ)
281 appenlight.track_exception(environ)
279 return HTTPInternalServerError()(environ, start_response)
282 return HTTPInternalServerError()(environ, start_response)
280 finally:
283 finally:
281 meta.Session.remove()
284 meta.Session.remove()
282
285
283 def _handle_request(self, environ, start_response):
286 def _handle_request(self, environ, start_response):
284
287
285 if not self._check_ssl(environ, start_response):
288 if not self._check_ssl(environ, start_response):
286 reason = ('SSL required, while RhodeCode was unable '
289 reason = ('SSL required, while RhodeCode was unable '
287 'to detect this as SSL request')
290 'to detect this as SSL request')
288 log.debug('User not allowed to proceed, %s', reason)
291 log.debug('User not allowed to proceed, %s', reason)
289 return HTTPNotAcceptable(reason)(environ, start_response)
292 return HTTPNotAcceptable(reason)(environ, start_response)
290
293
291 if not self.repo_name:
294 if not self.repo_name:
292 log.warning('Repository name is empty: %s', self.repo_name)
295 log.warning('Repository name is empty: %s', self.repo_name)
293 # failed to get repo name, we fail now
296 # failed to get repo name, we fail now
294 return HTTPNotFound()(environ, start_response)
297 return HTTPNotFound()(environ, start_response)
295 log.debug('Extracted repo name is %s', self.repo_name)
298 log.debug('Extracted repo name is %s', self.repo_name)
296
299
297 ip_addr = get_ip_addr(environ)
300 ip_addr = get_ip_addr(environ)
298 username = None
301 username = None
299
302
300 # skip passing error to error controller
303 # skip passing error to error controller
301 environ['pylons.status_code_redirect'] = True
304 environ['pylons.status_code_redirect'] = True
302
305
303 # ======================================================================
306 # ======================================================================
304 # GET ACTION PULL or PUSH
307 # GET ACTION PULL or PUSH
305 # ======================================================================
308 # ======================================================================
306 action = self._get_action(environ)
309 action = self._get_action(environ)
307
310
308 # ======================================================================
311 # ======================================================================
309 # Check if this is a request to a shadow repository of a pull request.
312 # Check if this is a request to a shadow repository of a pull request.
310 # In this case only pull action is allowed.
313 # In this case only pull action is allowed.
311 # ======================================================================
314 # ======================================================================
312 if self.pr_id is not None and action != 'pull':
315 if self.pr_id is not None and action != 'pull':
313 reason = 'Only pull action is allowed for shadow repositories.'
316 reason = 'Only pull action is allowed for shadow repositories.'
314 log.debug('User not allowed to proceed, %s', reason)
317 log.debug('User not allowed to proceed, %s', reason)
315 return HTTPNotAcceptable(reason)(environ, start_response)
318 return HTTPNotAcceptable(reason)(environ, start_response)
316
319
317 # ======================================================================
320 # ======================================================================
318 # CHECK ANONYMOUS PERMISSION
321 # CHECK ANONYMOUS PERMISSION
319 # ======================================================================
322 # ======================================================================
320 if action in ['pull', 'push']:
323 if action in ['pull', 'push']:
321 anonymous_user = User.get_default_user()
324 anonymous_user = User.get_default_user()
322 username = anonymous_user.username
325 username = anonymous_user.username
323 if anonymous_user.active:
326 if anonymous_user.active:
324 # ONLY check permissions if the user is activated
327 # ONLY check permissions if the user is activated
325 anonymous_perm = self._check_permission(
328 anonymous_perm = self._check_permission(
326 action, anonymous_user, self.repo_name, ip_addr)
329 action, anonymous_user, self.repo_name, ip_addr)
327 else:
330 else:
328 anonymous_perm = False
331 anonymous_perm = False
329
332
330 if not anonymous_user.active or not anonymous_perm:
333 if not anonymous_user.active or not anonymous_perm:
331 if not anonymous_user.active:
334 if not anonymous_user.active:
332 log.debug('Anonymous access is disabled, running '
335 log.debug('Anonymous access is disabled, running '
333 'authentication')
336 'authentication')
334
337
335 if not anonymous_perm:
338 if not anonymous_perm:
336 log.debug('Not enough credentials to access this '
339 log.debug('Not enough credentials to access this '
337 'repository as anonymous user')
340 'repository as anonymous user')
338
341
339 username = None
342 username = None
340 # ==============================================================
343 # ==============================================================
341 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
344 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
342 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
345 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
343 # ==============================================================
346 # ==============================================================
344
347
345 # try to auth based on environ, container auth methods
348 # try to auth based on environ, container auth methods
346 log.debug('Running PRE-AUTH for container based authentication')
349 log.debug('Running PRE-AUTH for container based authentication')
347 pre_auth = authenticate(
350 pre_auth = authenticate(
348 '', '', environ, VCS_TYPE, registry=self.registry)
351 '', '', environ, VCS_TYPE, registry=self.registry)
349 if pre_auth and pre_auth.get('username'):
352 if pre_auth and pre_auth.get('username'):
350 username = pre_auth['username']
353 username = pre_auth['username']
351 log.debug('PRE-AUTH got %s as username', username)
354 log.debug('PRE-AUTH got %s as username', username)
352
355
353 # If not authenticated by the container, running basic auth
356 # If not authenticated by the container, running basic auth
354 if not username:
357 if not username:
355 self.authenticate.realm = get_rhodecode_realm()
358 self.authenticate.realm = get_rhodecode_realm()
356
359
357 try:
360 try:
358 result = self.authenticate(environ)
361 result = self.authenticate(environ)
359 except (UserCreationError, NotAllowedToCreateUserError) as e:
362 except (UserCreationError, NotAllowedToCreateUserError) as e:
360 log.error(e)
363 log.error(e)
361 reason = safe_str(e)
364 reason = safe_str(e)
362 return HTTPNotAcceptable(reason)(environ, start_response)
365 return HTTPNotAcceptable(reason)(environ, start_response)
363
366
364 if isinstance(result, str):
367 if isinstance(result, str):
365 AUTH_TYPE.update(environ, 'basic')
368 AUTH_TYPE.update(environ, 'basic')
366 REMOTE_USER.update(environ, result)
369 REMOTE_USER.update(environ, result)
367 username = result
370 username = result
368 else:
371 else:
369 return result.wsgi_application(environ, start_response)
372 return result.wsgi_application(environ, start_response)
370
373
371 # ==============================================================
374 # ==============================================================
372 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
375 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
373 # ==============================================================
376 # ==============================================================
374 user = User.get_by_username(username)
377 user = User.get_by_username(username)
375 if not self.valid_and_active_user(user):
378 if not self.valid_and_active_user(user):
376 return HTTPForbidden()(environ, start_response)
379 return HTTPForbidden()(environ, start_response)
377 username = user.username
380 username = user.username
378 user.update_lastactivity()
381 user.update_lastactivity()
379 meta.Session().commit()
382 meta.Session().commit()
380
383
381 # check user attributes for password change flag
384 # check user attributes for password change flag
382 user_obj = user
385 user_obj = user
383 if user_obj and user_obj.username != User.DEFAULT_USER and \
386 if user_obj and user_obj.username != User.DEFAULT_USER and \
384 user_obj.user_data.get('force_password_change'):
387 user_obj.user_data.get('force_password_change'):
385 reason = 'password change required'
388 reason = 'password change required'
386 log.debug('User not allowed to authenticate, %s', reason)
389 log.debug('User not allowed to authenticate, %s', reason)
387 return HTTPNotAcceptable(reason)(environ, start_response)
390 return HTTPNotAcceptable(reason)(environ, start_response)
388
391
389 # check permissions for this repository
392 # check permissions for this repository
390 perm = self._check_permission(
393 perm = self._check_permission(
391 action, user, self.repo_name, ip_addr)
394 action, user, self.repo_name, ip_addr)
392 if not perm:
395 if not perm:
393 return HTTPForbidden()(environ, start_response)
396 return HTTPForbidden()(environ, start_response)
394
397
395 # extras are injected into UI object and later available
398 # extras are injected into UI object and later available
396 # in hooks executed by rhodecode
399 # in hooks executed by rhodecode
397 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
400 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
398 extras = vcs_operation_context(
401 extras = vcs_operation_context(
399 environ, repo_name=self.repo_name, username=username,
402 environ, repo_name=self.repo_name, username=username,
400 action=action, scm=self.SCM,
403 action=action, scm=self.SCM,
401 check_locking=check_locking)
404 check_locking=check_locking)
402
405
403 # ======================================================================
406 # ======================================================================
404 # REQUEST HANDLING
407 # REQUEST HANDLING
405 # ======================================================================
408 # ======================================================================
406 str_repo_name = safe_str(self.repo_name)
409 str_repo_name = safe_str(self.repo_name)
407 repo_path = os.path.join(
410 repo_path = os.path.join(
408 safe_str(self.basepath), safe_str(self.vcs_repo_name))
411 safe_str(self.basepath), safe_str(self.vcs_repo_name))
409 log.debug('Repository path is %s', repo_path)
412 log.debug('Repository path is %s', repo_path)
410
413
411 fix_PATH()
414 fix_PATH()
412
415
413 log.info(
416 log.info(
414 '%s action on %s repo "%s" by "%s" from %s',
417 '%s action on %s repo "%s" by "%s" from %s',
415 action, self.SCM, str_repo_name, safe_str(username), ip_addr)
418 action, self.SCM, str_repo_name, safe_str(username), ip_addr)
416
419
417 return self._generate_vcs_response(
420 return self._generate_vcs_response(
418 environ, start_response, repo_path, self.url_repo_name, extras, action)
421 environ, start_response, repo_path, self.url_repo_name, extras, action)
419
422
420 @initialize_generator
423 @initialize_generator
421 def _generate_vcs_response(
424 def _generate_vcs_response(
422 self, environ, start_response, repo_path, repo_name, extras,
425 self, environ, start_response, repo_path, repo_name, extras,
423 action):
426 action):
424 """
427 """
425 Returns a generator for the response content.
428 Returns a generator for the response content.
426
429
427 This method is implemented as a generator, so that it can trigger
430 This method is implemented as a generator, so that it can trigger
428 the cache validation after all content sent back to the client. It
431 the cache validation after all content sent back to the client. It
429 also handles the locking exceptions which will be triggered when
432 also handles the locking exceptions which will be triggered when
430 the first chunk is produced by the underlying WSGI application.
433 the first chunk is produced by the underlying WSGI application.
431 """
434 """
432 callback_daemon, extras = self._prepare_callback_daemon(extras)
435 callback_daemon, extras = self._prepare_callback_daemon(extras)
433 config = self._create_config(extras, self.acl_repo_name)
436 config = self._create_config(extras, self.acl_repo_name)
434 log.debug('HOOKS extras is %s', extras)
437 log.debug('HOOKS extras is %s', extras)
435 app = self._create_wsgi_app(repo_path, repo_name, config)
438 app = self._create_wsgi_app(repo_path, repo_name, config)
436
439
437 try:
440 try:
438 with callback_daemon:
441 with callback_daemon:
439 try:
442 try:
440 response = app(environ, start_response)
443 response = app(environ, start_response)
441 finally:
444 finally:
442 # This statement works together with the decorator
445 # This statement works together with the decorator
443 # "initialize_generator" above. The decorator ensures that
446 # "initialize_generator" above. The decorator ensures that
444 # we hit the first yield statement before the generator is
447 # we hit the first yield statement before the generator is
445 # returned back to the WSGI server. This is needed to
448 # returned back to the WSGI server. This is needed to
446 # ensure that the call to "app" above triggers the
449 # ensure that the call to "app" above triggers the
447 # needed callback to "start_response" before the
450 # needed callback to "start_response" before the
448 # generator is actually used.
451 # generator is actually used.
449 yield "__init__"
452 yield "__init__"
450
453
451 for chunk in response:
454 for chunk in response:
452 yield chunk
455 yield chunk
453 except Exception as exc:
456 except Exception as exc:
454 # TODO: johbo: Improve "translating" back the exception.
457 # TODO: johbo: Improve "translating" back the exception.
455 if getattr(exc, '_vcs_kind', None) == 'repo_locked':
458 if getattr(exc, '_vcs_kind', None) == 'repo_locked':
456 exc = HTTPLockedRC(*exc.args)
459 exc = HTTPLockedRC(*exc.args)
457 _code = rhodecode.CONFIG.get('lock_ret_code')
460 _code = rhodecode.CONFIG.get('lock_ret_code')
458 log.debug('Repository LOCKED ret code %s!', (_code,))
461 log.debug('Repository LOCKED ret code %s!', (_code,))
459 elif getattr(exc, '_vcs_kind', None) == 'requirement':
462 elif getattr(exc, '_vcs_kind', None) == 'requirement':
460 log.debug(
463 log.debug(
461 'Repository requires features unknown to this Mercurial')
464 'Repository requires features unknown to this Mercurial')
462 exc = HTTPRequirementError(*exc.args)
465 exc = HTTPRequirementError(*exc.args)
463 else:
466 else:
464 raise
467 raise
465
468
466 for chunk in exc(environ, start_response):
469 for chunk in exc(environ, start_response):
467 yield chunk
470 yield chunk
468 finally:
471 finally:
469 # invalidate cache on push
472 # invalidate cache on push
470 try:
473 try:
471 if action == 'push':
474 if action == 'push':
472 self._invalidate_cache(repo_name)
475 self._invalidate_cache(repo_name)
473 finally:
476 finally:
474 meta.Session.remove()
477 meta.Session.remove()
475
478
476 def _get_repository_name(self, environ):
479 def _get_repository_name(self, environ):
477 """Get repository name out of the environmnent
480 """Get repository name out of the environmnent
478
481
479 :param environ: WSGI environment
482 :param environ: WSGI environment
480 """
483 """
481 raise NotImplementedError()
484 raise NotImplementedError()
482
485
483 def _get_action(self, environ):
486 def _get_action(self, environ):
484 """Map request commands into a pull or push command.
487 """Map request commands into a pull or push command.
485
488
486 :param environ: WSGI environment
489 :param environ: WSGI environment
487 """
490 """
488 raise NotImplementedError()
491 raise NotImplementedError()
489
492
490 def _create_wsgi_app(self, repo_path, repo_name, config):
493 def _create_wsgi_app(self, repo_path, repo_name, config):
491 """Return the WSGI app that will finally handle the request."""
494 """Return the WSGI app that will finally handle the request."""
492 raise NotImplementedError()
495 raise NotImplementedError()
493
496
494 def _create_config(self, extras, repo_name):
497 def _create_config(self, extras, repo_name):
495 """Create a Pyro safe config representation."""
498 """Create a Pyro safe config representation."""
496 raise NotImplementedError()
499 raise NotImplementedError()
497
500
498 def _prepare_callback_daemon(self, extras):
501 def _prepare_callback_daemon(self, extras):
499 return prepare_callback_daemon(
502 return prepare_callback_daemon(
500 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
503 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
501 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
504 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
502
505
503
506
504 def _should_check_locking(query_string):
507 def _should_check_locking(query_string):
505 # this is kind of hacky, but due to how mercurial handles client-server
508 # this is kind of hacky, but due to how mercurial handles client-server
506 # server see all operation on commit; bookmarks, phases and
509 # server see all operation on commit; bookmarks, phases and
507 # obsolescence marker in different transaction, we don't want to check
510 # obsolescence marker in different transaction, we don't want to check
508 # locking on those
511 # locking on those
509 return query_string not in ['cmd=listkeys']
512 return query_string not in ['cmd=listkeys']
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now