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