##// END OF EJS Templates
api: added comment_resolved_id into aprameters to resolve TODO notes via API.
marcink -
r1338:d0b1d315 default
parent child Browse files
Show More
@@ -1,717 +1,731 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 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 validate_repo_permissions, resolve_ref_or_error)
28 validate_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 CommentsModel
33 from rhodecode.model.comment import CommentsModel
34 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment
34 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment
35 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
35 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
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 "merge": {
99 "merge": {
100 "clone_url": "<clone_url>",
100 "clone_url": "<clone_url>",
101 "reference":
101 "reference":
102 {
102 {
103 "name": "<name>",
103 "name": "<name>",
104 "type": "<type>",
104 "type": "<type>",
105 "commit_id": "<commit_id>",
105 "commit_id": "<commit_id>",
106 }
106 }
107 },
107 },
108 "author": <user_obj>,
108 "author": <user_obj>,
109 "reviewers": [
109 "reviewers": [
110 ...
110 ...
111 {
111 {
112 "user": "<user_obj>",
112 "user": "<user_obj>",
113 "review_status": "<review_status>",
113 "review_status": "<review_status>",
114 }
114 }
115 ...
115 ...
116 ]
116 ]
117 },
117 },
118 "error": null
118 "error": null
119 """
119 """
120 get_repo_or_error(repoid)
120 get_repo_or_error(repoid)
121 pull_request = get_pull_request_or_error(pullrequestid)
121 pull_request = get_pull_request_or_error(pullrequestid)
122 if not PullRequestModel().check_user_read(
122 if not PullRequestModel().check_user_read(
123 pull_request, apiuser, api=True):
123 pull_request, apiuser, api=True):
124 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
124 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
125 data = pull_request.get_api_data()
125 data = pull_request.get_api_data()
126 return data
126 return data
127
127
128
128
129 @jsonrpc_method()
129 @jsonrpc_method()
130 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
130 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
131 """
131 """
132 Get all pull requests from the repository specified in `repoid`.
132 Get all pull requests from the repository specified in `repoid`.
133
133
134 :param apiuser: This is filled automatically from the |authtoken|.
134 :param apiuser: This is filled automatically from the |authtoken|.
135 :type apiuser: AuthUser
135 :type apiuser: AuthUser
136 :param repoid: Repository name or repository ID.
136 :param repoid: Repository name or repository ID.
137 :type repoid: str or int
137 :type repoid: str or int
138 :param status: Only return pull requests with the specified status.
138 :param status: Only return pull requests with the specified status.
139 Valid options are.
139 Valid options are.
140 * ``new`` (default)
140 * ``new`` (default)
141 * ``open``
141 * ``open``
142 * ``closed``
142 * ``closed``
143 :type status: str
143 :type status: str
144
144
145 Example output:
145 Example output:
146
146
147 .. code-block:: bash
147 .. code-block:: bash
148
148
149 "id": <id_given_in_input>,
149 "id": <id_given_in_input>,
150 "result":
150 "result":
151 [
151 [
152 ...
152 ...
153 {
153 {
154 "pull_request_id": "<pull_request_id>",
154 "pull_request_id": "<pull_request_id>",
155 "url": "<url>",
155 "url": "<url>",
156 "title" : "<title>",
156 "title" : "<title>",
157 "description": "<description>",
157 "description": "<description>",
158 "status": "<status>",
158 "status": "<status>",
159 "created_on": "<date_time_created>",
159 "created_on": "<date_time_created>",
160 "updated_on": "<date_time_updated>",
160 "updated_on": "<date_time_updated>",
161 "commit_ids": [
161 "commit_ids": [
162 ...
162 ...
163 "<commit_id>",
163 "<commit_id>",
164 "<commit_id>",
164 "<commit_id>",
165 ...
165 ...
166 ],
166 ],
167 "review_status": "<review_status>",
167 "review_status": "<review_status>",
168 "mergeable": {
168 "mergeable": {
169 "status": "<bool>",
169 "status": "<bool>",
170 "message: "<message>",
170 "message: "<message>",
171 },
171 },
172 "source": {
172 "source": {
173 "clone_url": "<clone_url>",
173 "clone_url": "<clone_url>",
174 "reference":
174 "reference":
175 {
175 {
176 "name": "<name>",
176 "name": "<name>",
177 "type": "<type>",
177 "type": "<type>",
178 "commit_id": "<commit_id>",
178 "commit_id": "<commit_id>",
179 }
179 }
180 },
180 },
181 "target": {
181 "target": {
182 "clone_url": "<clone_url>",
182 "clone_url": "<clone_url>",
183 "reference":
183 "reference":
184 {
184 {
185 "name": "<name>",
185 "name": "<name>",
186 "type": "<type>",
186 "type": "<type>",
187 "commit_id": "<commit_id>",
187 "commit_id": "<commit_id>",
188 }
188 }
189 },
189 },
190 "merge": {
190 "merge": {
191 "clone_url": "<clone_url>",
191 "clone_url": "<clone_url>",
192 "reference":
192 "reference":
193 {
193 {
194 "name": "<name>",
194 "name": "<name>",
195 "type": "<type>",
195 "type": "<type>",
196 "commit_id": "<commit_id>",
196 "commit_id": "<commit_id>",
197 }
197 }
198 },
198 },
199 "author": <user_obj>,
199 "author": <user_obj>,
200 "reviewers": [
200 "reviewers": [
201 ...
201 ...
202 {
202 {
203 "user": "<user_obj>",
203 "user": "<user_obj>",
204 "review_status": "<review_status>",
204 "review_status": "<review_status>",
205 }
205 }
206 ...
206 ...
207 ]
207 ]
208 }
208 }
209 ...
209 ...
210 ],
210 ],
211 "error": null
211 "error": null
212
212
213 """
213 """
214 repo = get_repo_or_error(repoid)
214 repo = get_repo_or_error(repoid)
215 if not has_superadmin_permission(apiuser):
215 if not has_superadmin_permission(apiuser):
216 _perms = (
216 _perms = (
217 'repository.admin', 'repository.write', 'repository.read',)
217 'repository.admin', 'repository.write', 'repository.read',)
218 validate_repo_permissions(apiuser, repoid, repo, _perms)
218 validate_repo_permissions(apiuser, repoid, repo, _perms)
219
219
220 status = Optional.extract(status)
220 status = Optional.extract(status)
221 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
221 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
222 data = [pr.get_api_data() for pr in pull_requests]
222 data = [pr.get_api_data() for pr in pull_requests]
223 return data
223 return data
224
224
225
225
226 @jsonrpc_method()
226 @jsonrpc_method()
227 def merge_pull_request(request, apiuser, repoid, pullrequestid,
227 def merge_pull_request(request, apiuser, repoid, pullrequestid,
228 userid=Optional(OAttr('apiuser'))):
228 userid=Optional(OAttr('apiuser'))):
229 """
229 """
230 Merge the pull request specified by `pullrequestid` into its target
230 Merge the pull request specified by `pullrequestid` into its target
231 repository.
231 repository.
232
232
233 :param apiuser: This is filled automatically from the |authtoken|.
233 :param apiuser: This is filled automatically from the |authtoken|.
234 :type apiuser: AuthUser
234 :type apiuser: AuthUser
235 :param repoid: The Repository name or repository ID of the
235 :param repoid: The Repository name or repository ID of the
236 target repository to which the |pr| is to be merged.
236 target repository to which the |pr| is to be merged.
237 :type repoid: str or int
237 :type repoid: str or int
238 :param pullrequestid: ID of the pull request which shall be merged.
238 :param pullrequestid: ID of the pull request which shall be merged.
239 :type pullrequestid: int
239 :type pullrequestid: int
240 :param userid: Merge the pull request as this user.
240 :param userid: Merge the pull request as this user.
241 :type userid: Optional(str or int)
241 :type userid: Optional(str or int)
242
242
243 Example output:
243 Example output:
244
244
245 .. code-block:: bash
245 .. code-block:: bash
246
246
247 "id": <id_given_in_input>,
247 "id": <id_given_in_input>,
248 "result":
248 "result":
249 {
249 {
250 "executed": "<bool>",
250 "executed": "<bool>",
251 "failure_reason": "<int>",
251 "failure_reason": "<int>",
252 "merge_commit_id": "<merge_commit_id>",
252 "merge_commit_id": "<merge_commit_id>",
253 "possible": "<bool>",
253 "possible": "<bool>",
254 "merge_ref": {
254 "merge_ref": {
255 "commit_id": "<commit_id>",
255 "commit_id": "<commit_id>",
256 "type": "<type>",
256 "type": "<type>",
257 "name": "<name>"
257 "name": "<name>"
258 }
258 }
259 },
259 },
260 "error": null
260 "error": null
261
261
262 """
262 """
263 repo = get_repo_or_error(repoid)
263 repo = get_repo_or_error(repoid)
264 if not isinstance(userid, Optional):
264 if not isinstance(userid, Optional):
265 if (has_superadmin_permission(apiuser) or
265 if (has_superadmin_permission(apiuser) or
266 HasRepoPermissionAnyApi('repository.admin')(
266 HasRepoPermissionAnyApi('repository.admin')(
267 user=apiuser, repo_name=repo.repo_name)):
267 user=apiuser, repo_name=repo.repo_name)):
268 apiuser = get_user_or_error(userid)
268 apiuser = get_user_or_error(userid)
269 else:
269 else:
270 raise JSONRPCError('userid is not the same as your user')
270 raise JSONRPCError('userid is not the same as your user')
271
271
272 pull_request = get_pull_request_or_error(pullrequestid)
272 pull_request = get_pull_request_or_error(pullrequestid)
273
273
274 check = MergeCheck.validate(pull_request, user=apiuser)
274 check = MergeCheck.validate(pull_request, user=apiuser)
275 merge_possible = not check.failed
275 merge_possible = not check.failed
276
276
277 if not merge_possible:
277 if not merge_possible:
278 reasons = ','.join([msg for _e, msg in check.errors])
278 reasons = ','.join([msg for _e, msg in check.errors])
279 raise JSONRPCError(
279 raise JSONRPCError(
280 'merge not possible for following reasons: {}'.format(reasons))
280 'merge not possible for following reasons: {}'.format(reasons))
281
281
282 target_repo = pull_request.target_repo
282 target_repo = pull_request.target_repo
283 extras = vcs_operation_context(
283 extras = vcs_operation_context(
284 request.environ, repo_name=target_repo.repo_name,
284 request.environ, repo_name=target_repo.repo_name,
285 username=apiuser.username, action='push',
285 username=apiuser.username, action='push',
286 scm=target_repo.repo_type)
286 scm=target_repo.repo_type)
287 merge_response = PullRequestModel().merge(
287 merge_response = PullRequestModel().merge(
288 pull_request, apiuser, extras=extras)
288 pull_request, apiuser, extras=extras)
289 if merge_response.executed:
289 if merge_response.executed:
290 PullRequestModel().close_pull_request(
290 PullRequestModel().close_pull_request(
291 pull_request.pull_request_id, apiuser)
291 pull_request.pull_request_id, apiuser)
292
292
293 Session().commit()
293 Session().commit()
294
294
295 # In previous versions the merge response directly contained the merge
295 # In previous versions the merge response directly contained the merge
296 # commit id. It is now contained in the merge reference object. To be
296 # commit id. It is now contained in the merge reference object. To be
297 # backwards compatible we have to extract it again.
297 # backwards compatible we have to extract it again.
298 merge_response = merge_response._asdict()
298 merge_response = merge_response._asdict()
299 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
299 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
300
300
301 return merge_response
301 return merge_response
302
302
303
303
304 @jsonrpc_method()
304 @jsonrpc_method()
305 def close_pull_request(request, apiuser, repoid, pullrequestid,
305 def close_pull_request(request, apiuser, repoid, pullrequestid,
306 userid=Optional(OAttr('apiuser'))):
306 userid=Optional(OAttr('apiuser'))):
307 """
307 """
308 Close the pull request specified by `pullrequestid`.
308 Close the pull request specified by `pullrequestid`.
309
309
310 :param apiuser: This is filled automatically from the |authtoken|.
310 :param apiuser: This is filled automatically from the |authtoken|.
311 :type apiuser: AuthUser
311 :type apiuser: AuthUser
312 :param repoid: Repository name or repository ID to which the pull
312 :param repoid: Repository name or repository ID to which the pull
313 request belongs.
313 request belongs.
314 :type repoid: str or int
314 :type repoid: str or int
315 :param pullrequestid: ID of the pull request to be closed.
315 :param pullrequestid: ID of the pull request to be closed.
316 :type pullrequestid: int
316 :type pullrequestid: int
317 :param userid: Close the pull request as this user.
317 :param userid: Close the pull request as this user.
318 :type userid: Optional(str or int)
318 :type userid: Optional(str or int)
319
319
320 Example output:
320 Example output:
321
321
322 .. code-block:: bash
322 .. code-block:: bash
323
323
324 "id": <id_given_in_input>,
324 "id": <id_given_in_input>,
325 "result":
325 "result":
326 {
326 {
327 "pull_request_id": "<int>",
327 "pull_request_id": "<int>",
328 "closed": "<bool>"
328 "closed": "<bool>"
329 },
329 },
330 "error": null
330 "error": null
331
331
332 """
332 """
333 repo = get_repo_or_error(repoid)
333 repo = get_repo_or_error(repoid)
334 if not isinstance(userid, Optional):
334 if not isinstance(userid, Optional):
335 if (has_superadmin_permission(apiuser) or
335 if (has_superadmin_permission(apiuser) or
336 HasRepoPermissionAnyApi('repository.admin')(
336 HasRepoPermissionAnyApi('repository.admin')(
337 user=apiuser, repo_name=repo.repo_name)):
337 user=apiuser, repo_name=repo.repo_name)):
338 apiuser = get_user_or_error(userid)
338 apiuser = get_user_or_error(userid)
339 else:
339 else:
340 raise JSONRPCError('userid is not the same as your user')
340 raise JSONRPCError('userid is not the same as your user')
341
341
342 pull_request = get_pull_request_or_error(pullrequestid)
342 pull_request = get_pull_request_or_error(pullrequestid)
343 if not PullRequestModel().check_user_update(
343 if not PullRequestModel().check_user_update(
344 pull_request, apiuser, api=True):
344 pull_request, apiuser, api=True):
345 raise JSONRPCError(
345 raise JSONRPCError(
346 'pull request `%s` close failed, no permission to close.' % (
346 'pull request `%s` close failed, no permission to close.' % (
347 pullrequestid,))
347 pullrequestid,))
348 if pull_request.is_closed():
348 if pull_request.is_closed():
349 raise JSONRPCError(
349 raise JSONRPCError(
350 'pull request `%s` is already closed' % (pullrequestid,))
350 'pull request `%s` is already closed' % (pullrequestid,))
351
351
352 PullRequestModel().close_pull_request(
352 PullRequestModel().close_pull_request(
353 pull_request.pull_request_id, apiuser)
353 pull_request.pull_request_id, apiuser)
354 Session().commit()
354 Session().commit()
355 data = {
355 data = {
356 'pull_request_id': pull_request.pull_request_id,
356 'pull_request_id': pull_request.pull_request_id,
357 'closed': True,
357 'closed': True,
358 }
358 }
359 return data
359 return data
360
360
361
361
362 @jsonrpc_method()
362 @jsonrpc_method()
363 def comment_pull_request(
363 def comment_pull_request(
364 request, apiuser, repoid, pullrequestid, message=Optional(None),
364 request, apiuser, repoid, pullrequestid, message=Optional(None),
365 commit_id=Optional(None), status=Optional(None),
365 commit_id=Optional(None), status=Optional(None),
366 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
366 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
367 resolves_comment_id=Optional(None),
367 userid=Optional(OAttr('apiuser'))):
368 userid=Optional(OAttr('apiuser'))):
368 """
369 """
369 Comment on the pull request specified with the `pullrequestid`,
370 Comment on the pull request specified with the `pullrequestid`,
370 in the |repo| specified by the `repoid`, and optionally change the
371 in the |repo| specified by the `repoid`, and optionally change the
371 review status.
372 review status.
372
373
373 :param apiuser: This is filled automatically from the |authtoken|.
374 :param apiuser: This is filled automatically from the |authtoken|.
374 :type apiuser: AuthUser
375 :type apiuser: AuthUser
375 :param repoid: The repository name or repository ID.
376 :param repoid: The repository name or repository ID.
376 :type repoid: str or int
377 :type repoid: str or int
377 :param pullrequestid: The pull request ID.
378 :param pullrequestid: The pull request ID.
378 :type pullrequestid: int
379 :type pullrequestid: int
379 :param commit_id: Specify the commit_id for which to set a comment. If
380 :param commit_id: Specify the commit_id for which to set a comment. If
380 given commit_id is different than latest in the PR status
381 given commit_id is different than latest in the PR status
381 change won't be performed.
382 change won't be performed.
382 :type commit_id: str
383 :type commit_id: str
383 :param message: The text content of the comment.
384 :param message: The text content of the comment.
384 :type message: str
385 :type message: str
385 :param status: (**Optional**) Set the approval status of the pull
386 :param status: (**Optional**) Set the approval status of the pull
386 request. One of: 'not_reviewed', 'approved', 'rejected',
387 request. One of: 'not_reviewed', 'approved', 'rejected',
387 'under_review'
388 'under_review'
388 :type status: str
389 :type status: str
389 :param comment_type: Comment type, one of: 'note', 'todo'
390 :param comment_type: Comment type, one of: 'note', 'todo'
390 :type comment_type: Optional(str), default: 'note'
391 :type comment_type: Optional(str), default: 'note'
391 :param userid: Comment on the pull request as this user
392 :param userid: Comment on the pull request as this user
392 :type userid: Optional(str or int)
393 :type userid: Optional(str or int)
393
394
394 Example output:
395 Example output:
395
396
396 .. code-block:: bash
397 .. code-block:: bash
397
398
398 id : <id_given_in_input>
399 id : <id_given_in_input>
399 result :
400 result :
400 {
401 {
401 "pull_request_id": "<Integer>",
402 "pull_request_id": "<Integer>",
402 "comment_id": "<Integer>",
403 "comment_id": "<Integer>",
403 "status": {"given": <given_status>,
404 "status": {"given": <given_status>,
404 "was_changed": <bool status_was_actually_changed> },
405 "was_changed": <bool status_was_actually_changed> },
405 }
406 }
406 error : null
407 error : null
407 """
408 """
408 repo = get_repo_or_error(repoid)
409 repo = get_repo_or_error(repoid)
409 if not isinstance(userid, Optional):
410 if not isinstance(userid, Optional):
410 if (has_superadmin_permission(apiuser) or
411 if (has_superadmin_permission(apiuser) or
411 HasRepoPermissionAnyApi('repository.admin')(
412 HasRepoPermissionAnyApi('repository.admin')(
412 user=apiuser, repo_name=repo.repo_name)):
413 user=apiuser, repo_name=repo.repo_name)):
413 apiuser = get_user_or_error(userid)
414 apiuser = get_user_or_error(userid)
414 else:
415 else:
415 raise JSONRPCError('userid is not the same as your user')
416 raise JSONRPCError('userid is not the same as your user')
416
417
417 pull_request = get_pull_request_or_error(pullrequestid)
418 pull_request = get_pull_request_or_error(pullrequestid)
418 if not PullRequestModel().check_user_read(
419 if not PullRequestModel().check_user_read(
419 pull_request, apiuser, api=True):
420 pull_request, apiuser, api=True):
420 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
421 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
421 message = Optional.extract(message)
422 message = Optional.extract(message)
422 status = Optional.extract(status)
423 status = Optional.extract(status)
423 commit_id = Optional.extract(commit_id)
424 commit_id = Optional.extract(commit_id)
424 comment_type = Optional.extract(comment_type)
425 comment_type = Optional.extract(comment_type)
426 resolves_comment_id = Optional.extract(resolves_comment_id)
425
427
426 if not message and not status:
428 if not message and not status:
427 raise JSONRPCError(
429 raise JSONRPCError(
428 'Both message and status parameters are missing. '
430 'Both message and status parameters are missing. '
429 'At least one is required.')
431 'At least one is required.')
430
432
431 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
433 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
432 status is not None):
434 status is not None):
433 raise JSONRPCError('Unknown comment status: `%s`' % status)
435 raise JSONRPCError('Unknown comment status: `%s`' % status)
434
436
435 if commit_id and commit_id not in pull_request.revisions:
437 if commit_id and commit_id not in pull_request.revisions:
436 raise JSONRPCError(
438 raise JSONRPCError(
437 'Invalid commit_id `%s` for this pull request.' % commit_id)
439 'Invalid commit_id `%s` for this pull request.' % commit_id)
438
440
439 allowed_to_change_status = PullRequestModel().check_user_change_status(
441 allowed_to_change_status = PullRequestModel().check_user_change_status(
440 pull_request, apiuser)
442 pull_request, apiuser)
441
443
442 # if commit_id is passed re-validated if user is allowed to change status
444 # if commit_id is passed re-validated if user is allowed to change status
443 # based on latest commit_id from the PR
445 # based on latest commit_id from the PR
444 if commit_id:
446 if commit_id:
445 commit_idx = pull_request.revisions.index(commit_id)
447 commit_idx = pull_request.revisions.index(commit_id)
446 if commit_idx != 0:
448 if commit_idx != 0:
447 allowed_to_change_status = False
449 allowed_to_change_status = False
448
450
451 if resolves_comment_id:
452 comment = ChangesetComment.get(resolves_comment_id)
453 if not comment:
454 raise JSONRPCError(
455 'Invalid resolves_comment_id `%s` for this pull request.'
456 % resolves_comment_id)
457 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
458 raise JSONRPCError(
459 'Comment `%s` is wrong type for setting status to resolved.'
460 % resolves_comment_id)
461
449 text = message
462 text = message
450 status_label = ChangesetStatus.get_status_lbl(status)
463 status_label = ChangesetStatus.get_status_lbl(status)
451 if status and allowed_to_change_status:
464 if status and allowed_to_change_status:
452 st_message = ('Status change %(transition_icon)s %(status)s'
465 st_message = ('Status change %(transition_icon)s %(status)s'
453 % {'transition_icon': '>', 'status': status_label})
466 % {'transition_icon': '>', 'status': status_label})
454 text = message or st_message
467 text = message or st_message
455
468
456 rc_config = SettingsModel().get_all_settings()
469 rc_config = SettingsModel().get_all_settings()
457 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
470 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
458
471
459 status_change = status and allowed_to_change_status
472 status_change = status and allowed_to_change_status
460 comment = CommentsModel().create(
473 comment = CommentsModel().create(
461 text=text,
474 text=text,
462 repo=pull_request.target_repo.repo_id,
475 repo=pull_request.target_repo.repo_id,
463 user=apiuser.user_id,
476 user=apiuser.user_id,
464 pull_request=pull_request.pull_request_id,
477 pull_request=pull_request.pull_request_id,
465 f_path=None,
478 f_path=None,
466 line_no=None,
479 line_no=None,
467 status_change=(status_label if status_change else None),
480 status_change=(status_label if status_change else None),
468 status_change_type=(status if status_change else None),
481 status_change_type=(status if status_change else None),
469 closing_pr=False,
482 closing_pr=False,
470 renderer=renderer,
483 renderer=renderer,
471 comment_type=comment_type
484 comment_type=comment_type,
485 resolves_comment_id=resolves_comment_id
472 )
486 )
473
487
474 if allowed_to_change_status and status:
488 if allowed_to_change_status and status:
475 ChangesetStatusModel().set_status(
489 ChangesetStatusModel().set_status(
476 pull_request.target_repo.repo_id,
490 pull_request.target_repo.repo_id,
477 status,
491 status,
478 apiuser.user_id,
492 apiuser.user_id,
479 comment,
493 comment,
480 pull_request=pull_request.pull_request_id
494 pull_request=pull_request.pull_request_id
481 )
495 )
482 Session().flush()
496 Session().flush()
483
497
484 Session().commit()
498 Session().commit()
485 data = {
499 data = {
486 'pull_request_id': pull_request.pull_request_id,
500 'pull_request_id': pull_request.pull_request_id,
487 'comment_id': comment.comment_id if comment else None,
501 'comment_id': comment.comment_id if comment else None,
488 'status': {'given': status, 'was_changed': status_change},
502 'status': {'given': status, 'was_changed': status_change},
489 }
503 }
490 return data
504 return data
491
505
492
506
493 @jsonrpc_method()
507 @jsonrpc_method()
494 def create_pull_request(
508 def create_pull_request(
495 request, apiuser, source_repo, target_repo, source_ref, target_ref,
509 request, apiuser, source_repo, target_repo, source_ref, target_ref,
496 title, description=Optional(''), reviewers=Optional(None)):
510 title, description=Optional(''), reviewers=Optional(None)):
497 """
511 """
498 Creates a new pull request.
512 Creates a new pull request.
499
513
500 Accepts refs in the following formats:
514 Accepts refs in the following formats:
501
515
502 * branch:<branch_name>:<sha>
516 * branch:<branch_name>:<sha>
503 * branch:<branch_name>
517 * branch:<branch_name>
504 * bookmark:<bookmark_name>:<sha> (Mercurial only)
518 * bookmark:<bookmark_name>:<sha> (Mercurial only)
505 * bookmark:<bookmark_name> (Mercurial only)
519 * bookmark:<bookmark_name> (Mercurial only)
506
520
507 :param apiuser: This is filled automatically from the |authtoken|.
521 :param apiuser: This is filled automatically from the |authtoken|.
508 :type apiuser: AuthUser
522 :type apiuser: AuthUser
509 :param source_repo: Set the source repository name.
523 :param source_repo: Set the source repository name.
510 :type source_repo: str
524 :type source_repo: str
511 :param target_repo: Set the target repository name.
525 :param target_repo: Set the target repository name.
512 :type target_repo: str
526 :type target_repo: str
513 :param source_ref: Set the source ref name.
527 :param source_ref: Set the source ref name.
514 :type source_ref: str
528 :type source_ref: str
515 :param target_ref: Set the target ref name.
529 :param target_ref: Set the target ref name.
516 :type target_ref: str
530 :type target_ref: str
517 :param title: Set the pull request title.
531 :param title: Set the pull request title.
518 :type title: str
532 :type title: str
519 :param description: Set the pull request description.
533 :param description: Set the pull request description.
520 :type description: Optional(str)
534 :type description: Optional(str)
521 :param reviewers: Set the new pull request reviewers list.
535 :param reviewers: Set the new pull request reviewers list.
522 :type reviewers: Optional(list)
536 :type reviewers: Optional(list)
523 Accepts username strings or objects of the format:
537 Accepts username strings or objects of the format:
524 {
538 {
525 'username': 'nick', 'reasons': ['original author']
539 'username': 'nick', 'reasons': ['original author']
526 }
540 }
527 """
541 """
528
542
529 source = get_repo_or_error(source_repo)
543 source = get_repo_or_error(source_repo)
530 target = get_repo_or_error(target_repo)
544 target = get_repo_or_error(target_repo)
531 if not has_superadmin_permission(apiuser):
545 if not has_superadmin_permission(apiuser):
532 _perms = ('repository.admin', 'repository.write', 'repository.read',)
546 _perms = ('repository.admin', 'repository.write', 'repository.read',)
533 validate_repo_permissions(apiuser, source_repo, source, _perms)
547 validate_repo_permissions(apiuser, source_repo, source, _perms)
534
548
535 full_source_ref = resolve_ref_or_error(source_ref, source)
549 full_source_ref = resolve_ref_or_error(source_ref, source)
536 full_target_ref = resolve_ref_or_error(target_ref, target)
550 full_target_ref = resolve_ref_or_error(target_ref, target)
537 source_commit = get_commit_or_error(full_source_ref, source)
551 source_commit = get_commit_or_error(full_source_ref, source)
538 target_commit = get_commit_or_error(full_target_ref, target)
552 target_commit = get_commit_or_error(full_target_ref, target)
539 source_scm = source.scm_instance()
553 source_scm = source.scm_instance()
540 target_scm = target.scm_instance()
554 target_scm = target.scm_instance()
541
555
542 commit_ranges = target_scm.compare(
556 commit_ranges = target_scm.compare(
543 target_commit.raw_id, source_commit.raw_id, source_scm,
557 target_commit.raw_id, source_commit.raw_id, source_scm,
544 merge=True, pre_load=[])
558 merge=True, pre_load=[])
545
559
546 ancestor = target_scm.get_common_ancestor(
560 ancestor = target_scm.get_common_ancestor(
547 target_commit.raw_id, source_commit.raw_id, source_scm)
561 target_commit.raw_id, source_commit.raw_id, source_scm)
548
562
549 if not commit_ranges:
563 if not commit_ranges:
550 raise JSONRPCError('no commits found')
564 raise JSONRPCError('no commits found')
551
565
552 if not ancestor:
566 if not ancestor:
553 raise JSONRPCError('no common ancestor found')
567 raise JSONRPCError('no common ancestor found')
554
568
555 reviewer_objects = Optional.extract(reviewers) or []
569 reviewer_objects = Optional.extract(reviewers) or []
556 if not isinstance(reviewer_objects, list):
570 if not isinstance(reviewer_objects, list):
557 raise JSONRPCError('reviewers should be specified as a list')
571 raise JSONRPCError('reviewers should be specified as a list')
558
572
559 reviewers_reasons = []
573 reviewers_reasons = []
560 for reviewer_object in reviewer_objects:
574 for reviewer_object in reviewer_objects:
561 reviewer_reasons = []
575 reviewer_reasons = []
562 if isinstance(reviewer_object, (basestring, int)):
576 if isinstance(reviewer_object, (basestring, int)):
563 reviewer_username = reviewer_object
577 reviewer_username = reviewer_object
564 else:
578 else:
565 reviewer_username = reviewer_object['username']
579 reviewer_username = reviewer_object['username']
566 reviewer_reasons = reviewer_object.get('reasons', [])
580 reviewer_reasons = reviewer_object.get('reasons', [])
567
581
568 user = get_user_or_error(reviewer_username)
582 user = get_user_or_error(reviewer_username)
569 reviewers_reasons.append((user.user_id, reviewer_reasons))
583 reviewers_reasons.append((user.user_id, reviewer_reasons))
570
584
571 pull_request_model = PullRequestModel()
585 pull_request_model = PullRequestModel()
572 pull_request = pull_request_model.create(
586 pull_request = pull_request_model.create(
573 created_by=apiuser.user_id,
587 created_by=apiuser.user_id,
574 source_repo=source_repo,
588 source_repo=source_repo,
575 source_ref=full_source_ref,
589 source_ref=full_source_ref,
576 target_repo=target_repo,
590 target_repo=target_repo,
577 target_ref=full_target_ref,
591 target_ref=full_target_ref,
578 revisions=reversed(
592 revisions=reversed(
579 [commit.raw_id for commit in reversed(commit_ranges)]),
593 [commit.raw_id for commit in reversed(commit_ranges)]),
580 reviewers=reviewers_reasons,
594 reviewers=reviewers_reasons,
581 title=title,
595 title=title,
582 description=Optional.extract(description)
596 description=Optional.extract(description)
583 )
597 )
584
598
585 Session().commit()
599 Session().commit()
586 data = {
600 data = {
587 'msg': 'Created new pull request `{}`'.format(title),
601 'msg': 'Created new pull request `{}`'.format(title),
588 'pull_request_id': pull_request.pull_request_id,
602 'pull_request_id': pull_request.pull_request_id,
589 }
603 }
590 return data
604 return data
591
605
592
606
593 @jsonrpc_method()
607 @jsonrpc_method()
594 def update_pull_request(
608 def update_pull_request(
595 request, apiuser, repoid, pullrequestid, title=Optional(''),
609 request, apiuser, repoid, pullrequestid, title=Optional(''),
596 description=Optional(''), reviewers=Optional(None),
610 description=Optional(''), reviewers=Optional(None),
597 update_commits=Optional(None), close_pull_request=Optional(None)):
611 update_commits=Optional(None), close_pull_request=Optional(None)):
598 """
612 """
599 Updates a pull request.
613 Updates a pull request.
600
614
601 :param apiuser: This is filled automatically from the |authtoken|.
615 :param apiuser: This is filled automatically from the |authtoken|.
602 :type apiuser: AuthUser
616 :type apiuser: AuthUser
603 :param repoid: The repository name or repository ID.
617 :param repoid: The repository name or repository ID.
604 :type repoid: str or int
618 :type repoid: str or int
605 :param pullrequestid: The pull request ID.
619 :param pullrequestid: The pull request ID.
606 :type pullrequestid: int
620 :type pullrequestid: int
607 :param title: Set the pull request title.
621 :param title: Set the pull request title.
608 :type title: str
622 :type title: str
609 :param description: Update pull request description.
623 :param description: Update pull request description.
610 :type description: Optional(str)
624 :type description: Optional(str)
611 :param reviewers: Update pull request reviewers list with new value.
625 :param reviewers: Update pull request reviewers list with new value.
612 :type reviewers: Optional(list)
626 :type reviewers: Optional(list)
613 :param update_commits: Trigger update of commits for this pull request
627 :param update_commits: Trigger update of commits for this pull request
614 :type: update_commits: Optional(bool)
628 :type: update_commits: Optional(bool)
615 :param close_pull_request: Close this pull request with rejected state
629 :param close_pull_request: Close this pull request with rejected state
616 :type: close_pull_request: Optional(bool)
630 :type: close_pull_request: Optional(bool)
617
631
618 Example output:
632 Example output:
619
633
620 .. code-block:: bash
634 .. code-block:: bash
621
635
622 id : <id_given_in_input>
636 id : <id_given_in_input>
623 result :
637 result :
624 {
638 {
625 "msg": "Updated pull request `63`",
639 "msg": "Updated pull request `63`",
626 "pull_request": <pull_request_object>,
640 "pull_request": <pull_request_object>,
627 "updated_reviewers": {
641 "updated_reviewers": {
628 "added": [
642 "added": [
629 "username"
643 "username"
630 ],
644 ],
631 "removed": []
645 "removed": []
632 },
646 },
633 "updated_commits": {
647 "updated_commits": {
634 "added": [
648 "added": [
635 "<sha1_hash>"
649 "<sha1_hash>"
636 ],
650 ],
637 "common": [
651 "common": [
638 "<sha1_hash>",
652 "<sha1_hash>",
639 "<sha1_hash>",
653 "<sha1_hash>",
640 ],
654 ],
641 "removed": []
655 "removed": []
642 }
656 }
643 }
657 }
644 error : null
658 error : null
645 """
659 """
646
660
647 repo = get_repo_or_error(repoid)
661 repo = get_repo_or_error(repoid)
648 pull_request = get_pull_request_or_error(pullrequestid)
662 pull_request = get_pull_request_or_error(pullrequestid)
649 if not PullRequestModel().check_user_update(
663 if not PullRequestModel().check_user_update(
650 pull_request, apiuser, api=True):
664 pull_request, apiuser, api=True):
651 raise JSONRPCError(
665 raise JSONRPCError(
652 'pull request `%s` update failed, no permission to update.' % (
666 'pull request `%s` update failed, no permission to update.' % (
653 pullrequestid,))
667 pullrequestid,))
654 if pull_request.is_closed():
668 if pull_request.is_closed():
655 raise JSONRPCError(
669 raise JSONRPCError(
656 'pull request `%s` update failed, pull request is closed' % (
670 'pull request `%s` update failed, pull request is closed' % (
657 pullrequestid,))
671 pullrequestid,))
658
672
659 reviewer_objects = Optional.extract(reviewers) or []
673 reviewer_objects = Optional.extract(reviewers) or []
660 if not isinstance(reviewer_objects, list):
674 if not isinstance(reviewer_objects, list):
661 raise JSONRPCError('reviewers should be specified as a list')
675 raise JSONRPCError('reviewers should be specified as a list')
662
676
663 reviewers_reasons = []
677 reviewers_reasons = []
664 reviewer_ids = set()
678 reviewer_ids = set()
665 for reviewer_object in reviewer_objects:
679 for reviewer_object in reviewer_objects:
666 reviewer_reasons = []
680 reviewer_reasons = []
667 if isinstance(reviewer_object, (int, basestring)):
681 if isinstance(reviewer_object, (int, basestring)):
668 reviewer_username = reviewer_object
682 reviewer_username = reviewer_object
669 else:
683 else:
670 reviewer_username = reviewer_object['username']
684 reviewer_username = reviewer_object['username']
671 reviewer_reasons = reviewer_object.get('reasons', [])
685 reviewer_reasons = reviewer_object.get('reasons', [])
672
686
673 user = get_user_or_error(reviewer_username)
687 user = get_user_or_error(reviewer_username)
674 reviewer_ids.add(user.user_id)
688 reviewer_ids.add(user.user_id)
675 reviewers_reasons.append((user.user_id, reviewer_reasons))
689 reviewers_reasons.append((user.user_id, reviewer_reasons))
676
690
677 title = Optional.extract(title)
691 title = Optional.extract(title)
678 description = Optional.extract(description)
692 description = Optional.extract(description)
679 if title or description:
693 if title or description:
680 PullRequestModel().edit(
694 PullRequestModel().edit(
681 pull_request, title or pull_request.title,
695 pull_request, title or pull_request.title,
682 description or pull_request.description)
696 description or pull_request.description)
683 Session().commit()
697 Session().commit()
684
698
685 commit_changes = {"added": [], "common": [], "removed": []}
699 commit_changes = {"added": [], "common": [], "removed": []}
686 if str2bool(Optional.extract(update_commits)):
700 if str2bool(Optional.extract(update_commits)):
687 if PullRequestModel().has_valid_update_type(pull_request):
701 if PullRequestModel().has_valid_update_type(pull_request):
688 update_response = PullRequestModel().update_commits(
702 update_response = PullRequestModel().update_commits(
689 pull_request)
703 pull_request)
690 commit_changes = update_response.changes or commit_changes
704 commit_changes = update_response.changes or commit_changes
691 Session().commit()
705 Session().commit()
692
706
693 reviewers_changes = {"added": [], "removed": []}
707 reviewers_changes = {"added": [], "removed": []}
694 if reviewer_ids:
708 if reviewer_ids:
695 added_reviewers, removed_reviewers = \
709 added_reviewers, removed_reviewers = \
696 PullRequestModel().update_reviewers(pull_request, reviewers_reasons)
710 PullRequestModel().update_reviewers(pull_request, reviewers_reasons)
697
711
698 reviewers_changes['added'] = sorted(
712 reviewers_changes['added'] = sorted(
699 [get_user_or_error(n).username for n in added_reviewers])
713 [get_user_or_error(n).username for n in added_reviewers])
700 reviewers_changes['removed'] = sorted(
714 reviewers_changes['removed'] = sorted(
701 [get_user_or_error(n).username for n in removed_reviewers])
715 [get_user_or_error(n).username for n in removed_reviewers])
702 Session().commit()
716 Session().commit()
703
717
704 if str2bool(Optional.extract(close_pull_request)):
718 if str2bool(Optional.extract(close_pull_request)):
705 PullRequestModel().close_pull_request_with_comment(
719 PullRequestModel().close_pull_request_with_comment(
706 pull_request, apiuser, repo)
720 pull_request, apiuser, repo)
707 Session().commit()
721 Session().commit()
708
722
709 data = {
723 data = {
710 'msg': 'Updated pull request `{}`'.format(
724 'msg': 'Updated pull request `{}`'.format(
711 pull_request.pull_request_id),
725 pull_request.pull_request_id),
712 'pull_request': pull_request.get_api_data(),
726 'pull_request': pull_request.get_api_data(),
713 'updated_commits': commit_changes,
727 'updated_commits': commit_changes,
714 'updated_reviewers': reviewers_changes
728 'updated_reviewers': reviewers_changes
715 }
729 }
716
730
717 return data
731 return data
@@ -1,1967 +1,1981 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 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 import logging
21 import logging
22 import time
22 import time
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.api import (
25 from rhodecode.api import (
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 from rhodecode.api.utils import (
27 from rhodecode.api.utils import (
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 validate_set_owner_permissions)
31 validate_set_owner_permissions)
32 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
32 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
33 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
33 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
34 from rhodecode.lib.utils2 import str2bool, time_to_datetime
34 from rhodecode.lib.utils2 import str2bool, time_to_datetime
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.model.changeset_status import ChangesetStatusModel
36 from rhodecode.model.changeset_status import ChangesetStatusModel
37 from rhodecode.model.comment import CommentsModel
37 from rhodecode.model.comment import CommentsModel
38 from rhodecode.model.db import (
38 from rhodecode.model.db import (
39 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
39 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
40 ChangesetComment)
40 ChangesetComment)
41 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.scm import ScmModel, RepoList
42 from rhodecode.model.scm import ScmModel, RepoList
43 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
43 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
44 from rhodecode.model import validation_schema
44 from rhodecode.model import validation_schema
45 from rhodecode.model.validation_schema.schemas import repo_schema
45 from rhodecode.model.validation_schema.schemas import repo_schema
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 @jsonrpc_method()
50 @jsonrpc_method()
51 def get_repo(request, apiuser, repoid, cache=Optional(True)):
51 def get_repo(request, apiuser, repoid, cache=Optional(True)):
52 """
52 """
53 Gets an existing repository by its name or repository_id.
53 Gets an existing repository by its name or repository_id.
54
54
55 The members section so the output returns users groups or users
55 The members section so the output returns users groups or users
56 associated with that repository.
56 associated with that repository.
57
57
58 This command can only be run using an |authtoken| with admin rights,
58 This command can only be run using an |authtoken| with admin rights,
59 or users with at least read rights to the |repo|.
59 or users with at least read rights to the |repo|.
60
60
61 :param apiuser: This is filled automatically from the |authtoken|.
61 :param apiuser: This is filled automatically from the |authtoken|.
62 :type apiuser: AuthUser
62 :type apiuser: AuthUser
63 :param repoid: The repository name or repository id.
63 :param repoid: The repository name or repository id.
64 :type repoid: str or int
64 :type repoid: str or int
65 :param cache: use the cached value for last changeset
65 :param cache: use the cached value for last changeset
66 :type: cache: Optional(bool)
66 :type: cache: Optional(bool)
67
67
68 Example output:
68 Example output:
69
69
70 .. code-block:: bash
70 .. code-block:: bash
71
71
72 {
72 {
73 "error": null,
73 "error": null,
74 "id": <repo_id>,
74 "id": <repo_id>,
75 "result": {
75 "result": {
76 "clone_uri": null,
76 "clone_uri": null,
77 "created_on": "timestamp",
77 "created_on": "timestamp",
78 "description": "repo description",
78 "description": "repo description",
79 "enable_downloads": false,
79 "enable_downloads": false,
80 "enable_locking": false,
80 "enable_locking": false,
81 "enable_statistics": false,
81 "enable_statistics": false,
82 "followers": [
82 "followers": [
83 {
83 {
84 "active": true,
84 "active": true,
85 "admin": false,
85 "admin": false,
86 "api_key": "****************************************",
86 "api_key": "****************************************",
87 "api_keys": [
87 "api_keys": [
88 "****************************************"
88 "****************************************"
89 ],
89 ],
90 "email": "user@example.com",
90 "email": "user@example.com",
91 "emails": [
91 "emails": [
92 "user@example.com"
92 "user@example.com"
93 ],
93 ],
94 "extern_name": "rhodecode",
94 "extern_name": "rhodecode",
95 "extern_type": "rhodecode",
95 "extern_type": "rhodecode",
96 "firstname": "username",
96 "firstname": "username",
97 "ip_addresses": [],
97 "ip_addresses": [],
98 "language": null,
98 "language": null,
99 "last_login": "2015-09-16T17:16:35.854",
99 "last_login": "2015-09-16T17:16:35.854",
100 "lastname": "surname",
100 "lastname": "surname",
101 "user_id": <user_id>,
101 "user_id": <user_id>,
102 "username": "name"
102 "username": "name"
103 }
103 }
104 ],
104 ],
105 "fork_of": "parent-repo",
105 "fork_of": "parent-repo",
106 "landing_rev": [
106 "landing_rev": [
107 "rev",
107 "rev",
108 "tip"
108 "tip"
109 ],
109 ],
110 "last_changeset": {
110 "last_changeset": {
111 "author": "User <user@example.com>",
111 "author": "User <user@example.com>",
112 "branch": "default",
112 "branch": "default",
113 "date": "timestamp",
113 "date": "timestamp",
114 "message": "last commit message",
114 "message": "last commit message",
115 "parents": [
115 "parents": [
116 {
116 {
117 "raw_id": "commit-id"
117 "raw_id": "commit-id"
118 }
118 }
119 ],
119 ],
120 "raw_id": "commit-id",
120 "raw_id": "commit-id",
121 "revision": <revision number>,
121 "revision": <revision number>,
122 "short_id": "short id"
122 "short_id": "short id"
123 },
123 },
124 "lock_reason": null,
124 "lock_reason": null,
125 "locked_by": null,
125 "locked_by": null,
126 "locked_date": null,
126 "locked_date": null,
127 "members": [
127 "members": [
128 {
128 {
129 "name": "super-admin-name",
129 "name": "super-admin-name",
130 "origin": "super-admin",
130 "origin": "super-admin",
131 "permission": "repository.admin",
131 "permission": "repository.admin",
132 "type": "user"
132 "type": "user"
133 },
133 },
134 {
134 {
135 "name": "owner-name",
135 "name": "owner-name",
136 "origin": "owner",
136 "origin": "owner",
137 "permission": "repository.admin",
137 "permission": "repository.admin",
138 "type": "user"
138 "type": "user"
139 },
139 },
140 {
140 {
141 "name": "user-group-name",
141 "name": "user-group-name",
142 "origin": "permission",
142 "origin": "permission",
143 "permission": "repository.write",
143 "permission": "repository.write",
144 "type": "user_group"
144 "type": "user_group"
145 }
145 }
146 ],
146 ],
147 "owner": "owner-name",
147 "owner": "owner-name",
148 "permissions": [
148 "permissions": [
149 {
149 {
150 "name": "super-admin-name",
150 "name": "super-admin-name",
151 "origin": "super-admin",
151 "origin": "super-admin",
152 "permission": "repository.admin",
152 "permission": "repository.admin",
153 "type": "user"
153 "type": "user"
154 },
154 },
155 {
155 {
156 "name": "owner-name",
156 "name": "owner-name",
157 "origin": "owner",
157 "origin": "owner",
158 "permission": "repository.admin",
158 "permission": "repository.admin",
159 "type": "user"
159 "type": "user"
160 },
160 },
161 {
161 {
162 "name": "user-group-name",
162 "name": "user-group-name",
163 "origin": "permission",
163 "origin": "permission",
164 "permission": "repository.write",
164 "permission": "repository.write",
165 "type": "user_group"
165 "type": "user_group"
166 }
166 }
167 ],
167 ],
168 "private": true,
168 "private": true,
169 "repo_id": 676,
169 "repo_id": 676,
170 "repo_name": "user-group/repo-name",
170 "repo_name": "user-group/repo-name",
171 "repo_type": "hg"
171 "repo_type": "hg"
172 }
172 }
173 }
173 }
174 """
174 """
175
175
176 repo = get_repo_or_error(repoid)
176 repo = get_repo_or_error(repoid)
177 cache = Optional.extract(cache)
177 cache = Optional.extract(cache)
178
178
179 include_secrets = False
179 include_secrets = False
180 if has_superadmin_permission(apiuser):
180 if has_superadmin_permission(apiuser):
181 include_secrets = True
181 include_secrets = True
182 else:
182 else:
183 # check if we have at least read permission for this repo !
183 # check if we have at least read permission for this repo !
184 _perms = (
184 _perms = (
185 'repository.admin', 'repository.write', 'repository.read',)
185 'repository.admin', 'repository.write', 'repository.read',)
186 validate_repo_permissions(apiuser, repoid, repo, _perms)
186 validate_repo_permissions(apiuser, repoid, repo, _perms)
187
187
188 permissions = []
188 permissions = []
189 for _user in repo.permissions():
189 for _user in repo.permissions():
190 user_data = {
190 user_data = {
191 'name': _user.username,
191 'name': _user.username,
192 'permission': _user.permission,
192 'permission': _user.permission,
193 'origin': get_origin(_user),
193 'origin': get_origin(_user),
194 'type': "user",
194 'type': "user",
195 }
195 }
196 permissions.append(user_data)
196 permissions.append(user_data)
197
197
198 for _user_group in repo.permission_user_groups():
198 for _user_group in repo.permission_user_groups():
199 user_group_data = {
199 user_group_data = {
200 'name': _user_group.users_group_name,
200 'name': _user_group.users_group_name,
201 'permission': _user_group.permission,
201 'permission': _user_group.permission,
202 'origin': get_origin(_user_group),
202 'origin': get_origin(_user_group),
203 'type': "user_group",
203 'type': "user_group",
204 }
204 }
205 permissions.append(user_group_data)
205 permissions.append(user_group_data)
206
206
207 following_users = [
207 following_users = [
208 user.user.get_api_data(include_secrets=include_secrets)
208 user.user.get_api_data(include_secrets=include_secrets)
209 for user in repo.followers]
209 for user in repo.followers]
210
210
211 if not cache:
211 if not cache:
212 repo.update_commit_cache()
212 repo.update_commit_cache()
213 data = repo.get_api_data(include_secrets=include_secrets)
213 data = repo.get_api_data(include_secrets=include_secrets)
214 data['members'] = permissions # TODO: this should be deprecated soon
214 data['members'] = permissions # TODO: this should be deprecated soon
215 data['permissions'] = permissions
215 data['permissions'] = permissions
216 data['followers'] = following_users
216 data['followers'] = following_users
217 return data
217 return data
218
218
219
219
220 @jsonrpc_method()
220 @jsonrpc_method()
221 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
221 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
222 """
222 """
223 Lists all existing repositories.
223 Lists all existing repositories.
224
224
225 This command can only be run using an |authtoken| with admin rights,
225 This command can only be run using an |authtoken| with admin rights,
226 or users with at least read rights to |repos|.
226 or users with at least read rights to |repos|.
227
227
228 :param apiuser: This is filled automatically from the |authtoken|.
228 :param apiuser: This is filled automatically from the |authtoken|.
229 :type apiuser: AuthUser
229 :type apiuser: AuthUser
230 :param root: specify root repository group to fetch repositories.
230 :param root: specify root repository group to fetch repositories.
231 filters the returned repositories to be members of given root group.
231 filters the returned repositories to be members of given root group.
232 :type root: Optional(None)
232 :type root: Optional(None)
233 :param traverse: traverse given root into subrepositories. With this flag
233 :param traverse: traverse given root into subrepositories. With this flag
234 set to False, it will only return top-level repositories from `root`.
234 set to False, it will only return top-level repositories from `root`.
235 if root is empty it will return just top-level repositories.
235 if root is empty it will return just top-level repositories.
236 :type traverse: Optional(True)
236 :type traverse: Optional(True)
237
237
238
238
239 Example output:
239 Example output:
240
240
241 .. code-block:: bash
241 .. code-block:: bash
242
242
243 id : <id_given_in_input>
243 id : <id_given_in_input>
244 result: [
244 result: [
245 {
245 {
246 "repo_id" : "<repo_id>",
246 "repo_id" : "<repo_id>",
247 "repo_name" : "<reponame>"
247 "repo_name" : "<reponame>"
248 "repo_type" : "<repo_type>",
248 "repo_type" : "<repo_type>",
249 "clone_uri" : "<clone_uri>",
249 "clone_uri" : "<clone_uri>",
250 "private": : "<bool>",
250 "private": : "<bool>",
251 "created_on" : "<datetimecreated>",
251 "created_on" : "<datetimecreated>",
252 "description" : "<description>",
252 "description" : "<description>",
253 "landing_rev": "<landing_rev>",
253 "landing_rev": "<landing_rev>",
254 "owner": "<repo_owner>",
254 "owner": "<repo_owner>",
255 "fork_of": "<name_of_fork_parent>",
255 "fork_of": "<name_of_fork_parent>",
256 "enable_downloads": "<bool>",
256 "enable_downloads": "<bool>",
257 "enable_locking": "<bool>",
257 "enable_locking": "<bool>",
258 "enable_statistics": "<bool>",
258 "enable_statistics": "<bool>",
259 },
259 },
260 ...
260 ...
261 ]
261 ]
262 error: null
262 error: null
263 """
263 """
264
264
265 include_secrets = has_superadmin_permission(apiuser)
265 include_secrets = has_superadmin_permission(apiuser)
266 _perms = ('repository.read', 'repository.write', 'repository.admin',)
266 _perms = ('repository.read', 'repository.write', 'repository.admin',)
267 extras = {'user': apiuser}
267 extras = {'user': apiuser}
268
268
269 root = Optional.extract(root)
269 root = Optional.extract(root)
270 traverse = Optional.extract(traverse, binary=True)
270 traverse = Optional.extract(traverse, binary=True)
271
271
272 if root:
272 if root:
273 # verify parent existance, if it's empty return an error
273 # verify parent existance, if it's empty return an error
274 parent = RepoGroup.get_by_group_name(root)
274 parent = RepoGroup.get_by_group_name(root)
275 if not parent:
275 if not parent:
276 raise JSONRPCError(
276 raise JSONRPCError(
277 'Root repository group `{}` does not exist'.format(root))
277 'Root repository group `{}` does not exist'.format(root))
278
278
279 if traverse:
279 if traverse:
280 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
280 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
281 else:
281 else:
282 repos = RepoModel().get_repos_for_root(root=parent)
282 repos = RepoModel().get_repos_for_root(root=parent)
283 else:
283 else:
284 if traverse:
284 if traverse:
285 repos = RepoModel().get_all()
285 repos = RepoModel().get_all()
286 else:
286 else:
287 # return just top-level
287 # return just top-level
288 repos = RepoModel().get_repos_for_root(root=None)
288 repos = RepoModel().get_repos_for_root(root=None)
289
289
290 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
290 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
291 return [repo.get_api_data(include_secrets=include_secrets)
291 return [repo.get_api_data(include_secrets=include_secrets)
292 for repo in repo_list]
292 for repo in repo_list]
293
293
294
294
295 @jsonrpc_method()
295 @jsonrpc_method()
296 def get_repo_changeset(request, apiuser, repoid, revision,
296 def get_repo_changeset(request, apiuser, repoid, revision,
297 details=Optional('basic')):
297 details=Optional('basic')):
298 """
298 """
299 Returns information about a changeset.
299 Returns information about a changeset.
300
300
301 Additionally parameters define the amount of details returned by
301 Additionally parameters define the amount of details returned by
302 this function.
302 this function.
303
303
304 This command can only be run using an |authtoken| with admin rights,
304 This command can only be run using an |authtoken| with admin rights,
305 or users with at least read rights to the |repo|.
305 or users with at least read rights to the |repo|.
306
306
307 :param apiuser: This is filled automatically from the |authtoken|.
307 :param apiuser: This is filled automatically from the |authtoken|.
308 :type apiuser: AuthUser
308 :type apiuser: AuthUser
309 :param repoid: The repository name or repository id
309 :param repoid: The repository name or repository id
310 :type repoid: str or int
310 :type repoid: str or int
311 :param revision: revision for which listing should be done
311 :param revision: revision for which listing should be done
312 :type revision: str
312 :type revision: str
313 :param details: details can be 'basic|extended|full' full gives diff
313 :param details: details can be 'basic|extended|full' full gives diff
314 info details like the diff itself, and number of changed files etc.
314 info details like the diff itself, and number of changed files etc.
315 :type details: Optional(str)
315 :type details: Optional(str)
316
316
317 """
317 """
318 repo = get_repo_or_error(repoid)
318 repo = get_repo_or_error(repoid)
319 if not has_superadmin_permission(apiuser):
319 if not has_superadmin_permission(apiuser):
320 _perms = (
320 _perms = (
321 'repository.admin', 'repository.write', 'repository.read',)
321 'repository.admin', 'repository.write', 'repository.read',)
322 validate_repo_permissions(apiuser, repoid, repo, _perms)
322 validate_repo_permissions(apiuser, repoid, repo, _perms)
323
323
324 changes_details = Optional.extract(details)
324 changes_details = Optional.extract(details)
325 _changes_details_types = ['basic', 'extended', 'full']
325 _changes_details_types = ['basic', 'extended', 'full']
326 if changes_details not in _changes_details_types:
326 if changes_details not in _changes_details_types:
327 raise JSONRPCError(
327 raise JSONRPCError(
328 'ret_type must be one of %s' % (
328 'ret_type must be one of %s' % (
329 ','.join(_changes_details_types)))
329 ','.join(_changes_details_types)))
330
330
331 pre_load = ['author', 'branch', 'date', 'message', 'parents',
331 pre_load = ['author', 'branch', 'date', 'message', 'parents',
332 'status', '_commit', '_file_paths']
332 'status', '_commit', '_file_paths']
333
333
334 try:
334 try:
335 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
335 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
336 except TypeError as e:
336 except TypeError as e:
337 raise JSONRPCError(e.message)
337 raise JSONRPCError(e.message)
338 _cs_json = cs.__json__()
338 _cs_json = cs.__json__()
339 _cs_json['diff'] = build_commit_data(cs, changes_details)
339 _cs_json['diff'] = build_commit_data(cs, changes_details)
340 if changes_details == 'full':
340 if changes_details == 'full':
341 _cs_json['refs'] = {
341 _cs_json['refs'] = {
342 'branches': [cs.branch],
342 'branches': [cs.branch],
343 'bookmarks': getattr(cs, 'bookmarks', []),
343 'bookmarks': getattr(cs, 'bookmarks', []),
344 'tags': cs.tags
344 'tags': cs.tags
345 }
345 }
346 return _cs_json
346 return _cs_json
347
347
348
348
349 @jsonrpc_method()
349 @jsonrpc_method()
350 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
350 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
351 details=Optional('basic')):
351 details=Optional('basic')):
352 """
352 """
353 Returns a set of commits limited by the number starting
353 Returns a set of commits limited by the number starting
354 from the `start_rev` option.
354 from the `start_rev` option.
355
355
356 Additional parameters define the amount of details returned by this
356 Additional parameters define the amount of details returned by this
357 function.
357 function.
358
358
359 This command can only be run using an |authtoken| with admin rights,
359 This command can only be run using an |authtoken| with admin rights,
360 or users with at least read rights to |repos|.
360 or users with at least read rights to |repos|.
361
361
362 :param apiuser: This is filled automatically from the |authtoken|.
362 :param apiuser: This is filled automatically from the |authtoken|.
363 :type apiuser: AuthUser
363 :type apiuser: AuthUser
364 :param repoid: The repository name or repository ID.
364 :param repoid: The repository name or repository ID.
365 :type repoid: str or int
365 :type repoid: str or int
366 :param start_rev: The starting revision from where to get changesets.
366 :param start_rev: The starting revision from where to get changesets.
367 :type start_rev: str
367 :type start_rev: str
368 :param limit: Limit the number of commits to this amount
368 :param limit: Limit the number of commits to this amount
369 :type limit: str or int
369 :type limit: str or int
370 :param details: Set the level of detail returned. Valid option are:
370 :param details: Set the level of detail returned. Valid option are:
371 ``basic``, ``extended`` and ``full``.
371 ``basic``, ``extended`` and ``full``.
372 :type details: Optional(str)
372 :type details: Optional(str)
373
373
374 .. note::
374 .. note::
375
375
376 Setting the parameter `details` to the value ``full`` is extensive
376 Setting the parameter `details` to the value ``full`` is extensive
377 and returns details like the diff itself, and the number
377 and returns details like the diff itself, and the number
378 of changed files.
378 of changed files.
379
379
380 """
380 """
381 repo = get_repo_or_error(repoid)
381 repo = get_repo_or_error(repoid)
382 if not has_superadmin_permission(apiuser):
382 if not has_superadmin_permission(apiuser):
383 _perms = (
383 _perms = (
384 'repository.admin', 'repository.write', 'repository.read',)
384 'repository.admin', 'repository.write', 'repository.read',)
385 validate_repo_permissions(apiuser, repoid, repo, _perms)
385 validate_repo_permissions(apiuser, repoid, repo, _perms)
386
386
387 changes_details = Optional.extract(details)
387 changes_details = Optional.extract(details)
388 _changes_details_types = ['basic', 'extended', 'full']
388 _changes_details_types = ['basic', 'extended', 'full']
389 if changes_details not in _changes_details_types:
389 if changes_details not in _changes_details_types:
390 raise JSONRPCError(
390 raise JSONRPCError(
391 'ret_type must be one of %s' % (
391 'ret_type must be one of %s' % (
392 ','.join(_changes_details_types)))
392 ','.join(_changes_details_types)))
393
393
394 limit = int(limit)
394 limit = int(limit)
395 pre_load = ['author', 'branch', 'date', 'message', 'parents',
395 pre_load = ['author', 'branch', 'date', 'message', 'parents',
396 'status', '_commit', '_file_paths']
396 'status', '_commit', '_file_paths']
397
397
398 vcs_repo = repo.scm_instance()
398 vcs_repo = repo.scm_instance()
399 # SVN needs a special case to distinguish its index and commit id
399 # SVN needs a special case to distinguish its index and commit id
400 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
400 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
401 start_rev = vcs_repo.commit_ids[0]
401 start_rev = vcs_repo.commit_ids[0]
402
402
403 try:
403 try:
404 commits = vcs_repo.get_commits(
404 commits = vcs_repo.get_commits(
405 start_id=start_rev, pre_load=pre_load)
405 start_id=start_rev, pre_load=pre_load)
406 except TypeError as e:
406 except TypeError as e:
407 raise JSONRPCError(e.message)
407 raise JSONRPCError(e.message)
408 except Exception:
408 except Exception:
409 log.exception('Fetching of commits failed')
409 log.exception('Fetching of commits failed')
410 raise JSONRPCError('Error occurred during commit fetching')
410 raise JSONRPCError('Error occurred during commit fetching')
411
411
412 ret = []
412 ret = []
413 for cnt, commit in enumerate(commits):
413 for cnt, commit in enumerate(commits):
414 if cnt >= limit != -1:
414 if cnt >= limit != -1:
415 break
415 break
416 _cs_json = commit.__json__()
416 _cs_json = commit.__json__()
417 _cs_json['diff'] = build_commit_data(commit, changes_details)
417 _cs_json['diff'] = build_commit_data(commit, changes_details)
418 if changes_details == 'full':
418 if changes_details == 'full':
419 _cs_json['refs'] = {
419 _cs_json['refs'] = {
420 'branches': [commit.branch],
420 'branches': [commit.branch],
421 'bookmarks': getattr(commit, 'bookmarks', []),
421 'bookmarks': getattr(commit, 'bookmarks', []),
422 'tags': commit.tags
422 'tags': commit.tags
423 }
423 }
424 ret.append(_cs_json)
424 ret.append(_cs_json)
425 return ret
425 return ret
426
426
427
427
428 @jsonrpc_method()
428 @jsonrpc_method()
429 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
429 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
430 ret_type=Optional('all'), details=Optional('basic'),
430 ret_type=Optional('all'), details=Optional('basic'),
431 max_file_bytes=Optional(None)):
431 max_file_bytes=Optional(None)):
432 """
432 """
433 Returns a list of nodes and children in a flat list for a given
433 Returns a list of nodes and children in a flat list for a given
434 path at given revision.
434 path at given revision.
435
435
436 It's possible to specify ret_type to show only `files` or `dirs`.
436 It's possible to specify ret_type to show only `files` or `dirs`.
437
437
438 This command can only be run using an |authtoken| with admin rights,
438 This command can only be run using an |authtoken| with admin rights,
439 or users with at least read rights to |repos|.
439 or users with at least read rights to |repos|.
440
440
441 :param apiuser: This is filled automatically from the |authtoken|.
441 :param apiuser: This is filled automatically from the |authtoken|.
442 :type apiuser: AuthUser
442 :type apiuser: AuthUser
443 :param repoid: The repository name or repository ID.
443 :param repoid: The repository name or repository ID.
444 :type repoid: str or int
444 :type repoid: str or int
445 :param revision: The revision for which listing should be done.
445 :param revision: The revision for which listing should be done.
446 :type revision: str
446 :type revision: str
447 :param root_path: The path from which to start displaying.
447 :param root_path: The path from which to start displaying.
448 :type root_path: str
448 :type root_path: str
449 :param ret_type: Set the return type. Valid options are
449 :param ret_type: Set the return type. Valid options are
450 ``all`` (default), ``files`` and ``dirs``.
450 ``all`` (default), ``files`` and ``dirs``.
451 :type ret_type: Optional(str)
451 :type ret_type: Optional(str)
452 :param details: Returns extended information about nodes, such as
452 :param details: Returns extended information about nodes, such as
453 md5, binary, and or content. The valid options are ``basic`` and
453 md5, binary, and or content. The valid options are ``basic`` and
454 ``full``.
454 ``full``.
455 :type details: Optional(str)
455 :type details: Optional(str)
456 :param max_file_bytes: Only return file content under this file size bytes
456 :param max_file_bytes: Only return file content under this file size bytes
457 :type details: Optional(int)
457 :type details: Optional(int)
458
458
459 Example output:
459 Example output:
460
460
461 .. code-block:: bash
461 .. code-block:: bash
462
462
463 id : <id_given_in_input>
463 id : <id_given_in_input>
464 result: [
464 result: [
465 {
465 {
466 "name" : "<name>"
466 "name" : "<name>"
467 "type" : "<type>",
467 "type" : "<type>",
468 "binary": "<true|false>" (only in extended mode)
468 "binary": "<true|false>" (only in extended mode)
469 "md5" : "<md5 of file content>" (only in extended mode)
469 "md5" : "<md5 of file content>" (only in extended mode)
470 },
470 },
471 ...
471 ...
472 ]
472 ]
473 error: null
473 error: null
474 """
474 """
475
475
476 repo = get_repo_or_error(repoid)
476 repo = get_repo_or_error(repoid)
477 if not has_superadmin_permission(apiuser):
477 if not has_superadmin_permission(apiuser):
478 _perms = (
478 _perms = (
479 'repository.admin', 'repository.write', 'repository.read',)
479 'repository.admin', 'repository.write', 'repository.read',)
480 validate_repo_permissions(apiuser, repoid, repo, _perms)
480 validate_repo_permissions(apiuser, repoid, repo, _perms)
481
481
482 ret_type = Optional.extract(ret_type)
482 ret_type = Optional.extract(ret_type)
483 details = Optional.extract(details)
483 details = Optional.extract(details)
484 _extended_types = ['basic', 'full']
484 _extended_types = ['basic', 'full']
485 if details not in _extended_types:
485 if details not in _extended_types:
486 raise JSONRPCError(
486 raise JSONRPCError(
487 'ret_type must be one of %s' % (','.join(_extended_types)))
487 'ret_type must be one of %s' % (','.join(_extended_types)))
488 extended_info = False
488 extended_info = False
489 content = False
489 content = False
490 if details == 'basic':
490 if details == 'basic':
491 extended_info = True
491 extended_info = True
492
492
493 if details == 'full':
493 if details == 'full':
494 extended_info = content = True
494 extended_info = content = True
495
495
496 _map = {}
496 _map = {}
497 try:
497 try:
498 # check if repo is not empty by any chance, skip quicker if it is.
498 # check if repo is not empty by any chance, skip quicker if it is.
499 _scm = repo.scm_instance()
499 _scm = repo.scm_instance()
500 if _scm.is_empty():
500 if _scm.is_empty():
501 return []
501 return []
502
502
503 _d, _f = ScmModel().get_nodes(
503 _d, _f = ScmModel().get_nodes(
504 repo, revision, root_path, flat=False,
504 repo, revision, root_path, flat=False,
505 extended_info=extended_info, content=content,
505 extended_info=extended_info, content=content,
506 max_file_bytes=max_file_bytes)
506 max_file_bytes=max_file_bytes)
507 _map = {
507 _map = {
508 'all': _d + _f,
508 'all': _d + _f,
509 'files': _f,
509 'files': _f,
510 'dirs': _d,
510 'dirs': _d,
511 }
511 }
512 return _map[ret_type]
512 return _map[ret_type]
513 except KeyError:
513 except KeyError:
514 raise JSONRPCError(
514 raise JSONRPCError(
515 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
515 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
516 except Exception:
516 except Exception:
517 log.exception("Exception occurred while trying to get repo nodes")
517 log.exception("Exception occurred while trying to get repo nodes")
518 raise JSONRPCError(
518 raise JSONRPCError(
519 'failed to get repo: `%s` nodes' % repo.repo_name
519 'failed to get repo: `%s` nodes' % repo.repo_name
520 )
520 )
521
521
522
522
523 @jsonrpc_method()
523 @jsonrpc_method()
524 def get_repo_refs(request, apiuser, repoid):
524 def get_repo_refs(request, apiuser, repoid):
525 """
525 """
526 Returns a dictionary of current references. It returns
526 Returns a dictionary of current references. It returns
527 bookmarks, branches, closed_branches, and tags for given repository
527 bookmarks, branches, closed_branches, and tags for given repository
528
528
529 It's possible to specify ret_type to show only `files` or `dirs`.
529 It's possible to specify ret_type to show only `files` or `dirs`.
530
530
531 This command can only be run using an |authtoken| with admin rights,
531 This command can only be run using an |authtoken| with admin rights,
532 or users with at least read rights to |repos|.
532 or users with at least read rights to |repos|.
533
533
534 :param apiuser: This is filled automatically from the |authtoken|.
534 :param apiuser: This is filled automatically from the |authtoken|.
535 :type apiuser: AuthUser
535 :type apiuser: AuthUser
536 :param repoid: The repository name or repository ID.
536 :param repoid: The repository name or repository ID.
537 :type repoid: str or int
537 :type repoid: str or int
538
538
539 Example output:
539 Example output:
540
540
541 .. code-block:: bash
541 .. code-block:: bash
542
542
543 id : <id_given_in_input>
543 id : <id_given_in_input>
544 "result": {
544 "result": {
545 "bookmarks": {
545 "bookmarks": {
546 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
546 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
547 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
547 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
548 },
548 },
549 "branches": {
549 "branches": {
550 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
550 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
551 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
551 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
552 },
552 },
553 "branches_closed": {},
553 "branches_closed": {},
554 "tags": {
554 "tags": {
555 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
555 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
556 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
556 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
557 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
557 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
558 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
558 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
559 }
559 }
560 }
560 }
561 error: null
561 error: null
562 """
562 """
563
563
564 repo = get_repo_or_error(repoid)
564 repo = get_repo_or_error(repoid)
565 if not has_superadmin_permission(apiuser):
565 if not has_superadmin_permission(apiuser):
566 _perms = ('repository.admin', 'repository.write', 'repository.read',)
566 _perms = ('repository.admin', 'repository.write', 'repository.read',)
567 validate_repo_permissions(apiuser, repoid, repo, _perms)
567 validate_repo_permissions(apiuser, repoid, repo, _perms)
568
568
569 try:
569 try:
570 # check if repo is not empty by any chance, skip quicker if it is.
570 # check if repo is not empty by any chance, skip quicker if it is.
571 vcs_instance = repo.scm_instance()
571 vcs_instance = repo.scm_instance()
572 refs = vcs_instance.refs()
572 refs = vcs_instance.refs()
573 return refs
573 return refs
574 except Exception:
574 except Exception:
575 log.exception("Exception occurred while trying to get repo refs")
575 log.exception("Exception occurred while trying to get repo refs")
576 raise JSONRPCError(
576 raise JSONRPCError(
577 'failed to get repo: `%s` references' % repo.repo_name
577 'failed to get repo: `%s` references' % repo.repo_name
578 )
578 )
579
579
580
580
581 @jsonrpc_method()
581 @jsonrpc_method()
582 def create_repo(
582 def create_repo(
583 request, apiuser, repo_name, repo_type,
583 request, apiuser, repo_name, repo_type,
584 owner=Optional(OAttr('apiuser')),
584 owner=Optional(OAttr('apiuser')),
585 description=Optional(''),
585 description=Optional(''),
586 private=Optional(False),
586 private=Optional(False),
587 clone_uri=Optional(None),
587 clone_uri=Optional(None),
588 landing_rev=Optional('rev:tip'),
588 landing_rev=Optional('rev:tip'),
589 enable_statistics=Optional(False),
589 enable_statistics=Optional(False),
590 enable_locking=Optional(False),
590 enable_locking=Optional(False),
591 enable_downloads=Optional(False),
591 enable_downloads=Optional(False),
592 copy_permissions=Optional(False)):
592 copy_permissions=Optional(False)):
593 """
593 """
594 Creates a repository.
594 Creates a repository.
595
595
596 * If the repository name contains "/", repository will be created inside
596 * If the repository name contains "/", repository will be created inside
597 a repository group or nested repository groups
597 a repository group or nested repository groups
598
598
599 For example "foo/bar/repo1" will create |repo| called "repo1" inside
599 For example "foo/bar/repo1" will create |repo| called "repo1" inside
600 group "foo/bar". You have to have permissions to access and write to
600 group "foo/bar". You have to have permissions to access and write to
601 the last repository group ("bar" in this example)
601 the last repository group ("bar" in this example)
602
602
603 This command can only be run using an |authtoken| with at least
603 This command can only be run using an |authtoken| with at least
604 permissions to create repositories, or write permissions to
604 permissions to create repositories, or write permissions to
605 parent repository groups.
605 parent repository groups.
606
606
607 :param apiuser: This is filled automatically from the |authtoken|.
607 :param apiuser: This is filled automatically from the |authtoken|.
608 :type apiuser: AuthUser
608 :type apiuser: AuthUser
609 :param repo_name: Set the repository name.
609 :param repo_name: Set the repository name.
610 :type repo_name: str
610 :type repo_name: str
611 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
611 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
612 :type repo_type: str
612 :type repo_type: str
613 :param owner: user_id or username
613 :param owner: user_id or username
614 :type owner: Optional(str)
614 :type owner: Optional(str)
615 :param description: Set the repository description.
615 :param description: Set the repository description.
616 :type description: Optional(str)
616 :type description: Optional(str)
617 :param private: set repository as private
617 :param private: set repository as private
618 :type private: bool
618 :type private: bool
619 :param clone_uri: set clone_uri
619 :param clone_uri: set clone_uri
620 :type clone_uri: str
620 :type clone_uri: str
621 :param landing_rev: <rev_type>:<rev>
621 :param landing_rev: <rev_type>:<rev>
622 :type landing_rev: str
622 :type landing_rev: str
623 :param enable_locking:
623 :param enable_locking:
624 :type enable_locking: bool
624 :type enable_locking: bool
625 :param enable_downloads:
625 :param enable_downloads:
626 :type enable_downloads: bool
626 :type enable_downloads: bool
627 :param enable_statistics:
627 :param enable_statistics:
628 :type enable_statistics: bool
628 :type enable_statistics: bool
629 :param copy_permissions: Copy permission from group in which the
629 :param copy_permissions: Copy permission from group in which the
630 repository is being created.
630 repository is being created.
631 :type copy_permissions: bool
631 :type copy_permissions: bool
632
632
633
633
634 Example output:
634 Example output:
635
635
636 .. code-block:: bash
636 .. code-block:: bash
637
637
638 id : <id_given_in_input>
638 id : <id_given_in_input>
639 result: {
639 result: {
640 "msg": "Created new repository `<reponame>`",
640 "msg": "Created new repository `<reponame>`",
641 "success": true,
641 "success": true,
642 "task": "<celery task id or None if done sync>"
642 "task": "<celery task id or None if done sync>"
643 }
643 }
644 error: null
644 error: null
645
645
646
646
647 Example error output:
647 Example error output:
648
648
649 .. code-block:: bash
649 .. code-block:: bash
650
650
651 id : <id_given_in_input>
651 id : <id_given_in_input>
652 result : null
652 result : null
653 error : {
653 error : {
654 'failed to create repository `<repo_name>`'
654 'failed to create repository `<repo_name>`'
655 }
655 }
656
656
657 """
657 """
658
658
659 owner = validate_set_owner_permissions(apiuser, owner)
659 owner = validate_set_owner_permissions(apiuser, owner)
660
660
661 description = Optional.extract(description)
661 description = Optional.extract(description)
662 copy_permissions = Optional.extract(copy_permissions)
662 copy_permissions = Optional.extract(copy_permissions)
663 clone_uri = Optional.extract(clone_uri)
663 clone_uri = Optional.extract(clone_uri)
664 landing_commit_ref = Optional.extract(landing_rev)
664 landing_commit_ref = Optional.extract(landing_rev)
665
665
666 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
666 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
667 if isinstance(private, Optional):
667 if isinstance(private, Optional):
668 private = defs.get('repo_private') or Optional.extract(private)
668 private = defs.get('repo_private') or Optional.extract(private)
669 if isinstance(repo_type, Optional):
669 if isinstance(repo_type, Optional):
670 repo_type = defs.get('repo_type')
670 repo_type = defs.get('repo_type')
671 if isinstance(enable_statistics, Optional):
671 if isinstance(enable_statistics, Optional):
672 enable_statistics = defs.get('repo_enable_statistics')
672 enable_statistics = defs.get('repo_enable_statistics')
673 if isinstance(enable_locking, Optional):
673 if isinstance(enable_locking, Optional):
674 enable_locking = defs.get('repo_enable_locking')
674 enable_locking = defs.get('repo_enable_locking')
675 if isinstance(enable_downloads, Optional):
675 if isinstance(enable_downloads, Optional):
676 enable_downloads = defs.get('repo_enable_downloads')
676 enable_downloads = defs.get('repo_enable_downloads')
677
677
678 schema = repo_schema.RepoSchema().bind(
678 schema = repo_schema.RepoSchema().bind(
679 repo_type_options=rhodecode.BACKENDS.keys(),
679 repo_type_options=rhodecode.BACKENDS.keys(),
680 # user caller
680 # user caller
681 user=apiuser)
681 user=apiuser)
682
682
683 try:
683 try:
684 schema_data = schema.deserialize(dict(
684 schema_data = schema.deserialize(dict(
685 repo_name=repo_name,
685 repo_name=repo_name,
686 repo_type=repo_type,
686 repo_type=repo_type,
687 repo_owner=owner.username,
687 repo_owner=owner.username,
688 repo_description=description,
688 repo_description=description,
689 repo_landing_commit_ref=landing_commit_ref,
689 repo_landing_commit_ref=landing_commit_ref,
690 repo_clone_uri=clone_uri,
690 repo_clone_uri=clone_uri,
691 repo_private=private,
691 repo_private=private,
692 repo_copy_permissions=copy_permissions,
692 repo_copy_permissions=copy_permissions,
693 repo_enable_statistics=enable_statistics,
693 repo_enable_statistics=enable_statistics,
694 repo_enable_downloads=enable_downloads,
694 repo_enable_downloads=enable_downloads,
695 repo_enable_locking=enable_locking))
695 repo_enable_locking=enable_locking))
696 except validation_schema.Invalid as err:
696 except validation_schema.Invalid as err:
697 raise JSONRPCValidationError(colander_exc=err)
697 raise JSONRPCValidationError(colander_exc=err)
698
698
699 try:
699 try:
700 data = {
700 data = {
701 'owner': owner,
701 'owner': owner,
702 'repo_name': schema_data['repo_group']['repo_name_without_group'],
702 'repo_name': schema_data['repo_group']['repo_name_without_group'],
703 'repo_name_full': schema_data['repo_name'],
703 'repo_name_full': schema_data['repo_name'],
704 'repo_group': schema_data['repo_group']['repo_group_id'],
704 'repo_group': schema_data['repo_group']['repo_group_id'],
705 'repo_type': schema_data['repo_type'],
705 'repo_type': schema_data['repo_type'],
706 'repo_description': schema_data['repo_description'],
706 'repo_description': schema_data['repo_description'],
707 'repo_private': schema_data['repo_private'],
707 'repo_private': schema_data['repo_private'],
708 'clone_uri': schema_data['repo_clone_uri'],
708 'clone_uri': schema_data['repo_clone_uri'],
709 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
709 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
710 'enable_statistics': schema_data['repo_enable_statistics'],
710 'enable_statistics': schema_data['repo_enable_statistics'],
711 'enable_locking': schema_data['repo_enable_locking'],
711 'enable_locking': schema_data['repo_enable_locking'],
712 'enable_downloads': schema_data['repo_enable_downloads'],
712 'enable_downloads': schema_data['repo_enable_downloads'],
713 'repo_copy_permissions': schema_data['repo_copy_permissions'],
713 'repo_copy_permissions': schema_data['repo_copy_permissions'],
714 }
714 }
715
715
716 task = RepoModel().create(form_data=data, cur_user=owner)
716 task = RepoModel().create(form_data=data, cur_user=owner)
717 from celery.result import BaseAsyncResult
717 from celery.result import BaseAsyncResult
718 task_id = None
718 task_id = None
719 if isinstance(task, BaseAsyncResult):
719 if isinstance(task, BaseAsyncResult):
720 task_id = task.task_id
720 task_id = task.task_id
721 # no commit, it's done in RepoModel, or async via celery
721 # no commit, it's done in RepoModel, or async via celery
722 return {
722 return {
723 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
723 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
724 'success': True, # cannot return the repo data here since fork
724 'success': True, # cannot return the repo data here since fork
725 # can be done async
725 # can be done async
726 'task': task_id
726 'task': task_id
727 }
727 }
728 except Exception:
728 except Exception:
729 log.exception(
729 log.exception(
730 u"Exception while trying to create the repository %s",
730 u"Exception while trying to create the repository %s",
731 schema_data['repo_name'])
731 schema_data['repo_name'])
732 raise JSONRPCError(
732 raise JSONRPCError(
733 'failed to create repository `%s`' % (schema_data['repo_name'],))
733 'failed to create repository `%s`' % (schema_data['repo_name'],))
734
734
735
735
736 @jsonrpc_method()
736 @jsonrpc_method()
737 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
737 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
738 description=Optional('')):
738 description=Optional('')):
739 """
739 """
740 Adds an extra field to a repository.
740 Adds an extra field to a repository.
741
741
742 This command can only be run using an |authtoken| with at least
742 This command can only be run using an |authtoken| with at least
743 write permissions to the |repo|.
743 write permissions to the |repo|.
744
744
745 :param apiuser: This is filled automatically from the |authtoken|.
745 :param apiuser: This is filled automatically from the |authtoken|.
746 :type apiuser: AuthUser
746 :type apiuser: AuthUser
747 :param repoid: Set the repository name or repository id.
747 :param repoid: Set the repository name or repository id.
748 :type repoid: str or int
748 :type repoid: str or int
749 :param key: Create a unique field key for this repository.
749 :param key: Create a unique field key for this repository.
750 :type key: str
750 :type key: str
751 :param label:
751 :param label:
752 :type label: Optional(str)
752 :type label: Optional(str)
753 :param description:
753 :param description:
754 :type description: Optional(str)
754 :type description: Optional(str)
755 """
755 """
756 repo = get_repo_or_error(repoid)
756 repo = get_repo_or_error(repoid)
757 if not has_superadmin_permission(apiuser):
757 if not has_superadmin_permission(apiuser):
758 _perms = ('repository.admin',)
758 _perms = ('repository.admin',)
759 validate_repo_permissions(apiuser, repoid, repo, _perms)
759 validate_repo_permissions(apiuser, repoid, repo, _perms)
760
760
761 label = Optional.extract(label) or key
761 label = Optional.extract(label) or key
762 description = Optional.extract(description)
762 description = Optional.extract(description)
763
763
764 field = RepositoryField.get_by_key_name(key, repo)
764 field = RepositoryField.get_by_key_name(key, repo)
765 if field:
765 if field:
766 raise JSONRPCError('Field with key '
766 raise JSONRPCError('Field with key '
767 '`%s` exists for repo `%s`' % (key, repoid))
767 '`%s` exists for repo `%s`' % (key, repoid))
768
768
769 try:
769 try:
770 RepoModel().add_repo_field(repo, key, field_label=label,
770 RepoModel().add_repo_field(repo, key, field_label=label,
771 field_desc=description)
771 field_desc=description)
772 Session().commit()
772 Session().commit()
773 return {
773 return {
774 'msg': "Added new repository field `%s`" % (key,),
774 'msg': "Added new repository field `%s`" % (key,),
775 'success': True,
775 'success': True,
776 }
776 }
777 except Exception:
777 except Exception:
778 log.exception("Exception occurred while trying to add field to repo")
778 log.exception("Exception occurred while trying to add field to repo")
779 raise JSONRPCError(
779 raise JSONRPCError(
780 'failed to create new field for repository `%s`' % (repoid,))
780 'failed to create new field for repository `%s`' % (repoid,))
781
781
782
782
783 @jsonrpc_method()
783 @jsonrpc_method()
784 def remove_field_from_repo(request, apiuser, repoid, key):
784 def remove_field_from_repo(request, apiuser, repoid, key):
785 """
785 """
786 Removes an extra field from a repository.
786 Removes an extra field from a repository.
787
787
788 This command can only be run using an |authtoken| with at least
788 This command can only be run using an |authtoken| with at least
789 write permissions to the |repo|.
789 write permissions to the |repo|.
790
790
791 :param apiuser: This is filled automatically from the |authtoken|.
791 :param apiuser: This is filled automatically from the |authtoken|.
792 :type apiuser: AuthUser
792 :type apiuser: AuthUser
793 :param repoid: Set the repository name or repository ID.
793 :param repoid: Set the repository name or repository ID.
794 :type repoid: str or int
794 :type repoid: str or int
795 :param key: Set the unique field key for this repository.
795 :param key: Set the unique field key for this repository.
796 :type key: str
796 :type key: str
797 """
797 """
798
798
799 repo = get_repo_or_error(repoid)
799 repo = get_repo_or_error(repoid)
800 if not has_superadmin_permission(apiuser):
800 if not has_superadmin_permission(apiuser):
801 _perms = ('repository.admin',)
801 _perms = ('repository.admin',)
802 validate_repo_permissions(apiuser, repoid, repo, _perms)
802 validate_repo_permissions(apiuser, repoid, repo, _perms)
803
803
804 field = RepositoryField.get_by_key_name(key, repo)
804 field = RepositoryField.get_by_key_name(key, repo)
805 if not field:
805 if not field:
806 raise JSONRPCError('Field with key `%s` does not '
806 raise JSONRPCError('Field with key `%s` does not '
807 'exists for repo `%s`' % (key, repoid))
807 'exists for repo `%s`' % (key, repoid))
808
808
809 try:
809 try:
810 RepoModel().delete_repo_field(repo, field_key=key)
810 RepoModel().delete_repo_field(repo, field_key=key)
811 Session().commit()
811 Session().commit()
812 return {
812 return {
813 'msg': "Deleted repository field `%s`" % (key,),
813 'msg': "Deleted repository field `%s`" % (key,),
814 'success': True,
814 'success': True,
815 }
815 }
816 except Exception:
816 except Exception:
817 log.exception(
817 log.exception(
818 "Exception occurred while trying to delete field from repo")
818 "Exception occurred while trying to delete field from repo")
819 raise JSONRPCError(
819 raise JSONRPCError(
820 'failed to delete field for repository `%s`' % (repoid,))
820 'failed to delete field for repository `%s`' % (repoid,))
821
821
822
822
823 @jsonrpc_method()
823 @jsonrpc_method()
824 def update_repo(
824 def update_repo(
825 request, apiuser, repoid, repo_name=Optional(None),
825 request, apiuser, repoid, repo_name=Optional(None),
826 owner=Optional(OAttr('apiuser')), description=Optional(''),
826 owner=Optional(OAttr('apiuser')), description=Optional(''),
827 private=Optional(False), clone_uri=Optional(None),
827 private=Optional(False), clone_uri=Optional(None),
828 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
828 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
829 enable_statistics=Optional(False),
829 enable_statistics=Optional(False),
830 enable_locking=Optional(False),
830 enable_locking=Optional(False),
831 enable_downloads=Optional(False), fields=Optional('')):
831 enable_downloads=Optional(False), fields=Optional('')):
832 """
832 """
833 Updates a repository with the given information.
833 Updates a repository with the given information.
834
834
835 This command can only be run using an |authtoken| with at least
835 This command can only be run using an |authtoken| with at least
836 admin permissions to the |repo|.
836 admin permissions to the |repo|.
837
837
838 * If the repository name contains "/", repository will be updated
838 * If the repository name contains "/", repository will be updated
839 accordingly with a repository group or nested repository groups
839 accordingly with a repository group or nested repository groups
840
840
841 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
841 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
842 called "repo-test" and place it inside group "foo/bar".
842 called "repo-test" and place it inside group "foo/bar".
843 You have to have permissions to access and write to the last repository
843 You have to have permissions to access and write to the last repository
844 group ("bar" in this example)
844 group ("bar" in this example)
845
845
846 :param apiuser: This is filled automatically from the |authtoken|.
846 :param apiuser: This is filled automatically from the |authtoken|.
847 :type apiuser: AuthUser
847 :type apiuser: AuthUser
848 :param repoid: repository name or repository ID.
848 :param repoid: repository name or repository ID.
849 :type repoid: str or int
849 :type repoid: str or int
850 :param repo_name: Update the |repo| name, including the
850 :param repo_name: Update the |repo| name, including the
851 repository group it's in.
851 repository group it's in.
852 :type repo_name: str
852 :type repo_name: str
853 :param owner: Set the |repo| owner.
853 :param owner: Set the |repo| owner.
854 :type owner: str
854 :type owner: str
855 :param fork_of: Set the |repo| as fork of another |repo|.
855 :param fork_of: Set the |repo| as fork of another |repo|.
856 :type fork_of: str
856 :type fork_of: str
857 :param description: Update the |repo| description.
857 :param description: Update the |repo| description.
858 :type description: str
858 :type description: str
859 :param private: Set the |repo| as private. (True | False)
859 :param private: Set the |repo| as private. (True | False)
860 :type private: bool
860 :type private: bool
861 :param clone_uri: Update the |repo| clone URI.
861 :param clone_uri: Update the |repo| clone URI.
862 :type clone_uri: str
862 :type clone_uri: str
863 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
863 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
864 :type landing_rev: str
864 :type landing_rev: str
865 :param enable_statistics: Enable statistics on the |repo|, (True | False).
865 :param enable_statistics: Enable statistics on the |repo|, (True | False).
866 :type enable_statistics: bool
866 :type enable_statistics: bool
867 :param enable_locking: Enable |repo| locking.
867 :param enable_locking: Enable |repo| locking.
868 :type enable_locking: bool
868 :type enable_locking: bool
869 :param enable_downloads: Enable downloads from the |repo|, (True | False).
869 :param enable_downloads: Enable downloads from the |repo|, (True | False).
870 :type enable_downloads: bool
870 :type enable_downloads: bool
871 :param fields: Add extra fields to the |repo|. Use the following
871 :param fields: Add extra fields to the |repo|. Use the following
872 example format: ``field_key=field_val,field_key2=fieldval2``.
872 example format: ``field_key=field_val,field_key2=fieldval2``.
873 Escape ', ' with \,
873 Escape ', ' with \,
874 :type fields: str
874 :type fields: str
875 """
875 """
876
876
877 repo = get_repo_or_error(repoid)
877 repo = get_repo_or_error(repoid)
878
878
879 include_secrets = False
879 include_secrets = False
880 if not has_superadmin_permission(apiuser):
880 if not has_superadmin_permission(apiuser):
881 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
881 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
882 else:
882 else:
883 include_secrets = True
883 include_secrets = True
884
884
885 updates = dict(
885 updates = dict(
886 repo_name=repo_name
886 repo_name=repo_name
887 if not isinstance(repo_name, Optional) else repo.repo_name,
887 if not isinstance(repo_name, Optional) else repo.repo_name,
888
888
889 fork_id=fork_of
889 fork_id=fork_of
890 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
890 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
891
891
892 user=owner
892 user=owner
893 if not isinstance(owner, Optional) else repo.user.username,
893 if not isinstance(owner, Optional) else repo.user.username,
894
894
895 repo_description=description
895 repo_description=description
896 if not isinstance(description, Optional) else repo.description,
896 if not isinstance(description, Optional) else repo.description,
897
897
898 repo_private=private
898 repo_private=private
899 if not isinstance(private, Optional) else repo.private,
899 if not isinstance(private, Optional) else repo.private,
900
900
901 clone_uri=clone_uri
901 clone_uri=clone_uri
902 if not isinstance(clone_uri, Optional) else repo.clone_uri,
902 if not isinstance(clone_uri, Optional) else repo.clone_uri,
903
903
904 repo_landing_rev=landing_rev
904 repo_landing_rev=landing_rev
905 if not isinstance(landing_rev, Optional) else repo._landing_revision,
905 if not isinstance(landing_rev, Optional) else repo._landing_revision,
906
906
907 repo_enable_statistics=enable_statistics
907 repo_enable_statistics=enable_statistics
908 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
908 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
909
909
910 repo_enable_locking=enable_locking
910 repo_enable_locking=enable_locking
911 if not isinstance(enable_locking, Optional) else repo.enable_locking,
911 if not isinstance(enable_locking, Optional) else repo.enable_locking,
912
912
913 repo_enable_downloads=enable_downloads
913 repo_enable_downloads=enable_downloads
914 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
914 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
915
915
916 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
916 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
917
917
918 schema = repo_schema.RepoSchema().bind(
918 schema = repo_schema.RepoSchema().bind(
919 repo_type_options=rhodecode.BACKENDS.keys(),
919 repo_type_options=rhodecode.BACKENDS.keys(),
920 repo_ref_options=ref_choices,
920 repo_ref_options=ref_choices,
921 # user caller
921 # user caller
922 user=apiuser,
922 user=apiuser,
923 old_values=repo.get_api_data())
923 old_values=repo.get_api_data())
924 try:
924 try:
925 schema_data = schema.deserialize(dict(
925 schema_data = schema.deserialize(dict(
926 # we save old value, users cannot change type
926 # we save old value, users cannot change type
927 repo_type=repo.repo_type,
927 repo_type=repo.repo_type,
928
928
929 repo_name=updates['repo_name'],
929 repo_name=updates['repo_name'],
930 repo_owner=updates['user'],
930 repo_owner=updates['user'],
931 repo_description=updates['repo_description'],
931 repo_description=updates['repo_description'],
932 repo_clone_uri=updates['clone_uri'],
932 repo_clone_uri=updates['clone_uri'],
933 repo_fork_of=updates['fork_id'],
933 repo_fork_of=updates['fork_id'],
934 repo_private=updates['repo_private'],
934 repo_private=updates['repo_private'],
935 repo_landing_commit_ref=updates['repo_landing_rev'],
935 repo_landing_commit_ref=updates['repo_landing_rev'],
936 repo_enable_statistics=updates['repo_enable_statistics'],
936 repo_enable_statistics=updates['repo_enable_statistics'],
937 repo_enable_downloads=updates['repo_enable_downloads'],
937 repo_enable_downloads=updates['repo_enable_downloads'],
938 repo_enable_locking=updates['repo_enable_locking']))
938 repo_enable_locking=updates['repo_enable_locking']))
939 except validation_schema.Invalid as err:
939 except validation_schema.Invalid as err:
940 raise JSONRPCValidationError(colander_exc=err)
940 raise JSONRPCValidationError(colander_exc=err)
941
941
942 # save validated data back into the updates dict
942 # save validated data back into the updates dict
943 validated_updates = dict(
943 validated_updates = dict(
944 repo_name=schema_data['repo_group']['repo_name_without_group'],
944 repo_name=schema_data['repo_group']['repo_name_without_group'],
945 repo_group=schema_data['repo_group']['repo_group_id'],
945 repo_group=schema_data['repo_group']['repo_group_id'],
946
946
947 user=schema_data['repo_owner'],
947 user=schema_data['repo_owner'],
948 repo_description=schema_data['repo_description'],
948 repo_description=schema_data['repo_description'],
949 repo_private=schema_data['repo_private'],
949 repo_private=schema_data['repo_private'],
950 clone_uri=schema_data['repo_clone_uri'],
950 clone_uri=schema_data['repo_clone_uri'],
951 repo_landing_rev=schema_data['repo_landing_commit_ref'],
951 repo_landing_rev=schema_data['repo_landing_commit_ref'],
952 repo_enable_statistics=schema_data['repo_enable_statistics'],
952 repo_enable_statistics=schema_data['repo_enable_statistics'],
953 repo_enable_locking=schema_data['repo_enable_locking'],
953 repo_enable_locking=schema_data['repo_enable_locking'],
954 repo_enable_downloads=schema_data['repo_enable_downloads'],
954 repo_enable_downloads=schema_data['repo_enable_downloads'],
955 )
955 )
956
956
957 if schema_data['repo_fork_of']:
957 if schema_data['repo_fork_of']:
958 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
958 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
959 validated_updates['fork_id'] = fork_repo.repo_id
959 validated_updates['fork_id'] = fork_repo.repo_id
960
960
961 # extra fields
961 # extra fields
962 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
962 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
963 if fields:
963 if fields:
964 validated_updates.update(fields)
964 validated_updates.update(fields)
965
965
966 try:
966 try:
967 RepoModel().update(repo, **validated_updates)
967 RepoModel().update(repo, **validated_updates)
968 Session().commit()
968 Session().commit()
969 return {
969 return {
970 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
970 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
971 'repository': repo.get_api_data(include_secrets=include_secrets)
971 'repository': repo.get_api_data(include_secrets=include_secrets)
972 }
972 }
973 except Exception:
973 except Exception:
974 log.exception(
974 log.exception(
975 u"Exception while trying to update the repository %s",
975 u"Exception while trying to update the repository %s",
976 repoid)
976 repoid)
977 raise JSONRPCError('failed to update repo `%s`' % repoid)
977 raise JSONRPCError('failed to update repo `%s`' % repoid)
978
978
979
979
980 @jsonrpc_method()
980 @jsonrpc_method()
981 def fork_repo(request, apiuser, repoid, fork_name,
981 def fork_repo(request, apiuser, repoid, fork_name,
982 owner=Optional(OAttr('apiuser')),
982 owner=Optional(OAttr('apiuser')),
983 description=Optional(''),
983 description=Optional(''),
984 private=Optional(False),
984 private=Optional(False),
985 clone_uri=Optional(None),
985 clone_uri=Optional(None),
986 landing_rev=Optional('rev:tip'),
986 landing_rev=Optional('rev:tip'),
987 copy_permissions=Optional(False)):
987 copy_permissions=Optional(False)):
988 """
988 """
989 Creates a fork of the specified |repo|.
989 Creates a fork of the specified |repo|.
990
990
991 * If the fork_name contains "/", fork will be created inside
991 * If the fork_name contains "/", fork will be created inside
992 a repository group or nested repository groups
992 a repository group or nested repository groups
993
993
994 For example "foo/bar/fork-repo" will create fork called "fork-repo"
994 For example "foo/bar/fork-repo" will create fork called "fork-repo"
995 inside group "foo/bar". You have to have permissions to access and
995 inside group "foo/bar". You have to have permissions to access and
996 write to the last repository group ("bar" in this example)
996 write to the last repository group ("bar" in this example)
997
997
998 This command can only be run using an |authtoken| with minimum
998 This command can only be run using an |authtoken| with minimum
999 read permissions of the forked repo, create fork permissions for an user.
999 read permissions of the forked repo, create fork permissions for an user.
1000
1000
1001 :param apiuser: This is filled automatically from the |authtoken|.
1001 :param apiuser: This is filled automatically from the |authtoken|.
1002 :type apiuser: AuthUser
1002 :type apiuser: AuthUser
1003 :param repoid: Set repository name or repository ID.
1003 :param repoid: Set repository name or repository ID.
1004 :type repoid: str or int
1004 :type repoid: str or int
1005 :param fork_name: Set the fork name, including it's repository group membership.
1005 :param fork_name: Set the fork name, including it's repository group membership.
1006 :type fork_name: str
1006 :type fork_name: str
1007 :param owner: Set the fork owner.
1007 :param owner: Set the fork owner.
1008 :type owner: str
1008 :type owner: str
1009 :param description: Set the fork description.
1009 :param description: Set the fork description.
1010 :type description: str
1010 :type description: str
1011 :param copy_permissions: Copy permissions from parent |repo|. The
1011 :param copy_permissions: Copy permissions from parent |repo|. The
1012 default is False.
1012 default is False.
1013 :type copy_permissions: bool
1013 :type copy_permissions: bool
1014 :param private: Make the fork private. The default is False.
1014 :param private: Make the fork private. The default is False.
1015 :type private: bool
1015 :type private: bool
1016 :param landing_rev: Set the landing revision. The default is tip.
1016 :param landing_rev: Set the landing revision. The default is tip.
1017
1017
1018 Example output:
1018 Example output:
1019
1019
1020 .. code-block:: bash
1020 .. code-block:: bash
1021
1021
1022 id : <id_for_response>
1022 id : <id_for_response>
1023 api_key : "<api_key>"
1023 api_key : "<api_key>"
1024 args: {
1024 args: {
1025 "repoid" : "<reponame or repo_id>",
1025 "repoid" : "<reponame or repo_id>",
1026 "fork_name": "<forkname>",
1026 "fork_name": "<forkname>",
1027 "owner": "<username or user_id = Optional(=apiuser)>",
1027 "owner": "<username or user_id = Optional(=apiuser)>",
1028 "description": "<description>",
1028 "description": "<description>",
1029 "copy_permissions": "<bool>",
1029 "copy_permissions": "<bool>",
1030 "private": "<bool>",
1030 "private": "<bool>",
1031 "landing_rev": "<landing_rev>"
1031 "landing_rev": "<landing_rev>"
1032 }
1032 }
1033
1033
1034 Example error output:
1034 Example error output:
1035
1035
1036 .. code-block:: bash
1036 .. code-block:: bash
1037
1037
1038 id : <id_given_in_input>
1038 id : <id_given_in_input>
1039 result: {
1039 result: {
1040 "msg": "Created fork of `<reponame>` as `<forkname>`",
1040 "msg": "Created fork of `<reponame>` as `<forkname>`",
1041 "success": true,
1041 "success": true,
1042 "task": "<celery task id or None if done sync>"
1042 "task": "<celery task id or None if done sync>"
1043 }
1043 }
1044 error: null
1044 error: null
1045
1045
1046 """
1046 """
1047
1047
1048 repo = get_repo_or_error(repoid)
1048 repo = get_repo_or_error(repoid)
1049 repo_name = repo.repo_name
1049 repo_name = repo.repo_name
1050
1050
1051 if not has_superadmin_permission(apiuser):
1051 if not has_superadmin_permission(apiuser):
1052 # check if we have at least read permission for
1052 # check if we have at least read permission for
1053 # this repo that we fork !
1053 # this repo that we fork !
1054 _perms = (
1054 _perms = (
1055 'repository.admin', 'repository.write', 'repository.read')
1055 'repository.admin', 'repository.write', 'repository.read')
1056 validate_repo_permissions(apiuser, repoid, repo, _perms)
1056 validate_repo_permissions(apiuser, repoid, repo, _perms)
1057
1057
1058 # check if the regular user has at least fork permissions as well
1058 # check if the regular user has at least fork permissions as well
1059 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1059 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1060 raise JSONRPCForbidden()
1060 raise JSONRPCForbidden()
1061
1061
1062 # check if user can set owner parameter
1062 # check if user can set owner parameter
1063 owner = validate_set_owner_permissions(apiuser, owner)
1063 owner = validate_set_owner_permissions(apiuser, owner)
1064
1064
1065 description = Optional.extract(description)
1065 description = Optional.extract(description)
1066 copy_permissions = Optional.extract(copy_permissions)
1066 copy_permissions = Optional.extract(copy_permissions)
1067 clone_uri = Optional.extract(clone_uri)
1067 clone_uri = Optional.extract(clone_uri)
1068 landing_commit_ref = Optional.extract(landing_rev)
1068 landing_commit_ref = Optional.extract(landing_rev)
1069 private = Optional.extract(private)
1069 private = Optional.extract(private)
1070
1070
1071 schema = repo_schema.RepoSchema().bind(
1071 schema = repo_schema.RepoSchema().bind(
1072 repo_type_options=rhodecode.BACKENDS.keys(),
1072 repo_type_options=rhodecode.BACKENDS.keys(),
1073 # user caller
1073 # user caller
1074 user=apiuser)
1074 user=apiuser)
1075
1075
1076 try:
1076 try:
1077 schema_data = schema.deserialize(dict(
1077 schema_data = schema.deserialize(dict(
1078 repo_name=fork_name,
1078 repo_name=fork_name,
1079 repo_type=repo.repo_type,
1079 repo_type=repo.repo_type,
1080 repo_owner=owner.username,
1080 repo_owner=owner.username,
1081 repo_description=description,
1081 repo_description=description,
1082 repo_landing_commit_ref=landing_commit_ref,
1082 repo_landing_commit_ref=landing_commit_ref,
1083 repo_clone_uri=clone_uri,
1083 repo_clone_uri=clone_uri,
1084 repo_private=private,
1084 repo_private=private,
1085 repo_copy_permissions=copy_permissions))
1085 repo_copy_permissions=copy_permissions))
1086 except validation_schema.Invalid as err:
1086 except validation_schema.Invalid as err:
1087 raise JSONRPCValidationError(colander_exc=err)
1087 raise JSONRPCValidationError(colander_exc=err)
1088
1088
1089 try:
1089 try:
1090 data = {
1090 data = {
1091 'fork_parent_id': repo.repo_id,
1091 'fork_parent_id': repo.repo_id,
1092
1092
1093 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1093 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1094 'repo_name_full': schema_data['repo_name'],
1094 'repo_name_full': schema_data['repo_name'],
1095 'repo_group': schema_data['repo_group']['repo_group_id'],
1095 'repo_group': schema_data['repo_group']['repo_group_id'],
1096 'repo_type': schema_data['repo_type'],
1096 'repo_type': schema_data['repo_type'],
1097 'description': schema_data['repo_description'],
1097 'description': schema_data['repo_description'],
1098 'private': schema_data['repo_private'],
1098 'private': schema_data['repo_private'],
1099 'copy_permissions': schema_data['repo_copy_permissions'],
1099 'copy_permissions': schema_data['repo_copy_permissions'],
1100 'landing_rev': schema_data['repo_landing_commit_ref'],
1100 'landing_rev': schema_data['repo_landing_commit_ref'],
1101 }
1101 }
1102
1102
1103 task = RepoModel().create_fork(data, cur_user=owner)
1103 task = RepoModel().create_fork(data, cur_user=owner)
1104 # no commit, it's done in RepoModel, or async via celery
1104 # no commit, it's done in RepoModel, or async via celery
1105 from celery.result import BaseAsyncResult
1105 from celery.result import BaseAsyncResult
1106 task_id = None
1106 task_id = None
1107 if isinstance(task, BaseAsyncResult):
1107 if isinstance(task, BaseAsyncResult):
1108 task_id = task.task_id
1108 task_id = task.task_id
1109 return {
1109 return {
1110 'msg': 'Created fork of `%s` as `%s`' % (
1110 'msg': 'Created fork of `%s` as `%s`' % (
1111 repo.repo_name, schema_data['repo_name']),
1111 repo.repo_name, schema_data['repo_name']),
1112 'success': True, # cannot return the repo data here since fork
1112 'success': True, # cannot return the repo data here since fork
1113 # can be done async
1113 # can be done async
1114 'task': task_id
1114 'task': task_id
1115 }
1115 }
1116 except Exception:
1116 except Exception:
1117 log.exception(
1117 log.exception(
1118 u"Exception while trying to create fork %s",
1118 u"Exception while trying to create fork %s",
1119 schema_data['repo_name'])
1119 schema_data['repo_name'])
1120 raise JSONRPCError(
1120 raise JSONRPCError(
1121 'failed to fork repository `%s` as `%s`' % (
1121 'failed to fork repository `%s` as `%s`' % (
1122 repo_name, schema_data['repo_name']))
1122 repo_name, schema_data['repo_name']))
1123
1123
1124
1124
1125 @jsonrpc_method()
1125 @jsonrpc_method()
1126 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1126 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1127 """
1127 """
1128 Deletes a repository.
1128 Deletes a repository.
1129
1129
1130 * When the `forks` parameter is set it's possible to detach or delete
1130 * When the `forks` parameter is set it's possible to detach or delete
1131 forks of deleted repository.
1131 forks of deleted repository.
1132
1132
1133 This command can only be run using an |authtoken| with admin
1133 This command can only be run using an |authtoken| with admin
1134 permissions on the |repo|.
1134 permissions on the |repo|.
1135
1135
1136 :param apiuser: This is filled automatically from the |authtoken|.
1136 :param apiuser: This is filled automatically from the |authtoken|.
1137 :type apiuser: AuthUser
1137 :type apiuser: AuthUser
1138 :param repoid: Set the repository name or repository ID.
1138 :param repoid: Set the repository name or repository ID.
1139 :type repoid: str or int
1139 :type repoid: str or int
1140 :param forks: Set to `detach` or `delete` forks from the |repo|.
1140 :param forks: Set to `detach` or `delete` forks from the |repo|.
1141 :type forks: Optional(str)
1141 :type forks: Optional(str)
1142
1142
1143 Example error output:
1143 Example error output:
1144
1144
1145 .. code-block:: bash
1145 .. code-block:: bash
1146
1146
1147 id : <id_given_in_input>
1147 id : <id_given_in_input>
1148 result: {
1148 result: {
1149 "msg": "Deleted repository `<reponame>`",
1149 "msg": "Deleted repository `<reponame>`",
1150 "success": true
1150 "success": true
1151 }
1151 }
1152 error: null
1152 error: null
1153 """
1153 """
1154
1154
1155 repo = get_repo_or_error(repoid)
1155 repo = get_repo_or_error(repoid)
1156 if not has_superadmin_permission(apiuser):
1156 if not has_superadmin_permission(apiuser):
1157 _perms = ('repository.admin',)
1157 _perms = ('repository.admin',)
1158 validate_repo_permissions(apiuser, repoid, repo, _perms)
1158 validate_repo_permissions(apiuser, repoid, repo, _perms)
1159
1159
1160 try:
1160 try:
1161 handle_forks = Optional.extract(forks)
1161 handle_forks = Optional.extract(forks)
1162 _forks_msg = ''
1162 _forks_msg = ''
1163 _forks = [f for f in repo.forks]
1163 _forks = [f for f in repo.forks]
1164 if handle_forks == 'detach':
1164 if handle_forks == 'detach':
1165 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1165 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1166 elif handle_forks == 'delete':
1166 elif handle_forks == 'delete':
1167 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1167 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1168 elif _forks:
1168 elif _forks:
1169 raise JSONRPCError(
1169 raise JSONRPCError(
1170 'Cannot delete `%s` it still contains attached forks' %
1170 'Cannot delete `%s` it still contains attached forks' %
1171 (repo.repo_name,)
1171 (repo.repo_name,)
1172 )
1172 )
1173
1173
1174 RepoModel().delete(repo, forks=forks)
1174 RepoModel().delete(repo, forks=forks)
1175 Session().commit()
1175 Session().commit()
1176 return {
1176 return {
1177 'msg': 'Deleted repository `%s`%s' % (
1177 'msg': 'Deleted repository `%s`%s' % (
1178 repo.repo_name, _forks_msg),
1178 repo.repo_name, _forks_msg),
1179 'success': True
1179 'success': True
1180 }
1180 }
1181 except Exception:
1181 except Exception:
1182 log.exception("Exception occurred while trying to delete repo")
1182 log.exception("Exception occurred while trying to delete repo")
1183 raise JSONRPCError(
1183 raise JSONRPCError(
1184 'failed to delete repository `%s`' % (repo.repo_name,)
1184 'failed to delete repository `%s`' % (repo.repo_name,)
1185 )
1185 )
1186
1186
1187
1187
1188 #TODO: marcink, change name ?
1188 #TODO: marcink, change name ?
1189 @jsonrpc_method()
1189 @jsonrpc_method()
1190 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1190 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1191 """
1191 """
1192 Invalidates the cache for the specified repository.
1192 Invalidates the cache for the specified repository.
1193
1193
1194 This command can only be run using an |authtoken| with admin rights to
1194 This command can only be run using an |authtoken| with admin rights to
1195 the specified repository.
1195 the specified repository.
1196
1196
1197 This command takes the following options:
1197 This command takes the following options:
1198
1198
1199 :param apiuser: This is filled automatically from |authtoken|.
1199 :param apiuser: This is filled automatically from |authtoken|.
1200 :type apiuser: AuthUser
1200 :type apiuser: AuthUser
1201 :param repoid: Sets the repository name or repository ID.
1201 :param repoid: Sets the repository name or repository ID.
1202 :type repoid: str or int
1202 :type repoid: str or int
1203 :param delete_keys: This deletes the invalidated keys instead of
1203 :param delete_keys: This deletes the invalidated keys instead of
1204 just flagging them.
1204 just flagging them.
1205 :type delete_keys: Optional(``True`` | ``False``)
1205 :type delete_keys: Optional(``True`` | ``False``)
1206
1206
1207 Example output:
1207 Example output:
1208
1208
1209 .. code-block:: bash
1209 .. code-block:: bash
1210
1210
1211 id : <id_given_in_input>
1211 id : <id_given_in_input>
1212 result : {
1212 result : {
1213 'msg': Cache for repository `<repository name>` was invalidated,
1213 'msg': Cache for repository `<repository name>` was invalidated,
1214 'repository': <repository name>
1214 'repository': <repository name>
1215 }
1215 }
1216 error : null
1216 error : null
1217
1217
1218 Example error output:
1218 Example error output:
1219
1219
1220 .. code-block:: bash
1220 .. code-block:: bash
1221
1221
1222 id : <id_given_in_input>
1222 id : <id_given_in_input>
1223 result : null
1223 result : null
1224 error : {
1224 error : {
1225 'Error occurred during cache invalidation action'
1225 'Error occurred during cache invalidation action'
1226 }
1226 }
1227
1227
1228 """
1228 """
1229
1229
1230 repo = get_repo_or_error(repoid)
1230 repo = get_repo_or_error(repoid)
1231 if not has_superadmin_permission(apiuser):
1231 if not has_superadmin_permission(apiuser):
1232 _perms = ('repository.admin', 'repository.write',)
1232 _perms = ('repository.admin', 'repository.write',)
1233 validate_repo_permissions(apiuser, repoid, repo, _perms)
1233 validate_repo_permissions(apiuser, repoid, repo, _perms)
1234
1234
1235 delete = Optional.extract(delete_keys)
1235 delete = Optional.extract(delete_keys)
1236 try:
1236 try:
1237 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1237 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1238 return {
1238 return {
1239 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1239 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1240 'repository': repo.repo_name
1240 'repository': repo.repo_name
1241 }
1241 }
1242 except Exception:
1242 except Exception:
1243 log.exception(
1243 log.exception(
1244 "Exception occurred while trying to invalidate repo cache")
1244 "Exception occurred while trying to invalidate repo cache")
1245 raise JSONRPCError(
1245 raise JSONRPCError(
1246 'Error occurred during cache invalidation action'
1246 'Error occurred during cache invalidation action'
1247 )
1247 )
1248
1248
1249
1249
1250 #TODO: marcink, change name ?
1250 #TODO: marcink, change name ?
1251 @jsonrpc_method()
1251 @jsonrpc_method()
1252 def lock(request, apiuser, repoid, locked=Optional(None),
1252 def lock(request, apiuser, repoid, locked=Optional(None),
1253 userid=Optional(OAttr('apiuser'))):
1253 userid=Optional(OAttr('apiuser'))):
1254 """
1254 """
1255 Sets the lock state of the specified |repo| by the given user.
1255 Sets the lock state of the specified |repo| by the given user.
1256 From more information, see :ref:`repo-locking`.
1256 From more information, see :ref:`repo-locking`.
1257
1257
1258 * If the ``userid`` option is not set, the repository is locked to the
1258 * If the ``userid`` option is not set, the repository is locked to the
1259 user who called the method.
1259 user who called the method.
1260 * If the ``locked`` parameter is not set, the current lock state of the
1260 * If the ``locked`` parameter is not set, the current lock state of the
1261 repository is displayed.
1261 repository is displayed.
1262
1262
1263 This command can only be run using an |authtoken| with admin rights to
1263 This command can only be run using an |authtoken| with admin rights to
1264 the specified repository.
1264 the specified repository.
1265
1265
1266 This command takes the following options:
1266 This command takes the following options:
1267
1267
1268 :param apiuser: This is filled automatically from the |authtoken|.
1268 :param apiuser: This is filled automatically from the |authtoken|.
1269 :type apiuser: AuthUser
1269 :type apiuser: AuthUser
1270 :param repoid: Sets the repository name or repository ID.
1270 :param repoid: Sets the repository name or repository ID.
1271 :type repoid: str or int
1271 :type repoid: str or int
1272 :param locked: Sets the lock state.
1272 :param locked: Sets the lock state.
1273 :type locked: Optional(``True`` | ``False``)
1273 :type locked: Optional(``True`` | ``False``)
1274 :param userid: Set the repository lock to this user.
1274 :param userid: Set the repository lock to this user.
1275 :type userid: Optional(str or int)
1275 :type userid: Optional(str or int)
1276
1276
1277 Example error output:
1277 Example error output:
1278
1278
1279 .. code-block:: bash
1279 .. code-block:: bash
1280
1280
1281 id : <id_given_in_input>
1281 id : <id_given_in_input>
1282 result : {
1282 result : {
1283 'repo': '<reponame>',
1283 'repo': '<reponame>',
1284 'locked': <bool: lock state>,
1284 'locked': <bool: lock state>,
1285 'locked_since': <int: lock timestamp>,
1285 'locked_since': <int: lock timestamp>,
1286 'locked_by': <username of person who made the lock>,
1286 'locked_by': <username of person who made the lock>,
1287 'lock_reason': <str: reason for locking>,
1287 'lock_reason': <str: reason for locking>,
1288 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1288 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1289 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1289 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1290 or
1290 or
1291 'msg': 'Repo `<repository name>` not locked.'
1291 'msg': 'Repo `<repository name>` not locked.'
1292 or
1292 or
1293 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1293 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1294 }
1294 }
1295 error : null
1295 error : null
1296
1296
1297 Example error output:
1297 Example error output:
1298
1298
1299 .. code-block:: bash
1299 .. code-block:: bash
1300
1300
1301 id : <id_given_in_input>
1301 id : <id_given_in_input>
1302 result : null
1302 result : null
1303 error : {
1303 error : {
1304 'Error occurred locking repository `<reponame>`'
1304 'Error occurred locking repository `<reponame>`'
1305 }
1305 }
1306 """
1306 """
1307
1307
1308 repo = get_repo_or_error(repoid)
1308 repo = get_repo_or_error(repoid)
1309 if not has_superadmin_permission(apiuser):
1309 if not has_superadmin_permission(apiuser):
1310 # check if we have at least write permission for this repo !
1310 # check if we have at least write permission for this repo !
1311 _perms = ('repository.admin', 'repository.write',)
1311 _perms = ('repository.admin', 'repository.write',)
1312 validate_repo_permissions(apiuser, repoid, repo, _perms)
1312 validate_repo_permissions(apiuser, repoid, repo, _perms)
1313
1313
1314 # make sure normal user does not pass someone else userid,
1314 # make sure normal user does not pass someone else userid,
1315 # he is not allowed to do that
1315 # he is not allowed to do that
1316 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1316 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1317 raise JSONRPCError('userid is not the same as your user')
1317 raise JSONRPCError('userid is not the same as your user')
1318
1318
1319 if isinstance(userid, Optional):
1319 if isinstance(userid, Optional):
1320 userid = apiuser.user_id
1320 userid = apiuser.user_id
1321
1321
1322 user = get_user_or_error(userid)
1322 user = get_user_or_error(userid)
1323
1323
1324 if isinstance(locked, Optional):
1324 if isinstance(locked, Optional):
1325 lockobj = repo.locked
1325 lockobj = repo.locked
1326
1326
1327 if lockobj[0] is None:
1327 if lockobj[0] is None:
1328 _d = {
1328 _d = {
1329 'repo': repo.repo_name,
1329 'repo': repo.repo_name,
1330 'locked': False,
1330 'locked': False,
1331 'locked_since': None,
1331 'locked_since': None,
1332 'locked_by': None,
1332 'locked_by': None,
1333 'lock_reason': None,
1333 'lock_reason': None,
1334 'lock_state_changed': False,
1334 'lock_state_changed': False,
1335 'msg': 'Repo `%s` not locked.' % repo.repo_name
1335 'msg': 'Repo `%s` not locked.' % repo.repo_name
1336 }
1336 }
1337 return _d
1337 return _d
1338 else:
1338 else:
1339 _user_id, _time, _reason = lockobj
1339 _user_id, _time, _reason = lockobj
1340 lock_user = get_user_or_error(userid)
1340 lock_user = get_user_or_error(userid)
1341 _d = {
1341 _d = {
1342 'repo': repo.repo_name,
1342 'repo': repo.repo_name,
1343 'locked': True,
1343 'locked': True,
1344 'locked_since': _time,
1344 'locked_since': _time,
1345 'locked_by': lock_user.username,
1345 'locked_by': lock_user.username,
1346 'lock_reason': _reason,
1346 'lock_reason': _reason,
1347 'lock_state_changed': False,
1347 'lock_state_changed': False,
1348 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1348 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1349 % (repo.repo_name, lock_user.username,
1349 % (repo.repo_name, lock_user.username,
1350 json.dumps(time_to_datetime(_time))))
1350 json.dumps(time_to_datetime(_time))))
1351 }
1351 }
1352 return _d
1352 return _d
1353
1353
1354 # force locked state through a flag
1354 # force locked state through a flag
1355 else:
1355 else:
1356 locked = str2bool(locked)
1356 locked = str2bool(locked)
1357 lock_reason = Repository.LOCK_API
1357 lock_reason = Repository.LOCK_API
1358 try:
1358 try:
1359 if locked:
1359 if locked:
1360 lock_time = time.time()
1360 lock_time = time.time()
1361 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1361 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1362 else:
1362 else:
1363 lock_time = None
1363 lock_time = None
1364 Repository.unlock(repo)
1364 Repository.unlock(repo)
1365 _d = {
1365 _d = {
1366 'repo': repo.repo_name,
1366 'repo': repo.repo_name,
1367 'locked': locked,
1367 'locked': locked,
1368 'locked_since': lock_time,
1368 'locked_since': lock_time,
1369 'locked_by': user.username,
1369 'locked_by': user.username,
1370 'lock_reason': lock_reason,
1370 'lock_reason': lock_reason,
1371 'lock_state_changed': True,
1371 'lock_state_changed': True,
1372 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1372 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1373 % (user.username, repo.repo_name, locked))
1373 % (user.username, repo.repo_name, locked))
1374 }
1374 }
1375 return _d
1375 return _d
1376 except Exception:
1376 except Exception:
1377 log.exception(
1377 log.exception(
1378 "Exception occurred while trying to lock repository")
1378 "Exception occurred while trying to lock repository")
1379 raise JSONRPCError(
1379 raise JSONRPCError(
1380 'Error occurred locking repository `%s`' % repo.repo_name
1380 'Error occurred locking repository `%s`' % repo.repo_name
1381 )
1381 )
1382
1382
1383
1383
1384 @jsonrpc_method()
1384 @jsonrpc_method()
1385 def comment_commit(
1385 def comment_commit(
1386 request, apiuser, repoid, commit_id, message, status=Optional(None),
1386 request, apiuser, repoid, commit_id, message, status=Optional(None),
1387 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1387 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1388 resolves_comment_id=Optional(None),
1388 userid=Optional(OAttr('apiuser'))):
1389 userid=Optional(OAttr('apiuser'))):
1389 """
1390 """
1390 Set a commit comment, and optionally change the status of the commit.
1391 Set a commit comment, and optionally change the status of the commit.
1391
1392
1392 :param apiuser: This is filled automatically from the |authtoken|.
1393 :param apiuser: This is filled automatically from the |authtoken|.
1393 :type apiuser: AuthUser
1394 :type apiuser: AuthUser
1394 :param repoid: Set the repository name or repository ID.
1395 :param repoid: Set the repository name or repository ID.
1395 :type repoid: str or int
1396 :type repoid: str or int
1396 :param commit_id: Specify the commit_id for which to set a comment.
1397 :param commit_id: Specify the commit_id for which to set a comment.
1397 :type commit_id: str
1398 :type commit_id: str
1398 :param message: The comment text.
1399 :param message: The comment text.
1399 :type message: str
1400 :type message: str
1400 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1401 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1401 'approved', 'rejected', 'under_review'
1402 'approved', 'rejected', 'under_review'
1402 :type status: str
1403 :type status: str
1403 :param comment_type: Comment type, one of: 'note', 'todo'
1404 :param comment_type: Comment type, one of: 'note', 'todo'
1404 :type comment_type: Optional(str), default: 'note'
1405 :type comment_type: Optional(str), default: 'note'
1405 :param userid: Set the user name of the comment creator.
1406 :param userid: Set the user name of the comment creator.
1406 :type userid: Optional(str or int)
1407 :type userid: Optional(str or int)
1407
1408
1408 Example error output:
1409 Example error output:
1409
1410
1410 .. code-block:: bash
1411 .. code-block:: bash
1411
1412
1412 {
1413 {
1413 "id" : <id_given_in_input>,
1414 "id" : <id_given_in_input>,
1414 "result" : {
1415 "result" : {
1415 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1416 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1416 "status_change": null or <status>,
1417 "status_change": null or <status>,
1417 "success": true
1418 "success": true
1418 },
1419 },
1419 "error" : null
1420 "error" : null
1420 }
1421 }
1421
1422
1422 """
1423 """
1423 repo = get_repo_or_error(repoid)
1424 repo = get_repo_or_error(repoid)
1424 if not has_superadmin_permission(apiuser):
1425 if not has_superadmin_permission(apiuser):
1425 _perms = ('repository.read', 'repository.write', 'repository.admin')
1426 _perms = ('repository.read', 'repository.write', 'repository.admin')
1426 validate_repo_permissions(apiuser, repoid, repo, _perms)
1427 validate_repo_permissions(apiuser, repoid, repo, _perms)
1427
1428
1428 if isinstance(userid, Optional):
1429 if isinstance(userid, Optional):
1429 userid = apiuser.user_id
1430 userid = apiuser.user_id
1430
1431
1431 user = get_user_or_error(userid)
1432 user = get_user_or_error(userid)
1432 status = Optional.extract(status)
1433 status = Optional.extract(status)
1433 comment_type = Optional.extract(comment_type)
1434 comment_type = Optional.extract(comment_type)
1435 resolves_comment_id = Optional.extract(resolves_comment_id)
1434
1436
1435 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1437 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1436 if status and status not in allowed_statuses:
1438 if status and status not in allowed_statuses:
1437 raise JSONRPCError('Bad status, must be on '
1439 raise JSONRPCError('Bad status, must be on '
1438 'of %s got %s' % (allowed_statuses, status,))
1440 'of %s got %s' % (allowed_statuses, status,))
1439
1441
1442 if resolves_comment_id:
1443 comment = ChangesetComment.get(resolves_comment_id)
1444 if not comment:
1445 raise JSONRPCError(
1446 'Invalid resolves_comment_id `%s` for this commit.'
1447 % resolves_comment_id)
1448 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1449 raise JSONRPCError(
1450 'Comment `%s` is wrong type for setting status to resolved.'
1451 % resolves_comment_id)
1452
1440 try:
1453 try:
1441 rc_config = SettingsModel().get_all_settings()
1454 rc_config = SettingsModel().get_all_settings()
1442 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1455 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1443 status_change_label = ChangesetStatus.get_status_lbl(status)
1456 status_change_label = ChangesetStatus.get_status_lbl(status)
1444 comm = CommentsModel().create(
1457 comm = CommentsModel().create(
1445 message, repo, user, commit_id=commit_id,
1458 message, repo, user, commit_id=commit_id,
1446 status_change=status_change_label,
1459 status_change=status_change_label,
1447 status_change_type=status,
1460 status_change_type=status,
1448 renderer=renderer,
1461 renderer=renderer,
1449 comment_type=comment_type
1462 comment_type=comment_type,
1463 resolves_comment_id=resolves_comment_id
1450 )
1464 )
1451 if status:
1465 if status:
1452 # also do a status change
1466 # also do a status change
1453 try:
1467 try:
1454 ChangesetStatusModel().set_status(
1468 ChangesetStatusModel().set_status(
1455 repo, status, user, comm, revision=commit_id,
1469 repo, status, user, comm, revision=commit_id,
1456 dont_allow_on_closed_pull_request=True
1470 dont_allow_on_closed_pull_request=True
1457 )
1471 )
1458 except StatusChangeOnClosedPullRequestError:
1472 except StatusChangeOnClosedPullRequestError:
1459 log.exception(
1473 log.exception(
1460 "Exception occurred while trying to change repo commit status")
1474 "Exception occurred while trying to change repo commit status")
1461 msg = ('Changing status on a changeset associated with '
1475 msg = ('Changing status on a changeset associated with '
1462 'a closed pull request is not allowed')
1476 'a closed pull request is not allowed')
1463 raise JSONRPCError(msg)
1477 raise JSONRPCError(msg)
1464
1478
1465 Session().commit()
1479 Session().commit()
1466 return {
1480 return {
1467 'msg': (
1481 'msg': (
1468 'Commented on commit `%s` for repository `%s`' % (
1482 'Commented on commit `%s` for repository `%s`' % (
1469 comm.revision, repo.repo_name)),
1483 comm.revision, repo.repo_name)),
1470 'status_change': status,
1484 'status_change': status,
1471 'success': True,
1485 'success': True,
1472 }
1486 }
1473 except JSONRPCError:
1487 except JSONRPCError:
1474 # catch any inside errors, and re-raise them to prevent from
1488 # catch any inside errors, and re-raise them to prevent from
1475 # below global catch to silence them
1489 # below global catch to silence them
1476 raise
1490 raise
1477 except Exception:
1491 except Exception:
1478 log.exception("Exception occurred while trying to comment on commit")
1492 log.exception("Exception occurred while trying to comment on commit")
1479 raise JSONRPCError(
1493 raise JSONRPCError(
1480 'failed to set comment on repository `%s`' % (repo.repo_name,)
1494 'failed to set comment on repository `%s`' % (repo.repo_name,)
1481 )
1495 )
1482
1496
1483
1497
1484 @jsonrpc_method()
1498 @jsonrpc_method()
1485 def grant_user_permission(request, apiuser, repoid, userid, perm):
1499 def grant_user_permission(request, apiuser, repoid, userid, perm):
1486 """
1500 """
1487 Grant permissions for the specified user on the given repository,
1501 Grant permissions for the specified user on the given repository,
1488 or update existing permissions if found.
1502 or update existing permissions if found.
1489
1503
1490 This command can only be run using an |authtoken| with admin
1504 This command can only be run using an |authtoken| with admin
1491 permissions on the |repo|.
1505 permissions on the |repo|.
1492
1506
1493 :param apiuser: This is filled automatically from the |authtoken|.
1507 :param apiuser: This is filled automatically from the |authtoken|.
1494 :type apiuser: AuthUser
1508 :type apiuser: AuthUser
1495 :param repoid: Set the repository name or repository ID.
1509 :param repoid: Set the repository name or repository ID.
1496 :type repoid: str or int
1510 :type repoid: str or int
1497 :param userid: Set the user name.
1511 :param userid: Set the user name.
1498 :type userid: str
1512 :type userid: str
1499 :param perm: Set the user permissions, using the following format
1513 :param perm: Set the user permissions, using the following format
1500 ``(repository.(none|read|write|admin))``
1514 ``(repository.(none|read|write|admin))``
1501 :type perm: str
1515 :type perm: str
1502
1516
1503 Example output:
1517 Example output:
1504
1518
1505 .. code-block:: bash
1519 .. code-block:: bash
1506
1520
1507 id : <id_given_in_input>
1521 id : <id_given_in_input>
1508 result: {
1522 result: {
1509 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1523 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1510 "success": true
1524 "success": true
1511 }
1525 }
1512 error: null
1526 error: null
1513 """
1527 """
1514
1528
1515 repo = get_repo_or_error(repoid)
1529 repo = get_repo_or_error(repoid)
1516 user = get_user_or_error(userid)
1530 user = get_user_or_error(userid)
1517 perm = get_perm_or_error(perm)
1531 perm = get_perm_or_error(perm)
1518 if not has_superadmin_permission(apiuser):
1532 if not has_superadmin_permission(apiuser):
1519 _perms = ('repository.admin',)
1533 _perms = ('repository.admin',)
1520 validate_repo_permissions(apiuser, repoid, repo, _perms)
1534 validate_repo_permissions(apiuser, repoid, repo, _perms)
1521
1535
1522 try:
1536 try:
1523
1537
1524 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1538 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1525
1539
1526 Session().commit()
1540 Session().commit()
1527 return {
1541 return {
1528 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1542 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1529 perm.permission_name, user.username, repo.repo_name
1543 perm.permission_name, user.username, repo.repo_name
1530 ),
1544 ),
1531 'success': True
1545 'success': True
1532 }
1546 }
1533 except Exception:
1547 except Exception:
1534 log.exception(
1548 log.exception(
1535 "Exception occurred while trying edit permissions for repo")
1549 "Exception occurred while trying edit permissions for repo")
1536 raise JSONRPCError(
1550 raise JSONRPCError(
1537 'failed to edit permission for user: `%s` in repo: `%s`' % (
1551 'failed to edit permission for user: `%s` in repo: `%s`' % (
1538 userid, repoid
1552 userid, repoid
1539 )
1553 )
1540 )
1554 )
1541
1555
1542
1556
1543 @jsonrpc_method()
1557 @jsonrpc_method()
1544 def revoke_user_permission(request, apiuser, repoid, userid):
1558 def revoke_user_permission(request, apiuser, repoid, userid):
1545 """
1559 """
1546 Revoke permission for a user on the specified repository.
1560 Revoke permission for a user on the specified repository.
1547
1561
1548 This command can only be run using an |authtoken| with admin
1562 This command can only be run using an |authtoken| with admin
1549 permissions on the |repo|.
1563 permissions on the |repo|.
1550
1564
1551 :param apiuser: This is filled automatically from the |authtoken|.
1565 :param apiuser: This is filled automatically from the |authtoken|.
1552 :type apiuser: AuthUser
1566 :type apiuser: AuthUser
1553 :param repoid: Set the repository name or repository ID.
1567 :param repoid: Set the repository name or repository ID.
1554 :type repoid: str or int
1568 :type repoid: str or int
1555 :param userid: Set the user name of revoked user.
1569 :param userid: Set the user name of revoked user.
1556 :type userid: str or int
1570 :type userid: str or int
1557
1571
1558 Example error output:
1572 Example error output:
1559
1573
1560 .. code-block:: bash
1574 .. code-block:: bash
1561
1575
1562 id : <id_given_in_input>
1576 id : <id_given_in_input>
1563 result: {
1577 result: {
1564 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1578 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1565 "success": true
1579 "success": true
1566 }
1580 }
1567 error: null
1581 error: null
1568 """
1582 """
1569
1583
1570 repo = get_repo_or_error(repoid)
1584 repo = get_repo_or_error(repoid)
1571 user = get_user_or_error(userid)
1585 user = get_user_or_error(userid)
1572 if not has_superadmin_permission(apiuser):
1586 if not has_superadmin_permission(apiuser):
1573 _perms = ('repository.admin',)
1587 _perms = ('repository.admin',)
1574 validate_repo_permissions(apiuser, repoid, repo, _perms)
1588 validate_repo_permissions(apiuser, repoid, repo, _perms)
1575
1589
1576 try:
1590 try:
1577 RepoModel().revoke_user_permission(repo=repo, user=user)
1591 RepoModel().revoke_user_permission(repo=repo, user=user)
1578 Session().commit()
1592 Session().commit()
1579 return {
1593 return {
1580 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1594 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1581 user.username, repo.repo_name
1595 user.username, repo.repo_name
1582 ),
1596 ),
1583 'success': True
1597 'success': True
1584 }
1598 }
1585 except Exception:
1599 except Exception:
1586 log.exception(
1600 log.exception(
1587 "Exception occurred while trying revoke permissions to repo")
1601 "Exception occurred while trying revoke permissions to repo")
1588 raise JSONRPCError(
1602 raise JSONRPCError(
1589 'failed to edit permission for user: `%s` in repo: `%s`' % (
1603 'failed to edit permission for user: `%s` in repo: `%s`' % (
1590 userid, repoid
1604 userid, repoid
1591 )
1605 )
1592 )
1606 )
1593
1607
1594
1608
1595 @jsonrpc_method()
1609 @jsonrpc_method()
1596 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1610 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1597 """
1611 """
1598 Grant permission for a user group on the specified repository,
1612 Grant permission for a user group on the specified repository,
1599 or update existing permissions.
1613 or update existing permissions.
1600
1614
1601 This command can only be run using an |authtoken| with admin
1615 This command can only be run using an |authtoken| with admin
1602 permissions on the |repo|.
1616 permissions on the |repo|.
1603
1617
1604 :param apiuser: This is filled automatically from the |authtoken|.
1618 :param apiuser: This is filled automatically from the |authtoken|.
1605 :type apiuser: AuthUser
1619 :type apiuser: AuthUser
1606 :param repoid: Set the repository name or repository ID.
1620 :param repoid: Set the repository name or repository ID.
1607 :type repoid: str or int
1621 :type repoid: str or int
1608 :param usergroupid: Specify the ID of the user group.
1622 :param usergroupid: Specify the ID of the user group.
1609 :type usergroupid: str or int
1623 :type usergroupid: str or int
1610 :param perm: Set the user group permissions using the following
1624 :param perm: Set the user group permissions using the following
1611 format: (repository.(none|read|write|admin))
1625 format: (repository.(none|read|write|admin))
1612 :type perm: str
1626 :type perm: str
1613
1627
1614 Example output:
1628 Example output:
1615
1629
1616 .. code-block:: bash
1630 .. code-block:: bash
1617
1631
1618 id : <id_given_in_input>
1632 id : <id_given_in_input>
1619 result : {
1633 result : {
1620 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1634 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1621 "success": true
1635 "success": true
1622
1636
1623 }
1637 }
1624 error : null
1638 error : null
1625
1639
1626 Example error output:
1640 Example error output:
1627
1641
1628 .. code-block:: bash
1642 .. code-block:: bash
1629
1643
1630 id : <id_given_in_input>
1644 id : <id_given_in_input>
1631 result : null
1645 result : null
1632 error : {
1646 error : {
1633 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1647 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1634 }
1648 }
1635
1649
1636 """
1650 """
1637
1651
1638 repo = get_repo_or_error(repoid)
1652 repo = get_repo_or_error(repoid)
1639 perm = get_perm_or_error(perm)
1653 perm = get_perm_or_error(perm)
1640 if not has_superadmin_permission(apiuser):
1654 if not has_superadmin_permission(apiuser):
1641 _perms = ('repository.admin',)
1655 _perms = ('repository.admin',)
1642 validate_repo_permissions(apiuser, repoid, repo, _perms)
1656 validate_repo_permissions(apiuser, repoid, repo, _perms)
1643
1657
1644 user_group = get_user_group_or_error(usergroupid)
1658 user_group = get_user_group_or_error(usergroupid)
1645 if not has_superadmin_permission(apiuser):
1659 if not has_superadmin_permission(apiuser):
1646 # check if we have at least read permission for this user group !
1660 # check if we have at least read permission for this user group !
1647 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1661 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1648 if not HasUserGroupPermissionAnyApi(*_perms)(
1662 if not HasUserGroupPermissionAnyApi(*_perms)(
1649 user=apiuser, user_group_name=user_group.users_group_name):
1663 user=apiuser, user_group_name=user_group.users_group_name):
1650 raise JSONRPCError(
1664 raise JSONRPCError(
1651 'user group `%s` does not exist' % (usergroupid,))
1665 'user group `%s` does not exist' % (usergroupid,))
1652
1666
1653 try:
1667 try:
1654 RepoModel().grant_user_group_permission(
1668 RepoModel().grant_user_group_permission(
1655 repo=repo, group_name=user_group, perm=perm)
1669 repo=repo, group_name=user_group, perm=perm)
1656
1670
1657 Session().commit()
1671 Session().commit()
1658 return {
1672 return {
1659 'msg': 'Granted perm: `%s` for user group: `%s` in '
1673 'msg': 'Granted perm: `%s` for user group: `%s` in '
1660 'repo: `%s`' % (
1674 'repo: `%s`' % (
1661 perm.permission_name, user_group.users_group_name,
1675 perm.permission_name, user_group.users_group_name,
1662 repo.repo_name
1676 repo.repo_name
1663 ),
1677 ),
1664 'success': True
1678 'success': True
1665 }
1679 }
1666 except Exception:
1680 except Exception:
1667 log.exception(
1681 log.exception(
1668 "Exception occurred while trying change permission on repo")
1682 "Exception occurred while trying change permission on repo")
1669 raise JSONRPCError(
1683 raise JSONRPCError(
1670 'failed to edit permission for user group: `%s` in '
1684 'failed to edit permission for user group: `%s` in '
1671 'repo: `%s`' % (
1685 'repo: `%s`' % (
1672 usergroupid, repo.repo_name
1686 usergroupid, repo.repo_name
1673 )
1687 )
1674 )
1688 )
1675
1689
1676
1690
1677 @jsonrpc_method()
1691 @jsonrpc_method()
1678 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1692 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1679 """
1693 """
1680 Revoke the permissions of a user group on a given repository.
1694 Revoke the permissions of a user group on a given repository.
1681
1695
1682 This command can only be run using an |authtoken| with admin
1696 This command can only be run using an |authtoken| with admin
1683 permissions on the |repo|.
1697 permissions on the |repo|.
1684
1698
1685 :param apiuser: This is filled automatically from the |authtoken|.
1699 :param apiuser: This is filled automatically from the |authtoken|.
1686 :type apiuser: AuthUser
1700 :type apiuser: AuthUser
1687 :param repoid: Set the repository name or repository ID.
1701 :param repoid: Set the repository name or repository ID.
1688 :type repoid: str or int
1702 :type repoid: str or int
1689 :param usergroupid: Specify the user group ID.
1703 :param usergroupid: Specify the user group ID.
1690 :type usergroupid: str or int
1704 :type usergroupid: str or int
1691
1705
1692 Example output:
1706 Example output:
1693
1707
1694 .. code-block:: bash
1708 .. code-block:: bash
1695
1709
1696 id : <id_given_in_input>
1710 id : <id_given_in_input>
1697 result: {
1711 result: {
1698 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1712 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1699 "success": true
1713 "success": true
1700 }
1714 }
1701 error: null
1715 error: null
1702 """
1716 """
1703
1717
1704 repo = get_repo_or_error(repoid)
1718 repo = get_repo_or_error(repoid)
1705 if not has_superadmin_permission(apiuser):
1719 if not has_superadmin_permission(apiuser):
1706 _perms = ('repository.admin',)
1720 _perms = ('repository.admin',)
1707 validate_repo_permissions(apiuser, repoid, repo, _perms)
1721 validate_repo_permissions(apiuser, repoid, repo, _perms)
1708
1722
1709 user_group = get_user_group_or_error(usergroupid)
1723 user_group = get_user_group_or_error(usergroupid)
1710 if not has_superadmin_permission(apiuser):
1724 if not has_superadmin_permission(apiuser):
1711 # check if we have at least read permission for this user group !
1725 # check if we have at least read permission for this user group !
1712 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1726 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1713 if not HasUserGroupPermissionAnyApi(*_perms)(
1727 if not HasUserGroupPermissionAnyApi(*_perms)(
1714 user=apiuser, user_group_name=user_group.users_group_name):
1728 user=apiuser, user_group_name=user_group.users_group_name):
1715 raise JSONRPCError(
1729 raise JSONRPCError(
1716 'user group `%s` does not exist' % (usergroupid,))
1730 'user group `%s` does not exist' % (usergroupid,))
1717
1731
1718 try:
1732 try:
1719 RepoModel().revoke_user_group_permission(
1733 RepoModel().revoke_user_group_permission(
1720 repo=repo, group_name=user_group)
1734 repo=repo, group_name=user_group)
1721
1735
1722 Session().commit()
1736 Session().commit()
1723 return {
1737 return {
1724 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1738 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1725 user_group.users_group_name, repo.repo_name
1739 user_group.users_group_name, repo.repo_name
1726 ),
1740 ),
1727 'success': True
1741 'success': True
1728 }
1742 }
1729 except Exception:
1743 except Exception:
1730 log.exception("Exception occurred while trying revoke "
1744 log.exception("Exception occurred while trying revoke "
1731 "user group permission on repo")
1745 "user group permission on repo")
1732 raise JSONRPCError(
1746 raise JSONRPCError(
1733 'failed to edit permission for user group: `%s` in '
1747 'failed to edit permission for user group: `%s` in '
1734 'repo: `%s`' % (
1748 'repo: `%s`' % (
1735 user_group.users_group_name, repo.repo_name
1749 user_group.users_group_name, repo.repo_name
1736 )
1750 )
1737 )
1751 )
1738
1752
1739
1753
1740 @jsonrpc_method()
1754 @jsonrpc_method()
1741 def pull(request, apiuser, repoid):
1755 def pull(request, apiuser, repoid):
1742 """
1756 """
1743 Triggers a pull on the given repository from a remote location. You
1757 Triggers a pull on the given repository from a remote location. You
1744 can use this to keep remote repositories up-to-date.
1758 can use this to keep remote repositories up-to-date.
1745
1759
1746 This command can only be run using an |authtoken| with admin
1760 This command can only be run using an |authtoken| with admin
1747 rights to the specified repository. For more information,
1761 rights to the specified repository. For more information,
1748 see :ref:`config-token-ref`.
1762 see :ref:`config-token-ref`.
1749
1763
1750 This command takes the following options:
1764 This command takes the following options:
1751
1765
1752 :param apiuser: This is filled automatically from the |authtoken|.
1766 :param apiuser: This is filled automatically from the |authtoken|.
1753 :type apiuser: AuthUser
1767 :type apiuser: AuthUser
1754 :param repoid: The repository name or repository ID.
1768 :param repoid: The repository name or repository ID.
1755 :type repoid: str or int
1769 :type repoid: str or int
1756
1770
1757 Example output:
1771 Example output:
1758
1772
1759 .. code-block:: bash
1773 .. code-block:: bash
1760
1774
1761 id : <id_given_in_input>
1775 id : <id_given_in_input>
1762 result : {
1776 result : {
1763 "msg": "Pulled from `<repository name>`"
1777 "msg": "Pulled from `<repository name>`"
1764 "repository": "<repository name>"
1778 "repository": "<repository name>"
1765 }
1779 }
1766 error : null
1780 error : null
1767
1781
1768 Example error output:
1782 Example error output:
1769
1783
1770 .. code-block:: bash
1784 .. code-block:: bash
1771
1785
1772 id : <id_given_in_input>
1786 id : <id_given_in_input>
1773 result : null
1787 result : null
1774 error : {
1788 error : {
1775 "Unable to pull changes from `<reponame>`"
1789 "Unable to pull changes from `<reponame>`"
1776 }
1790 }
1777
1791
1778 """
1792 """
1779
1793
1780 repo = get_repo_or_error(repoid)
1794 repo = get_repo_or_error(repoid)
1781 if not has_superadmin_permission(apiuser):
1795 if not has_superadmin_permission(apiuser):
1782 _perms = ('repository.admin',)
1796 _perms = ('repository.admin',)
1783 validate_repo_permissions(apiuser, repoid, repo, _perms)
1797 validate_repo_permissions(apiuser, repoid, repo, _perms)
1784
1798
1785 try:
1799 try:
1786 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1800 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1787 return {
1801 return {
1788 'msg': 'Pulled from `%s`' % repo.repo_name,
1802 'msg': 'Pulled from `%s`' % repo.repo_name,
1789 'repository': repo.repo_name
1803 'repository': repo.repo_name
1790 }
1804 }
1791 except Exception:
1805 except Exception:
1792 log.exception("Exception occurred while trying to "
1806 log.exception("Exception occurred while trying to "
1793 "pull changes from remote location")
1807 "pull changes from remote location")
1794 raise JSONRPCError(
1808 raise JSONRPCError(
1795 'Unable to pull changes from `%s`' % repo.repo_name
1809 'Unable to pull changes from `%s`' % repo.repo_name
1796 )
1810 )
1797
1811
1798
1812
1799 @jsonrpc_method()
1813 @jsonrpc_method()
1800 def strip(request, apiuser, repoid, revision, branch):
1814 def strip(request, apiuser, repoid, revision, branch):
1801 """
1815 """
1802 Strips the given revision from the specified repository.
1816 Strips the given revision from the specified repository.
1803
1817
1804 * This will remove the revision and all of its decendants.
1818 * This will remove the revision and all of its decendants.
1805
1819
1806 This command can only be run using an |authtoken| with admin rights to
1820 This command can only be run using an |authtoken| with admin rights to
1807 the specified repository.
1821 the specified repository.
1808
1822
1809 This command takes the following options:
1823 This command takes the following options:
1810
1824
1811 :param apiuser: This is filled automatically from the |authtoken|.
1825 :param apiuser: This is filled automatically from the |authtoken|.
1812 :type apiuser: AuthUser
1826 :type apiuser: AuthUser
1813 :param repoid: The repository name or repository ID.
1827 :param repoid: The repository name or repository ID.
1814 :type repoid: str or int
1828 :type repoid: str or int
1815 :param revision: The revision you wish to strip.
1829 :param revision: The revision you wish to strip.
1816 :type revision: str
1830 :type revision: str
1817 :param branch: The branch from which to strip the revision.
1831 :param branch: The branch from which to strip the revision.
1818 :type branch: str
1832 :type branch: str
1819
1833
1820 Example output:
1834 Example output:
1821
1835
1822 .. code-block:: bash
1836 .. code-block:: bash
1823
1837
1824 id : <id_given_in_input>
1838 id : <id_given_in_input>
1825 result : {
1839 result : {
1826 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1840 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1827 "repository": "<repository name>"
1841 "repository": "<repository name>"
1828 }
1842 }
1829 error : null
1843 error : null
1830
1844
1831 Example error output:
1845 Example error output:
1832
1846
1833 .. code-block:: bash
1847 .. code-block:: bash
1834
1848
1835 id : <id_given_in_input>
1849 id : <id_given_in_input>
1836 result : null
1850 result : null
1837 error : {
1851 error : {
1838 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1852 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1839 }
1853 }
1840
1854
1841 """
1855 """
1842
1856
1843 repo = get_repo_or_error(repoid)
1857 repo = get_repo_or_error(repoid)
1844 if not has_superadmin_permission(apiuser):
1858 if not has_superadmin_permission(apiuser):
1845 _perms = ('repository.admin',)
1859 _perms = ('repository.admin',)
1846 validate_repo_permissions(apiuser, repoid, repo, _perms)
1860 validate_repo_permissions(apiuser, repoid, repo, _perms)
1847
1861
1848 try:
1862 try:
1849 ScmModel().strip(repo, revision, branch)
1863 ScmModel().strip(repo, revision, branch)
1850 return {
1864 return {
1851 'msg': 'Stripped commit %s from repo `%s`' % (
1865 'msg': 'Stripped commit %s from repo `%s`' % (
1852 revision, repo.repo_name),
1866 revision, repo.repo_name),
1853 'repository': repo.repo_name
1867 'repository': repo.repo_name
1854 }
1868 }
1855 except Exception:
1869 except Exception:
1856 log.exception("Exception while trying to strip")
1870 log.exception("Exception while trying to strip")
1857 raise JSONRPCError(
1871 raise JSONRPCError(
1858 'Unable to strip commit %s from repo `%s`' % (
1872 'Unable to strip commit %s from repo `%s`' % (
1859 revision, repo.repo_name)
1873 revision, repo.repo_name)
1860 )
1874 )
1861
1875
1862
1876
1863 @jsonrpc_method()
1877 @jsonrpc_method()
1864 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1878 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1865 """
1879 """
1866 Returns all settings for a repository. If key is given it only returns the
1880 Returns all settings for a repository. If key is given it only returns the
1867 setting identified by the key or null.
1881 setting identified by the key or null.
1868
1882
1869 :param apiuser: This is filled automatically from the |authtoken|.
1883 :param apiuser: This is filled automatically from the |authtoken|.
1870 :type apiuser: AuthUser
1884 :type apiuser: AuthUser
1871 :param repoid: The repository name or repository id.
1885 :param repoid: The repository name or repository id.
1872 :type repoid: str or int
1886 :type repoid: str or int
1873 :param key: Key of the setting to return.
1887 :param key: Key of the setting to return.
1874 :type: key: Optional(str)
1888 :type: key: Optional(str)
1875
1889
1876 Example output:
1890 Example output:
1877
1891
1878 .. code-block:: bash
1892 .. code-block:: bash
1879
1893
1880 {
1894 {
1881 "error": null,
1895 "error": null,
1882 "id": 237,
1896 "id": 237,
1883 "result": {
1897 "result": {
1884 "extensions_largefiles": true,
1898 "extensions_largefiles": true,
1885 "hooks_changegroup_push_logger": true,
1899 "hooks_changegroup_push_logger": true,
1886 "hooks_changegroup_repo_size": false,
1900 "hooks_changegroup_repo_size": false,
1887 "hooks_outgoing_pull_logger": true,
1901 "hooks_outgoing_pull_logger": true,
1888 "phases_publish": "True",
1902 "phases_publish": "True",
1889 "rhodecode_hg_use_rebase_for_merging": true,
1903 "rhodecode_hg_use_rebase_for_merging": true,
1890 "rhodecode_pr_merge_enabled": true,
1904 "rhodecode_pr_merge_enabled": true,
1891 "rhodecode_use_outdated_comments": true
1905 "rhodecode_use_outdated_comments": true
1892 }
1906 }
1893 }
1907 }
1894 """
1908 """
1895
1909
1896 # Restrict access to this api method to admins only.
1910 # Restrict access to this api method to admins only.
1897 if not has_superadmin_permission(apiuser):
1911 if not has_superadmin_permission(apiuser):
1898 raise JSONRPCForbidden()
1912 raise JSONRPCForbidden()
1899
1913
1900 try:
1914 try:
1901 repo = get_repo_or_error(repoid)
1915 repo = get_repo_or_error(repoid)
1902 settings_model = VcsSettingsModel(repo=repo)
1916 settings_model = VcsSettingsModel(repo=repo)
1903 settings = settings_model.get_global_settings()
1917 settings = settings_model.get_global_settings()
1904 settings.update(settings_model.get_repo_settings())
1918 settings.update(settings_model.get_repo_settings())
1905
1919
1906 # If only a single setting is requested fetch it from all settings.
1920 # If only a single setting is requested fetch it from all settings.
1907 key = Optional.extract(key)
1921 key = Optional.extract(key)
1908 if key is not None:
1922 if key is not None:
1909 settings = settings.get(key, None)
1923 settings = settings.get(key, None)
1910 except Exception:
1924 except Exception:
1911 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1925 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1912 log.exception(msg)
1926 log.exception(msg)
1913 raise JSONRPCError(msg)
1927 raise JSONRPCError(msg)
1914
1928
1915 return settings
1929 return settings
1916
1930
1917
1931
1918 @jsonrpc_method()
1932 @jsonrpc_method()
1919 def set_repo_settings(request, apiuser, repoid, settings):
1933 def set_repo_settings(request, apiuser, repoid, settings):
1920 """
1934 """
1921 Update repository settings. Returns true on success.
1935 Update repository settings. Returns true on success.
1922
1936
1923 :param apiuser: This is filled automatically from the |authtoken|.
1937 :param apiuser: This is filled automatically from the |authtoken|.
1924 :type apiuser: AuthUser
1938 :type apiuser: AuthUser
1925 :param repoid: The repository name or repository id.
1939 :param repoid: The repository name or repository id.
1926 :type repoid: str or int
1940 :type repoid: str or int
1927 :param settings: The new settings for the repository.
1941 :param settings: The new settings for the repository.
1928 :type: settings: dict
1942 :type: settings: dict
1929
1943
1930 Example output:
1944 Example output:
1931
1945
1932 .. code-block:: bash
1946 .. code-block:: bash
1933
1947
1934 {
1948 {
1935 "error": null,
1949 "error": null,
1936 "id": 237,
1950 "id": 237,
1937 "result": true
1951 "result": true
1938 }
1952 }
1939 """
1953 """
1940 # Restrict access to this api method to admins only.
1954 # Restrict access to this api method to admins only.
1941 if not has_superadmin_permission(apiuser):
1955 if not has_superadmin_permission(apiuser):
1942 raise JSONRPCForbidden()
1956 raise JSONRPCForbidden()
1943
1957
1944 if type(settings) is not dict:
1958 if type(settings) is not dict:
1945 raise JSONRPCError('Settings have to be a JSON Object.')
1959 raise JSONRPCError('Settings have to be a JSON Object.')
1946
1960
1947 try:
1961 try:
1948 settings_model = VcsSettingsModel(repo=repoid)
1962 settings_model = VcsSettingsModel(repo=repoid)
1949
1963
1950 # Merge global, repo and incoming settings.
1964 # Merge global, repo and incoming settings.
1951 new_settings = settings_model.get_global_settings()
1965 new_settings = settings_model.get_global_settings()
1952 new_settings.update(settings_model.get_repo_settings())
1966 new_settings.update(settings_model.get_repo_settings())
1953 new_settings.update(settings)
1967 new_settings.update(settings)
1954
1968
1955 # Update the settings.
1969 # Update the settings.
1956 inherit_global_settings = new_settings.get(
1970 inherit_global_settings = new_settings.get(
1957 'inherit_global_settings', False)
1971 'inherit_global_settings', False)
1958 settings_model.create_or_update_repo_settings(
1972 settings_model.create_or_update_repo_settings(
1959 new_settings, inherit_global_settings=inherit_global_settings)
1973 new_settings, inherit_global_settings=inherit_global_settings)
1960 Session().commit()
1974 Session().commit()
1961 except Exception:
1975 except Exception:
1962 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1976 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1963 log.exception(msg)
1977 log.exception(msg)
1964 raise JSONRPCError(msg)
1978 raise JSONRPCError(msg)
1965
1979
1966 # Indicate success.
1980 # Indicate success.
1967 return True
1981 return True
General Comments 0
You need to be logged in to leave comments. Login now