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