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