##// END OF EJS Templates
api: add a max_file_bytes parameter to get_nodes so that large...
dan -
r502:8bb872ec default
parent child Browse files
Show More
@@ -1,2824 +1,2825 b''
1 1 .. _api:
2 2
3 3 API Documentation
4 4 =================
5 5
6 6 The |RCE| API uses a single scheme for calling all API methods. The API is
7 7 implemented with JSON protocol in both directions. To send API requests to
8 8 your instance of |RCE|, use the following URL format
9 9 ``<your_server>/_admin``
10 10
11 11 .. note::
12 12
13 13 To use the API, you should configure the :file:`~/.rhoderc` file with
14 14 access details per instance. For more information, see
15 15 :ref:`config-rhoderc`.
16 16
17 17
18 18 API ACCESS FOR WEB VIEWS
19 19 ------------------------
20 20
21 21 API access can also be turned on for each web view in |RCE| that is
22 22 decorated with a `@LoginRequired` decorator. To enable API access, change
23 23 the standard login decorator to `@LoginRequired(api_access=True)`.
24 24
25 25 From |RCM| version 1.7.0 you can configure a white list
26 26 of views that have API access enabled by default. To enable these,
27 27 edit the |RCM| configuration ``.ini`` file. The default location is:
28 28
29 29 * |RCM| Pre-2.2.7 :file:`root/rhodecode/data/production.ini`
30 30 * |RCM| 3.0 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
31 31
32 32 To configure the white list, edit this section of the file. In this
33 33 configuration example, API access is granted to the patch/diff raw file and
34 34 archive.
35 35
36 36 .. code-block:: ini
37 37
38 38 ## List of controllers (using glob syntax) that AUTH TOKENS could be used for access.
39 39 ## Adding ?auth_token = <token> to the url authenticates this request as if it
40 40 ## came from the the logged in user who own this authentication token.
41 41 ##
42 42 ## Syntax is <ControllerClass>:<function_pattern>.
43 43 ## The list should be "," separated and on a single line.
44 44 ##
45 45 api_access_controllers_whitelist = ChangesetController:changeset_patch,ChangesetController:changeset_raw,ilesController:raw,FilesController:archivefile,
46 46
47 47 After this change, a |RCE| view can be accessed without login by adding a
48 48 GET parameter ``?auth_token=<auth_token>`` to a url. For example to
49 49 access the raw diff.
50 50
51 51 .. code-block:: html
52 52
53 53 http://<server>/<repo>/changeset-diff/<sha>?auth_token=<auth_token>
54 54
55 55 By default this is only enabled on RSS/ATOM feed views. Exposing raw diffs is a
56 56 good way to integrate with 3rd party services like code review, or build farms
57 57 that could download archives.
58 58
59 59 API ACCESS
60 60 ----------
61 61
62 62 All clients are required to send JSON-RPC spec JSON data.
63 63
64 64 .. code-block:: bash
65 65
66 66 {
67 67 "id:"<id>",
68 68 "auth_token":"<auth_token>",
69 69 "method":"<method_name>",
70 70 "args":{"<arg_key>":"<arg_val>"}
71 71 }
72 72
73 73 Example call for auto pulling from remote repositories using curl:
74 74
75 75 .. code-block:: bash
76 76
77 77 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,
78 78 "auth_token":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull", "args":{"repo":"CPython"}}'
79 79
80 80 Provide those parameters:
81 81 - **id** A value of any type, which is used to match the response with the
82 82 request that it is replying to.
83 83 - **auth_token** for access and permission validation.
84 84 - **method** is name of method to call
85 85 - **args** is an ``key:value`` list of arguments to pass to method
86 86
87 87 .. note::
88 88
89 89 To get your |authtoken|, from the |RCE| interface,
90 90 go to:
91 91 :menuselection:`username --> My account --> Auth tokens`
92 92
93 93 For security reasons you should always create a dedicated |authtoken| for
94 94 API use only.
95 95
96 96
97 97 The |RCE| API will always return a JSON-RPC response:
98 98
99 99 .. code-block:: bash
100 100
101 101 {
102 102 "id": <id>, # matching id sent by request
103 103 "result": "<result>"|null, # JSON formatted result, null if any errors
104 104 "error": "null"|<error_message> # JSON formatted error (if any)
105 105 }
106 106
107 107 All responses from API will be with `HTTP/1.0 200 OK` status code.
108 108 If there is an error when calling the API, the *error* key will contain a
109 109 failure description and the *result* will be `null`.
110 110
111 111 API CLIENT
112 112 ----------
113 113
114 114 To install the |RCE| API, see :ref:`install-tools`. To configure the API per
115 115 instance, see the :ref:`rc-tools` section as you need to configure a
116 116 :file:`~/.rhoderc` file with your |authtokens|.
117 117
118 118 Once you have set up your instance API access, use the following examples to
119 119 get started.
120 120
121 121 .. code-block:: bash
122 122
123 123 # Getting the 'rhodecode' repository
124 124 # from a RhodeCode Enterprise instance
125 125 rhodecode-api --instance-name=enterprise-1 get_repo repoid:rhodecode
126 126
127 127 Calling method get_repo => http://127.0.0.1:5000
128 128 Server response
129 129 {
130 130 <json data>
131 131 }
132 132
133 133 # Creating a new mercurial repository called 'brand-new'
134 134 # with a description 'Repo-description'
135 135 rhodecode-api --instance-name=enterprise-1 create_repo repo_name:brand-new repo_type:hg description:Repo-description
136 136 {
137 137 "error": null,
138 138 "id": 1110,
139 139 "result": {
140 140 "msg": "Created new repository `brand-new`",
141 141 "success": true,
142 142 "task": null
143 143 }
144 144 }
145 145
146 146 A broken example, what not to do.
147 147
148 148 .. code-block:: bash
149 149
150 150 # A call missing the required arguments
151 151 # and not specifying the instance
152 152 rhodecode-api get_repo
153 153
154 154 Calling method get_repo => http://127.0.0.1:5000
155 155 Server response
156 156 "Missing non optional `repoid` arg in JSON DATA"
157 157
158 158 You can specify pure JSON using the ``--format`` parameter.
159 159
160 160 .. code-block:: bash
161 161
162 162 rhodecode-api --format=json get_repo repoid:rhodecode
163 163
164 164 In such case only output that this function shows is pure JSON, we can use that
165 165 and pipe output to some json formatter.
166 166
167 167 If output is in pure JSON format, you can pipe output to a JSON formatter.
168 168
169 169 .. code-block:: bash
170 170
171 171 rhodecode-api --instance-name=enterprise-1 --format=json get_repo repoid:rhodecode | python -m json.tool
172 172
173 173 API METHODS
174 174 -----------
175 175
176 176 Each method by default required following arguments.
177 177
178 178 .. code-block:: bash
179 179
180 180 id : "<id_for_response>"
181 181 auth_token : "<auth_token>"
182 182 method : "<method name>"
183 183 args : {}
184 184
185 185 Use each **param** from docs and put it in args, Optional parameters
186 186 are not required in args.
187 187
188 188 .. code-block:: bash
189 189
190 190 args: {"repoid": "rhodecode"}
191 191
192 192 .. Note: From this point on things are generated by the script in
193 193 `scripts/fabfile.py`. To change things below, update the docstrings in the
194 194 ApiController.
195 195
196 196 .. --- API DEFS MARKER ---
197 197
198 198 pull
199 199 ----
200 200
201 201 .. py:function:: pull(apiuser, repoid)
202 202
203 203 Triggers a pull on the given repository from a remote location. You
204 204 can use this to keep remote repositories up-to-date.
205 205
206 206 This command can only be run using an |authtoken| with admin
207 207 rights to the specified repository. For more information,
208 208 see :ref:`config-token-ref`.
209 209
210 210 This command takes the following options:
211 211
212 212 :param apiuser: This is filled automatically from the |authtoken|.
213 213 :type apiuser: AuthUser
214 214 :param repoid: The repository name or repository ID.
215 215 :type repoid: str or int
216 216
217 217 Example output:
218 218
219 219 .. code-block:: bash
220 220
221 221 id : <id_given_in_input>
222 222 result : {
223 223 "msg": "Pulled from `<repository name>`"
224 224 "repository": "<repository name>"
225 225 }
226 226 error : null
227 227
228 228 Example error output:
229 229
230 230 .. code-block:: bash
231 231
232 232 id : <id_given_in_input>
233 233 result : null
234 234 error : {
235 235 "Unable to pull changes from `<reponame>`"
236 236 }
237 237
238 238
239 239 strip
240 240 -----
241 241
242 242 .. py:function:: strip(apiuser, repoid, revision, branch)
243 243
244 244 Strips the given revision from the specified repository.
245 245
246 246 * This will remove the revision and all of its decendants.
247 247
248 248 This command can only be run using an |authtoken| with admin rights to
249 249 the specified repository.
250 250
251 251 This command takes the following options:
252 252
253 253 :param apiuser: This is filled automatically from the |authtoken|.
254 254 :type apiuser: AuthUser
255 255 :param repoid: The repository name or repository ID.
256 256 :type repoid: str or int
257 257 :param revision: The revision you wish to strip.
258 258 :type revision: str
259 259 :param branch: The branch from which to strip the revision.
260 260 :type branch: str
261 261
262 262 Example output:
263 263
264 264 .. code-block:: bash
265 265
266 266 id : <id_given_in_input>
267 267 result : {
268 268 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
269 269 "repository": "<repository name>"
270 270 }
271 271 error : null
272 272
273 273 Example error output:
274 274
275 275 .. code-block:: bash
276 276
277 277 id : <id_given_in_input>
278 278 result : null
279 279 error : {
280 280 "Unable to strip commit <commit_hash> from repo `<repository name>`"
281 281 }
282 282
283 283
284 284 rescan_repos
285 285 ------------
286 286
287 287 .. py:function:: rescan_repos(apiuser, remove_obsolete=<Optional:False>)
288 288
289 289 Triggers a rescan of the specified repositories.
290 290
291 291 * If the ``remove_obsolete`` option is set, it also deletes repositories
292 292 that are found in the database but not on the file system, so called
293 293 "clean zombies".
294 294
295 295 This command can only be run using an |authtoken| with admin rights to
296 296 the specified repository.
297 297
298 298 This command takes the following options:
299 299
300 300 :param apiuser: This is filled automatically from the |authtoken|.
301 301 :type apiuser: AuthUser
302 302 :param remove_obsolete: Deletes repositories from the database that
303 303 are not found on the filesystem.
304 304 :type remove_obsolete: Optional(``True`` | ``False``)
305 305
306 306 Example output:
307 307
308 308 .. code-block:: bash
309 309
310 310 id : <id_given_in_input>
311 311 result : {
312 312 'added': [<added repository name>,...]
313 313 'removed': [<removed repository name>,...]
314 314 }
315 315 error : null
316 316
317 317 Example error output:
318 318
319 319 .. code-block:: bash
320 320
321 321 id : <id_given_in_input>
322 322 result : null
323 323 error : {
324 324 'Error occurred during rescan repositories action'
325 325 }
326 326
327 327
328 328 invalidate_cache
329 329 ----------------
330 330
331 331 .. py:function:: invalidate_cache(apiuser, repoid, delete_keys=<Optional:False>)
332 332
333 333 Invalidates the cache for the specified repository.
334 334
335 335 This command can only be run using an |authtoken| with admin rights to
336 336 the specified repository.
337 337
338 338 This command takes the following options:
339 339
340 340 :param apiuser: This is filled automatically from |authtoken|.
341 341 :type apiuser: AuthUser
342 342 :param repoid: Sets the repository name or repository ID.
343 343 :type repoid: str or int
344 344 :param delete_keys: This deletes the invalidated keys instead of
345 345 just flagging them.
346 346 :type delete_keys: Optional(``True`` | ``False``)
347 347
348 348 Example output:
349 349
350 350 .. code-block:: bash
351 351
352 352 id : <id_given_in_input>
353 353 result : {
354 354 'msg': Cache for repository `<repository name>` was invalidated,
355 355 'repository': <repository name>
356 356 }
357 357 error : null
358 358
359 359 Example error output:
360 360
361 361 .. code-block:: bash
362 362
363 363 id : <id_given_in_input>
364 364 result : null
365 365 error : {
366 366 'Error occurred during cache invalidation action'
367 367 }
368 368
369 369
370 370 lock
371 371 ----
372 372
373 373 .. py:function:: lock(apiuser, repoid, locked=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
374 374
375 375 Sets the lock state of the specified |repo| by the given user.
376 376 From more information, see :ref:`repo-locking`.
377 377
378 378 * If the ``userid`` option is not set, the repository is locked to the
379 379 user who called the method.
380 380 * If the ``locked`` parameter is not set, the current lock state of the
381 381 repository is displayed.
382 382
383 383 This command can only be run using an |authtoken| with admin rights to
384 384 the specified repository.
385 385
386 386 This command takes the following options:
387 387
388 388 :param apiuser: This is filled automatically from the |authtoken|.
389 389 :type apiuser: AuthUser
390 390 :param repoid: Sets the repository name or repository ID.
391 391 :type repoid: str or int
392 392 :param locked: Sets the lock state.
393 393 :type locked: Optional(``True`` | ``False``)
394 394 :param userid: Set the repository lock to this user.
395 395 :type userid: Optional(str or int)
396 396
397 397 Example error output:
398 398
399 399 .. code-block:: bash
400 400
401 401 id : <id_given_in_input>
402 402 result : {
403 403 'repo': '<reponame>',
404 404 'locked': <bool: lock state>,
405 405 'locked_since': <int: lock timestamp>,
406 406 'locked_by': <username of person who made the lock>,
407 407 'lock_reason': <str: reason for locking>,
408 408 'lock_state_changed': <bool: True if lock state has been changed in this request>,
409 409 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
410 410 or
411 411 'msg': 'Repo `<repository name>` not locked.'
412 412 or
413 413 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
414 414 }
415 415 error : null
416 416
417 417 Example error output:
418 418
419 419 .. code-block:: bash
420 420
421 421 id : <id_given_in_input>
422 422 result : null
423 423 error : {
424 424 'Error occurred locking repository `<reponame>`
425 425 }
426 426
427 427
428 428 get_locks
429 429 ---------
430 430
431 431 .. py:function:: get_locks(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
432 432
433 433 Displays all repositories locked by the specified user.
434 434
435 435 * If this command is run by a non-admin user, it returns
436 436 a list of |repos| locked by that user.
437 437
438 438 This command takes the following options:
439 439
440 440 :param apiuser: This is filled automatically from the |authtoken|.
441 441 :type apiuser: AuthUser
442 442 :param userid: Sets the userid whose list of locked |repos| will be
443 443 displayed.
444 444 :type userid: Optional(str or int)
445 445
446 446 Example output:
447 447
448 448 .. code-block:: bash
449 449
450 450 id : <id_given_in_input>
451 451 result : {
452 452 [repo_object, repo_object,...]
453 453 }
454 454 error : null
455 455
456 456
457 457 get_ip
458 458 ------
459 459
460 460 .. py:function:: get_ip(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
461 461
462 462 Displays the IP Address as seen from the |RCE| server.
463 463
464 464 * This command displays the IP Address, as well as all the defined IP
465 465 addresses for the specified user. If the ``userid`` is not set, the
466 466 data returned is for the user calling the method.
467 467
468 468 This command can only be run using an |authtoken| with admin rights to
469 469 the specified repository.
470 470
471 471 This command takes the following options:
472 472
473 473 :param apiuser: This is filled automatically from |authtoken|.
474 474 :type apiuser: AuthUser
475 475 :param userid: Sets the userid for which associated IP Address data
476 476 is returned.
477 477 :type userid: Optional(str or int)
478 478
479 479 Example output:
480 480
481 481 .. code-block:: bash
482 482
483 483 id : <id_given_in_input>
484 484 result : {
485 485 "server_ip_addr": "<ip_from_clien>",
486 486 "user_ips": [
487 487 {
488 488 "ip_addr": "<ip_with_mask>",
489 489 "ip_range": ["<start_ip>", "<end_ip>"],
490 490 },
491 491 ...
492 492 ]
493 493 }
494 494
495 495
496 496 show_ip
497 497 -------
498 498
499 499 .. py:function:: show_ip(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
500 500
501 501 Displays the IP Address as seen from the |RCE| server.
502 502
503 503 * This command displays the IP Address, as well as all the defined IP
504 504 addresses for the specified user. If the ``userid`` is not set, the
505 505 data returned is for the user calling the method.
506 506
507 507 This command can only be run using an |authtoken| with admin rights to
508 508 the specified repository.
509 509
510 510 This command takes the following options:
511 511
512 512 :param apiuser: This is filled automatically from |authtoken|.
513 513 :type apiuser: AuthUser
514 514 :param userid: Sets the userid for which associated IP Address data
515 515 is returned.
516 516 :type userid: Optional(str or int)
517 517
518 518 Example output:
519 519
520 520 .. code-block:: bash
521 521
522 522 id : <id_given_in_input>
523 523 result : {
524 524 "server_ip_addr": "<ip_from_clien>",
525 525 "user_ips": [
526 526 {
527 527 "ip_addr": "<ip_with_mask>",
528 528 "ip_range": ["<start_ip>", "<end_ip>"],
529 529 },
530 530 ...
531 531 ]
532 532 }
533 533
534 534
535 535 get_license_info
536 536 ----------------
537 537
538 538 .. py:function:: get_license_info(apiuser)
539 539
540 540 Returns the |RCE| license information.
541 541
542 542 :param apiuser: This is filled automatically from the |authtoken|.
543 543 :type apiuser: AuthUser
544 544
545 545 Example output:
546 546
547 547 .. code-block:: bash
548 548
549 549 id : <id_given_in_input>
550 550 result : {
551 551 'rhodecode_version': <rhodecode version>,
552 552 'token': <license token>,
553 553 'issued_to': <license owner>,
554 554 'issued_on': <license issue date>,
555 555 'expires_on': <license expiration date>,
556 556 'type': <license type>,
557 557 'users_limit': <license users limit>,
558 558 'key': <license key>
559 559 }
560 560 error : null
561 561
562 562
563 563 set_license_key
564 564 ---------------
565 565
566 566 .. py:function:: set_license_key(apiuser, key)
567 567
568 568 Sets the |RCE| license key.
569 569
570 570 :param apiuser: This is filled automatically from the |authtoken|.
571 571 :type apiuser: AuthUser
572 572 :param key: This is the license key to be set.
573 573 :type key: str
574 574
575 575 Example output:
576 576
577 577 .. code-block:: bash
578 578
579 579 id : <id_given_in_input>
580 580 result: {
581 581 "msg" : "updated license information",
582 582 "key": <key>
583 583 }
584 584 error: null
585 585
586 586 Example error output:
587 587
588 588 .. code-block:: bash
589 589
590 590 id : <id_given_in_input>
591 591 result : null
592 592 error : {
593 593 "license key is not valid"
594 594 or
595 595 "trial licenses cannot be uploaded"
596 596 or
597 597 "error occurred while updating license"
598 598 }
599 599
600 600
601 601 get_server_info
602 602 ---------------
603 603
604 604 .. py:function:: get_server_info(apiuser)
605 605
606 606 Returns the |RCE| server information.
607 607
608 608 This includes the running version of |RCE| and all installed
609 609 packages. This command takes the following options:
610 610
611 611 :param apiuser: This is filled automatically from the |authtoken|.
612 612 :type apiuser: AuthUser
613 613
614 614 Example output:
615 615
616 616 .. code-block:: bash
617 617
618 618 id : <id_given_in_input>
619 619 result : {
620 620 'modules': [<module name>,...]
621 621 'py_version': <python version>,
622 622 'platform': <platform type>,
623 623 'rhodecode_version': <rhodecode version>
624 624 }
625 625 error : null
626 626
627 627
628 628 get_user
629 629 --------
630 630
631 631 .. py:function:: get_user(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
632 632
633 633 Returns the information associated with a username or userid.
634 634
635 635 * If the ``userid`` is not set, this command returns the information
636 636 for the ``userid`` calling the method.
637 637
638 638 .. note::
639 639
640 640 Normal users may only run this command against their ``userid``. For
641 641 full privileges you must run this command using an |authtoken| with
642 642 admin rights.
643 643
644 644 This command takes the following options:
645 645
646 646 :param apiuser: This is filled automatically from the |authtoken|.
647 647 :type apiuser: AuthUser
648 648 :param userid: Sets the userid for which data will be returned.
649 649 :type userid: Optional(str or int)
650 650
651 651 Example output:
652 652
653 653 .. code-block:: bash
654 654
655 655 {
656 656 "error": null,
657 657 "id": <id>,
658 658 "result": {
659 659 "active": true,
660 660 "admin": false,
661 661 "api_key": "api-key",
662 662 "api_keys": [ list of keys ],
663 663 "email": "user@example.com",
664 664 "emails": [
665 665 "user@example.com"
666 666 ],
667 667 "extern_name": "rhodecode",
668 668 "extern_type": "rhodecode",
669 669 "firstname": "username",
670 670 "ip_addresses": [],
671 671 "language": null,
672 672 "last_login": "Timestamp",
673 673 "lastname": "surnae",
674 674 "permissions": {
675 675 "global": [
676 676 "hg.inherit_default_perms.true",
677 677 "usergroup.read",
678 678 "hg.repogroup.create.false",
679 679 "hg.create.none",
680 680 "hg.extern_activate.manual",
681 681 "hg.create.write_on_repogroup.false",
682 682 "hg.usergroup.create.false",
683 683 "group.none",
684 684 "repository.none",
685 685 "hg.register.none",
686 686 "hg.fork.repository"
687 687 ],
688 688 "repositories": { "username/example": "repository.write"},
689 689 "repositories_groups": { "user-group/repo": "group.none" },
690 690 "user_groups": { "user_group_name": "usergroup.read" }
691 691 },
692 692 "user_id": 32,
693 693 "username": "username"
694 694 }
695 695 }
696 696
697 697
698 698 get_users
699 699 ---------
700 700
701 701 .. py:function:: get_users(apiuser)
702 702
703 703 Lists all users in the |RCE| user database.
704 704
705 705 This command can only be run using an |authtoken| with admin rights to
706 706 the specified repository.
707 707
708 708 This command takes the following options:
709 709
710 710 :param apiuser: This is filled automatically from the |authtoken|.
711 711 :type apiuser: AuthUser
712 712
713 713 Example output:
714 714
715 715 .. code-block:: bash
716 716
717 717 id : <id_given_in_input>
718 718 result: [<user_object>, ...]
719 719 error: null
720 720
721 721
722 722 create_user
723 723 -----------
724 724
725 725 .. py:function:: create_user(apiuser, username, email, password=<Optional:''>, firstname=<Optional:''>, lastname=<Optional:''>, active=<Optional:True>, admin=<Optional:False>, extern_name=<Optional:'rhodecode'>, extern_type=<Optional:'rhodecode'>, force_password_change=<Optional:False>)
726 726
727 727 Creates a new user and returns the new user object.
728 728
729 729 This command can only be run using an |authtoken| with admin rights to
730 730 the specified repository.
731 731
732 732 This command takes the following options:
733 733
734 734 :param apiuser: This is filled automatically from the |authtoken|.
735 735 :type apiuser: AuthUser
736 736 :param username: Set the new username.
737 737 :type username: str or int
738 738 :param email: Set the user email address.
739 739 :type email: str
740 740 :param password: Set the new user password.
741 741 :type password: Optional(str)
742 742 :param firstname: Set the new user firstname.
743 743 :type firstname: Optional(str)
744 744 :param lastname: Set the new user surname.
745 745 :type lastname: Optional(str)
746 746 :param active: Set the user as active.
747 747 :type active: Optional(``True`` | ``False``)
748 748 :param admin: Give the new user admin rights.
749 749 :type admin: Optional(``True`` | ``False``)
750 750 :param extern_name: Set the authentication plugin name.
751 751 Using LDAP this is filled with LDAP UID.
752 752 :type extern_name: Optional(str)
753 753 :param extern_type: Set the new user authentication plugin.
754 754 :type extern_type: Optional(str)
755 755 :param force_password_change: Force the new user to change password
756 756 on next login.
757 757 :type force_password_change: Optional(``True`` | ``False``)
758 758
759 759 Example output:
760 760
761 761 .. code-block:: bash
762 762
763 763 id : <id_given_in_input>
764 764 result: {
765 765 "msg" : "created new user `<username>`",
766 766 "user": <user_obj>
767 767 }
768 768 error: null
769 769
770 770 Example error output:
771 771
772 772 .. code-block:: bash
773 773
774 774 id : <id_given_in_input>
775 775 result : null
776 776 error : {
777 777 "user `<username>` already exist"
778 778 or
779 779 "email `<email>` already exist"
780 780 or
781 781 "failed to create user `<username>`"
782 782 }
783 783
784 784
785 785 update_user
786 786 -----------
787 787
788 788 .. py:function:: update_user(apiuser, userid, username=<Optional:None>, email=<Optional:None>, password=<Optional:None>, firstname=<Optional:None>, lastname=<Optional:None>, active=<Optional:None>, admin=<Optional:None>, extern_type=<Optional:None>, extern_name=<Optional:None>)
789 789
790 790 Updates the details for the specified user, if that user exists.
791 791
792 792 This command can only be run using an |authtoken| with admin rights to
793 793 the specified repository.
794 794
795 795 This command takes the following options:
796 796
797 797 :param apiuser: This is filled automatically from |authtoken|.
798 798 :type apiuser: AuthUser
799 799 :param userid: Set the ``userid`` to update.
800 800 :type userid: str or int
801 801 :param username: Set the new username.
802 802 :type username: str or int
803 803 :param email: Set the new email.
804 804 :type email: str
805 805 :param password: Set the new password.
806 806 :type password: Optional(str)
807 807 :param firstname: Set the new first name.
808 808 :type firstname: Optional(str)
809 809 :param lastname: Set the new surname.
810 810 :type lastname: Optional(str)
811 811 :param active: Set the new user as active.
812 812 :type active: Optional(``True`` | ``False``)
813 813 :param admin: Give the user admin rights.
814 814 :type admin: Optional(``True`` | ``False``)
815 815 :param extern_name: Set the authentication plugin user name.
816 816 Using LDAP this is filled with LDAP UID.
817 817 :type extern_name: Optional(str)
818 818 :param extern_type: Set the authentication plugin type.
819 819 :type extern_type: Optional(str)
820 820
821 821
822 822 Example output:
823 823
824 824 .. code-block:: bash
825 825
826 826 id : <id_given_in_input>
827 827 result: {
828 828 "msg" : "updated user ID:<userid> <username>",
829 829 "user": <user_object>,
830 830 }
831 831 error: null
832 832
833 833 Example error output:
834 834
835 835 .. code-block:: bash
836 836
837 837 id : <id_given_in_input>
838 838 result : null
839 839 error : {
840 840 "failed to update user `<username>`"
841 841 }
842 842
843 843
844 844 delete_user
845 845 -----------
846 846
847 847 .. py:function:: delete_user(apiuser, userid)
848 848
849 849 Deletes the specified user from the |RCE| user database.
850 850
851 851 This command can only be run using an |authtoken| with admin rights to
852 852 the specified repository.
853 853
854 854 .. important::
855 855
856 856 Ensure all open pull requests and open code review
857 857 requests to this user are close.
858 858
859 859 Also ensure all repositories, or repository groups owned by this
860 860 user are reassigned before deletion.
861 861
862 862 This command takes the following options:
863 863
864 864 :param apiuser: This is filled automatically from the |authtoken|.
865 865 :type apiuser: AuthUser
866 866 :param userid: Set the user to delete.
867 867 :type userid: str or int
868 868
869 869 Example output:
870 870
871 871 .. code-block:: bash
872 872
873 873 id : <id_given_in_input>
874 874 result: {
875 875 "msg" : "deleted user ID:<userid> <username>",
876 876 "user": null
877 877 }
878 878 error: null
879 879
880 880 Example error output:
881 881
882 882 .. code-block:: bash
883 883
884 884 id : <id_given_in_input>
885 885 result : null
886 886 error : {
887 887 "failed to delete user ID:<userid> <username>"
888 888 }
889 889
890 890
891 891 get_user_group
892 892 --------------
893 893
894 894 .. py:function:: get_user_group(apiuser, usergroupid)
895 895
896 896 Returns the data of an existing user group.
897 897
898 898 This command can only be run using an |authtoken| with admin rights to
899 899 the specified repository.
900 900
901 901 :param apiuser: This is filled automatically from the |authtoken|.
902 902 :type apiuser: AuthUser
903 903 :param usergroupid: Set the user group from which to return data.
904 904 :type usergroupid: str or int
905 905
906 906 Example error output:
907 907
908 908 .. code-block:: bash
909 909
910 910 {
911 911 "error": null,
912 912 "id": <id>,
913 913 "result": {
914 914 "active": true,
915 915 "group_description": "group description",
916 916 "group_name": "group name",
917 917 "members": [
918 918 {
919 919 "name": "owner-name",
920 920 "origin": "owner",
921 921 "permission": "usergroup.admin",
922 922 "type": "user"
923 923 },
924 924 {
925 925 {
926 926 "name": "user name",
927 927 "origin": "permission",
928 928 "permission": "usergroup.admin",
929 929 "type": "user"
930 930 },
931 931 {
932 932 "name": "user group name",
933 933 "origin": "permission",
934 934 "permission": "usergroup.write",
935 935 "type": "user_group"
936 936 }
937 937 ],
938 938 "owner": "owner name",
939 939 "users": [],
940 940 "users_group_id": 2
941 941 }
942 942 }
943 943
944 944
945 945 get_user_groups
946 946 ---------------
947 947
948 948 .. py:function:: get_user_groups(apiuser)
949 949
950 950 Lists all the existing user groups within RhodeCode.
951 951
952 952 This command can only be run using an |authtoken| with admin rights to
953 953 the specified repository.
954 954
955 955 This command takes the following options:
956 956
957 957 :param apiuser: This is filled automatically from the |authtoken|.
958 958 :type apiuser: AuthUser
959 959
960 960 Example error output:
961 961
962 962 .. code-block:: bash
963 963
964 964 id : <id_given_in_input>
965 965 result : [<user_group_obj>,...]
966 966 error : null
967 967
968 968
969 969 create_user_group
970 970 -----------------
971 971
972 972 .. py:function:: create_user_group(apiuser, group_name, description=<Optional:''>, owner=<Optional:<OptionalAttr:apiuser>>, active=<Optional:True>)
973 973
974 974 Creates a new user group.
975 975
976 976 This command can only be run using an |authtoken| with admin rights to
977 977 the specified repository.
978 978
979 979 This command takes the following options:
980 980
981 981 :param apiuser: This is filled automatically from the |authtoken|.
982 982 :type apiuser: AuthUser
983 983 :param group_name: Set the name of the new user group.
984 984 :type group_name: str
985 985 :param description: Give a description of the new user group.
986 986 :type description: str
987 987 :param owner: Set the owner of the new user group.
988 988 If not set, the owner is the |authtoken| user.
989 989 :type owner: Optional(str or int)
990 990 :param active: Set this group as active.
991 991 :type active: Optional(``True`` | ``False``)
992 992
993 993 Example output:
994 994
995 995 .. code-block:: bash
996 996
997 997 id : <id_given_in_input>
998 998 result: {
999 999 "msg": "created new user group `<groupname>`",
1000 1000 "user_group": <user_group_object>
1001 1001 }
1002 1002 error: null
1003 1003
1004 1004 Example error output:
1005 1005
1006 1006 .. code-block:: bash
1007 1007
1008 1008 id : <id_given_in_input>
1009 1009 result : null
1010 1010 error : {
1011 1011 "user group `<group name>` already exist"
1012 1012 or
1013 1013 "failed to create group `<group name>`"
1014 1014 }
1015 1015
1016 1016
1017 1017 update_user_group
1018 1018 -----------------
1019 1019
1020 1020 .. py:function:: update_user_group(apiuser, usergroupid, group_name=<Optional:''>, description=<Optional:''>, owner=<Optional:None>, active=<Optional:True>)
1021 1021
1022 1022 Updates the specified `user group` with the details provided.
1023 1023
1024 1024 This command can only be run using an |authtoken| with admin rights to
1025 1025 the specified repository.
1026 1026
1027 1027 :param apiuser: This is filled automatically from the |authtoken|.
1028 1028 :type apiuser: AuthUser
1029 1029 :param usergroupid: Set the id of the `user group` to update.
1030 1030 :type usergroupid: str or int
1031 1031 :param group_name: Set the new name the `user group`
1032 1032 :type group_name: str
1033 1033 :param description: Give a description for the `user group`
1034 1034 :type description: str
1035 1035 :param owner: Set the owner of the `user group`.
1036 1036 :type owner: Optional(str or int)
1037 1037 :param active: Set the group as active.
1038 1038 :type active: Optional(``True`` | ``False``)
1039 1039
1040 1040 Example output:
1041 1041
1042 1042 .. code-block:: bash
1043 1043
1044 1044 id : <id_given_in_input>
1045 1045 result : {
1046 1046 "msg": 'updated user group ID:<user group id> <user group name>',
1047 1047 "user_group": <user_group_object>
1048 1048 }
1049 1049 error : null
1050 1050
1051 1051 Example error output:
1052 1052
1053 1053 .. code-block:: bash
1054 1054
1055 1055 id : <id_given_in_input>
1056 1056 result : null
1057 1057 error : {
1058 1058 "failed to update user group `<user group name>`"
1059 1059 }
1060 1060
1061 1061
1062 1062 delete_user_group
1063 1063 -----------------
1064 1064
1065 1065 .. py:function:: delete_user_group(apiuser, usergroupid)
1066 1066
1067 1067 Deletes the specified `user group`.
1068 1068
1069 1069 This command can only be run using an |authtoken| with admin rights to
1070 1070 the specified repository.
1071 1071
1072 1072 This command takes the following options:
1073 1073
1074 1074 :param apiuser: filled automatically from apikey
1075 1075 :type apiuser: AuthUser
1076 1076 :param usergroupid:
1077 1077 :type usergroupid: int
1078 1078
1079 1079 Example output:
1080 1080
1081 1081 .. code-block:: bash
1082 1082
1083 1083 id : <id_given_in_input>
1084 1084 result : {
1085 1085 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
1086 1086 }
1087 1087 error : null
1088 1088
1089 1089 Example error output:
1090 1090
1091 1091 .. code-block:: bash
1092 1092
1093 1093 id : <id_given_in_input>
1094 1094 result : null
1095 1095 error : {
1096 1096 "failed to delete user group ID:<user_group_id> <user_group_name>"
1097 1097 or
1098 1098 "RepoGroup assigned to <repo_groups_list>"
1099 1099 }
1100 1100
1101 1101
1102 1102 add_user_to_user_group
1103 1103 ----------------------
1104 1104
1105 1105 .. py:function:: add_user_to_user_group(apiuser, usergroupid, userid)
1106 1106
1107 1107 Adds a user to a `user group`. If the user already exists in the group
1108 1108 this command will return false.
1109 1109
1110 1110 This command can only be run using an |authtoken| with admin rights to
1111 1111 the specified user group.
1112 1112
1113 1113 This command takes the following options:
1114 1114
1115 1115 :param apiuser: This is filled automatically from the |authtoken|.
1116 1116 :type apiuser: AuthUser
1117 1117 :param usergroupid: Set the name of the `user group` to which a
1118 1118 user will be added.
1119 1119 :type usergroupid: int
1120 1120 :param userid: Set the `user_id` of the user to add to the group.
1121 1121 :type userid: int
1122 1122
1123 1123 Example output:
1124 1124
1125 1125 .. code-block:: bash
1126 1126
1127 1127 id : <id_given_in_input>
1128 1128 result : {
1129 1129 "success": True|False # depends on if member is in group
1130 1130 "msg": "added member `<username>` to user group `<groupname>` |
1131 1131 User is already in that group"
1132 1132
1133 1133 }
1134 1134 error : null
1135 1135
1136 1136 Example error output:
1137 1137
1138 1138 .. code-block:: bash
1139 1139
1140 1140 id : <id_given_in_input>
1141 1141 result : null
1142 1142 error : {
1143 1143 "failed to add member to user group `<user_group_name>`"
1144 1144 }
1145 1145
1146 1146
1147 1147 remove_user_from_user_group
1148 1148 ---------------------------
1149 1149
1150 1150 .. py:function:: remove_user_from_user_group(apiuser, usergroupid, userid)
1151 1151
1152 1152 Removes a user from a user group.
1153 1153
1154 1154 * If the specified user is not in the group, this command will return
1155 1155 `false`.
1156 1156
1157 1157 This command can only be run using an |authtoken| with admin rights to
1158 1158 the specified user group.
1159 1159
1160 1160 :param apiuser: This is filled automatically from the |authtoken|.
1161 1161 :type apiuser: AuthUser
1162 1162 :param usergroupid: Sets the user group name.
1163 1163 :type usergroupid: str or int
1164 1164 :param userid: The user you wish to remove from |RCE|.
1165 1165 :type userid: str or int
1166 1166
1167 1167 Example output:
1168 1168
1169 1169 .. code-block:: bash
1170 1170
1171 1171 id : <id_given_in_input>
1172 1172 result: {
1173 1173 "success": True|False, # depends on if member is in group
1174 1174 "msg": "removed member <username> from user group <groupname> |
1175 1175 User wasn't in group"
1176 1176 }
1177 1177 error: null
1178 1178
1179 1179
1180 1180 grant_user_permission_to_user_group
1181 1181 -----------------------------------
1182 1182
1183 1183 .. py:function:: grant_user_permission_to_user_group(apiuser, usergroupid, userid, perm)
1184 1184
1185 1185 Set permissions for a user in a user group.
1186 1186
1187 1187 :param apiuser: This is filled automatically from the |authtoken|.
1188 1188 :type apiuser: AuthUser
1189 1189 :param usergroupid: Set the user group to edit permissions on.
1190 1190 :type usergroupid: str or int
1191 1191 :param userid: Set the user from whom you wish to set permissions.
1192 1192 :type userid: str
1193 1193 :param perm: (usergroup.(none|read|write|admin))
1194 1194 :type perm: str
1195 1195
1196 1196 Example output:
1197 1197
1198 1198 .. code-block:: bash
1199 1199
1200 1200 id : <id_given_in_input>
1201 1201 result : {
1202 1202 "msg": "Granted perm: `<perm_name>` for user: `<username>` in user group: `<user_group_name>`",
1203 1203 "success": true
1204 1204 }
1205 1205 error : null
1206 1206
1207 1207
1208 1208 revoke_user_permission_from_user_group
1209 1209 --------------------------------------
1210 1210
1211 1211 .. py:function:: revoke_user_permission_from_user_group(apiuser, usergroupid, userid)
1212 1212
1213 1213 Revoke a users permissions in a user group.
1214 1214
1215 1215 :param apiuser: This is filled automatically from the |authtoken|.
1216 1216 :type apiuser: AuthUser
1217 1217 :param usergroupid: Set the user group from which to revoke the user
1218 1218 permissions.
1219 1219 :type: usergroupid: str or int
1220 1220 :param userid: Set the userid of the user whose permissions will be
1221 1221 revoked.
1222 1222 :type userid: str
1223 1223
1224 1224 Example output:
1225 1225
1226 1226 .. code-block:: bash
1227 1227
1228 1228 id : <id_given_in_input>
1229 1229 result : {
1230 1230 "msg": "Revoked perm for user: `<username>` in user group: `<user_group_name>`",
1231 1231 "success": true
1232 1232 }
1233 1233 error : null
1234 1234
1235 1235
1236 1236 grant_user_group_permission_to_user_group
1237 1237 -----------------------------------------
1238 1238
1239 1239 .. py:function:: grant_user_group_permission_to_user_group(apiuser, usergroupid, sourceusergroupid, perm)
1240 1240
1241 1241 Give one user group permissions to another user group.
1242 1242
1243 1243 :param apiuser: This is filled automatically from the |authtoken|.
1244 1244 :type apiuser: AuthUser
1245 1245 :param usergroupid: Set the user group on which to edit permissions.
1246 1246 :type usergroupid: str or int
1247 1247 :param sourceusergroupid: Set the source user group to which
1248 1248 access/permissions will be granted.
1249 1249 :type sourceusergroupid: str or int
1250 1250 :param perm: (usergroup.(none|read|write|admin))
1251 1251 :type perm: str
1252 1252
1253 1253 Example output:
1254 1254
1255 1255 .. code-block:: bash
1256 1256
1257 1257 id : <id_given_in_input>
1258 1258 result : {
1259 1259 "msg": "Granted perm: `<perm_name>` for user group: `<source_user_group_name>` in user group: `<user_group_name>`",
1260 1260 "success": true
1261 1261 }
1262 1262 error : null
1263 1263
1264 1264
1265 1265 revoke_user_group_permission_from_user_group
1266 1266 --------------------------------------------
1267 1267
1268 1268 .. py:function:: revoke_user_group_permission_from_user_group(apiuser, usergroupid, sourceusergroupid)
1269 1269
1270 1270 Revoke the permissions that one user group has to another.
1271 1271
1272 1272 :param apiuser: This is filled automatically from the |authtoken|.
1273 1273 :type apiuser: AuthUser
1274 1274 :param usergroupid: Set the user group on which to edit permissions.
1275 1275 :type usergroupid: str or int
1276 1276 :param sourceusergroupid: Set the user group from which permissions
1277 1277 are revoked.
1278 1278 :type sourceusergroupid: str or int
1279 1279
1280 1280 Example output:
1281 1281
1282 1282 .. code-block:: bash
1283 1283
1284 1284 id : <id_given_in_input>
1285 1285 result : {
1286 1286 "msg": "Revoked perm for user group: `<user_group_name>` in user group: `<target_user_group_name>`",
1287 1287 "success": true
1288 1288 }
1289 1289 error : null
1290 1290
1291 1291
1292 1292 get_pull_request
1293 1293 ----------------
1294 1294
1295 1295 .. py:function:: get_pull_request(apiuser, repoid, pullrequestid)
1296 1296
1297 1297 Get a pull request based on the given ID.
1298 1298
1299 1299 :param apiuser: This is filled automatically from the |authtoken|.
1300 1300 :type apiuser: AuthUser
1301 1301 :param repoid: Repository name or repository ID from where the pull
1302 1302 request was opened.
1303 1303 :type repoid: str or int
1304 1304 :param pullrequestid: ID of the requested pull request.
1305 1305 :type pullrequestid: int
1306 1306
1307 1307 Example output:
1308 1308
1309 1309 .. code-block:: bash
1310 1310
1311 1311 "id": <id_given_in_input>,
1312 1312 "result":
1313 1313 {
1314 1314 "pull_request_id": "<pull_request_id>",
1315 1315 "url": "<url>",
1316 1316 "title": "<title>",
1317 1317 "description": "<description>",
1318 1318 "status" : "<status>",
1319 1319 "created_on": "<date_time_created>",
1320 1320 "updated_on": "<date_time_updated>",
1321 1321 "commit_ids": [
1322 1322 ...
1323 1323 "<commit_id>",
1324 1324 "<commit_id>",
1325 1325 ...
1326 1326 ],
1327 1327 "review_status": "<review_status>",
1328 1328 "mergeable": {
1329 1329 "status": "<bool>",
1330 1330 "message": "<message>",
1331 1331 },
1332 1332 "source": {
1333 1333 "clone_url": "<clone_url>",
1334 1334 "repository": "<repository_name>",
1335 1335 "reference":
1336 1336 {
1337 1337 "name": "<name>",
1338 1338 "type": "<type>",
1339 1339 "commit_id": "<commit_id>",
1340 1340 }
1341 1341 },
1342 1342 "target": {
1343 1343 "clone_url": "<clone_url>",
1344 1344 "repository": "<repository_name>",
1345 1345 "reference":
1346 1346 {
1347 1347 "name": "<name>",
1348 1348 "type": "<type>",
1349 1349 "commit_id": "<commit_id>",
1350 1350 }
1351 1351 },
1352 1352 "author": <user_obj>,
1353 1353 "reviewers": [
1354 1354 ...
1355 1355 {
1356 1356 "user": "<user_obj>",
1357 1357 "review_status": "<review_status>",
1358 1358 }
1359 1359 ...
1360 1360 ]
1361 1361 },
1362 1362 "error": null
1363 1363
1364 1364
1365 1365 get_pull_requests
1366 1366 -----------------
1367 1367
1368 1368 .. py:function:: get_pull_requests(apiuser, repoid, status=<Optional:'new'>)
1369 1369
1370 1370 Get all pull requests from the repository specified in `repoid`.
1371 1371
1372 1372 :param apiuser: This is filled automatically from the |authtoken|.
1373 1373 :type apiuser: AuthUser
1374 1374 :param repoid: Repository name or repository ID.
1375 1375 :type repoid: str or int
1376 1376 :param status: Only return pull requests with the specified status.
1377 1377 Valid options are.
1378 1378 * ``new`` (default)
1379 1379 * ``open``
1380 1380 * ``closed``
1381 1381 :type status: str
1382 1382
1383 1383 Example output:
1384 1384
1385 1385 .. code-block:: bash
1386 1386
1387 1387 "id": <id_given_in_input>,
1388 1388 "result":
1389 1389 [
1390 1390 ...
1391 1391 {
1392 1392 "pull_request_id": "<pull_request_id>",
1393 1393 "url": "<url>",
1394 1394 "title" : "<title>",
1395 1395 "description": "<description>",
1396 1396 "status": "<status>",
1397 1397 "created_on": "<date_time_created>",
1398 1398 "updated_on": "<date_time_updated>",
1399 1399 "commit_ids": [
1400 1400 ...
1401 1401 "<commit_id>",
1402 1402 "<commit_id>",
1403 1403 ...
1404 1404 ],
1405 1405 "review_status": "<review_status>",
1406 1406 "mergeable": {
1407 1407 "status": "<bool>",
1408 1408 "message: "<message>",
1409 1409 },
1410 1410 "source": {
1411 1411 "clone_url": "<clone_url>",
1412 1412 "reference":
1413 1413 {
1414 1414 "name": "<name>",
1415 1415 "type": "<type>",
1416 1416 "commit_id": "<commit_id>",
1417 1417 }
1418 1418 },
1419 1419 "target": {
1420 1420 "clone_url": "<clone_url>",
1421 1421 "reference":
1422 1422 {
1423 1423 "name": "<name>",
1424 1424 "type": "<type>",
1425 1425 "commit_id": "<commit_id>",
1426 1426 }
1427 1427 },
1428 1428 "author": <user_obj>,
1429 1429 "reviewers": [
1430 1430 ...
1431 1431 {
1432 1432 "user": "<user_obj>",
1433 1433 "review_status": "<review_status>",
1434 1434 }
1435 1435 ...
1436 1436 ]
1437 1437 }
1438 1438 ...
1439 1439 ],
1440 1440 "error": null
1441 1441
1442 1442
1443 1443 merge_pull_request
1444 1444 ------------------
1445 1445
1446 1446 .. py:function:: merge_pull_request(apiuser, repoid, pullrequestid, userid=<Optional:<OptionalAttr:apiuser>>)
1447 1447
1448 1448 Merge the pull request specified by `pullrequestid` into its target
1449 1449 repository.
1450 1450
1451 1451 :param apiuser: This is filled automatically from the |authtoken|.
1452 1452 :type apiuser: AuthUser
1453 1453 :param repoid: The Repository name or repository ID of the
1454 1454 target repository to which the |pr| is to be merged.
1455 1455 :type repoid: str or int
1456 1456 :param pullrequestid: ID of the pull request which shall be merged.
1457 1457 :type pullrequestid: int
1458 1458 :param userid: Merge the pull request as this user.
1459 1459 :type userid: Optional(str or int)
1460 1460
1461 1461 Example output:
1462 1462
1463 1463 .. code-block:: bash
1464 1464
1465 1465 "id": <id_given_in_input>,
1466 1466 "result":
1467 1467 {
1468 1468 "executed": "<bool>",
1469 1469 "failure_reason": "<int>",
1470 1470 "merge_commit_id": "<merge_commit_id>",
1471 1471 "possible": "<bool>"
1472 1472 },
1473 1473 "error": null
1474 1474
1475 1475
1476 1476 close_pull_request
1477 1477 ------------------
1478 1478
1479 1479 .. py:function:: close_pull_request(apiuser, repoid, pullrequestid, userid=<Optional:<OptionalAttr:apiuser>>)
1480 1480
1481 1481 Close the pull request specified by `pullrequestid`.
1482 1482
1483 1483 :param apiuser: This is filled automatically from the |authtoken|.
1484 1484 :type apiuser: AuthUser
1485 1485 :param repoid: Repository name or repository ID to which the pull
1486 1486 request belongs.
1487 1487 :type repoid: str or int
1488 1488 :param pullrequestid: ID of the pull request to be closed.
1489 1489 :type pullrequestid: int
1490 1490 :param userid: Close the pull request as this user.
1491 1491 :type userid: Optional(str or int)
1492 1492
1493 1493 Example output:
1494 1494
1495 1495 .. code-block:: bash
1496 1496
1497 1497 "id": <id_given_in_input>,
1498 1498 "result":
1499 1499 {
1500 1500 "pull_request_id": "<int>",
1501 1501 "closed": "<bool>"
1502 1502 },
1503 1503 "error": null
1504 1504
1505 1505
1506 1506 comment_pull_request
1507 1507 --------------------
1508 1508
1509 1509 .. py:function:: comment_pull_request(apiuser, repoid, pullrequestid, message=<Optional:None>, status=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
1510 1510
1511 1511 Comment on the pull request specified with the `pullrequestid`,
1512 1512 in the |repo| specified by the `repoid`, and optionally change the
1513 1513 review status.
1514 1514
1515 1515 :param apiuser: This is filled automatically from the |authtoken|.
1516 1516 :type apiuser: AuthUser
1517 1517 :param repoid: The repository name or repository ID.
1518 1518 :type repoid: str or int
1519 1519 :param pullrequestid: The pull request ID.
1520 1520 :type pullrequestid: int
1521 1521 :param message: The text content of the comment.
1522 1522 :type message: str
1523 1523 :param status: (**Optional**) Set the approval status of the pull
1524 1524 request. Valid options are:
1525 1525 * not_reviewed
1526 1526 * approved
1527 1527 * rejected
1528 1528 * under_review
1529 1529 :type status: str
1530 1530 :param userid: Comment on the pull request as this user
1531 1531 :type userid: Optional(str or int)
1532 1532
1533 1533 Example output:
1534 1534
1535 1535 .. code-block:: bash
1536 1536
1537 1537 id : <id_given_in_input>
1538 1538 result :
1539 1539 {
1540 1540 "pull_request_id": "<Integer>",
1541 1541 "comment_id": "<Integer>"
1542 1542 }
1543 1543 error : null
1544 1544
1545 1545
1546 1546 create_pull_request
1547 1547 -------------------
1548 1548
1549 1549 .. py:function:: create_pull_request(apiuser, source_repo, target_repo, source_ref, target_ref, title, description=<Optional:''>, reviewers=<Optional:None>)
1550 1550
1551 1551 Creates a new pull request.
1552 1552
1553 1553 Accepts refs in the following formats:
1554 1554
1555 1555 * branch:<branch_name>:<sha>
1556 1556 * branch:<branch_name>
1557 1557 * bookmark:<bookmark_name>:<sha> (Mercurial only)
1558 1558 * bookmark:<bookmark_name> (Mercurial only)
1559 1559
1560 1560 :param apiuser: This is filled automatically from the |authtoken|.
1561 1561 :type apiuser: AuthUser
1562 1562 :param source_repo: Set the source repository name.
1563 1563 :type source_repo: str
1564 1564 :param target_repo: Set the target repository name.
1565 1565 :type target_repo: str
1566 1566 :param source_ref: Set the source ref name.
1567 1567 :type source_ref: str
1568 1568 :param target_ref: Set the target ref name.
1569 1569 :type target_ref: str
1570 1570 :param title: Set the pull request title.
1571 1571 :type title: str
1572 1572 :param description: Set the pull request description.
1573 1573 :type description: Optional(str)
1574 1574 :param reviewers: Set the new pull request reviewers list.
1575 1575 :type reviewers: Optional(list)
1576 1576
1577 1577
1578 1578 update_pull_request
1579 1579 -------------------
1580 1580
1581 1581 .. py:function:: update_pull_request(apiuser, repoid, pullrequestid, title=<Optional:''>, description=<Optional:''>, reviewers=<Optional:None>, update_commits=<Optional:None>, close_pull_request=<Optional:None>)
1582 1582
1583 1583 Updates a pull request.
1584 1584
1585 1585 :param apiuser: This is filled automatically from the |authtoken|.
1586 1586 :type apiuser: AuthUser
1587 1587 :param repoid: The repository name or repository ID.
1588 1588 :type repoid: str or int
1589 1589 :param pullrequestid: The pull request ID.
1590 1590 :type pullrequestid: int
1591 1591 :param title: Set the pull request title.
1592 1592 :type title: str
1593 1593 :param description: Update pull request description.
1594 1594 :type description: Optional(str)
1595 1595 :param reviewers: Update pull request reviewers list with new value.
1596 1596 :type reviewers: Optional(list)
1597 1597 :param update_commits: Trigger update of commits for this pull request
1598 1598 :type: update_commits: Optional(bool)
1599 1599 :param close_pull_request: Close this pull request with rejected state
1600 1600 :type: close_pull_request: Optional(bool)
1601 1601
1602 1602 Example output:
1603 1603
1604 1604 .. code-block:: bash
1605 1605
1606 1606 id : <id_given_in_input>
1607 1607 result :
1608 1608 {
1609 1609 "msg": "Updated pull request `63`",
1610 1610 "pull_request": <pull_request_object>,
1611 1611 "updated_reviewers": {
1612 1612 "added": [
1613 1613 "username"
1614 1614 ],
1615 1615 "removed": []
1616 1616 },
1617 1617 "updated_commits": {
1618 1618 "added": [
1619 1619 "<sha1_hash>"
1620 1620 ],
1621 1621 "common": [
1622 1622 "<sha1_hash>",
1623 1623 "<sha1_hash>",
1624 1624 ],
1625 1625 "removed": []
1626 1626 }
1627 1627 }
1628 1628 error : null
1629 1629
1630 1630
1631 1631 get_repo
1632 1632 --------
1633 1633
1634 1634 .. py:function:: get_repo(apiuser, repoid, cache=<Optional:True>)
1635 1635
1636 1636 Gets an existing repository by its name or repository_id.
1637 1637
1638 1638 The members section so the output returns users groups or users
1639 1639 associated with that repository.
1640 1640
1641 1641 This command can only be run using an |authtoken| with admin rights,
1642 1642 or users with at least read rights to the |repo|.
1643 1643
1644 1644 :param apiuser: This is filled automatically from the |authtoken|.
1645 1645 :type apiuser: AuthUser
1646 1646 :param repoid: The repository name or repository id.
1647 1647 :type repoid: str or int
1648 1648 :param cache: use the cached value for last changeset
1649 1649 :type: cache: Optional(bool)
1650 1650
1651 1651 Example output:
1652 1652
1653 1653 .. code-block:: bash
1654 1654
1655 1655 {
1656 1656 "error": null,
1657 1657 "id": <repo_id>,
1658 1658 "result": {
1659 1659 "clone_uri": null,
1660 1660 "created_on": "timestamp",
1661 1661 "description": "repo description",
1662 1662 "enable_downloads": false,
1663 1663 "enable_locking": false,
1664 1664 "enable_statistics": false,
1665 1665 "followers": [
1666 1666 {
1667 1667 "active": true,
1668 1668 "admin": false,
1669 1669 "api_key": "****************************************",
1670 1670 "api_keys": [
1671 1671 "****************************************"
1672 1672 ],
1673 1673 "email": "user@example.com",
1674 1674 "emails": [
1675 1675 "user@example.com"
1676 1676 ],
1677 1677 "extern_name": "rhodecode",
1678 1678 "extern_type": "rhodecode",
1679 1679 "firstname": "username",
1680 1680 "ip_addresses": [],
1681 1681 "language": null,
1682 1682 "last_login": "2015-09-16T17:16:35.854",
1683 1683 "lastname": "surname",
1684 1684 "user_id": <user_id>,
1685 1685 "username": "name"
1686 1686 }
1687 1687 ],
1688 1688 "fork_of": "parent-repo",
1689 1689 "landing_rev": [
1690 1690 "rev",
1691 1691 "tip"
1692 1692 ],
1693 1693 "last_changeset": {
1694 1694 "author": "User <user@example.com>",
1695 1695 "branch": "default",
1696 1696 "date": "timestamp",
1697 1697 "message": "last commit message",
1698 1698 "parents": [
1699 1699 {
1700 1700 "raw_id": "commit-id"
1701 1701 }
1702 1702 ],
1703 1703 "raw_id": "commit-id",
1704 1704 "revision": <revision number>,
1705 1705 "short_id": "short id"
1706 1706 },
1707 1707 "lock_reason": null,
1708 1708 "locked_by": null,
1709 1709 "locked_date": null,
1710 1710 "members": [
1711 1711 {
1712 1712 "name": "super-admin-name",
1713 1713 "origin": "super-admin",
1714 1714 "permission": "repository.admin",
1715 1715 "type": "user"
1716 1716 },
1717 1717 {
1718 1718 "name": "owner-name",
1719 1719 "origin": "owner",
1720 1720 "permission": "repository.admin",
1721 1721 "type": "user"
1722 1722 },
1723 1723 {
1724 1724 "name": "user-group-name",
1725 1725 "origin": "permission",
1726 1726 "permission": "repository.write",
1727 1727 "type": "user_group"
1728 1728 }
1729 1729 ],
1730 1730 "owner": "owner-name",
1731 1731 "permissions": [
1732 1732 {
1733 1733 "name": "super-admin-name",
1734 1734 "origin": "super-admin",
1735 1735 "permission": "repository.admin",
1736 1736 "type": "user"
1737 1737 },
1738 1738 {
1739 1739 "name": "owner-name",
1740 1740 "origin": "owner",
1741 1741 "permission": "repository.admin",
1742 1742 "type": "user"
1743 1743 },
1744 1744 {
1745 1745 "name": "user-group-name",
1746 1746 "origin": "permission",
1747 1747 "permission": "repository.write",
1748 1748 "type": "user_group"
1749 1749 }
1750 1750 ],
1751 1751 "private": true,
1752 1752 "repo_id": 676,
1753 1753 "repo_name": "user-group/repo-name",
1754 1754 "repo_type": "hg"
1755 1755 }
1756 1756 }
1757 1757
1758 1758
1759 1759 get_repos
1760 1760 ---------
1761 1761
1762 1762 .. py:function:: get_repos(apiuser)
1763 1763
1764 1764 Lists all existing repositories.
1765 1765
1766 1766 This command can only be run using an |authtoken| with admin rights,
1767 1767 or users with at least read rights to |repos|.
1768 1768
1769 1769 :param apiuser: This is filled automatically from the |authtoken|.
1770 1770 :type apiuser: AuthUser
1771 1771
1772 1772 Example output:
1773 1773
1774 1774 .. code-block:: bash
1775 1775
1776 1776 id : <id_given_in_input>
1777 1777 result: [
1778 1778 {
1779 1779 "repo_id" : "<repo_id>",
1780 1780 "repo_name" : "<reponame>"
1781 1781 "repo_type" : "<repo_type>",
1782 1782 "clone_uri" : "<clone_uri>",
1783 1783 "private": : "<bool>",
1784 1784 "created_on" : "<datetimecreated>",
1785 1785 "description" : "<description>",
1786 1786 "landing_rev": "<landing_rev>",
1787 1787 "owner": "<repo_owner>",
1788 1788 "fork_of": "<name_of_fork_parent>",
1789 1789 "enable_downloads": "<bool>",
1790 1790 "enable_locking": "<bool>",
1791 1791 "enable_statistics": "<bool>",
1792 1792 },
1793 1793 ...
1794 1794 ]
1795 1795 error: null
1796 1796
1797 1797
1798 1798 get_repo_changeset
1799 1799 ------------------
1800 1800
1801 1801 .. py:function:: get_repo_changeset(apiuser, repoid, revision, details=<Optional:'basic'>)
1802 1802
1803 1803 Returns information about a changeset.
1804 1804
1805 1805 Additionally parameters define the amount of details returned by
1806 1806 this function.
1807 1807
1808 1808 This command can only be run using an |authtoken| with admin rights,
1809 1809 or users with at least read rights to the |repo|.
1810 1810
1811 1811 :param apiuser: This is filled automatically from the |authtoken|.
1812 1812 :type apiuser: AuthUser
1813 1813 :param repoid: The repository name or repository id
1814 1814 :type repoid: str or int
1815 1815 :param revision: revision for which listing should be done
1816 1816 :type revision: str
1817 1817 :param details: details can be 'basic|extended|full' full gives diff
1818 1818 info details like the diff itself, and number of changed files etc.
1819 1819 :type details: Optional(str)
1820 1820
1821 1821
1822 1822 get_repo_changesets
1823 1823 -------------------
1824 1824
1825 1825 .. py:function:: get_repo_changesets(apiuser, repoid, start_rev, limit, details=<Optional:'basic'>)
1826 1826
1827 1827 Returns a set of changesets limited by the number of commits starting
1828 1828 from the `start_rev` option.
1829 1829
1830 1830 Additional parameters define the amount of details returned by this
1831 1831 function.
1832 1832
1833 1833 This command can only be run using an |authtoken| with admin rights,
1834 1834 or users with at least read rights to |repos|.
1835 1835
1836 1836 :param apiuser: This is filled automatically from the |authtoken|.
1837 1837 :type apiuser: AuthUser
1838 1838 :param repoid: The repository name or repository ID.
1839 1839 :type repoid: str or int
1840 1840 :param start_rev: The starting revision from where to get changesets.
1841 1841 :type start_rev: str
1842 1842 :param limit: Limit the number of changesets to this amount
1843 1843 :type limit: str or int
1844 1844 :param details: Set the level of detail returned. Valid option are:
1845 1845 ``basic``, ``extended`` and ``full``.
1846 1846 :type details: Optional(str)
1847 1847
1848 1848 .. note::
1849 1849
1850 1850 Setting the parameter `details` to the value ``full`` is extensive
1851 1851 and returns details like the diff itself, and the number
1852 1852 of changed files.
1853 1853
1854 1854
1855 1855 get_repo_nodes
1856 1856 --------------
1857 1857
1858 1858 .. py:function:: get_repo_nodes(apiuser, repoid, revision, root_path, ret_type=<Optional:'all'>, details=<Optional:'basic'>)
1859 1859
1860 1860 Returns a list of nodes and children in a flat list for a given
1861 1861 path at given revision.
1862 1862
1863 1863 It's possible to specify ret_type to show only `files` or `dirs`.
1864 1864
1865 1865 This command can only be run using an |authtoken| with admin rights,
1866 1866 or users with at least read rights to |repos|.
1867 1867
1868 1868 :param apiuser: This is filled automatically from the |authtoken|.
1869 1869 :type apiuser: AuthUser
1870 1870 :param repoid: The repository name or repository ID.
1871 1871 :type repoid: str or int
1872 1872 :param revision: The revision for which listing should be done.
1873 1873 :type revision: str
1874 1874 :param root_path: The path from which to start displaying.
1875 1875 :type root_path: str
1876 1876 :param ret_type: Set the return type. Valid options are
1877 1877 ``all`` (default), ``files`` and ``dirs``.
1878 1878 :type ret_type: Optional(str)
1879 1879 :param details: Returns extended information about nodes, such as
1880 1880 md5, binary, and or content. The valid options are ``basic`` and
1881 1881 ``full``.
1882 1882 :type details: Optional(str)
1883 :param max_file_bytes: Only return file content under this file size bytes
1884 :type details: Optional(int)
1883 1885
1884 1886 Example output:
1885 1887
1886 1888 .. code-block:: bash
1887 1889
1888 1890 id : <id_given_in_input>
1889 1891 result: [
1890 1892 {
1891 1893 "name" : "<name>"
1892 1894 "type" : "<type>",
1893 1895 "binary": "<true|false>" (only in extended mode)
1894 1896 "md5" : "<md5 of file content>" (only in extended mode)
1895 1897 },
1896 1898 ...
1897 1899 ]
1898 1900 error: null
1899 1901
1900 1902
1901 1903 create_repo
1902 1904 -----------
1903 1905
1904 1906 .. py:function:: create_repo(apiuser, repo_name, repo_type, owner=<Optional:<OptionalAttr:apiuser>>, description=<Optional:''>, private=<Optional:False>, clone_uri=<Optional:None>, landing_rev=<Optional:'rev:tip'>, enable_statistics=<Optional:False>, enable_locking=<Optional:False>, enable_downloads=<Optional:False>, copy_permissions=<Optional:False>)
1905 1907
1906 1908 Creates a repository.
1907 1909
1908 1910 * If the repository name contains "/", all the required repository
1909 1911 groups will be created.
1910 1912
1911 1913 For example "foo/bar/baz" will create |repo| groups "foo" and "bar"
1912 1914 (with "foo" as parent). It will also create the "baz" repository
1913 1915 with "bar" as |repo| group.
1914 1916
1915 1917 This command can only be run using an |authtoken| with at least
1916 1918 write permissions to the |repo|.
1917 1919
1918 1920 :param apiuser: This is filled automatically from the |authtoken|.
1919 1921 :type apiuser: AuthUser
1920 1922 :param repo_name: Set the repository name.
1921 1923 :type repo_name: str
1922 1924 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
1923 1925 :type repo_type: str
1924 1926 :param owner: user_id or username
1925 1927 :type owner: Optional(str)
1926 1928 :param description: Set the repository description.
1927 1929 :type description: Optional(str)
1928 1930 :param private:
1929 1931 :type private: bool
1930 1932 :param clone_uri:
1931 1933 :type clone_uri: str
1932 1934 :param landing_rev: <rev_type>:<rev>
1933 1935 :type landing_rev: str
1934 1936 :param enable_locking:
1935 1937 :type enable_locking: bool
1936 1938 :param enable_downloads:
1937 1939 :type enable_downloads: bool
1938 1940 :param enable_statistics:
1939 1941 :type enable_statistics: bool
1940 1942 :param copy_permissions: Copy permission from group in which the
1941 1943 repository is being created.
1942 1944 :type copy_permissions: bool
1943 1945
1944 1946
1945 1947 Example output:
1946 1948
1947 1949 .. code-block:: bash
1948 1950
1949 1951 id : <id_given_in_input>
1950 1952 result: {
1951 1953 "msg": "Created new repository `<reponame>`",
1952 1954 "success": true,
1953 1955 "task": "<celery task id or None if done sync>"
1954 1956 }
1955 1957 error: null
1956 1958
1957 1959
1958 1960 Example error output:
1959 1961
1960 1962 .. code-block:: bash
1961 1963
1962 1964 id : <id_given_in_input>
1963 1965 result : null
1964 1966 error : {
1965 1967 'failed to create repository `<repo_name>`
1966 1968 }
1967 1969
1968 1970
1969 1971 add_field_to_repo
1970 1972 -----------------
1971 1973
1972 1974 .. py:function:: add_field_to_repo(apiuser, repoid, key, label=<Optional:''>, description=<Optional:''>)
1973 1975
1974 1976 Adds an extra field to a repository.
1975 1977
1976 1978 This command can only be run using an |authtoken| with at least
1977 1979 write permissions to the |repo|.
1978 1980
1979 1981 :param apiuser: This is filled automatically from the |authtoken|.
1980 1982 :type apiuser: AuthUser
1981 1983 :param repoid: Set the repository name or repository id.
1982 1984 :type repoid: str or int
1983 1985 :param key: Create a unique field key for this repository.
1984 1986 :type key: str
1985 1987 :param label:
1986 1988 :type label: Optional(str)
1987 1989 :param description:
1988 1990 :type description: Optional(str)
1989 1991
1990 1992
1991 1993 remove_field_from_repo
1992 1994 ----------------------
1993 1995
1994 1996 .. py:function:: remove_field_from_repo(apiuser, repoid, key)
1995 1997
1996 1998 Removes an extra field from a repository.
1997 1999
1998 2000 This command can only be run using an |authtoken| with at least
1999 2001 write permissions to the |repo|.
2000 2002
2001 2003 :param apiuser: This is filled automatically from the |authtoken|.
2002 2004 :type apiuser: AuthUser
2003 2005 :param repoid: Set the repository name or repository ID.
2004 2006 :type repoid: str or int
2005 2007 :param key: Set the unique field key for this repository.
2006 2008 :type key: str
2007 2009
2008 2010
2009 2011 update_repo
2010 2012 -----------
2011 2013
2012 2014 .. py:function:: update_repo(apiuser, repoid, name=<Optional:None>, owner=<Optional:<OptionalAttr:apiuser>>, group=<Optional:None>, fork_of=<Optional:None>, description=<Optional:''>, private=<Optional:False>, clone_uri=<Optional:None>, landing_rev=<Optional:'rev:tip'>, enable_statistics=<Optional:False>, enable_locking=<Optional:False>, enable_downloads=<Optional:False>, fields=<Optional:''>)
2013 2015
2014 2016 Updates a repository with the given information.
2015 2017
2016 2018 This command can only be run using an |authtoken| with at least
2017 2019 write permissions to the |repo|.
2018 2020
2019 2021 :param apiuser: This is filled automatically from the |authtoken|.
2020 2022 :type apiuser: AuthUser
2021 2023 :param repoid: repository name or repository ID.
2022 2024 :type repoid: str or int
2023 2025 :param name: Update the |repo| name.
2024 2026 :type name: str
2025 2027 :param owner: Set the |repo| owner.
2026 2028 :type owner: str
2027 2029 :param group: Set the |repo| group the |repo| belongs to.
2028 2030 :type group: str
2029 2031 :param fork_of: Set the master |repo| name.
2030 2032 :type fork_of: str
2031 2033 :param description: Update the |repo| description.
2032 2034 :type description: str
2033 2035 :param private: Set the |repo| as private. (True | False)
2034 2036 :type private: bool
2035 2037 :param clone_uri: Update the |repo| clone URI.
2036 2038 :type clone_uri: str
2037 2039 :param landing_rev: Set the |repo| landing revision. Default is
2038 2040 ``tip``.
2039 2041 :type landing_rev: str
2040 2042 :param enable_statistics: Enable statistics on the |repo|,
2041 2043 (True | False).
2042 2044 :type enable_statistics: bool
2043 2045 :param enable_locking: Enable |repo| locking.
2044 2046 :type enable_locking: bool
2045 2047 :param enable_downloads: Enable downloads from the |repo|,
2046 2048 (True | False).
2047 2049 :type enable_downloads: bool
2048 2050 :param fields: Add extra fields to the |repo|. Use the following
2049 2051 example format: ``field_key=field_val,field_key2=fieldval2``.
2050 2052 Escape ', ' with \,
2051 2053 :type fields: str
2052 2054
2053 2055
2054 2056 fork_repo
2055 2057 ---------
2056 2058
2057 2059 .. py:function:: fork_repo(apiuser, repoid, fork_name, owner=<Optional:<OptionalAttr:apiuser>>, description=<Optional:''>, copy_permissions=<Optional:False>, private=<Optional:False>, landing_rev=<Optional:'rev:tip'>)
2058 2060
2059 2061 Creates a fork of the specified |repo|.
2060 2062
2061 2063 * If using |RCE| with Celery this will immediately return a success
2062 2064 message, even though the fork will be created asynchronously.
2063 2065
2064 2066 This command can only be run using an |authtoken| with fork
2065 2067 permissions on the |repo|.
2066 2068
2067 2069 :param apiuser: This is filled automatically from the |authtoken|.
2068 2070 :type apiuser: AuthUser
2069 2071 :param repoid: Set repository name or repository ID.
2070 2072 :type repoid: str or int
2071 2073 :param fork_name: Set the fork name.
2072 2074 :type fork_name: str
2073 2075 :param owner: Set the fork owner.
2074 2076 :type owner: str
2075 2077 :param description: Set the fork descripton.
2076 2078 :type description: str
2077 2079 :param copy_permissions: Copy permissions from parent |repo|. The
2078 2080 default is False.
2079 2081 :type copy_permissions: bool
2080 2082 :param private: Make the fork private. The default is False.
2081 2083 :type private: bool
2082 2084 :param landing_rev: Set the landing revision. The default is tip.
2083 2085
2084 2086 Example output:
2085 2087
2086 2088 .. code-block:: bash
2087 2089
2088 2090 id : <id_for_response>
2089 2091 api_key : "<api_key>"
2090 2092 args: {
2091 2093 "repoid" : "<reponame or repo_id>",
2092 2094 "fork_name": "<forkname>",
2093 2095 "owner": "<username or user_id = Optional(=apiuser)>",
2094 2096 "description": "<description>",
2095 2097 "copy_permissions": "<bool>",
2096 2098 "private": "<bool>",
2097 2099 "landing_rev": "<landing_rev>"
2098 2100 }
2099 2101
2100 2102 Example error output:
2101 2103
2102 2104 .. code-block:: bash
2103 2105
2104 2106 id : <id_given_in_input>
2105 2107 result: {
2106 2108 "msg": "Created fork of `<reponame>` as `<forkname>`",
2107 2109 "success": true,
2108 2110 "task": "<celery task id or None if done sync>"
2109 2111 }
2110 2112 error: null
2111 2113
2112 2114
2113 2115 delete_repo
2114 2116 -----------
2115 2117
2116 2118 .. py:function:: delete_repo(apiuser, repoid, forks=<Optional:''>)
2117 2119
2118 2120 Deletes a repository.
2119 2121
2120 2122 * When the `forks` parameter is set it's possible to detach or delete
2121 2123 forks of deleted repository.
2122 2124
2123 2125 This command can only be run using an |authtoken| with admin
2124 2126 permissions on the |repo|.
2125 2127
2126 2128 :param apiuser: This is filled automatically from the |authtoken|.
2127 2129 :type apiuser: AuthUser
2128 2130 :param repoid: Set the repository name or repository ID.
2129 2131 :type repoid: str or int
2130 2132 :param forks: Set to `detach` or `delete` forks from the |repo|.
2131 2133 :type forks: Optional(str)
2132 2134
2133 2135 Example error output:
2134 2136
2135 2137 .. code-block:: bash
2136 2138
2137 2139 id : <id_given_in_input>
2138 2140 result: {
2139 2141 "msg": "Deleted repository `<reponame>`",
2140 2142 "success": true
2141 2143 }
2142 2144 error: null
2143 2145
2144 2146
2145 2147 comment_commit
2146 2148 --------------
2147 2149
2148 2150 .. py:function:: comment_commit(apiuser, repoid, commit_id, message, userid=<Optional:<OptionalAttr:apiuser>>, status=<Optional:None>)
2149 2151
2150 2152 Set a commit comment, and optionally change the status of the commit.
2151 2153 This command can be executed only using api_key belonging to user
2152 2154 with admin rights, or repository administrator.
2153 2155
2154 2156 :param apiuser: This is filled automatically from the |authtoken|.
2155 2157 :type apiuser: AuthUser
2156 2158 :param repoid: Set the repository name or repository ID.
2157 2159 :type repoid: str or int
2158 2160 :param commit_id: Specify the commit_id for which to set a comment.
2159 2161 :type commit_id: str
2160 2162 :param message: The comment text.
2161 2163 :type message: str
2162 2164 :param userid: Set the user name of the comment creator.
2163 2165 :type userid: Optional(str or int)
2164 2166 :param status: status, one of 'not_reviewed', 'approved', 'rejected',
2165 2167 'under_review'
2166 2168 :type status: str
2167 2169
2168 2170 Example error output:
2169 2171
2170 2172 .. code-block:: json
2171 2173
2172 2174 {
2173 2175 "id" : <id_given_in_input>,
2174 2176 "result" : {
2175 2177 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
2176 2178 "status_change": null or <status>,
2177 2179 "success": true
2178 2180 },
2179 2181 "error" : null
2180 2182 }
2181 2183
2182 2184
2183 2185 changeset_comment
2184 2186 -----------------
2185 2187
2186 2188 .. py:function:: changeset_comment(apiuser, repoid, revision, message, userid=<Optional:<OptionalAttr:apiuser>>, status=<Optional:None>)
2187 2189
2188 2190 .. deprecated:: 3.4.0
2189 2191
2190 2192 Please use method `comment_commit` instead.
2191 2193
2192 2194
2193 2195 Set a changeset comment, and optionally change the status of the
2194 2196 changeset.
2195 2197
2196 2198 This command can only be run using an |authtoken| with admin
2197 2199 permissions on the |repo|.
2198 2200
2199 2201 :param apiuser: This is filled automatically from the |authtoken|.
2200 2202 :type apiuser: AuthUser
2201 2203 :param repoid: Set the repository name or repository ID.
2202 2204 :type repoid: str or int
2203 2205 :param revision: Specify the revision for which to set a comment.
2204 2206 :type revision: str
2205 2207 :param message: The comment text.
2206 2208 :type message: str
2207 2209 :param userid: Set the user name of the comment creator.
2208 2210 :type userid: Optional(str or int)
2209 2211 :param status: Set the comment status. The following are valid options:
2210 2212 * not_reviewed
2211 2213 * approved
2212 2214 * rejected
2213 2215 * under_review
2214 2216 :type status: str
2215 2217
2216 2218 Example error output:
2217 2219
2218 2220 .. code-block:: json
2219 2221
2220 2222 {
2221 2223 "id" : <id_given_in_input>,
2222 2224 "result" : {
2223 2225 "msg": "Commented on commit `<revision>` for repository `<repoid>`",
2224 2226 "status_change": null or <status>,
2225 2227 "success": true
2226 2228 },
2227 2229 "error" : null
2228 2230 }
2229 2231
2230 2232
2231 2233 grant_user_permission
2232 2234 ---------------------
2233 2235
2234 2236 .. py:function:: grant_user_permission(apiuser, repoid, userid, perm)
2235 2237
2236 2238 Grant permissions for the specified user on the given repository,
2237 2239 or update existing permissions if found.
2238 2240
2239 2241 This command can only be run using an |authtoken| with admin
2240 2242 permissions on the |repo|.
2241 2243
2242 2244 :param apiuser: This is filled automatically from the |authtoken|.
2243 2245 :type apiuser: AuthUser
2244 2246 :param repoid: Set the repository name or repository ID.
2245 2247 :type repoid: str or int
2246 2248 :param userid: Set the user name.
2247 2249 :type userid: str
2248 2250 :param perm: Set the user permissions, using the following format
2249 2251 ``(repository.(none|read|write|admin))``
2250 2252 :type perm: str
2251 2253
2252 2254 Example output:
2253 2255
2254 2256 .. code-block:: bash
2255 2257
2256 2258 id : <id_given_in_input>
2257 2259 result: {
2258 2260 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
2259 2261 "success": true
2260 2262 }
2261 2263 error: null
2262 2264
2263 2265
2264 2266 revoke_user_permission
2265 2267 ----------------------
2266 2268
2267 2269 .. py:function:: revoke_user_permission(apiuser, repoid, userid)
2268 2270
2269 2271 Revoke permission for a user on the specified repository.
2270 2272
2271 2273 This command can only be run using an |authtoken| with admin
2272 2274 permissions on the |repo|.
2273 2275
2274 2276 :param apiuser: This is filled automatically from the |authtoken|.
2275 2277 :type apiuser: AuthUser
2276 2278 :param repoid: Set the repository name or repository ID.
2277 2279 :type repoid: str or int
2278 2280 :param userid: Set the user name of revoked user.
2279 2281 :type userid: str or int
2280 2282
2281 2283 Example error output:
2282 2284
2283 2285 .. code-block:: bash
2284 2286
2285 2287 id : <id_given_in_input>
2286 2288 result: {
2287 2289 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
2288 2290 "success": true
2289 2291 }
2290 2292 error: null
2291 2293
2292 2294
2293 2295 grant_user_group_permission
2294 2296 ---------------------------
2295 2297
2296 2298 .. py:function:: grant_user_group_permission(apiuser, repoid, usergroupid, perm)
2297 2299
2298 2300 Grant permission for a user group on the specified repository,
2299 2301 or update existing permissions.
2300 2302
2301 2303 This command can only be run using an |authtoken| with admin
2302 2304 permissions on the |repo|.
2303 2305
2304 2306 :param apiuser: This is filled automatically from the |authtoken|.
2305 2307 :type apiuser: AuthUser
2306 2308 :param repoid: Set the repository name or repository ID.
2307 2309 :type repoid: str or int
2308 2310 :param usergroupid: Specify the ID of the user group.
2309 2311 :type usergroupid: str or int
2310 2312 :param perm: Set the user group permissions using the following
2311 2313 format: (repository.(none|read|write|admin))
2312 2314 :type perm: str
2313 2315
2314 2316 Example output:
2315 2317
2316 2318 .. code-block:: bash
2317 2319
2318 2320 id : <id_given_in_input>
2319 2321 result : {
2320 2322 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
2321 2323 "success": true
2322 2324
2323 2325 }
2324 2326 error : null
2325 2327
2326 2328 Example error output:
2327 2329
2328 2330 .. code-block:: bash
2329 2331
2330 2332 id : <id_given_in_input>
2331 2333 result : null
2332 2334 error : {
2333 2335 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
2334 2336 }
2335 2337
2336 2338
2337 2339 revoke_user_group_permission
2338 2340 ----------------------------
2339 2341
2340 2342 .. py:function:: revoke_user_group_permission(apiuser, repoid, usergroupid)
2341 2343
2342 2344 Revoke the permissions of a user group on a given repository.
2343 2345
2344 2346 This command can only be run using an |authtoken| with admin
2345 2347 permissions on the |repo|.
2346 2348
2347 2349 :param apiuser: This is filled automatically from the |authtoken|.
2348 2350 :type apiuser: AuthUser
2349 2351 :param repoid: Set the repository name or repository ID.
2350 2352 :type repoid: str or int
2351 2353 :param usergroupid: Specify the user group ID.
2352 2354 :type usergroupid: str or int
2353 2355
2354 2356 Example output:
2355 2357
2356 2358 .. code-block:: bash
2357 2359
2358 2360 id : <id_given_in_input>
2359 2361 result: {
2360 2362 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
2361 2363 "success": true
2362 2364 }
2363 2365 error: null
2364 2366
2365 2367
2366 2368 get_repo_group
2367 2369 --------------
2368 2370
2369 2371 .. py:function:: get_repo_group(apiuser, repogroupid)
2370 2372
2371 2373 Return the specified |repo| group, along with permissions,
2372 2374 and repositories inside the group
2373 2375
2374 2376 :param apiuser: This is filled automatically from the |authtoken|.
2375 2377 :type apiuser: AuthUser
2376 2378 :param repogroupid: Specify the name of ID of the repository group.
2377 2379 :type repogroupid: str or int
2378 2380
2379 2381
2380 2382 Example output:
2381 2383
2382 2384 .. code-block:: bash
2383 2385
2384 2386 {
2385 2387 "error": null,
2386 2388 "id": repo-group-id,
2387 2389 "result": {
2388 2390 "group_description": "repo group description",
2389 2391 "group_id": 14,
2390 2392 "group_name": "group name",
2391 2393 "members": [
2392 2394 {
2393 2395 "name": "super-admin-username",
2394 2396 "origin": "super-admin",
2395 2397 "permission": "group.admin",
2396 2398 "type": "user"
2397 2399 },
2398 2400 {
2399 2401 "name": "owner-name",
2400 2402 "origin": "owner",
2401 2403 "permission": "group.admin",
2402 2404 "type": "user"
2403 2405 },
2404 2406 {
2405 2407 "name": "user-group-name",
2406 2408 "origin": "permission",
2407 2409 "permission": "group.write",
2408 2410 "type": "user_group"
2409 2411 }
2410 2412 ],
2411 2413 "owner": "owner-name",
2412 2414 "parent_group": null,
2413 2415 "repositories": [ repo-list ]
2414 2416 }
2415 2417 }
2416 2418
2417 2419
2418 2420 get_repo_groups
2419 2421 ---------------
2420 2422
2421 2423 .. py:function:: get_repo_groups(apiuser)
2422 2424
2423 2425 Returns all repository groups.
2424 2426
2425 2427 :param apiuser: This is filled automatically from the |authtoken|.
2426 2428 :type apiuser: AuthUser
2427 2429
2428 2430
2429 2431 create_repo_group
2430 2432 -----------------
2431 2433
2432 2434 .. py:function:: create_repo_group(apiuser, group_name, description=<Optional:''>, owner=<Optional:<OptionalAttr:apiuser>>, copy_permissions=<Optional:False>)
2433 2435
2434 2436 Creates a repository group.
2435 2437
2436 2438 * If the repository group name contains "/", all the required repository
2437 2439 groups will be created.
2438 2440
2439 2441 For example "foo/bar/baz" will create |repo| groups "foo" and "bar"
2440 2442 (with "foo" as parent). It will also create the "baz" repository
2441 2443 with "bar" as |repo| group.
2442 2444
2443 2445 This command can only be run using an |authtoken| with admin
2444 2446 permissions.
2445 2447
2446 2448 :param apiuser: This is filled automatically from the |authtoken|.
2447 2449 :type apiuser: AuthUser
2448 2450 :param group_name: Set the repository group name.
2449 2451 :type group_name: str
2450 2452 :param description: Set the |repo| group description.
2451 2453 :type description: str
2452 2454 :param owner: Set the |repo| group owner.
2453 2455 :type owner: str
2454 2456 :param copy_permissions:
2455 2457 :type copy_permissions:
2456 2458
2457 2459 Example output:
2458 2460
2459 2461 .. code-block:: bash
2460 2462
2461 2463 id : <id_given_in_input>
2462 2464 result : {
2463 2465 "msg": "Created new repo group `<repo_group_name>`"
2464 2466 "repo_group": <repogroup_object>
2465 2467 }
2466 2468 error : null
2467 2469
2468 2470
2469 2471 Example error output:
2470 2472
2471 2473 .. code-block:: bash
2472 2474
2473 2475 id : <id_given_in_input>
2474 2476 result : null
2475 2477 error : {
2476 2478 failed to create repo group `<repogroupid>`
2477 2479 }
2478 2480
2479 2481
2480 2482 update_repo_group
2481 2483 -----------------
2482 2484
2483 2485 .. py:function:: update_repo_group(apiuser, repogroupid, group_name=<Optional:''>, description=<Optional:''>, owner=<Optional:<OptionalAttr:apiuser>>, parent=<Optional:None>, enable_locking=<Optional:False>)
2484 2486
2485 2487 Updates repository group with the details given.
2486 2488
2487 2489 This command can only be run using an |authtoken| with admin
2488 2490 permissions.
2489 2491
2490 2492 :param apiuser: This is filled automatically from the |authtoken|.
2491 2493 :type apiuser: AuthUser
2492 2494 :param repogroupid: Set the ID of repository group.
2493 2495 :type repogroupid: str or int
2494 2496 :param group_name: Set the name of the |repo| group.
2495 2497 :type group_name: str
2496 2498 :param description: Set a description for the group.
2497 2499 :type description: str
2498 2500 :param owner: Set the |repo| group owner.
2499 2501 :type owner: str
2500 2502 :param parent: Set the |repo| group parent.
2501 2503 :type parent: str or int
2502 2504 :param enable_locking: Enable |repo| locking. The default is false.
2503 2505 :type enable_locking: bool
2504 2506
2505 2507
2506 2508 delete_repo_group
2507 2509 -----------------
2508 2510
2509 2511 .. py:function:: delete_repo_group(apiuser, repogroupid)
2510 2512
2511 2513 Deletes a |repo| group.
2512 2514
2513 2515 :param apiuser: This is filled automatically from the |authtoken|.
2514 2516 :type apiuser: AuthUser
2515 2517 :param repogroupid: Set the name or ID of repository group to be
2516 2518 deleted.
2517 2519 :type repogroupid: str or int
2518 2520
2519 2521 Example output:
2520 2522
2521 2523 .. code-block:: bash
2522 2524
2523 2525 id : <id_given_in_input>
2524 2526 result : {
2525 2527 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>
2526 2528 'repo_group': null
2527 2529 }
2528 2530 error : null
2529 2531
2530 2532 Example error output:
2531 2533
2532 2534 .. code-block:: bash
2533 2535
2534 2536 id : <id_given_in_input>
2535 2537 result : null
2536 2538 error : {
2537 2539 "failed to delete repo group ID:<repogroupid> <repogroupname>"
2538 2540 }
2539 2541
2540 2542
2541 2543 grant_user_permission_to_repo_group
2542 2544 -----------------------------------
2543 2545
2544 2546 .. py:function:: grant_user_permission_to_repo_group(apiuser, repogroupid, userid, perm, apply_to_children=<Optional:'none'>)
2545 2547
2546 2548 Grant permission for a user on the given repository group, or update
2547 2549 existing permissions if found.
2548 2550
2549 2551 This command can only be run using an |authtoken| with admin
2550 2552 permissions.
2551 2553
2552 2554 :param apiuser: This is filled automatically from the |authtoken|.
2553 2555 :type apiuser: AuthUser
2554 2556 :param repogroupid: Set the name or ID of repository group.
2555 2557 :type repogroupid: str or int
2556 2558 :param userid: Set the user name.
2557 2559 :type userid: str
2558 2560 :param perm: (group.(none|read|write|admin))
2559 2561 :type perm: str
2560 2562 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2561 2563 :type apply_to_children: str
2562 2564
2563 2565 Example output:
2564 2566
2565 2567 .. code-block:: bash
2566 2568
2567 2569 id : <id_given_in_input>
2568 2570 result: {
2569 2571 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
2570 2572 "success": true
2571 2573 }
2572 2574 error: null
2573 2575
2574 2576 Example error output:
2575 2577
2576 2578 .. code-block:: bash
2577 2579
2578 2580 id : <id_given_in_input>
2579 2581 result : null
2580 2582 error : {
2581 2583 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
2582 2584 }
2583 2585
2584 2586
2585 2587 revoke_user_permission_from_repo_group
2586 2588 --------------------------------------
2587 2589
2588 2590 .. py:function:: revoke_user_permission_from_repo_group(apiuser, repogroupid, userid, apply_to_children=<Optional:'none'>)
2589 2591
2590 2592 Revoke permission for a user in a given repository group.
2591 2593
2592 2594 This command can only be run using an |authtoken| with admin
2593 2595 permissions on the |repo| group.
2594 2596
2595 2597 :param apiuser: This is filled automatically from the |authtoken|.
2596 2598 :type apiuser: AuthUser
2597 2599 :param repogroupid: Set the name or ID of the repository group.
2598 2600 :type repogroupid: str or int
2599 2601 :param userid: Set the user name to revoke.
2600 2602 :type userid: str
2601 2603 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2602 2604 :type apply_to_children: str
2603 2605
2604 2606 Example output:
2605 2607
2606 2608 .. code-block:: bash
2607 2609
2608 2610 id : <id_given_in_input>
2609 2611 result: {
2610 2612 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
2611 2613 "success": true
2612 2614 }
2613 2615 error: null
2614 2616
2615 2617 Example error output:
2616 2618
2617 2619 .. code-block:: bash
2618 2620
2619 2621 id : <id_given_in_input>
2620 2622 result : null
2621 2623 error : {
2622 2624 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
2623 2625 }
2624 2626
2625 2627
2626 2628 grant_user_group_permission_to_repo_group
2627 2629 -----------------------------------------
2628 2630
2629 2631 .. py:function:: grant_user_group_permission_to_repo_group(apiuser, repogroupid, usergroupid, perm, apply_to_children=<Optional:'none'>)
2630 2632
2631 2633 Grant permission for a user group on given repository group, or update
2632 2634 existing permissions if found.
2633 2635
2634 2636 This command can only be run using an |authtoken| with admin
2635 2637 permissions on the |repo| group.
2636 2638
2637 2639 :param apiuser: This is filled automatically from the |authtoken|.
2638 2640 :type apiuser: AuthUser
2639 2641 :param repogroupid: Set the name or id of repository group
2640 2642 :type repogroupid: str or int
2641 2643 :param usergroupid: id of usergroup
2642 2644 :type usergroupid: str or int
2643 2645 :param perm: (group.(none|read|write|admin))
2644 2646 :type perm: str
2645 2647 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2646 2648 :type apply_to_children: str
2647 2649
2648 2650 Example output:
2649 2651
2650 2652 .. code-block:: bash
2651 2653
2652 2654 id : <id_given_in_input>
2653 2655 result : {
2654 2656 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2655 2657 "success": true
2656 2658
2657 2659 }
2658 2660 error : null
2659 2661
2660 2662 Example error output:
2661 2663
2662 2664 .. code-block:: bash
2663 2665
2664 2666 id : <id_given_in_input>
2665 2667 result : null
2666 2668 error : {
2667 2669 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2668 2670 }
2669 2671
2670 2672
2671 2673 revoke_user_group_permission_from_repo_group
2672 2674 --------------------------------------------
2673 2675
2674 2676 .. py:function:: revoke_user_group_permission_from_repo_group(apiuser, repogroupid, usergroupid, apply_to_children=<Optional:'none'>)
2675 2677
2676 2678 Revoke permission for user group on given repository.
2677 2679
2678 2680 This command can only be run using an |authtoken| with admin
2679 2681 permissions on the |repo| group.
2680 2682
2681 2683 :param apiuser: This is filled automatically from the |authtoken|.
2682 2684 :type apiuser: AuthUser
2683 2685 :param repogroupid: name or id of repository group
2684 2686 :type repogroupid: str or int
2685 2687 :param usergroupid:
2686 2688 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2687 2689 :type apply_to_children: str
2688 2690
2689 2691 Example output:
2690 2692
2691 2693 .. code-block:: bash
2692 2694
2693 2695 id : <id_given_in_input>
2694 2696 result: {
2695 2697 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2696 2698 "success": true
2697 2699 }
2698 2700 error: null
2699 2701
2700 2702 Example error output:
2701 2703
2702 2704 .. code-block:: bash
2703 2705
2704 2706 id : <id_given_in_input>
2705 2707 result : null
2706 2708 error : {
2707 2709 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2708 2710 }
2709 2711
2710 2712
2711 2713 get_gist
2712 2714 --------
2713 2715
2714 2716 .. py:function:: get_gist(apiuser, gistid, content=<Optional:False>)
2715 2717
2716 2718 Get the specified gist, based on the gist ID.
2717 2719
2718 2720 :param apiuser: This is filled automatically from the |authtoken|.
2719 2721 :type apiuser: AuthUser
2720 2722 :param gistid: Set the id of the private or public gist
2721 2723 :type gistid: str
2722 2724 :param content: Return the gist content. Default is false.
2723 2725 :type content: Optional(bool)
2724 2726
2725 2727
2726 2728 get_gists
2727 2729 ---------
2728 2730
2729 2731 .. py:function:: get_gists(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
2730 2732
2731 2733 Get all gists for given user. If userid is empty returned gists
2732 2734 are for user who called the api
2733 2735
2734 2736 :param apiuser: This is filled automatically from the |authtoken|.
2735 2737 :type apiuser: AuthUser
2736 2738 :param userid: user to get gists for
2737 2739 :type userid: Optional(str or int)
2738 2740
2739 2741
2740 2742 create_gist
2741 2743 -----------
2742 2744
2743 2745 .. py:function:: create_gist(apiuser, files, owner=<Optional:<OptionalAttr:apiuser>>, gist_type=<Optional:u'public'>, lifetime=<Optional:-1>, acl_level=<Optional:u'acl_public'>, description=<Optional:''>)
2744 2746
2745 2747 Creates a new Gist.
2746 2748
2747 2749 :param apiuser: This is filled automatically from the |authtoken|.
2748 2750 :type apiuser: AuthUser
2749 2751 :param files: files to be added to the gist. The data structure has
2750 2752 to match the following example::
2751 2753
2752 2754 {'filename': {'content':'...', 'lexer': null},
2753 2755 'filename2': {'content':'...', 'lexer': null}}
2754 2756
2755 2757 :type files: dict
2756 2758 :param owner: Set the gist owner, defaults to api method caller
2757 2759 :type owner: Optional(str or int)
2758 2760 :param gist_type: type of gist ``public`` or ``private``
2759 2761 :type gist_type: Optional(str)
2760 2762 :param lifetime: time in minutes of gist lifetime
2761 2763 :type lifetime: Optional(int)
2762 2764 :param acl_level: acl level for this gist, can be
2763 2765 ``acl_public`` or ``acl_private`` If the value is set to
2764 2766 ``acl_private`` only logged in users are able to access this gist.
2765 2767 If not set it defaults to ``acl_public``.
2766 2768 :type acl_level: Optional(str)
2767 2769 :param description: gist description
2768 2770 :type description: Optional(str)
2769 2771
2770 2772 Example output:
2771 2773
2772 2774 .. code-block:: bash
2773 2775
2774 2776 id : <id_given_in_input>
2775 2777 result : {
2776 2778 "msg": "created new gist",
2777 2779 "gist": {}
2778 2780 }
2779 2781 error : null
2780 2782
2781 2783 Example error output:
2782 2784
2783 2785 .. code-block:: bash
2784 2786
2785 2787 id : <id_given_in_input>
2786 2788 result : null
2787 2789 error : {
2788 2790 "failed to create gist"
2789 2791 }
2790 2792
2791 2793
2792 2794 delete_gist
2793 2795 -----------
2794 2796
2795 2797 .. py:function:: delete_gist(apiuser, gistid)
2796 2798
2797 2799 Deletes existing gist
2798 2800
2799 2801 :param apiuser: filled automatically from apikey
2800 2802 :type apiuser: AuthUser
2801 2803 :param gistid: id of gist to delete
2802 2804 :type gistid: str
2803 2805
2804 2806 Example output:
2805 2807
2806 2808 .. code-block:: bash
2807 2809
2808 2810 id : <id_given_in_input>
2809 2811 result : {
2810 2812 "deleted gist ID: <gist_id>",
2811 2813 "gist": null
2812 2814 }
2813 2815 error : null
2814 2816
2815 2817 Example error output:
2816 2818
2817 2819 .. code-block:: bash
2818 2820
2819 2821 id : <id_given_in_input>
2820 2822 result : null
2821 2823 error : {
2822 2824 "failed to delete gist ID:<gist_id>"
2823 2825 }
2824
@@ -1,119 +1,142 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 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 pytest
23 23
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.repo import RepoModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestGetRepoNodes(object):
32 32 @pytest.mark.parametrize("name, ret_type", [
33 33 ('all', 'all'),
34 34 ('dirs', 'dirs'),
35 35 ('files', 'files'),
36 36 ])
37 37 def test_api_get_repo_nodes(self, name, ret_type, backend):
38 38 commit_id = 'tip'
39 39 path = '/'
40 40 id_, params = build_data(
41 41 self.apikey, 'get_repo_nodes',
42 42 repoid=backend.repo_name, revision=commit_id,
43 43 root_path=path,
44 44 ret_type=ret_type)
45 45 response = api_call(self.app, params)
46 46
47 47 # we don't the actual return types here since it's tested somewhere
48 48 # else
49 49 expected = response.json['result']
50 50 assert_ok(id_, expected, given=response.body)
51 51
52 52 def test_api_get_repo_nodes_bad_commits(self, backend):
53 53 commit_id = 'i-dont-exist'
54 54 path = '/'
55 55 id_, params = build_data(
56 56 self.apikey, 'get_repo_nodes',
57 57 repoid=backend.repo_name, revision=commit_id,
58 58 root_path=path, )
59 59 response = api_call(self.app, params)
60 60
61 61 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
62 62 assert_error(id_, expected, given=response.body)
63 63
64 64 def test_api_get_repo_nodes_bad_path(self, backend):
65 65 commit_id = 'tip'
66 66 path = '/idontexits'
67 67 id_, params = build_data(
68 68 self.apikey, 'get_repo_nodes',
69 69 repoid=backend.repo_name, revision=commit_id,
70 70 root_path=path, )
71 71 response = api_call(self.app, params)
72 72
73 73 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
74 74 assert_error(id_, expected, given=response.body)
75 75
76 def test_api_get_repo_nodes_max_file_bytes(self, backend):
77 commit_id = 'tip'
78 path = '/'
79 max_file_bytes = 500
80
81 id_, params = build_data(
82 self.apikey, 'get_repo_nodes',
83 repoid=backend.repo_name, revision=commit_id, details='full',
84 root_path=path)
85 response = api_call(self.app, params)
86 assert any(file['content'] and len(file['content']) > max_file_bytes
87 for file in response.json['result'])
88
89 id_, params = build_data(
90 self.apikey, 'get_repo_nodes',
91 repoid=backend.repo_name, revision=commit_id,
92 root_path=path, details='full',
93 max_file_bytes=max_file_bytes)
94 response = api_call(self.app, params)
95 assert all(
96 file['content'] is None if file['size'] > max_file_bytes else True
97 for file in response.json['result'])
98
76 99 def test_api_get_repo_nodes_bad_ret_type(self, backend):
77 100 commit_id = 'tip'
78 101 path = '/'
79 102 ret_type = 'error'
80 103 id_, params = build_data(
81 104 self.apikey, 'get_repo_nodes',
82 105 repoid=backend.repo_name, revision=commit_id,
83 106 root_path=path,
84 107 ret_type=ret_type)
85 108 response = api_call(self.app, params)
86 109
87 110 expected = ('ret_type must be one of %s'
88 111 % (','.join(['all', 'dirs', 'files'])))
89 112 assert_error(id_, expected, given=response.body)
90 113
91 114 @pytest.mark.parametrize("name, ret_type, grant_perm", [
92 115 ('all', 'all', 'repository.write'),
93 116 ('dirs', 'dirs', 'repository.admin'),
94 117 ('files', 'files', 'repository.read'),
95 118 ])
96 119 def test_api_get_repo_nodes_by_regular_user(
97 120 self, name, ret_type, grant_perm, backend):
98 121 RepoModel().grant_user_permission(repo=backend.repo_name,
99 122 user=self.TEST_USER_LOGIN,
100 123 perm=grant_perm)
101 124 Session().commit()
102 125
103 126 commit_id = 'tip'
104 127 path = '/'
105 128 id_, params = build_data(
106 129 self.apikey_regular, 'get_repo_nodes',
107 130 repoid=backend.repo_name, revision=commit_id,
108 131 root_path=path,
109 132 ret_type=ret_type)
110 133 response = api_call(self.app, params)
111 134
112 135 # we don't the actual return types here since it's tested somewhere
113 136 # else
114 137 expected = response.json['result']
115 138 try:
116 139 assert_ok(id_, expected, given=response.body)
117 140 finally:
118 141 RepoModel().revoke_user_permission(
119 142 backend.repo_name, self.TEST_USER_LOGIN)
@@ -1,1882 +1,1886 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2016 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 colander
25 25
26 26 from rhodecode import BACKENDS
27 27 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden, json
28 28 from rhodecode.api.utils import (
29 29 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
30 30 get_user_group_or_error, get_user_or_error, has_repo_permissions,
31 31 get_perm_or_error, store_update, get_repo_group_or_error, parse_args,
32 32 get_origin, build_commit_data)
33 33 from rhodecode.lib.auth import (
34 34 HasPermissionAnyApi, HasRepoGroupPermissionAnyApi,
35 35 HasUserGroupPermissionAnyApi)
36 36 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
37 37 from rhodecode.lib.utils import map_groups
38 38 from rhodecode.lib.utils2 import str2bool, time_to_datetime
39 39 from rhodecode.model.changeset_status import ChangesetStatusModel
40 40 from rhodecode.model.comment import ChangesetCommentsModel
41 41 from rhodecode.model.db import (
42 42 Session, ChangesetStatus, RepositoryField, Repository)
43 43 from rhodecode.model.repo import RepoModel
44 44 from rhodecode.model.repo_group import RepoGroupModel
45 45 from rhodecode.model.scm import ScmModel, RepoList
46 46 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
47 47 from rhodecode.model.validation_schema import RepoSchema
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 @jsonrpc_method()
53 53 def get_repo(request, apiuser, repoid, cache=Optional(True)):
54 54 """
55 55 Gets an existing repository by its name or repository_id.
56 56
57 57 The members section so the output returns users groups or users
58 58 associated with that repository.
59 59
60 60 This command can only be run using an |authtoken| with admin rights,
61 61 or users with at least read rights to the |repo|.
62 62
63 63 :param apiuser: This is filled automatically from the |authtoken|.
64 64 :type apiuser: AuthUser
65 65 :param repoid: The repository name or repository id.
66 66 :type repoid: str or int
67 67 :param cache: use the cached value for last changeset
68 68 :type: cache: Optional(bool)
69 69
70 70 Example output:
71 71
72 72 .. code-block:: bash
73 73
74 74 {
75 75 "error": null,
76 76 "id": <repo_id>,
77 77 "result": {
78 78 "clone_uri": null,
79 79 "created_on": "timestamp",
80 80 "description": "repo description",
81 81 "enable_downloads": false,
82 82 "enable_locking": false,
83 83 "enable_statistics": false,
84 84 "followers": [
85 85 {
86 86 "active": true,
87 87 "admin": false,
88 88 "api_key": "****************************************",
89 89 "api_keys": [
90 90 "****************************************"
91 91 ],
92 92 "email": "user@example.com",
93 93 "emails": [
94 94 "user@example.com"
95 95 ],
96 96 "extern_name": "rhodecode",
97 97 "extern_type": "rhodecode",
98 98 "firstname": "username",
99 99 "ip_addresses": [],
100 100 "language": null,
101 101 "last_login": "2015-09-16T17:16:35.854",
102 102 "lastname": "surname",
103 103 "user_id": <user_id>,
104 104 "username": "name"
105 105 }
106 106 ],
107 107 "fork_of": "parent-repo",
108 108 "landing_rev": [
109 109 "rev",
110 110 "tip"
111 111 ],
112 112 "last_changeset": {
113 113 "author": "User <user@example.com>",
114 114 "branch": "default",
115 115 "date": "timestamp",
116 116 "message": "last commit message",
117 117 "parents": [
118 118 {
119 119 "raw_id": "commit-id"
120 120 }
121 121 ],
122 122 "raw_id": "commit-id",
123 123 "revision": <revision number>,
124 124 "short_id": "short id"
125 125 },
126 126 "lock_reason": null,
127 127 "locked_by": null,
128 128 "locked_date": null,
129 129 "members": [
130 130 {
131 131 "name": "super-admin-name",
132 132 "origin": "super-admin",
133 133 "permission": "repository.admin",
134 134 "type": "user"
135 135 },
136 136 {
137 137 "name": "owner-name",
138 138 "origin": "owner",
139 139 "permission": "repository.admin",
140 140 "type": "user"
141 141 },
142 142 {
143 143 "name": "user-group-name",
144 144 "origin": "permission",
145 145 "permission": "repository.write",
146 146 "type": "user_group"
147 147 }
148 148 ],
149 149 "owner": "owner-name",
150 150 "permissions": [
151 151 {
152 152 "name": "super-admin-name",
153 153 "origin": "super-admin",
154 154 "permission": "repository.admin",
155 155 "type": "user"
156 156 },
157 157 {
158 158 "name": "owner-name",
159 159 "origin": "owner",
160 160 "permission": "repository.admin",
161 161 "type": "user"
162 162 },
163 163 {
164 164 "name": "user-group-name",
165 165 "origin": "permission",
166 166 "permission": "repository.write",
167 167 "type": "user_group"
168 168 }
169 169 ],
170 170 "private": true,
171 171 "repo_id": 676,
172 172 "repo_name": "user-group/repo-name",
173 173 "repo_type": "hg"
174 174 }
175 175 }
176 176 """
177 177
178 178 repo = get_repo_or_error(repoid)
179 179 cache = Optional.extract(cache)
180 180 include_secrets = False
181 181 if has_superadmin_permission(apiuser):
182 182 include_secrets = True
183 183 else:
184 184 # check if we have at least read permission for this repo !
185 185 _perms = (
186 186 'repository.admin', 'repository.write', 'repository.read',)
187 187 has_repo_permissions(apiuser, repoid, repo, _perms)
188 188
189 189 permissions = []
190 190 for _user in repo.permissions():
191 191 user_data = {
192 192 'name': _user.username,
193 193 'permission': _user.permission,
194 194 'origin': get_origin(_user),
195 195 'type': "user",
196 196 }
197 197 permissions.append(user_data)
198 198
199 199 for _user_group in repo.permission_user_groups():
200 200 user_group_data = {
201 201 'name': _user_group.users_group_name,
202 202 'permission': _user_group.permission,
203 203 'origin': get_origin(_user_group),
204 204 'type': "user_group",
205 205 }
206 206 permissions.append(user_group_data)
207 207
208 208 following_users = [
209 209 user.user.get_api_data(include_secrets=include_secrets)
210 210 for user in repo.followers]
211 211
212 212 if not cache:
213 213 repo.update_commit_cache()
214 214 data = repo.get_api_data(include_secrets=include_secrets)
215 215 data['members'] = permissions # TODO: this should be deprecated soon
216 216 data['permissions'] = permissions
217 217 data['followers'] = following_users
218 218 return data
219 219
220 220
221 221 @jsonrpc_method()
222 222 def get_repos(request, apiuser):
223 223 """
224 224 Lists all existing repositories.
225 225
226 226 This command can only be run using an |authtoken| with admin rights,
227 227 or users with at least read rights to |repos|.
228 228
229 229 :param apiuser: This is filled automatically from the |authtoken|.
230 230 :type apiuser: AuthUser
231 231
232 232 Example output:
233 233
234 234 .. code-block:: bash
235 235
236 236 id : <id_given_in_input>
237 237 result: [
238 238 {
239 239 "repo_id" : "<repo_id>",
240 240 "repo_name" : "<reponame>"
241 241 "repo_type" : "<repo_type>",
242 242 "clone_uri" : "<clone_uri>",
243 243 "private": : "<bool>",
244 244 "created_on" : "<datetimecreated>",
245 245 "description" : "<description>",
246 246 "landing_rev": "<landing_rev>",
247 247 "owner": "<repo_owner>",
248 248 "fork_of": "<name_of_fork_parent>",
249 249 "enable_downloads": "<bool>",
250 250 "enable_locking": "<bool>",
251 251 "enable_statistics": "<bool>",
252 252 },
253 253 ...
254 254 ]
255 255 error: null
256 256 """
257 257
258 258 include_secrets = has_superadmin_permission(apiuser)
259 259 _perms = ('repository.read', 'repository.write', 'repository.admin',)
260 260 extras = {'user': apiuser}
261 261
262 262 repo_list = RepoList(
263 263 RepoModel().get_all(), perm_set=_perms, extra_kwargs=extras)
264 264 return [repo.get_api_data(include_secrets=include_secrets)
265 265 for repo in repo_list]
266 266
267 267
268 268 @jsonrpc_method()
269 269 def get_repo_changeset(request, apiuser, repoid, revision,
270 270 details=Optional('basic')):
271 271 """
272 272 Returns information about a changeset.
273 273
274 274 Additionally parameters define the amount of details returned by
275 275 this function.
276 276
277 277 This command can only be run using an |authtoken| with admin rights,
278 278 or users with at least read rights to the |repo|.
279 279
280 280 :param apiuser: This is filled automatically from the |authtoken|.
281 281 :type apiuser: AuthUser
282 282 :param repoid: The repository name or repository id
283 283 :type repoid: str or int
284 284 :param revision: revision for which listing should be done
285 285 :type revision: str
286 286 :param details: details can be 'basic|extended|full' full gives diff
287 287 info details like the diff itself, and number of changed files etc.
288 288 :type details: Optional(str)
289 289
290 290 """
291 291 repo = get_repo_or_error(repoid)
292 292 if not has_superadmin_permission(apiuser):
293 293 _perms = (
294 294 'repository.admin', 'repository.write', 'repository.read',)
295 295 has_repo_permissions(apiuser, repoid, repo, _perms)
296 296
297 297 changes_details = Optional.extract(details)
298 298 _changes_details_types = ['basic', 'extended', 'full']
299 299 if changes_details not in _changes_details_types:
300 300 raise JSONRPCError(
301 301 'ret_type must be one of %s' % (
302 302 ','.join(_changes_details_types)))
303 303
304 304 pre_load = ['author', 'branch', 'date', 'message', 'parents',
305 305 'status', '_commit', '_file_paths']
306 306
307 307 try:
308 308 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
309 309 except TypeError as e:
310 310 raise JSONRPCError(e.message)
311 311 _cs_json = cs.__json__()
312 312 _cs_json['diff'] = build_commit_data(cs, changes_details)
313 313 if changes_details == 'full':
314 314 _cs_json['refs'] = {
315 315 'branches': [cs.branch],
316 316 'bookmarks': getattr(cs, 'bookmarks', []),
317 317 'tags': cs.tags
318 318 }
319 319 return _cs_json
320 320
321 321
322 322 @jsonrpc_method()
323 323 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
324 324 details=Optional('basic')):
325 325 """
326 326 Returns a set of commits limited by the number starting
327 327 from the `start_rev` option.
328 328
329 329 Additional parameters define the amount of details returned by this
330 330 function.
331 331
332 332 This command can only be run using an |authtoken| with admin rights,
333 333 or users with at least read rights to |repos|.
334 334
335 335 :param apiuser: This is filled automatically from the |authtoken|.
336 336 :type apiuser: AuthUser
337 337 :param repoid: The repository name or repository ID.
338 338 :type repoid: str or int
339 339 :param start_rev: The starting revision from where to get changesets.
340 340 :type start_rev: str
341 341 :param limit: Limit the number of commits to this amount
342 342 :type limit: str or int
343 343 :param details: Set the level of detail returned. Valid option are:
344 344 ``basic``, ``extended`` and ``full``.
345 345 :type details: Optional(str)
346 346
347 347 .. note::
348 348
349 349 Setting the parameter `details` to the value ``full`` is extensive
350 350 and returns details like the diff itself, and the number
351 351 of changed files.
352 352
353 353 """
354 354 repo = get_repo_or_error(repoid)
355 355 if not has_superadmin_permission(apiuser):
356 356 _perms = (
357 357 'repository.admin', 'repository.write', 'repository.read',)
358 358 has_repo_permissions(apiuser, repoid, repo, _perms)
359 359
360 360 changes_details = Optional.extract(details)
361 361 _changes_details_types = ['basic', 'extended', 'full']
362 362 if changes_details not in _changes_details_types:
363 363 raise JSONRPCError(
364 364 'ret_type must be one of %s' % (
365 365 ','.join(_changes_details_types)))
366 366
367 367 limit = int(limit)
368 368 pre_load = ['author', 'branch', 'date', 'message', 'parents',
369 369 'status', '_commit', '_file_paths']
370 370
371 371 vcs_repo = repo.scm_instance()
372 372 # SVN needs a special case to distinguish its index and commit id
373 373 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
374 374 start_rev = vcs_repo.commit_ids[0]
375 375
376 376 try:
377 377 commits = vcs_repo.get_commits(
378 378 start_id=start_rev, pre_load=pre_load)
379 379 except TypeError as e:
380 380 raise JSONRPCError(e.message)
381 381 except Exception:
382 382 log.exception('Fetching of commits failed')
383 383 raise JSONRPCError('Error occurred during commit fetching')
384 384
385 385 ret = []
386 386 for cnt, commit in enumerate(commits):
387 387 if cnt >= limit != -1:
388 388 break
389 389 _cs_json = commit.__json__()
390 390 _cs_json['diff'] = build_commit_data(commit, changes_details)
391 391 if changes_details == 'full':
392 392 _cs_json['refs'] = {
393 393 'branches': [commit.branch],
394 394 'bookmarks': getattr(commit, 'bookmarks', []),
395 395 'tags': commit.tags
396 396 }
397 397 ret.append(_cs_json)
398 398 return ret
399 399
400 400
401 401 @jsonrpc_method()
402 402 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
403 ret_type=Optional('all'), details=Optional('basic')):
403 ret_type=Optional('all'), details=Optional('basic'),
404 max_file_bytes=Optional(None)):
404 405 """
405 406 Returns a list of nodes and children in a flat list for a given
406 407 path at given revision.
407 408
408 409 It's possible to specify ret_type to show only `files` or `dirs`.
409 410
410 411 This command can only be run using an |authtoken| with admin rights,
411 412 or users with at least read rights to |repos|.
412 413
413 414 :param apiuser: This is filled automatically from the |authtoken|.
414 415 :type apiuser: AuthUser
415 416 :param repoid: The repository name or repository ID.
416 417 :type repoid: str or int
417 418 :param revision: The revision for which listing should be done.
418 419 :type revision: str
419 420 :param root_path: The path from which to start displaying.
420 421 :type root_path: str
421 422 :param ret_type: Set the return type. Valid options are
422 423 ``all`` (default), ``files`` and ``dirs``.
423 424 :type ret_type: Optional(str)
424 425 :param details: Returns extended information about nodes, such as
425 426 md5, binary, and or content. The valid options are ``basic`` and
426 427 ``full``.
427 428 :type details: Optional(str)
429 :param max_file_bytes: Only return file content under this file size bytes
430 :type details: Optional(int)
428 431
429 432 Example output:
430 433
431 434 .. code-block:: bash
432 435
433 436 id : <id_given_in_input>
434 437 result: [
435 438 {
436 439 "name" : "<name>"
437 440 "type" : "<type>",
438 441 "binary": "<true|false>" (only in extended mode)
439 442 "md5" : "<md5 of file content>" (only in extended mode)
440 443 },
441 444 ...
442 445 ]
443 446 error: null
444 447 """
445 448
446 449 repo = get_repo_or_error(repoid)
447 450 if not has_superadmin_permission(apiuser):
448 451 _perms = (
449 452 'repository.admin', 'repository.write', 'repository.read',)
450 453 has_repo_permissions(apiuser, repoid, repo, _perms)
451 454
452 455 ret_type = Optional.extract(ret_type)
453 456 details = Optional.extract(details)
454 457 _extended_types = ['basic', 'full']
455 458 if details not in _extended_types:
456 459 raise JSONRPCError(
457 460 'ret_type must be one of %s' % (','.join(_extended_types)))
458 461 extended_info = False
459 462 content = False
460 463 if details == 'basic':
461 464 extended_info = True
462 465
463 466 if details == 'full':
464 467 extended_info = content = True
465 468
466 469 _map = {}
467 470 try:
468 471 # check if repo is not empty by any chance, skip quicker if it is.
469 472 _scm = repo.scm_instance()
470 473 if _scm.is_empty():
471 474 return []
472 475
473 476 _d, _f = ScmModel().get_nodes(
474 477 repo, revision, root_path, flat=False,
475 extended_info=extended_info, content=content)
478 extended_info=extended_info, content=content,
479 max_file_bytes=max_file_bytes)
476 480 _map = {
477 481 'all': _d + _f,
478 482 'files': _f,
479 483 'dirs': _d,
480 484 }
481 485 return _map[ret_type]
482 486 except KeyError:
483 487 raise JSONRPCError(
484 488 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
485 489 except Exception:
486 490 log.exception("Exception occurred while trying to get repo nodes")
487 491 raise JSONRPCError(
488 492 'failed to get repo: `%s` nodes' % repo.repo_name
489 493 )
490 494
491 495
492 496 @jsonrpc_method()
493 497 def get_repo_refs(request, apiuser, repoid):
494 498 """
495 499 Returns a dictionary of current references. It returns
496 500 bookmarks, branches, closed_branches, and tags for given repository
497 501
498 502 It's possible to specify ret_type to show only `files` or `dirs`.
499 503
500 504 This command can only be run using an |authtoken| with admin rights,
501 505 or users with at least read rights to |repos|.
502 506
503 507 :param apiuser: This is filled automatically from the |authtoken|.
504 508 :type apiuser: AuthUser
505 509 :param repoid: The repository name or repository ID.
506 510 :type repoid: str or int
507 511
508 512 Example output:
509 513
510 514 .. code-block:: bash
511 515
512 516 id : <id_given_in_input>
513 517 result: [
514 518 TODO...
515 519 ]
516 520 error: null
517 521 """
518 522
519 523 repo = get_repo_or_error(repoid)
520 524 if not has_superadmin_permission(apiuser):
521 525 _perms = ('repository.admin', 'repository.write', 'repository.read',)
522 526 has_repo_permissions(apiuser, repoid, repo, _perms)
523 527
524 528 try:
525 529 # check if repo is not empty by any chance, skip quicker if it is.
526 530 vcs_instance = repo.scm_instance()
527 531 refs = vcs_instance.refs()
528 532 return refs
529 533 except Exception:
530 534 log.exception("Exception occurred while trying to get repo refs")
531 535 raise JSONRPCError(
532 536 'failed to get repo: `%s` references' % repo.repo_name
533 537 )
534 538
535 539
536 540 @jsonrpc_method()
537 541 def create_repo(request, apiuser, repo_name, repo_type,
538 542 owner=Optional(OAttr('apiuser')), description=Optional(''),
539 543 private=Optional(False), clone_uri=Optional(None),
540 544 landing_rev=Optional('rev:tip'),
541 545 enable_statistics=Optional(False),
542 546 enable_locking=Optional(False),
543 547 enable_downloads=Optional(False),
544 548 copy_permissions=Optional(False)):
545 549 """
546 550 Creates a repository.
547 551
548 552 * If the repository name contains "/", all the required repository
549 553 groups will be created.
550 554
551 555 For example "foo/bar/baz" will create |repo| groups "foo" and "bar"
552 556 (with "foo" as parent). It will also create the "baz" repository
553 557 with "bar" as |repo| group.
554 558
555 559 This command can only be run using an |authtoken| with at least
556 560 write permissions to the |repo|.
557 561
558 562 :param apiuser: This is filled automatically from the |authtoken|.
559 563 :type apiuser: AuthUser
560 564 :param repo_name: Set the repository name.
561 565 :type repo_name: str
562 566 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
563 567 :type repo_type: str
564 568 :param owner: user_id or username
565 569 :type owner: Optional(str)
566 570 :param description: Set the repository description.
567 571 :type description: Optional(str)
568 572 :param private:
569 573 :type private: bool
570 574 :param clone_uri:
571 575 :type clone_uri: str
572 576 :param landing_rev: <rev_type>:<rev>
573 577 :type landing_rev: str
574 578 :param enable_locking:
575 579 :type enable_locking: bool
576 580 :param enable_downloads:
577 581 :type enable_downloads: bool
578 582 :param enable_statistics:
579 583 :type enable_statistics: bool
580 584 :param copy_permissions: Copy permission from group in which the
581 585 repository is being created.
582 586 :type copy_permissions: bool
583 587
584 588
585 589 Example output:
586 590
587 591 .. code-block:: bash
588 592
589 593 id : <id_given_in_input>
590 594 result: {
591 595 "msg": "Created new repository `<reponame>`",
592 596 "success": true,
593 597 "task": "<celery task id or None if done sync>"
594 598 }
595 599 error: null
596 600
597 601
598 602 Example error output:
599 603
600 604 .. code-block:: bash
601 605
602 606 id : <id_given_in_input>
603 607 result : null
604 608 error : {
605 609 'failed to create repository `<repo_name>`
606 610 }
607 611
608 612 """
609 613 schema = RepoSchema()
610 614 try:
611 615 data = schema.deserialize({
612 616 'repo_name': repo_name
613 617 })
614 618 except colander.Invalid as e:
615 619 raise JSONRPCError("Validation failed: %s" % (e.asdict(),))
616 620 repo_name = data['repo_name']
617 621
618 622 (repo_name_cleaned,
619 623 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(
620 624 repo_name)
621 625
622 626 if not HasPermissionAnyApi(
623 627 'hg.admin', 'hg.create.repository')(user=apiuser):
624 628 # check if we have admin permission for this repo group if given !
625 629
626 630 if parent_group_name:
627 631 repogroupid = parent_group_name
628 632 repo_group = get_repo_group_or_error(parent_group_name)
629 633
630 634 _perms = ('group.admin',)
631 635 if not HasRepoGroupPermissionAnyApi(*_perms)(
632 636 user=apiuser, group_name=repo_group.group_name):
633 637 raise JSONRPCError(
634 638 'repository group `%s` does not exist' % (
635 639 repogroupid,))
636 640 else:
637 641 raise JSONRPCForbidden()
638 642
639 643 if not has_superadmin_permission(apiuser):
640 644 if not isinstance(owner, Optional):
641 645 # forbid setting owner for non-admins
642 646 raise JSONRPCError(
643 647 'Only RhodeCode admin can specify `owner` param')
644 648
645 649 if isinstance(owner, Optional):
646 650 owner = apiuser.user_id
647 651
648 652 owner = get_user_or_error(owner)
649 653
650 654 if RepoModel().get_by_repo_name(repo_name):
651 655 raise JSONRPCError("repo `%s` already exist" % repo_name)
652 656
653 657 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
654 658 if isinstance(private, Optional):
655 659 private = defs.get('repo_private') or Optional.extract(private)
656 660 if isinstance(repo_type, Optional):
657 661 repo_type = defs.get('repo_type')
658 662 if isinstance(enable_statistics, Optional):
659 663 enable_statistics = defs.get('repo_enable_statistics')
660 664 if isinstance(enable_locking, Optional):
661 665 enable_locking = defs.get('repo_enable_locking')
662 666 if isinstance(enable_downloads, Optional):
663 667 enable_downloads = defs.get('repo_enable_downloads')
664 668
665 669 clone_uri = Optional.extract(clone_uri)
666 670 description = Optional.extract(description)
667 671 landing_rev = Optional.extract(landing_rev)
668 672 copy_permissions = Optional.extract(copy_permissions)
669 673
670 674 try:
671 675 # create structure of groups and return the last group
672 676 repo_group = map_groups(repo_name)
673 677 data = {
674 678 'repo_name': repo_name_cleaned,
675 679 'repo_name_full': repo_name,
676 680 'repo_type': repo_type,
677 681 'repo_description': description,
678 682 'owner': owner,
679 683 'repo_private': private,
680 684 'clone_uri': clone_uri,
681 685 'repo_group': repo_group.group_id if repo_group else None,
682 686 'repo_landing_rev': landing_rev,
683 687 'enable_statistics': enable_statistics,
684 688 'enable_locking': enable_locking,
685 689 'enable_downloads': enable_downloads,
686 690 'repo_copy_permissions': copy_permissions,
687 691 }
688 692
689 693 if repo_type not in BACKENDS.keys():
690 694 raise Exception("Invalid backend type %s" % repo_type)
691 695 task = RepoModel().create(form_data=data, cur_user=owner)
692 696 from celery.result import BaseAsyncResult
693 697 task_id = None
694 698 if isinstance(task, BaseAsyncResult):
695 699 task_id = task.task_id
696 700 # no commit, it's done in RepoModel, or async via celery
697 701 return {
698 702 'msg': "Created new repository `%s`" % (repo_name,),
699 703 'success': True, # cannot return the repo data here since fork
700 704 # cann be done async
701 705 'task': task_id
702 706 }
703 707 except Exception:
704 708 log.exception(
705 709 u"Exception while trying to create the repository %s",
706 710 repo_name)
707 711 raise JSONRPCError(
708 712 'failed to create repository `%s`' % (repo_name,))
709 713
710 714
711 715 @jsonrpc_method()
712 716 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
713 717 description=Optional('')):
714 718 """
715 719 Adds an extra field to a repository.
716 720
717 721 This command can only be run using an |authtoken| with at least
718 722 write permissions to the |repo|.
719 723
720 724 :param apiuser: This is filled automatically from the |authtoken|.
721 725 :type apiuser: AuthUser
722 726 :param repoid: Set the repository name or repository id.
723 727 :type repoid: str or int
724 728 :param key: Create a unique field key for this repository.
725 729 :type key: str
726 730 :param label:
727 731 :type label: Optional(str)
728 732 :param description:
729 733 :type description: Optional(str)
730 734 """
731 735 repo = get_repo_or_error(repoid)
732 736 if not has_superadmin_permission(apiuser):
733 737 _perms = ('repository.admin',)
734 738 has_repo_permissions(apiuser, repoid, repo, _perms)
735 739
736 740 label = Optional.extract(label) or key
737 741 description = Optional.extract(description)
738 742
739 743 field = RepositoryField.get_by_key_name(key, repo)
740 744 if field:
741 745 raise JSONRPCError('Field with key '
742 746 '`%s` exists for repo `%s`' % (key, repoid))
743 747
744 748 try:
745 749 RepoModel().add_repo_field(repo, key, field_label=label,
746 750 field_desc=description)
747 751 Session().commit()
748 752 return {
749 753 'msg': "Added new repository field `%s`" % (key,),
750 754 'success': True,
751 755 }
752 756 except Exception:
753 757 log.exception("Exception occurred while trying to add field to repo")
754 758 raise JSONRPCError(
755 759 'failed to create new field for repository `%s`' % (repoid,))
756 760
757 761
758 762 @jsonrpc_method()
759 763 def remove_field_from_repo(request, apiuser, repoid, key):
760 764 """
761 765 Removes an extra field from a repository.
762 766
763 767 This command can only be run using an |authtoken| with at least
764 768 write permissions to the |repo|.
765 769
766 770 :param apiuser: This is filled automatically from the |authtoken|.
767 771 :type apiuser: AuthUser
768 772 :param repoid: Set the repository name or repository ID.
769 773 :type repoid: str or int
770 774 :param key: Set the unique field key for this repository.
771 775 :type key: str
772 776 """
773 777
774 778 repo = get_repo_or_error(repoid)
775 779 if not has_superadmin_permission(apiuser):
776 780 _perms = ('repository.admin',)
777 781 has_repo_permissions(apiuser, repoid, repo, _perms)
778 782
779 783 field = RepositoryField.get_by_key_name(key, repo)
780 784 if not field:
781 785 raise JSONRPCError('Field with key `%s` does not '
782 786 'exists for repo `%s`' % (key, repoid))
783 787
784 788 try:
785 789 RepoModel().delete_repo_field(repo, field_key=key)
786 790 Session().commit()
787 791 return {
788 792 'msg': "Deleted repository field `%s`" % (key,),
789 793 'success': True,
790 794 }
791 795 except Exception:
792 796 log.exception(
793 797 "Exception occurred while trying to delete field from repo")
794 798 raise JSONRPCError(
795 799 'failed to delete field for repository `%s`' % (repoid,))
796 800
797 801
798 802 @jsonrpc_method()
799 803 def update_repo(request, apiuser, repoid, name=Optional(None),
800 804 owner=Optional(OAttr('apiuser')),
801 805 group=Optional(None),
802 806 fork_of=Optional(None),
803 807 description=Optional(''), private=Optional(False),
804 808 clone_uri=Optional(None), landing_rev=Optional('rev:tip'),
805 809 enable_statistics=Optional(False),
806 810 enable_locking=Optional(False),
807 811 enable_downloads=Optional(False),
808 812 fields=Optional('')):
809 813 """
810 814 Updates a repository with the given information.
811 815
812 816 This command can only be run using an |authtoken| with at least
813 817 write permissions to the |repo|.
814 818
815 819 :param apiuser: This is filled automatically from the |authtoken|.
816 820 :type apiuser: AuthUser
817 821 :param repoid: repository name or repository ID.
818 822 :type repoid: str or int
819 823 :param name: Update the |repo| name.
820 824 :type name: str
821 825 :param owner: Set the |repo| owner.
822 826 :type owner: str
823 827 :param group: Set the |repo| group the |repo| belongs to.
824 828 :type group: str
825 829 :param fork_of: Set the master |repo| name.
826 830 :type fork_of: str
827 831 :param description: Update the |repo| description.
828 832 :type description: str
829 833 :param private: Set the |repo| as private. (True | False)
830 834 :type private: bool
831 835 :param clone_uri: Update the |repo| clone URI.
832 836 :type clone_uri: str
833 837 :param landing_rev: Set the |repo| landing revision. Default is
834 838 ``tip``.
835 839 :type landing_rev: str
836 840 :param enable_statistics: Enable statistics on the |repo|,
837 841 (True | False).
838 842 :type enable_statistics: bool
839 843 :param enable_locking: Enable |repo| locking.
840 844 :type enable_locking: bool
841 845 :param enable_downloads: Enable downloads from the |repo|,
842 846 (True | False).
843 847 :type enable_downloads: bool
844 848 :param fields: Add extra fields to the |repo|. Use the following
845 849 example format: ``field_key=field_val,field_key2=fieldval2``.
846 850 Escape ', ' with \,
847 851 :type fields: str
848 852 """
849 853 repo = get_repo_or_error(repoid)
850 854 include_secrets = False
851 855 if has_superadmin_permission(apiuser):
852 856 include_secrets = True
853 857 else:
854 858 _perms = ('repository.admin',)
855 859 has_repo_permissions(apiuser, repoid, repo, _perms)
856 860
857 861 updates = {
858 862 # update function requires this.
859 863 'repo_name': repo.just_name
860 864 }
861 865 repo_group = group
862 866 if not isinstance(repo_group, Optional):
863 867 repo_group = get_repo_group_or_error(repo_group)
864 868 repo_group = repo_group.group_id
865 869
866 870 repo_fork_of = fork_of
867 871 if not isinstance(repo_fork_of, Optional):
868 872 repo_fork_of = get_repo_or_error(repo_fork_of)
869 873 repo_fork_of = repo_fork_of.repo_id
870 874
871 875 try:
872 876 store_update(updates, name, 'repo_name')
873 877 store_update(updates, repo_group, 'repo_group')
874 878 store_update(updates, repo_fork_of, 'fork_id')
875 879 store_update(updates, owner, 'user')
876 880 store_update(updates, description, 'repo_description')
877 881 store_update(updates, private, 'repo_private')
878 882 store_update(updates, clone_uri, 'clone_uri')
879 883 store_update(updates, landing_rev, 'repo_landing_rev')
880 884 store_update(updates, enable_statistics, 'repo_enable_statistics')
881 885 store_update(updates, enable_locking, 'repo_enable_locking')
882 886 store_update(updates, enable_downloads, 'repo_enable_downloads')
883 887
884 888 # extra fields
885 889 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
886 890 if fields:
887 891 updates.update(fields)
888 892
889 893 RepoModel().update(repo, **updates)
890 894 Session().commit()
891 895 return {
892 896 'msg': 'updated repo ID:%s %s' % (
893 897 repo.repo_id, repo.repo_name),
894 898 'repository': repo.get_api_data(
895 899 include_secrets=include_secrets)
896 900 }
897 901 except Exception:
898 902 log.exception(
899 903 u"Exception while trying to update the repository %s",
900 904 repoid)
901 905 raise JSONRPCError('failed to update repo `%s`' % repoid)
902 906
903 907
904 908 @jsonrpc_method()
905 909 def fork_repo(request, apiuser, repoid, fork_name,
906 910 owner=Optional(OAttr('apiuser')),
907 911 description=Optional(''), copy_permissions=Optional(False),
908 912 private=Optional(False), landing_rev=Optional('rev:tip')):
909 913 """
910 914 Creates a fork of the specified |repo|.
911 915
912 916 * If using |RCE| with Celery this will immediately return a success
913 917 message, even though the fork will be created asynchronously.
914 918
915 919 This command can only be run using an |authtoken| with fork
916 920 permissions on the |repo|.
917 921
918 922 :param apiuser: This is filled automatically from the |authtoken|.
919 923 :type apiuser: AuthUser
920 924 :param repoid: Set repository name or repository ID.
921 925 :type repoid: str or int
922 926 :param fork_name: Set the fork name.
923 927 :type fork_name: str
924 928 :param owner: Set the fork owner.
925 929 :type owner: str
926 930 :param description: Set the fork descripton.
927 931 :type description: str
928 932 :param copy_permissions: Copy permissions from parent |repo|. The
929 933 default is False.
930 934 :type copy_permissions: bool
931 935 :param private: Make the fork private. The default is False.
932 936 :type private: bool
933 937 :param landing_rev: Set the landing revision. The default is tip.
934 938
935 939 Example output:
936 940
937 941 .. code-block:: bash
938 942
939 943 id : <id_for_response>
940 944 api_key : "<api_key>"
941 945 args: {
942 946 "repoid" : "<reponame or repo_id>",
943 947 "fork_name": "<forkname>",
944 948 "owner": "<username or user_id = Optional(=apiuser)>",
945 949 "description": "<description>",
946 950 "copy_permissions": "<bool>",
947 951 "private": "<bool>",
948 952 "landing_rev": "<landing_rev>"
949 953 }
950 954
951 955 Example error output:
952 956
953 957 .. code-block:: bash
954 958
955 959 id : <id_given_in_input>
956 960 result: {
957 961 "msg": "Created fork of `<reponame>` as `<forkname>`",
958 962 "success": true,
959 963 "task": "<celery task id or None if done sync>"
960 964 }
961 965 error: null
962 966
963 967 """
964 968 if not has_superadmin_permission(apiuser):
965 969 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
966 970 raise JSONRPCForbidden()
967 971
968 972 repo = get_repo_or_error(repoid)
969 973 repo_name = repo.repo_name
970 974
971 975 (fork_name_cleaned,
972 976 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(
973 977 fork_name)
974 978
975 979 if not has_superadmin_permission(apiuser):
976 980 # check if we have at least read permission for
977 981 # this repo that we fork !
978 982 _perms = (
979 983 'repository.admin', 'repository.write', 'repository.read')
980 984 has_repo_permissions(apiuser, repoid, repo, _perms)
981 985
982 986 if not isinstance(owner, Optional):
983 987 # forbid setting owner for non super admins
984 988 raise JSONRPCError(
985 989 'Only RhodeCode admin can specify `owner` param'
986 990 )
987 991 # check if we have a create.repo permission if not maybe the parent
988 992 # group permission
989 993 if not HasPermissionAnyApi('hg.create.repository')(user=apiuser):
990 994 if parent_group_name:
991 995 repogroupid = parent_group_name
992 996 repo_group = get_repo_group_or_error(parent_group_name)
993 997
994 998 _perms = ('group.admin',)
995 999 if not HasRepoGroupPermissionAnyApi(*_perms)(
996 1000 user=apiuser, group_name=repo_group.group_name):
997 1001 raise JSONRPCError(
998 1002 'repository group `%s` does not exist' % (
999 1003 repogroupid,))
1000 1004 else:
1001 1005 raise JSONRPCForbidden()
1002 1006
1003 1007 _repo = RepoModel().get_by_repo_name(fork_name)
1004 1008 if _repo:
1005 1009 type_ = 'fork' if _repo.fork else 'repo'
1006 1010 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
1007 1011
1008 1012 if isinstance(owner, Optional):
1009 1013 owner = apiuser.user_id
1010 1014
1011 1015 owner = get_user_or_error(owner)
1012 1016
1013 1017 try:
1014 1018 # create structure of groups and return the last group
1015 1019 repo_group = map_groups(fork_name)
1016 1020 form_data = {
1017 1021 'repo_name': fork_name_cleaned,
1018 1022 'repo_name_full': fork_name,
1019 1023 'repo_group': repo_group.group_id if repo_group else None,
1020 1024 'repo_type': repo.repo_type,
1021 1025 'description': Optional.extract(description),
1022 1026 'private': Optional.extract(private),
1023 1027 'copy_permissions': Optional.extract(copy_permissions),
1024 1028 'landing_rev': Optional.extract(landing_rev),
1025 1029 'fork_parent_id': repo.repo_id,
1026 1030 }
1027 1031
1028 1032 task = RepoModel().create_fork(form_data, cur_user=owner)
1029 1033 # no commit, it's done in RepoModel, or async via celery
1030 1034 from celery.result import BaseAsyncResult
1031 1035 task_id = None
1032 1036 if isinstance(task, BaseAsyncResult):
1033 1037 task_id = task.task_id
1034 1038 return {
1035 1039 'msg': 'Created fork of `%s` as `%s`' % (
1036 1040 repo.repo_name, fork_name),
1037 1041 'success': True, # cannot return the repo data here since fork
1038 1042 # can be done async
1039 1043 'task': task_id
1040 1044 }
1041 1045 except Exception:
1042 1046 log.exception("Exception occurred while trying to fork a repo")
1043 1047 raise JSONRPCError(
1044 1048 'failed to fork repository `%s` as `%s`' % (
1045 1049 repo_name, fork_name))
1046 1050
1047 1051
1048 1052 @jsonrpc_method()
1049 1053 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1050 1054 """
1051 1055 Deletes a repository.
1052 1056
1053 1057 * When the `forks` parameter is set it's possible to detach or delete
1054 1058 forks of deleted repository.
1055 1059
1056 1060 This command can only be run using an |authtoken| with admin
1057 1061 permissions on the |repo|.
1058 1062
1059 1063 :param apiuser: This is filled automatically from the |authtoken|.
1060 1064 :type apiuser: AuthUser
1061 1065 :param repoid: Set the repository name or repository ID.
1062 1066 :type repoid: str or int
1063 1067 :param forks: Set to `detach` or `delete` forks from the |repo|.
1064 1068 :type forks: Optional(str)
1065 1069
1066 1070 Example error output:
1067 1071
1068 1072 .. code-block:: bash
1069 1073
1070 1074 id : <id_given_in_input>
1071 1075 result: {
1072 1076 "msg": "Deleted repository `<reponame>`",
1073 1077 "success": true
1074 1078 }
1075 1079 error: null
1076 1080 """
1077 1081
1078 1082 repo = get_repo_or_error(repoid)
1079 1083 if not has_superadmin_permission(apiuser):
1080 1084 _perms = ('repository.admin',)
1081 1085 has_repo_permissions(apiuser, repoid, repo, _perms)
1082 1086
1083 1087 try:
1084 1088 handle_forks = Optional.extract(forks)
1085 1089 _forks_msg = ''
1086 1090 _forks = [f for f in repo.forks]
1087 1091 if handle_forks == 'detach':
1088 1092 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1089 1093 elif handle_forks == 'delete':
1090 1094 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1091 1095 elif _forks:
1092 1096 raise JSONRPCError(
1093 1097 'Cannot delete `%s` it still contains attached forks' %
1094 1098 (repo.repo_name,)
1095 1099 )
1096 1100
1097 1101 RepoModel().delete(repo, forks=forks)
1098 1102 Session().commit()
1099 1103 return {
1100 1104 'msg': 'Deleted repository `%s`%s' % (
1101 1105 repo.repo_name, _forks_msg),
1102 1106 'success': True
1103 1107 }
1104 1108 except Exception:
1105 1109 log.exception("Exception occurred while trying to delete repo")
1106 1110 raise JSONRPCError(
1107 1111 'failed to delete repository `%s`' % (repo.repo_name,)
1108 1112 )
1109 1113
1110 1114
1111 1115 #TODO: marcink, change name ?
1112 1116 @jsonrpc_method()
1113 1117 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1114 1118 """
1115 1119 Invalidates the cache for the specified repository.
1116 1120
1117 1121 This command can only be run using an |authtoken| with admin rights to
1118 1122 the specified repository.
1119 1123
1120 1124 This command takes the following options:
1121 1125
1122 1126 :param apiuser: This is filled automatically from |authtoken|.
1123 1127 :type apiuser: AuthUser
1124 1128 :param repoid: Sets the repository name or repository ID.
1125 1129 :type repoid: str or int
1126 1130 :param delete_keys: This deletes the invalidated keys instead of
1127 1131 just flagging them.
1128 1132 :type delete_keys: Optional(``True`` | ``False``)
1129 1133
1130 1134 Example output:
1131 1135
1132 1136 .. code-block:: bash
1133 1137
1134 1138 id : <id_given_in_input>
1135 1139 result : {
1136 1140 'msg': Cache for repository `<repository name>` was invalidated,
1137 1141 'repository': <repository name>
1138 1142 }
1139 1143 error : null
1140 1144
1141 1145 Example error output:
1142 1146
1143 1147 .. code-block:: bash
1144 1148
1145 1149 id : <id_given_in_input>
1146 1150 result : null
1147 1151 error : {
1148 1152 'Error occurred during cache invalidation action'
1149 1153 }
1150 1154
1151 1155 """
1152 1156
1153 1157 repo = get_repo_or_error(repoid)
1154 1158 if not has_superadmin_permission(apiuser):
1155 1159 _perms = ('repository.admin', 'repository.write',)
1156 1160 has_repo_permissions(apiuser, repoid, repo, _perms)
1157 1161
1158 1162 delete = Optional.extract(delete_keys)
1159 1163 try:
1160 1164 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1161 1165 return {
1162 1166 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1163 1167 'repository': repo.repo_name
1164 1168 }
1165 1169 except Exception:
1166 1170 log.exception(
1167 1171 "Exception occurred while trying to invalidate repo cache")
1168 1172 raise JSONRPCError(
1169 1173 'Error occurred during cache invalidation action'
1170 1174 )
1171 1175
1172 1176
1173 1177 #TODO: marcink, change name ?
1174 1178 @jsonrpc_method()
1175 1179 def lock(request, apiuser, repoid, locked=Optional(None),
1176 1180 userid=Optional(OAttr('apiuser'))):
1177 1181 """
1178 1182 Sets the lock state of the specified |repo| by the given user.
1179 1183 From more information, see :ref:`repo-locking`.
1180 1184
1181 1185 * If the ``userid`` option is not set, the repository is locked to the
1182 1186 user who called the method.
1183 1187 * If the ``locked`` parameter is not set, the current lock state of the
1184 1188 repository is displayed.
1185 1189
1186 1190 This command can only be run using an |authtoken| with admin rights to
1187 1191 the specified repository.
1188 1192
1189 1193 This command takes the following options:
1190 1194
1191 1195 :param apiuser: This is filled automatically from the |authtoken|.
1192 1196 :type apiuser: AuthUser
1193 1197 :param repoid: Sets the repository name or repository ID.
1194 1198 :type repoid: str or int
1195 1199 :param locked: Sets the lock state.
1196 1200 :type locked: Optional(``True`` | ``False``)
1197 1201 :param userid: Set the repository lock to this user.
1198 1202 :type userid: Optional(str or int)
1199 1203
1200 1204 Example error output:
1201 1205
1202 1206 .. code-block:: bash
1203 1207
1204 1208 id : <id_given_in_input>
1205 1209 result : {
1206 1210 'repo': '<reponame>',
1207 1211 'locked': <bool: lock state>,
1208 1212 'locked_since': <int: lock timestamp>,
1209 1213 'locked_by': <username of person who made the lock>,
1210 1214 'lock_reason': <str: reason for locking>,
1211 1215 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1212 1216 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1213 1217 or
1214 1218 'msg': 'Repo `<repository name>` not locked.'
1215 1219 or
1216 1220 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1217 1221 }
1218 1222 error : null
1219 1223
1220 1224 Example error output:
1221 1225
1222 1226 .. code-block:: bash
1223 1227
1224 1228 id : <id_given_in_input>
1225 1229 result : null
1226 1230 error : {
1227 1231 'Error occurred locking repository `<reponame>`
1228 1232 }
1229 1233 """
1230 1234
1231 1235 repo = get_repo_or_error(repoid)
1232 1236 if not has_superadmin_permission(apiuser):
1233 1237 # check if we have at least write permission for this repo !
1234 1238 _perms = ('repository.admin', 'repository.write',)
1235 1239 has_repo_permissions(apiuser, repoid, repo, _perms)
1236 1240
1237 1241 # make sure normal user does not pass someone else userid,
1238 1242 # he is not allowed to do that
1239 1243 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1240 1244 raise JSONRPCError('userid is not the same as your user')
1241 1245
1242 1246 if isinstance(userid, Optional):
1243 1247 userid = apiuser.user_id
1244 1248
1245 1249 user = get_user_or_error(userid)
1246 1250
1247 1251 if isinstance(locked, Optional):
1248 1252 lockobj = repo.locked
1249 1253
1250 1254 if lockobj[0] is None:
1251 1255 _d = {
1252 1256 'repo': repo.repo_name,
1253 1257 'locked': False,
1254 1258 'locked_since': None,
1255 1259 'locked_by': None,
1256 1260 'lock_reason': None,
1257 1261 'lock_state_changed': False,
1258 1262 'msg': 'Repo `%s` not locked.' % repo.repo_name
1259 1263 }
1260 1264 return _d
1261 1265 else:
1262 1266 _user_id, _time, _reason = lockobj
1263 1267 lock_user = get_user_or_error(userid)
1264 1268 _d = {
1265 1269 'repo': repo.repo_name,
1266 1270 'locked': True,
1267 1271 'locked_since': _time,
1268 1272 'locked_by': lock_user.username,
1269 1273 'lock_reason': _reason,
1270 1274 'lock_state_changed': False,
1271 1275 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1272 1276 % (repo.repo_name, lock_user.username,
1273 1277 json.dumps(time_to_datetime(_time))))
1274 1278 }
1275 1279 return _d
1276 1280
1277 1281 # force locked state through a flag
1278 1282 else:
1279 1283 locked = str2bool(locked)
1280 1284 lock_reason = Repository.LOCK_API
1281 1285 try:
1282 1286 if locked:
1283 1287 lock_time = time.time()
1284 1288 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1285 1289 else:
1286 1290 lock_time = None
1287 1291 Repository.unlock(repo)
1288 1292 _d = {
1289 1293 'repo': repo.repo_name,
1290 1294 'locked': locked,
1291 1295 'locked_since': lock_time,
1292 1296 'locked_by': user.username,
1293 1297 'lock_reason': lock_reason,
1294 1298 'lock_state_changed': True,
1295 1299 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1296 1300 % (user.username, repo.repo_name, locked))
1297 1301 }
1298 1302 return _d
1299 1303 except Exception:
1300 1304 log.exception(
1301 1305 "Exception occurred while trying to lock repository")
1302 1306 raise JSONRPCError(
1303 1307 'Error occurred locking repository `%s`' % repo.repo_name
1304 1308 )
1305 1309
1306 1310
1307 1311 @jsonrpc_method()
1308 1312 def comment_commit(
1309 1313 request, apiuser, repoid, commit_id, message,
1310 1314 userid=Optional(OAttr('apiuser')), status=Optional(None)):
1311 1315 """
1312 1316 Set a commit comment, and optionally change the status of the commit.
1313 1317
1314 1318 :param apiuser: This is filled automatically from the |authtoken|.
1315 1319 :type apiuser: AuthUser
1316 1320 :param repoid: Set the repository name or repository ID.
1317 1321 :type repoid: str or int
1318 1322 :param commit_id: Specify the commit_id for which to set a comment.
1319 1323 :type commit_id: str
1320 1324 :param message: The comment text.
1321 1325 :type message: str
1322 1326 :param userid: Set the user name of the comment creator.
1323 1327 :type userid: Optional(str or int)
1324 1328 :param status: status, one of 'not_reviewed', 'approved', 'rejected',
1325 1329 'under_review'
1326 1330 :type status: str
1327 1331
1328 1332 Example error output:
1329 1333
1330 1334 .. code-block:: json
1331 1335
1332 1336 {
1333 1337 "id" : <id_given_in_input>,
1334 1338 "result" : {
1335 1339 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1336 1340 "status_change": null or <status>,
1337 1341 "success": true
1338 1342 },
1339 1343 "error" : null
1340 1344 }
1341 1345
1342 1346 """
1343 1347 repo = get_repo_or_error(repoid)
1344 1348 if not has_superadmin_permission(apiuser):
1345 1349 _perms = ('repository.read', 'repository.write', 'repository.admin')
1346 1350 has_repo_permissions(apiuser, repoid, repo, _perms)
1347 1351
1348 1352 if isinstance(userid, Optional):
1349 1353 userid = apiuser.user_id
1350 1354
1351 1355 user = get_user_or_error(userid)
1352 1356 status = Optional.extract(status)
1353 1357
1354 1358 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1355 1359 if status and status not in allowed_statuses:
1356 1360 raise JSONRPCError('Bad status, must be on '
1357 1361 'of %s got %s' % (allowed_statuses, status,))
1358 1362
1359 1363 try:
1360 1364 rc_config = SettingsModel().get_all_settings()
1361 1365 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1362 1366
1363 1367 comm = ChangesetCommentsModel().create(
1364 1368 message, repo, user, revision=commit_id, status_change=status,
1365 1369 renderer=renderer)
1366 1370 if status:
1367 1371 # also do a status change
1368 1372 try:
1369 1373 ChangesetStatusModel().set_status(
1370 1374 repo, status, user, comm, revision=commit_id,
1371 1375 dont_allow_on_closed_pull_request=True
1372 1376 )
1373 1377 except StatusChangeOnClosedPullRequestError:
1374 1378 log.exception(
1375 1379 "Exception occurred while trying to change repo commit status")
1376 1380 msg = ('Changing status on a changeset associated with '
1377 1381 'a closed pull request is not allowed')
1378 1382 raise JSONRPCError(msg)
1379 1383
1380 1384 Session().commit()
1381 1385 return {
1382 1386 'msg': (
1383 1387 'Commented on commit `%s` for repository `%s`' % (
1384 1388 comm.revision, repo.repo_name)),
1385 1389 'status_change': status,
1386 1390 'success': True,
1387 1391 }
1388 1392 except JSONRPCError:
1389 1393 # catch any inside errors, and re-raise them to prevent from
1390 1394 # below global catch to silence them
1391 1395 raise
1392 1396 except Exception:
1393 1397 log.exception("Exception occurred while trying to comment on commit")
1394 1398 raise JSONRPCError(
1395 1399 'failed to set comment on repository `%s`' % (repo.repo_name,)
1396 1400 )
1397 1401
1398 1402
1399 1403 @jsonrpc_method()
1400 1404 def grant_user_permission(request, apiuser, repoid, userid, perm):
1401 1405 """
1402 1406 Grant permissions for the specified user on the given repository,
1403 1407 or update existing permissions if found.
1404 1408
1405 1409 This command can only be run using an |authtoken| with admin
1406 1410 permissions on the |repo|.
1407 1411
1408 1412 :param apiuser: This is filled automatically from the |authtoken|.
1409 1413 :type apiuser: AuthUser
1410 1414 :param repoid: Set the repository name or repository ID.
1411 1415 :type repoid: str or int
1412 1416 :param userid: Set the user name.
1413 1417 :type userid: str
1414 1418 :param perm: Set the user permissions, using the following format
1415 1419 ``(repository.(none|read|write|admin))``
1416 1420 :type perm: str
1417 1421
1418 1422 Example output:
1419 1423
1420 1424 .. code-block:: bash
1421 1425
1422 1426 id : <id_given_in_input>
1423 1427 result: {
1424 1428 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1425 1429 "success": true
1426 1430 }
1427 1431 error: null
1428 1432 """
1429 1433
1430 1434 repo = get_repo_or_error(repoid)
1431 1435 user = get_user_or_error(userid)
1432 1436 perm = get_perm_or_error(perm)
1433 1437 if not has_superadmin_permission(apiuser):
1434 1438 _perms = ('repository.admin',)
1435 1439 has_repo_permissions(apiuser, repoid, repo, _perms)
1436 1440
1437 1441 try:
1438 1442
1439 1443 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1440 1444
1441 1445 Session().commit()
1442 1446 return {
1443 1447 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1444 1448 perm.permission_name, user.username, repo.repo_name
1445 1449 ),
1446 1450 'success': True
1447 1451 }
1448 1452 except Exception:
1449 1453 log.exception(
1450 1454 "Exception occurred while trying edit permissions for repo")
1451 1455 raise JSONRPCError(
1452 1456 'failed to edit permission for user: `%s` in repo: `%s`' % (
1453 1457 userid, repoid
1454 1458 )
1455 1459 )
1456 1460
1457 1461
1458 1462 @jsonrpc_method()
1459 1463 def revoke_user_permission(request, apiuser, repoid, userid):
1460 1464 """
1461 1465 Revoke permission for a user on the specified repository.
1462 1466
1463 1467 This command can only be run using an |authtoken| with admin
1464 1468 permissions on the |repo|.
1465 1469
1466 1470 :param apiuser: This is filled automatically from the |authtoken|.
1467 1471 :type apiuser: AuthUser
1468 1472 :param repoid: Set the repository name or repository ID.
1469 1473 :type repoid: str or int
1470 1474 :param userid: Set the user name of revoked user.
1471 1475 :type userid: str or int
1472 1476
1473 1477 Example error output:
1474 1478
1475 1479 .. code-block:: bash
1476 1480
1477 1481 id : <id_given_in_input>
1478 1482 result: {
1479 1483 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1480 1484 "success": true
1481 1485 }
1482 1486 error: null
1483 1487 """
1484 1488
1485 1489 repo = get_repo_or_error(repoid)
1486 1490 user = get_user_or_error(userid)
1487 1491 if not has_superadmin_permission(apiuser):
1488 1492 _perms = ('repository.admin',)
1489 1493 has_repo_permissions(apiuser, repoid, repo, _perms)
1490 1494
1491 1495 try:
1492 1496 RepoModel().revoke_user_permission(repo=repo, user=user)
1493 1497 Session().commit()
1494 1498 return {
1495 1499 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1496 1500 user.username, repo.repo_name
1497 1501 ),
1498 1502 'success': True
1499 1503 }
1500 1504 except Exception:
1501 1505 log.exception(
1502 1506 "Exception occurred while trying revoke permissions to repo")
1503 1507 raise JSONRPCError(
1504 1508 'failed to edit permission for user: `%s` in repo: `%s`' % (
1505 1509 userid, repoid
1506 1510 )
1507 1511 )
1508 1512
1509 1513
1510 1514 @jsonrpc_method()
1511 1515 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1512 1516 """
1513 1517 Grant permission for a user group on the specified repository,
1514 1518 or update existing permissions.
1515 1519
1516 1520 This command can only be run using an |authtoken| with admin
1517 1521 permissions on the |repo|.
1518 1522
1519 1523 :param apiuser: This is filled automatically from the |authtoken|.
1520 1524 :type apiuser: AuthUser
1521 1525 :param repoid: Set the repository name or repository ID.
1522 1526 :type repoid: str or int
1523 1527 :param usergroupid: Specify the ID of the user group.
1524 1528 :type usergroupid: str or int
1525 1529 :param perm: Set the user group permissions using the following
1526 1530 format: (repository.(none|read|write|admin))
1527 1531 :type perm: str
1528 1532
1529 1533 Example output:
1530 1534
1531 1535 .. code-block:: bash
1532 1536
1533 1537 id : <id_given_in_input>
1534 1538 result : {
1535 1539 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1536 1540 "success": true
1537 1541
1538 1542 }
1539 1543 error : null
1540 1544
1541 1545 Example error output:
1542 1546
1543 1547 .. code-block:: bash
1544 1548
1545 1549 id : <id_given_in_input>
1546 1550 result : null
1547 1551 error : {
1548 1552 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1549 1553 }
1550 1554
1551 1555 """
1552 1556
1553 1557 repo = get_repo_or_error(repoid)
1554 1558 perm = get_perm_or_error(perm)
1555 1559 if not has_superadmin_permission(apiuser):
1556 1560 _perms = ('repository.admin',)
1557 1561 has_repo_permissions(apiuser, repoid, repo, _perms)
1558 1562
1559 1563 user_group = get_user_group_or_error(usergroupid)
1560 1564 if not has_superadmin_permission(apiuser):
1561 1565 # check if we have at least read permission for this user group !
1562 1566 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1563 1567 if not HasUserGroupPermissionAnyApi(*_perms)(
1564 1568 user=apiuser, user_group_name=user_group.users_group_name):
1565 1569 raise JSONRPCError(
1566 1570 'user group `%s` does not exist' % (usergroupid,))
1567 1571
1568 1572 try:
1569 1573 RepoModel().grant_user_group_permission(
1570 1574 repo=repo, group_name=user_group, perm=perm)
1571 1575
1572 1576 Session().commit()
1573 1577 return {
1574 1578 'msg': 'Granted perm: `%s` for user group: `%s` in '
1575 1579 'repo: `%s`' % (
1576 1580 perm.permission_name, user_group.users_group_name,
1577 1581 repo.repo_name
1578 1582 ),
1579 1583 'success': True
1580 1584 }
1581 1585 except Exception:
1582 1586 log.exception(
1583 1587 "Exception occurred while trying change permission on repo")
1584 1588 raise JSONRPCError(
1585 1589 'failed to edit permission for user group: `%s` in '
1586 1590 'repo: `%s`' % (
1587 1591 usergroupid, repo.repo_name
1588 1592 )
1589 1593 )
1590 1594
1591 1595
1592 1596 @jsonrpc_method()
1593 1597 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1594 1598 """
1595 1599 Revoke the permissions of a user group on a given repository.
1596 1600
1597 1601 This command can only be run using an |authtoken| with admin
1598 1602 permissions on the |repo|.
1599 1603
1600 1604 :param apiuser: This is filled automatically from the |authtoken|.
1601 1605 :type apiuser: AuthUser
1602 1606 :param repoid: Set the repository name or repository ID.
1603 1607 :type repoid: str or int
1604 1608 :param usergroupid: Specify the user group ID.
1605 1609 :type usergroupid: str or int
1606 1610
1607 1611 Example output:
1608 1612
1609 1613 .. code-block:: bash
1610 1614
1611 1615 id : <id_given_in_input>
1612 1616 result: {
1613 1617 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1614 1618 "success": true
1615 1619 }
1616 1620 error: null
1617 1621 """
1618 1622
1619 1623 repo = get_repo_or_error(repoid)
1620 1624 if not has_superadmin_permission(apiuser):
1621 1625 _perms = ('repository.admin',)
1622 1626 has_repo_permissions(apiuser, repoid, repo, _perms)
1623 1627
1624 1628 user_group = get_user_group_or_error(usergroupid)
1625 1629 if not has_superadmin_permission(apiuser):
1626 1630 # check if we have at least read permission for this user group !
1627 1631 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1628 1632 if not HasUserGroupPermissionAnyApi(*_perms)(
1629 1633 user=apiuser, user_group_name=user_group.users_group_name):
1630 1634 raise JSONRPCError(
1631 1635 'user group `%s` does not exist' % (usergroupid,))
1632 1636
1633 1637 try:
1634 1638 RepoModel().revoke_user_group_permission(
1635 1639 repo=repo, group_name=user_group)
1636 1640
1637 1641 Session().commit()
1638 1642 return {
1639 1643 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1640 1644 user_group.users_group_name, repo.repo_name
1641 1645 ),
1642 1646 'success': True
1643 1647 }
1644 1648 except Exception:
1645 1649 log.exception("Exception occurred while trying revoke "
1646 1650 "user group permission on repo")
1647 1651 raise JSONRPCError(
1648 1652 'failed to edit permission for user group: `%s` in '
1649 1653 'repo: `%s`' % (
1650 1654 user_group.users_group_name, repo.repo_name
1651 1655 )
1652 1656 )
1653 1657
1654 1658
1655 1659 @jsonrpc_method()
1656 1660 def pull(request, apiuser, repoid):
1657 1661 """
1658 1662 Triggers a pull on the given repository from a remote location. You
1659 1663 can use this to keep remote repositories up-to-date.
1660 1664
1661 1665 This command can only be run using an |authtoken| with admin
1662 1666 rights to the specified repository. For more information,
1663 1667 see :ref:`config-token-ref`.
1664 1668
1665 1669 This command takes the following options:
1666 1670
1667 1671 :param apiuser: This is filled automatically from the |authtoken|.
1668 1672 :type apiuser: AuthUser
1669 1673 :param repoid: The repository name or repository ID.
1670 1674 :type repoid: str or int
1671 1675
1672 1676 Example output:
1673 1677
1674 1678 .. code-block:: bash
1675 1679
1676 1680 id : <id_given_in_input>
1677 1681 result : {
1678 1682 "msg": "Pulled from `<repository name>`"
1679 1683 "repository": "<repository name>"
1680 1684 }
1681 1685 error : null
1682 1686
1683 1687 Example error output:
1684 1688
1685 1689 .. code-block:: bash
1686 1690
1687 1691 id : <id_given_in_input>
1688 1692 result : null
1689 1693 error : {
1690 1694 "Unable to pull changes from `<reponame>`"
1691 1695 }
1692 1696
1693 1697 """
1694 1698
1695 1699 repo = get_repo_or_error(repoid)
1696 1700 if not has_superadmin_permission(apiuser):
1697 1701 _perms = ('repository.admin',)
1698 1702 has_repo_permissions(apiuser, repoid, repo, _perms)
1699 1703
1700 1704 try:
1701 1705 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1702 1706 return {
1703 1707 'msg': 'Pulled from `%s`' % repo.repo_name,
1704 1708 'repository': repo.repo_name
1705 1709 }
1706 1710 except Exception:
1707 1711 log.exception("Exception occurred while trying to "
1708 1712 "pull changes from remote location")
1709 1713 raise JSONRPCError(
1710 1714 'Unable to pull changes from `%s`' % repo.repo_name
1711 1715 )
1712 1716
1713 1717
1714 1718 @jsonrpc_method()
1715 1719 def strip(request, apiuser, repoid, revision, branch):
1716 1720 """
1717 1721 Strips the given revision from the specified repository.
1718 1722
1719 1723 * This will remove the revision and all of its decendants.
1720 1724
1721 1725 This command can only be run using an |authtoken| with admin rights to
1722 1726 the specified repository.
1723 1727
1724 1728 This command takes the following options:
1725 1729
1726 1730 :param apiuser: This is filled automatically from the |authtoken|.
1727 1731 :type apiuser: AuthUser
1728 1732 :param repoid: The repository name or repository ID.
1729 1733 :type repoid: str or int
1730 1734 :param revision: The revision you wish to strip.
1731 1735 :type revision: str
1732 1736 :param branch: The branch from which to strip the revision.
1733 1737 :type branch: str
1734 1738
1735 1739 Example output:
1736 1740
1737 1741 .. code-block:: bash
1738 1742
1739 1743 id : <id_given_in_input>
1740 1744 result : {
1741 1745 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1742 1746 "repository": "<repository name>"
1743 1747 }
1744 1748 error : null
1745 1749
1746 1750 Example error output:
1747 1751
1748 1752 .. code-block:: bash
1749 1753
1750 1754 id : <id_given_in_input>
1751 1755 result : null
1752 1756 error : {
1753 1757 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1754 1758 }
1755 1759
1756 1760 """
1757 1761
1758 1762 repo = get_repo_or_error(repoid)
1759 1763 if not has_superadmin_permission(apiuser):
1760 1764 _perms = ('repository.admin',)
1761 1765 has_repo_permissions(apiuser, repoid, repo, _perms)
1762 1766
1763 1767 try:
1764 1768 ScmModel().strip(repo, revision, branch)
1765 1769 return {
1766 1770 'msg': 'Stripped commit %s from repo `%s`' % (
1767 1771 revision, repo.repo_name),
1768 1772 'repository': repo.repo_name
1769 1773 }
1770 1774 except Exception:
1771 1775 log.exception("Exception while trying to strip")
1772 1776 raise JSONRPCError(
1773 1777 'Unable to strip commit %s from repo `%s`' % (
1774 1778 revision, repo.repo_name)
1775 1779 )
1776 1780
1777 1781
1778 1782 @jsonrpc_method()
1779 1783 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1780 1784 """
1781 1785 Returns all settings for a repository. If key is given it only returns the
1782 1786 setting identified by the key or null.
1783 1787
1784 1788 :param apiuser: This is filled automatically from the |authtoken|.
1785 1789 :type apiuser: AuthUser
1786 1790 :param repoid: The repository name or repository id.
1787 1791 :type repoid: str or int
1788 1792 :param key: Key of the setting to return.
1789 1793 :type: key: Optional(str)
1790 1794
1791 1795 Example output:
1792 1796
1793 1797 .. code-block:: bash
1794 1798
1795 1799 {
1796 1800 "error": null,
1797 1801 "id": 237,
1798 1802 "result": {
1799 1803 "extensions_largefiles": true,
1800 1804 "hooks_changegroup_push_logger": true,
1801 1805 "hooks_changegroup_repo_size": false,
1802 1806 "hooks_outgoing_pull_logger": true,
1803 1807 "phases_publish": "True",
1804 1808 "rhodecode_hg_use_rebase_for_merging": true,
1805 1809 "rhodecode_pr_merge_enabled": true,
1806 1810 "rhodecode_use_outdated_comments": true
1807 1811 }
1808 1812 }
1809 1813 """
1810 1814
1811 1815 # Restrict access to this api method to admins only.
1812 1816 if not has_superadmin_permission(apiuser):
1813 1817 raise JSONRPCForbidden()
1814 1818
1815 1819 try:
1816 1820 repo = get_repo_or_error(repoid)
1817 1821 settings_model = VcsSettingsModel(repo=repo)
1818 1822 settings = settings_model.get_global_settings()
1819 1823 settings.update(settings_model.get_repo_settings())
1820 1824
1821 1825 # If only a single setting is requested fetch it from all settings.
1822 1826 key = Optional.extract(key)
1823 1827 if key is not None:
1824 1828 settings = settings.get(key, None)
1825 1829 except Exception:
1826 1830 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1827 1831 log.exception(msg)
1828 1832 raise JSONRPCError(msg)
1829 1833
1830 1834 return settings
1831 1835
1832 1836
1833 1837 @jsonrpc_method()
1834 1838 def set_repo_settings(request, apiuser, repoid, settings):
1835 1839 """
1836 1840 Update repository settings. Returns true on success.
1837 1841
1838 1842 :param apiuser: This is filled automatically from the |authtoken|.
1839 1843 :type apiuser: AuthUser
1840 1844 :param repoid: The repository name or repository id.
1841 1845 :type repoid: str or int
1842 1846 :param settings: The new settings for the repository.
1843 1847 :type: settings: dict
1844 1848
1845 1849 Example output:
1846 1850
1847 1851 .. code-block:: bash
1848 1852
1849 1853 {
1850 1854 "error": null,
1851 1855 "id": 237,
1852 1856 "result": true
1853 1857 }
1854 1858 """
1855 1859 # Restrict access to this api method to admins only.
1856 1860 if not has_superadmin_permission(apiuser):
1857 1861 raise JSONRPCForbidden()
1858 1862
1859 1863 if type(settings) is not dict:
1860 1864 raise JSONRPCError('Settings have to be a JSON Object.')
1861 1865
1862 1866 try:
1863 1867 settings_model = VcsSettingsModel(repo=repoid)
1864 1868
1865 1869 # Merge global, repo and incoming settings.
1866 1870 new_settings = settings_model.get_global_settings()
1867 1871 new_settings.update(settings_model.get_repo_settings())
1868 1872 new_settings.update(settings)
1869 1873
1870 1874 # Update the settings.
1871 1875 inherit_global_settings = new_settings.get(
1872 1876 'inherit_global_settings', False)
1873 1877 settings_model.create_or_update_repo_settings(
1874 1878 new_settings, inherit_global_settings=inherit_global_settings)
1875 1879 Session().commit()
1876 1880 except Exception:
1877 1881 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1878 1882 log.exception(msg)
1879 1883 raise JSONRPCError(msg)
1880 1884
1881 1885 # Indicate success.
1882 1886 return True
@@ -1,1099 +1,1102 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 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 Scm model for RhodeCode
23 23 """
24 24
25 25 import os.path
26 26 import re
27 27 import sys
28 28 import time
29 29 import traceback
30 30 import logging
31 31 import cStringIO
32 32 import pkg_resources
33 33
34 34 import pylons
35 35 from pylons.i18n.translation import _
36 36 from sqlalchemy import func
37 37 from zope.cachedescriptors.property import Lazy as LazyProperty
38 38
39 39 import rhodecode
40 40 from rhodecode.lib.vcs import get_backend
41 41 from rhodecode.lib.vcs.exceptions import RepositoryError, NodeNotChangedError
42 42 from rhodecode.lib.vcs.nodes import FileNode
43 43 from rhodecode.lib.vcs.backends.base import EmptyCommit
44 44 from rhodecode.lib import helpers as h
45 45
46 46 from rhodecode.lib.auth import (
47 47 HasRepoPermissionAny, HasRepoGroupPermissionAny,
48 48 HasUserGroupPermissionAny)
49 49 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
50 50 from rhodecode.lib import hooks_utils, caches
51 51 from rhodecode.lib.utils import (
52 52 get_filesystem_repos, action_logger, make_db_config)
53 53 from rhodecode.lib.utils2 import (
54 54 safe_str, safe_unicode, get_server_url, md5)
55 55 from rhodecode.model import BaseModel
56 56 from rhodecode.model.db import (
57 57 Repository, CacheKey, UserFollowing, UserLog, User, RepoGroup,
58 58 PullRequest, DbMigrateVersion)
59 59 from rhodecode.model.settings import VcsSettingsModel
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63
64 64 class UserTemp(object):
65 65 def __init__(self, user_id):
66 66 self.user_id = user_id
67 67
68 68 def __repr__(self):
69 69 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
70 70
71 71
72 72 class RepoTemp(object):
73 73 def __init__(self, repo_id):
74 74 self.repo_id = repo_id
75 75
76 76 def __repr__(self):
77 77 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
78 78
79 79
80 80 class SimpleCachedRepoList(object):
81 81 """
82 82 Lighter version of of iteration of repos without the scm initialisation,
83 83 and with cache usage
84 84 """
85 85 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
86 86 self.db_repo_list = db_repo_list
87 87 self.repos_path = repos_path
88 88 self.order_by = order_by
89 89 self.reversed = (order_by or '').startswith('-')
90 90 if not perm_set:
91 91 perm_set = ['repository.read', 'repository.write',
92 92 'repository.admin']
93 93 self.perm_set = perm_set
94 94
95 95 def __len__(self):
96 96 return len(self.db_repo_list)
97 97
98 98 def __repr__(self):
99 99 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
100 100
101 101 def __iter__(self):
102 102 for dbr in self.db_repo_list:
103 103 # check permission at this level
104 104 has_perm = HasRepoPermissionAny(*self.perm_set)(
105 105 dbr.repo_name, 'SimpleCachedRepoList check')
106 106 if not has_perm:
107 107 continue
108 108
109 109 tmp_d = {
110 110 'name': dbr.repo_name,
111 111 'dbrepo': dbr.get_dict(),
112 112 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
113 113 }
114 114 yield tmp_d
115 115
116 116
117 117 class _PermCheckIterator(object):
118 118
119 119 def __init__(
120 120 self, obj_list, obj_attr, perm_set, perm_checker,
121 121 extra_kwargs=None):
122 122 """
123 123 Creates iterator from given list of objects, additionally
124 124 checking permission for them from perm_set var
125 125
126 126 :param obj_list: list of db objects
127 127 :param obj_attr: attribute of object to pass into perm_checker
128 128 :param perm_set: list of permissions to check
129 129 :param perm_checker: callable to check permissions against
130 130 """
131 131 self.obj_list = obj_list
132 132 self.obj_attr = obj_attr
133 133 self.perm_set = perm_set
134 134 self.perm_checker = perm_checker
135 135 self.extra_kwargs = extra_kwargs or {}
136 136
137 137 def __len__(self):
138 138 return len(self.obj_list)
139 139
140 140 def __repr__(self):
141 141 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
142 142
143 143 def __iter__(self):
144 144 checker = self.perm_checker(*self.perm_set)
145 145 for db_obj in self.obj_list:
146 146 # check permission at this level
147 147 name = getattr(db_obj, self.obj_attr, None)
148 148 if not checker(name, self.__class__.__name__, **self.extra_kwargs):
149 149 continue
150 150
151 151 yield db_obj
152 152
153 153
154 154 class RepoList(_PermCheckIterator):
155 155
156 156 def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
157 157 if not perm_set:
158 158 perm_set = [
159 159 'repository.read', 'repository.write', 'repository.admin']
160 160
161 161 super(RepoList, self).__init__(
162 162 obj_list=db_repo_list,
163 163 obj_attr='repo_name', perm_set=perm_set,
164 164 perm_checker=HasRepoPermissionAny,
165 165 extra_kwargs=extra_kwargs)
166 166
167 167
168 168 class RepoGroupList(_PermCheckIterator):
169 169
170 170 def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
171 171 if not perm_set:
172 172 perm_set = ['group.read', 'group.write', 'group.admin']
173 173
174 174 super(RepoGroupList, self).__init__(
175 175 obj_list=db_repo_group_list,
176 176 obj_attr='group_name', perm_set=perm_set,
177 177 perm_checker=HasRepoGroupPermissionAny,
178 178 extra_kwargs=extra_kwargs)
179 179
180 180
181 181 class UserGroupList(_PermCheckIterator):
182 182
183 183 def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
184 184 if not perm_set:
185 185 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
186 186
187 187 super(UserGroupList, self).__init__(
188 188 obj_list=db_user_group_list,
189 189 obj_attr='users_group_name', perm_set=perm_set,
190 190 perm_checker=HasUserGroupPermissionAny,
191 191 extra_kwargs=extra_kwargs)
192 192
193 193
194 194 class ScmModel(BaseModel):
195 195 """
196 196 Generic Scm Model
197 197 """
198 198
199 199 @LazyProperty
200 200 def repos_path(self):
201 201 """
202 202 Gets the repositories root path from database
203 203 """
204 204
205 205 settings_model = VcsSettingsModel(sa=self.sa)
206 206 return settings_model.get_repos_location()
207 207
208 208 def repo_scan(self, repos_path=None):
209 209 """
210 210 Listing of repositories in given path. This path should not be a
211 211 repository itself. Return a dictionary of repository objects
212 212
213 213 :param repos_path: path to directory containing repositories
214 214 """
215 215
216 216 if repos_path is None:
217 217 repos_path = self.repos_path
218 218
219 219 log.info('scanning for repositories in %s', repos_path)
220 220
221 221 config = make_db_config()
222 222 config.set('extensions', 'largefiles', '')
223 223 repos = {}
224 224
225 225 for name, path in get_filesystem_repos(repos_path, recursive=True):
226 226 # name need to be decomposed and put back together using the /
227 227 # since this is internal storage separator for rhodecode
228 228 name = Repository.normalize_repo_name(name)
229 229
230 230 try:
231 231 if name in repos:
232 232 raise RepositoryError('Duplicate repository name %s '
233 233 'found in %s' % (name, path))
234 234 elif path[0] in rhodecode.BACKENDS:
235 235 klass = get_backend(path[0])
236 236 repos[name] = klass(path[1], config=config)
237 237 except OSError:
238 238 continue
239 239 log.debug('found %s paths with repositories', len(repos))
240 240 return repos
241 241
242 242 def get_repos(self, all_repos=None, sort_key=None):
243 243 """
244 244 Get all repositories from db and for each repo create it's
245 245 backend instance and fill that backed with information from database
246 246
247 247 :param all_repos: list of repository names as strings
248 248 give specific repositories list, good for filtering
249 249
250 250 :param sort_key: initial sorting of repositories
251 251 """
252 252 if all_repos is None:
253 253 all_repos = self.sa.query(Repository)\
254 254 .filter(Repository.group_id == None)\
255 255 .order_by(func.lower(Repository.repo_name)).all()
256 256 repo_iter = SimpleCachedRepoList(
257 257 all_repos, repos_path=self.repos_path, order_by=sort_key)
258 258 return repo_iter
259 259
260 260 def get_repo_groups(self, all_groups=None):
261 261 if all_groups is None:
262 262 all_groups = RepoGroup.query()\
263 263 .filter(RepoGroup.group_parent_id == None).all()
264 264 return [x for x in RepoGroupList(all_groups)]
265 265
266 266 def mark_for_invalidation(self, repo_name, delete=False):
267 267 """
268 268 Mark caches of this repo invalid in the database. `delete` flag
269 269 removes the cache entries
270 270
271 271 :param repo_name: the repo_name for which caches should be marked
272 272 invalid, or deleted
273 273 :param delete: delete the entry keys instead of setting bool
274 274 flag on them
275 275 """
276 276 CacheKey.set_invalidate(repo_name, delete=delete)
277 277 repo = Repository.get_by_repo_name(repo_name)
278 278
279 279 if repo:
280 280 config = repo._config
281 281 config.set('extensions', 'largefiles', '')
282 282 repo.update_commit_cache(config=config, cs_cache=None)
283 283 caches.clear_repo_caches(repo_name)
284 284
285 285 def toggle_following_repo(self, follow_repo_id, user_id):
286 286
287 287 f = self.sa.query(UserFollowing)\
288 288 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
289 289 .filter(UserFollowing.user_id == user_id).scalar()
290 290
291 291 if f is not None:
292 292 try:
293 293 self.sa.delete(f)
294 294 action_logger(UserTemp(user_id),
295 295 'stopped_following_repo',
296 296 RepoTemp(follow_repo_id))
297 297 return
298 298 except Exception:
299 299 log.error(traceback.format_exc())
300 300 raise
301 301
302 302 try:
303 303 f = UserFollowing()
304 304 f.user_id = user_id
305 305 f.follows_repo_id = follow_repo_id
306 306 self.sa.add(f)
307 307
308 308 action_logger(UserTemp(user_id),
309 309 'started_following_repo',
310 310 RepoTemp(follow_repo_id))
311 311 except Exception:
312 312 log.error(traceback.format_exc())
313 313 raise
314 314
315 315 def toggle_following_user(self, follow_user_id, user_id):
316 316 f = self.sa.query(UserFollowing)\
317 317 .filter(UserFollowing.follows_user_id == follow_user_id)\
318 318 .filter(UserFollowing.user_id == user_id).scalar()
319 319
320 320 if f is not None:
321 321 try:
322 322 self.sa.delete(f)
323 323 return
324 324 except Exception:
325 325 log.error(traceback.format_exc())
326 326 raise
327 327
328 328 try:
329 329 f = UserFollowing()
330 330 f.user_id = user_id
331 331 f.follows_user_id = follow_user_id
332 332 self.sa.add(f)
333 333 except Exception:
334 334 log.error(traceback.format_exc())
335 335 raise
336 336
337 337 def is_following_repo(self, repo_name, user_id, cache=False):
338 338 r = self.sa.query(Repository)\
339 339 .filter(Repository.repo_name == repo_name).scalar()
340 340
341 341 f = self.sa.query(UserFollowing)\
342 342 .filter(UserFollowing.follows_repository == r)\
343 343 .filter(UserFollowing.user_id == user_id).scalar()
344 344
345 345 return f is not None
346 346
347 347 def is_following_user(self, username, user_id, cache=False):
348 348 u = User.get_by_username(username)
349 349
350 350 f = self.sa.query(UserFollowing)\
351 351 .filter(UserFollowing.follows_user == u)\
352 352 .filter(UserFollowing.user_id == user_id).scalar()
353 353
354 354 return f is not None
355 355
356 356 def get_followers(self, repo):
357 357 repo = self._get_repo(repo)
358 358
359 359 return self.sa.query(UserFollowing)\
360 360 .filter(UserFollowing.follows_repository == repo).count()
361 361
362 362 def get_forks(self, repo):
363 363 repo = self._get_repo(repo)
364 364 return self.sa.query(Repository)\
365 365 .filter(Repository.fork == repo).count()
366 366
367 367 def get_pull_requests(self, repo):
368 368 repo = self._get_repo(repo)
369 369 return self.sa.query(PullRequest)\
370 370 .filter(PullRequest.target_repo == repo)\
371 371 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
372 372
373 373 def mark_as_fork(self, repo, fork, user):
374 374 repo = self._get_repo(repo)
375 375 fork = self._get_repo(fork)
376 376 if fork and repo.repo_id == fork.repo_id:
377 377 raise Exception("Cannot set repository as fork of itself")
378 378
379 379 if fork and repo.repo_type != fork.repo_type:
380 380 raise RepositoryError(
381 381 "Cannot set repository as fork of repository with other type")
382 382
383 383 repo.fork = fork
384 384 self.sa.add(repo)
385 385 return repo
386 386
387 387 def pull_changes(self, repo, username):
388 388 dbrepo = self._get_repo(repo)
389 389 clone_uri = dbrepo.clone_uri
390 390 if not clone_uri:
391 391 raise Exception("This repository doesn't have a clone uri")
392 392
393 393 repo = dbrepo.scm_instance(cache=False)
394 394 # TODO: marcink fix this an re-enable since we need common logic
395 395 # for hg/git remove hooks so we don't trigger them on fetching
396 396 # commits from remote
397 397 repo.config.clear_section('hooks')
398 398
399 399 repo_name = dbrepo.repo_name
400 400 try:
401 401 # TODO: we need to make sure those operations call proper hooks !
402 402 repo.pull(clone_uri)
403 403
404 404 self.mark_for_invalidation(repo_name)
405 405 except Exception:
406 406 log.error(traceback.format_exc())
407 407 raise
408 408
409 409 def commit_change(self, repo, repo_name, commit, user, author, message,
410 410 content, f_path):
411 411 """
412 412 Commits changes
413 413
414 414 :param repo: SCM instance
415 415
416 416 """
417 417 user = self._get_user(user)
418 418
419 419 # decoding here will force that we have proper encoded values
420 420 # in any other case this will throw exceptions and deny commit
421 421 content = safe_str(content)
422 422 path = safe_str(f_path)
423 423 # message and author needs to be unicode
424 424 # proper backend should then translate that into required type
425 425 message = safe_unicode(message)
426 426 author = safe_unicode(author)
427 427 imc = repo.in_memory_commit
428 428 imc.change(FileNode(path, content, mode=commit.get_file_mode(f_path)))
429 429 try:
430 430 # TODO: handle pre-push action !
431 431 tip = imc.commit(
432 432 message=message, author=author, parents=[commit],
433 433 branch=commit.branch)
434 434 except Exception as e:
435 435 log.error(traceback.format_exc())
436 436 raise IMCCommitError(str(e))
437 437 finally:
438 438 # always clear caches, if commit fails we want fresh object also
439 439 self.mark_for_invalidation(repo_name)
440 440
441 441 # We trigger the post-push action
442 442 hooks_utils.trigger_post_push_hook(
443 443 username=user.username, action='push_local', repo_name=repo_name,
444 444 repo_alias=repo.alias, commit_ids=[tip.raw_id])
445 445 return tip
446 446
447 447 def _sanitize_path(self, f_path):
448 448 if f_path.startswith('/') or f_path.startswith('./') or '../' in f_path:
449 449 raise NonRelativePathError('%s is not an relative path' % f_path)
450 450 if f_path:
451 451 f_path = os.path.normpath(f_path)
452 452 return f_path
453 453
454 454 def get_dirnode_metadata(self, commit, dir_node):
455 455 if not dir_node.is_dir():
456 456 return []
457 457
458 458 data = []
459 459 for node in dir_node:
460 460 if not node.is_file():
461 461 # we skip file-nodes
462 462 continue
463 463
464 464 last_commit = node.last_commit
465 465 last_commit_date = last_commit.date
466 466 data.append({
467 467 'name': node.name,
468 468 'size': h.format_byte_size_binary(node.size),
469 469 'modified_at': h.format_date(last_commit_date),
470 470 'modified_ts': last_commit_date.isoformat(),
471 471 'revision': last_commit.revision,
472 472 'short_id': last_commit.short_id,
473 473 'message': h.escape(last_commit.message),
474 474 'author': h.escape(last_commit.author),
475 475 'user_profile': h.gravatar_with_user(last_commit.author),
476 476 })
477 477
478 478 return data
479 479
480 480 def get_nodes(self, repo_name, commit_id, root_path='/', flat=True,
481 extended_info=False, content=False):
481 extended_info=False, content=False, max_file_bytes=None):
482 482 """
483 483 recursive walk in root dir and return a set of all path in that dir
484 484 based on repository walk function
485 485
486 486 :param repo_name: name of repository
487 487 :param commit_id: commit id for which to list nodes
488 488 :param root_path: root path to list
489 489 :param flat: return as a list, if False returns a dict with description
490 :param max_file_bytes: will not return file contents over this limit
490 491
491 492 """
492 493 _files = list()
493 494 _dirs = list()
494 495 try:
495 496 _repo = self._get_repo(repo_name)
496 497 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
497 498 root_path = root_path.lstrip('/')
498 499 for __, dirs, files in commit.walk(root_path):
499 500 for f in files:
500 501 _content = None
501 502 _data = f.unicode_path
503 over_size_limit = (max_file_bytes is not None
504 and f.size > max_file_bytes)
502 505
503 506 if not flat:
504 507 _data = {
505 508 "name": f.unicode_path,
506 509 "type": "file",
507 510 }
508 511 if extended_info:
509 512 _data.update({
510 513 "md5": f.md5,
511 514 "binary": f.is_binary,
512 515 "size": f.size,
513 516 "extension": f.extension,
514 517 "mimetype": f.mimetype,
515 518 "lines": f.lines()[0]
516 519 })
517 520
518 521 if content:
519 522 full_content = None
520 if not f.is_binary:
523 if not f.is_binary and not over_size_limit:
521 524 full_content = safe_str(f.content)
522 525
523 526 _data.update({
524 527 "content": full_content,
525 528 })
526 529 _files.append(_data)
527 530 for d in dirs:
528 531 _data = d.unicode_path
529 532 if not flat:
530 533 _data = {
531 534 "name": d.unicode_path,
532 535 "type": "dir",
533 536 }
534 537 if extended_info:
535 538 _data.update({
536 539 "md5": None,
537 540 "binary": None,
538 541 "size": None,
539 542 "extension": None,
540 543 })
541 544 if content:
542 545 _data.update({
543 546 "content": None
544 547 })
545 548 _dirs.append(_data)
546 549 except RepositoryError:
547 550 log.debug("Exception in get_nodes", exc_info=True)
548 551 raise
549 552
550 553 return _dirs, _files
551 554
552 555 def create_nodes(self, user, repo, message, nodes, parent_commit=None,
553 556 author=None, trigger_push_hook=True):
554 557 """
555 558 Commits given multiple nodes into repo
556 559
557 560 :param user: RhodeCode User object or user_id, the commiter
558 561 :param repo: RhodeCode Repository object
559 562 :param message: commit message
560 563 :param nodes: mapping {filename:{'content':content},...}
561 564 :param parent_commit: parent commit, can be empty than it's
562 565 initial commit
563 566 :param author: author of commit, cna be different that commiter
564 567 only for git
565 568 :param trigger_push_hook: trigger push hooks
566 569
567 570 :returns: new commited commit
568 571 """
569 572
570 573 user = self._get_user(user)
571 574 scm_instance = repo.scm_instance(cache=False)
572 575
573 576 processed_nodes = []
574 577 for f_path in nodes:
575 578 f_path = self._sanitize_path(f_path)
576 579 content = nodes[f_path]['content']
577 580 f_path = safe_str(f_path)
578 581 # decoding here will force that we have proper encoded values
579 582 # in any other case this will throw exceptions and deny commit
580 583 if isinstance(content, (basestring,)):
581 584 content = safe_str(content)
582 585 elif isinstance(content, (file, cStringIO.OutputType,)):
583 586 content = content.read()
584 587 else:
585 588 raise Exception('Content is of unrecognized type %s' % (
586 589 type(content)
587 590 ))
588 591 processed_nodes.append((f_path, content))
589 592
590 593 message = safe_unicode(message)
591 594 commiter = user.full_contact
592 595 author = safe_unicode(author) if author else commiter
593 596
594 597 imc = scm_instance.in_memory_commit
595 598
596 599 if not parent_commit:
597 600 parent_commit = EmptyCommit(alias=scm_instance.alias)
598 601
599 602 if isinstance(parent_commit, EmptyCommit):
600 603 # EmptyCommit means we we're editing empty repository
601 604 parents = None
602 605 else:
603 606 parents = [parent_commit]
604 607 # add multiple nodes
605 608 for path, content in processed_nodes:
606 609 imc.add(FileNode(path, content=content))
607 610 # TODO: handle pre push scenario
608 611 tip = imc.commit(message=message,
609 612 author=author,
610 613 parents=parents,
611 614 branch=parent_commit.branch)
612 615
613 616 self.mark_for_invalidation(repo.repo_name)
614 617 if trigger_push_hook:
615 618 hooks_utils.trigger_post_push_hook(
616 619 username=user.username, action='push_local',
617 620 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
618 621 commit_ids=[tip.raw_id])
619 622 return tip
620 623
621 624 def update_nodes(self, user, repo, message, nodes, parent_commit=None,
622 625 author=None, trigger_push_hook=True):
623 626 user = self._get_user(user)
624 627 scm_instance = repo.scm_instance(cache=False)
625 628
626 629 message = safe_unicode(message)
627 630 commiter = user.full_contact
628 631 author = safe_unicode(author) if author else commiter
629 632
630 633 imc = scm_instance.in_memory_commit
631 634
632 635 if not parent_commit:
633 636 parent_commit = EmptyCommit(alias=scm_instance.alias)
634 637
635 638 if isinstance(parent_commit, EmptyCommit):
636 639 # EmptyCommit means we we're editing empty repository
637 640 parents = None
638 641 else:
639 642 parents = [parent_commit]
640 643
641 644 # add multiple nodes
642 645 for _filename, data in nodes.items():
643 646 # new filename, can be renamed from the old one, also sanitaze
644 647 # the path for any hack around relative paths like ../../ etc.
645 648 filename = self._sanitize_path(data['filename'])
646 649 old_filename = self._sanitize_path(_filename)
647 650 content = data['content']
648 651
649 652 filenode = FileNode(old_filename, content=content)
650 653 op = data['op']
651 654 if op == 'add':
652 655 imc.add(filenode)
653 656 elif op == 'del':
654 657 imc.remove(filenode)
655 658 elif op == 'mod':
656 659 if filename != old_filename:
657 660 # TODO: handle renames more efficient, needs vcs lib
658 661 # changes
659 662 imc.remove(filenode)
660 663 imc.add(FileNode(filename, content=content))
661 664 else:
662 665 imc.change(filenode)
663 666
664 667 try:
665 668 # TODO: handle pre push scenario
666 669 # commit changes
667 670 tip = imc.commit(message=message,
668 671 author=author,
669 672 parents=parents,
670 673 branch=parent_commit.branch)
671 674 except NodeNotChangedError:
672 675 raise
673 676 except Exception as e:
674 677 log.exception("Unexpected exception during call to imc.commit")
675 678 raise IMCCommitError(str(e))
676 679 finally:
677 680 # always clear caches, if commit fails we want fresh object also
678 681 self.mark_for_invalidation(repo.repo_name)
679 682
680 683 if trigger_push_hook:
681 684 hooks_utils.trigger_post_push_hook(
682 685 username=user.username, action='push_local',
683 686 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
684 687 commit_ids=[tip.raw_id])
685 688
686 689 def delete_nodes(self, user, repo, message, nodes, parent_commit=None,
687 690 author=None, trigger_push_hook=True):
688 691 """
689 692 Deletes given multiple nodes into `repo`
690 693
691 694 :param user: RhodeCode User object or user_id, the committer
692 695 :param repo: RhodeCode Repository object
693 696 :param message: commit message
694 697 :param nodes: mapping {filename:{'content':content},...}
695 698 :param parent_commit: parent commit, can be empty than it's initial
696 699 commit
697 700 :param author: author of commit, cna be different that commiter only
698 701 for git
699 702 :param trigger_push_hook: trigger push hooks
700 703
701 704 :returns: new commit after deletion
702 705 """
703 706
704 707 user = self._get_user(user)
705 708 scm_instance = repo.scm_instance(cache=False)
706 709
707 710 processed_nodes = []
708 711 for f_path in nodes:
709 712 f_path = self._sanitize_path(f_path)
710 713 # content can be empty but for compatabilty it allows same dicts
711 714 # structure as add_nodes
712 715 content = nodes[f_path].get('content')
713 716 processed_nodes.append((f_path, content))
714 717
715 718 message = safe_unicode(message)
716 719 commiter = user.full_contact
717 720 author = safe_unicode(author) if author else commiter
718 721
719 722 imc = scm_instance.in_memory_commit
720 723
721 724 if not parent_commit:
722 725 parent_commit = EmptyCommit(alias=scm_instance.alias)
723 726
724 727 if isinstance(parent_commit, EmptyCommit):
725 728 # EmptyCommit means we we're editing empty repository
726 729 parents = None
727 730 else:
728 731 parents = [parent_commit]
729 732 # add multiple nodes
730 733 for path, content in processed_nodes:
731 734 imc.remove(FileNode(path, content=content))
732 735
733 736 # TODO: handle pre push scenario
734 737 tip = imc.commit(message=message,
735 738 author=author,
736 739 parents=parents,
737 740 branch=parent_commit.branch)
738 741
739 742 self.mark_for_invalidation(repo.repo_name)
740 743 if trigger_push_hook:
741 744 hooks_utils.trigger_post_push_hook(
742 745 username=user.username, action='push_local',
743 746 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
744 747 commit_ids=[tip.raw_id])
745 748 return tip
746 749
747 750 def strip(self, repo, commit_id, branch):
748 751 scm_instance = repo.scm_instance(cache=False)
749 752 scm_instance.config.clear_section('hooks')
750 753 scm_instance.strip(commit_id, branch)
751 754 self.mark_for_invalidation(repo.repo_name)
752 755
753 756 def get_unread_journal(self):
754 757 return self.sa.query(UserLog).count()
755 758
756 759 def get_repo_landing_revs(self, repo=None):
757 760 """
758 761 Generates select option with tags branches and bookmarks (for hg only)
759 762 grouped by type
760 763
761 764 :param repo:
762 765 """
763 766
764 767 hist_l = []
765 768 choices = []
766 769 repo = self._get_repo(repo)
767 770 hist_l.append(['rev:tip', _('latest tip')])
768 771 choices.append('rev:tip')
769 772 if not repo:
770 773 return choices, hist_l
771 774
772 775 repo = repo.scm_instance()
773 776
774 777 branches_group = (
775 778 [(u'branch:%s' % safe_unicode(b), safe_unicode(b))
776 779 for b in repo.branches],
777 780 _("Branches"))
778 781 hist_l.append(branches_group)
779 782 choices.extend([x[0] for x in branches_group[0]])
780 783
781 784 if repo.alias == 'hg':
782 785 bookmarks_group = (
783 786 [(u'book:%s' % safe_unicode(b), safe_unicode(b))
784 787 for b in repo.bookmarks],
785 788 _("Bookmarks"))
786 789 hist_l.append(bookmarks_group)
787 790 choices.extend([x[0] for x in bookmarks_group[0]])
788 791
789 792 tags_group = (
790 793 [(u'tag:%s' % safe_unicode(t), safe_unicode(t))
791 794 for t in repo.tags],
792 795 _("Tags"))
793 796 hist_l.append(tags_group)
794 797 choices.extend([x[0] for x in tags_group[0]])
795 798
796 799 return choices, hist_l
797 800
798 801 def install_git_hook(self, repo, force_create=False):
799 802 """
800 803 Creates a rhodecode hook inside a git repository
801 804
802 805 :param repo: Instance of VCS repo
803 806 :param force_create: Create even if same name hook exists
804 807 """
805 808
806 809 loc = os.path.join(repo.path, 'hooks')
807 810 if not repo.bare:
808 811 loc = os.path.join(repo.path, '.git', 'hooks')
809 812 if not os.path.isdir(loc):
810 813 os.makedirs(loc, mode=0777)
811 814
812 815 tmpl_post = pkg_resources.resource_string(
813 816 'rhodecode', '/'.join(
814 817 ('config', 'hook_templates', 'git_post_receive.py.tmpl')))
815 818 tmpl_pre = pkg_resources.resource_string(
816 819 'rhodecode', '/'.join(
817 820 ('config', 'hook_templates', 'git_pre_receive.py.tmpl')))
818 821
819 822 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
820 823 _hook_file = os.path.join(loc, '%s-receive' % h_type)
821 824 log.debug('Installing git hook in repo %s', repo)
822 825 _rhodecode_hook = _check_rhodecode_hook(_hook_file)
823 826
824 827 if _rhodecode_hook or force_create:
825 828 log.debug('writing %s hook file !', h_type)
826 829 try:
827 830 with open(_hook_file, 'wb') as f:
828 831 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
829 832 tmpl = tmpl.replace('_ENV_', sys.executable)
830 833 f.write(tmpl)
831 834 os.chmod(_hook_file, 0755)
832 835 except IOError:
833 836 log.exception('error writing hook file %s', _hook_file)
834 837 else:
835 838 log.debug('skipping writing hook file')
836 839
837 840 def install_svn_hooks(self, repo, force_create=False):
838 841 """
839 842 Creates rhodecode hooks inside a svn repository
840 843
841 844 :param repo: Instance of VCS repo
842 845 :param force_create: Create even if same name hook exists
843 846 """
844 847 hooks_path = os.path.join(repo.path, 'hooks')
845 848 if not os.path.isdir(hooks_path):
846 849 os.makedirs(hooks_path)
847 850 post_commit_tmpl = pkg_resources.resource_string(
848 851 'rhodecode', '/'.join(
849 852 ('config', 'hook_templates', 'svn_post_commit_hook.py.tmpl')))
850 853 pre_commit_template = pkg_resources.resource_string(
851 854 'rhodecode', '/'.join(
852 855 ('config', 'hook_templates', 'svn_pre_commit_hook.py.tmpl')))
853 856 templates = {
854 857 'post-commit': post_commit_tmpl,
855 858 'pre-commit': pre_commit_template
856 859 }
857 860 for filename in templates:
858 861 _hook_file = os.path.join(hooks_path, filename)
859 862 _rhodecode_hook = _check_rhodecode_hook(_hook_file)
860 863 if _rhodecode_hook or force_create:
861 864 log.debug('writing %s hook file !', filename)
862 865 template = templates[filename]
863 866 try:
864 867 with open(_hook_file, 'wb') as f:
865 868 template = template.replace(
866 869 '_TMPL_', rhodecode.__version__)
867 870 template = template.replace('_ENV_', sys.executable)
868 871 f.write(template)
869 872 os.chmod(_hook_file, 0755)
870 873 except IOError:
871 874 log.exception('error writing hook file %s', filename)
872 875 else:
873 876 log.debug('skipping writing hook file')
874 877
875 878 def install_hooks(self, repo, repo_type):
876 879 if repo_type == 'git':
877 880 self.install_git_hook(repo)
878 881 elif repo_type == 'svn':
879 882 self.install_svn_hooks(repo)
880 883
881 884 def get_server_info(self, environ=None):
882 885 import platform
883 886 import rhodecode
884 887 import pkg_resources
885 888 from rhodecode.model.meta import Base as sql_base, Session
886 889 from sqlalchemy.engine import url
887 890 from rhodecode.lib.base import get_server_ip_addr, get_server_port
888 891 from rhodecode.lib.vcs.backends.git import discover_git_version
889 892 from rhodecode.model.gist import GIST_STORE_LOC
890 893
891 894 try:
892 895 # cygwin cannot have yet psutil support.
893 896 import psutil
894 897 except ImportError:
895 898 psutil = None
896 899
897 900 environ = environ or {}
898 901 _NA = 'NOT AVAILABLE'
899 902 _memory = _NA
900 903 _uptime = _NA
901 904 _boot_time = _NA
902 905 _cpu = _NA
903 906 _disk = dict(percent=0, used=0, total=0, error='')
904 907 _load = {'1_min': _NA, '5_min': _NA, '15_min': _NA}
905 908
906 909 model = VcsSettingsModel()
907 910 storage_path = model.get_repos_location()
908 911 gist_storage_path = os.path.join(storage_path, GIST_STORE_LOC)
909 912 archive_storage_path = rhodecode.CONFIG.get('archive_cache_dir', '')
910 913 search_index_storage_path = rhodecode.CONFIG.get('search.location', '')
911 914
912 915 if psutil:
913 916 # disk storage
914 917 try:
915 918 _disk = dict(psutil.disk_usage(storage_path)._asdict())
916 919 except Exception as e:
917 920 log.exception('Failed to fetch disk info')
918 921 _disk = {'percent': 0, 'used': 0, 'total': 0, 'error': str(e)}
919 922
920 923 # memory
921 924 _memory = dict(psutil.virtual_memory()._asdict())
922 925 _memory['percent2'] = psutil._common.usage_percent(
923 926 (_memory['total'] - _memory['free']),
924 927 _memory['total'], 1)
925 928
926 929 # load averages
927 930 if hasattr(psutil.os, 'getloadavg'):
928 931 _load = dict(zip(
929 932 ['1_min', '5_min', '15_min'], psutil.os.getloadavg()))
930 933 _uptime = time.time() - psutil.boot_time()
931 934 _boot_time = psutil.boot_time()
932 935 _cpu = psutil.cpu_percent(0.5)
933 936
934 937 mods = dict([(p.project_name, p.version)
935 938 for p in pkg_resources.working_set])
936 939
937 940 def get_storage_size(storage_path):
938 941 sizes = []
939 942 for file_ in os.listdir(storage_path):
940 943 storage_file = os.path.join(storage_path, file_)
941 944 if os.path.isfile(storage_file):
942 945 try:
943 946 sizes.append(os.path.getsize(storage_file))
944 947 except OSError:
945 948 log.exception('Failed to get size of storage file %s',
946 949 storage_file)
947 950 pass
948 951
949 952 return sum(sizes)
950 953
951 954 # archive cache storage
952 955 _disk_archive = {'percent': 0, 'used': 0, 'total': 0}
953 956 try:
954 957 archive_storage_path_exists = os.path.isdir(
955 958 archive_storage_path)
956 959 if archive_storage_path and archive_storage_path_exists:
957 960 used = get_storage_size(archive_storage_path)
958 961 _disk_archive.update({
959 962 'used': used,
960 963 'total': used,
961 964 })
962 965 except Exception as e:
963 966 log.exception('failed to fetch archive cache storage')
964 967 _disk_archive['error'] = str(e)
965 968
966 969 # search index storage
967 970 _disk_index = {'percent': 0, 'used': 0, 'total': 0}
968 971 try:
969 972 search_index_storage_path_exists = os.path.isdir(
970 973 search_index_storage_path)
971 974 if search_index_storage_path_exists:
972 975 used = get_storage_size(search_index_storage_path)
973 976 _disk_index.update({
974 977 'percent': 100,
975 978 'used': used,
976 979 'total': used,
977 980 })
978 981 except Exception as e:
979 982 log.exception('failed to fetch search index storage')
980 983 _disk_index['error'] = str(e)
981 984
982 985 # gist storage
983 986 _disk_gist = {'percent': 0, 'used': 0, 'total': 0, 'items': 0}
984 987 try:
985 988 items_count = 0
986 989 used = 0
987 990 for root, dirs, files in os.walk(safe_str(gist_storage_path)):
988 991 if root == gist_storage_path:
989 992 items_count = len(dirs)
990 993
991 994 for f in files:
992 995 try:
993 996 used += os.path.getsize(os.path.join(root, f))
994 997 except OSError:
995 998 pass
996 999 _disk_gist.update({
997 1000 'percent': 100,
998 1001 'used': used,
999 1002 'total': used,
1000 1003 'items': items_count
1001 1004 })
1002 1005 except Exception as e:
1003 1006 log.exception('failed to fetch gist storage items')
1004 1007 _disk_gist['error'] = str(e)
1005 1008
1006 1009 # GIT info
1007 1010 git_ver = discover_git_version()
1008 1011
1009 1012 # SVN info
1010 1013 # TODO: johbo: Add discover_svn_version to replace this code.
1011 1014 try:
1012 1015 import svn.core
1013 1016 svn_ver = svn.core.SVN_VERSION
1014 1017 except ImportError:
1015 1018 svn_ver = None
1016 1019
1017 1020 # DB stuff
1018 1021 db_info = url.make_url(rhodecode.CONFIG['sqlalchemy.db1.url'])
1019 1022 db_type = db_info.__to_string__()
1020 1023 try:
1021 1024 engine = sql_base.metadata.bind
1022 1025 db_server_info = engine.dialect._get_server_version_info(
1023 1026 Session.connection(bind=engine))
1024 1027 db_version = '%s %s' % (db_info.drivername,
1025 1028 '.'.join(map(str, db_server_info)))
1026 1029 except Exception:
1027 1030 log.exception('failed to fetch db version')
1028 1031 db_version = '%s %s' % (db_info.drivername, '?')
1029 1032
1030 1033 db_migrate = DbMigrateVersion.query().filter(
1031 1034 DbMigrateVersion.repository_id == 'rhodecode_db_migrations').one()
1032 1035 db_migrate_version = db_migrate.version
1033 1036
1034 1037 info = {
1035 1038 'py_version': ' '.join(platform._sys_version()),
1036 1039 'py_path': sys.executable,
1037 1040 'py_modules': sorted(mods.items(), key=lambda k: k[0].lower()),
1038 1041
1039 1042 'platform': safe_unicode(platform.platform()),
1040 1043 'storage': storage_path,
1041 1044 'archive_storage': archive_storage_path,
1042 1045 'index_storage': search_index_storage_path,
1043 1046 'gist_storage': gist_storage_path,
1044 1047
1045 1048
1046 1049 'db_type': db_type,
1047 1050 'db_version': db_version,
1048 1051 'db_migrate_version': db_migrate_version,
1049 1052
1050 1053 'rhodecode_version': rhodecode.__version__,
1051 1054 'rhodecode_config_ini': rhodecode.CONFIG.get('__file__'),
1052 1055 'server_ip': '%s:%s' % (
1053 1056 get_server_ip_addr(environ, log_errors=False),
1054 1057 get_server_port(environ)
1055 1058 ),
1056 1059 'server_id': rhodecode.CONFIG.get('instance_id'),
1057 1060
1058 1061 'git_version': safe_unicode(git_ver),
1059 1062 'hg_version': mods.get('mercurial'),
1060 1063 'svn_version': svn_ver,
1061 1064
1062 1065 'uptime': _uptime,
1063 1066 'boot_time': _boot_time,
1064 1067 'load': _load,
1065 1068 'cpu': _cpu,
1066 1069 'memory': _memory,
1067 1070 'disk': _disk,
1068 1071 'disk_archive': _disk_archive,
1069 1072 'disk_gist': _disk_gist,
1070 1073 'disk_index': _disk_index,
1071 1074 }
1072 1075 return info
1073 1076
1074 1077
1075 1078 def _check_rhodecode_hook(hook_path):
1076 1079 """
1077 1080 Check if the hook was created by RhodeCode
1078 1081 """
1079 1082 if not os.path.exists(hook_path):
1080 1083 return True
1081 1084
1082 1085 log.debug('hook exists, checking if it is from rhodecode')
1083 1086 hook_content = _read_hook(hook_path)
1084 1087 matches = re.search(r'(?:RC_HOOK_VER)\s*=\s*(.*)', hook_content)
1085 1088 if matches:
1086 1089 try:
1087 1090 version = matches.groups()[0]
1088 1091 log.debug('got %s, it is rhodecode', version)
1089 1092 return True
1090 1093 except Exception:
1091 1094 log.exception("Exception while reading the hook version.")
1092 1095
1093 1096 return False
1094 1097
1095 1098
1096 1099 def _read_hook(hook_path):
1097 1100 with open(hook_path, 'rb') as f:
1098 1101 content = f.read()
1099 return content No newline at end of file
1102 return content
@@ -1,318 +1,335 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 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 os
22 22 import stat
23 23 import sys
24 24
25 25 import pytest
26 26 from mock import Mock, patch, DEFAULT
27 27
28 28 import rhodecode
29 29 from rhodecode.model import db, scm
30 30
31 31
32 32 def test_scm_instance_config(backend):
33 33 repo = backend.create_repo()
34 34 with patch.multiple('rhodecode.model.db.Repository',
35 35 _get_instance=DEFAULT,
36 36 _get_instance_cached=DEFAULT) as mocks:
37 37 repo.scm_instance()
38 38 mocks['_get_instance'].assert_called_with(
39 39 config=None, cache=False)
40 40
41 41 config = {'some': 'value'}
42 42 repo.scm_instance(config=config)
43 43 mocks['_get_instance'].assert_called_with(
44 44 config=config, cache=False)
45 45
46 46 with patch.dict(rhodecode.CONFIG, {'vcs_full_cache': 'true'}):
47 47 repo.scm_instance(config=config)
48 48 mocks['_get_instance_cached'].assert_called()
49 49
50 50
51 51 def test__get_instance_config(backend):
52 52 repo = backend.create_repo()
53 53 vcs_class = Mock()
54 54 with patch.multiple('rhodecode.lib.vcs.backends',
55 55 get_scm=DEFAULT,
56 56 get_backend=DEFAULT) as mocks:
57 57 mocks['get_scm'].return_value = backend.alias
58 58 mocks['get_backend'].return_value = vcs_class
59 59 with patch('rhodecode.model.db.Repository._config') as config_mock:
60 60 repo._get_instance()
61 61 vcs_class.assert_called_with(
62 62 repo_path=repo.repo_full_path, config=config_mock,
63 63 create=False, with_wire={'cache': True})
64 64
65 65 new_config = {'override': 'old_config'}
66 66 repo._get_instance(config=new_config)
67 67 vcs_class.assert_called_with(
68 68 repo_path=repo.repo_full_path, config=new_config, create=False,
69 69 with_wire={'cache': True})
70 70
71 71
72 72 def test_mark_for_invalidation_config(backend):
73 73 repo = backend.create_repo()
74 74 with patch('rhodecode.model.db.Repository.update_commit_cache') as _mock:
75 75 scm.ScmModel().mark_for_invalidation(repo.repo_name)
76 76 _, kwargs = _mock.call_args
77 77 assert kwargs['config'].__dict__ == repo._config.__dict__
78 78
79 79
80 80 def test_mark_for_invalidation_with_delete_updates_last_commit(backend):
81 81 commits = [{'message': 'A'}, {'message': 'B'}]
82 82 repo = backend.create_repo(commits=commits)
83 83 scm.ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
84 84 assert repo.changeset_cache['revision'] == 1
85 85
86 86
87 87 def test_mark_for_invalidation_with_delete_updates_last_commit_empty(backend):
88 88 repo = backend.create_repo()
89 89 scm.ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
90 90 assert repo.changeset_cache['revision'] == -1
91 91
92 92
93 93 def test_strip_with_multiple_heads(backend_hg):
94 94 commits = [
95 95 {'message': 'A'},
96 96 {'message': 'a'},
97 97 {'message': 'b'},
98 98 {'message': 'B', 'parents': ['A']},
99 99 {'message': 'a1'},
100 100 ]
101 101 repo = backend_hg.create_repo(commits=commits)
102 102 commit_ids = backend_hg.commit_ids
103 103
104 104 model = scm.ScmModel()
105 105 model.strip(repo, commit_ids['b'], branch=None)
106 106
107 107 vcs_repo = repo.scm_instance()
108 108 rest_commit_ids = [c.raw_id for c in vcs_repo.get_changesets()]
109 109 assert len(rest_commit_ids) == 4
110 110 assert commit_ids['b'] not in rest_commit_ids
111 111
112 112
113 113 def test_strip_with_single_heads(backend_hg):
114 114 commits = [
115 115 {'message': 'A'},
116 116 {'message': 'a'},
117 117 {'message': 'b'},
118 118 ]
119 119 repo = backend_hg.create_repo(commits=commits)
120 120 commit_ids = backend_hg.commit_ids
121 121
122 122 model = scm.ScmModel()
123 123 model.strip(repo, commit_ids['b'], branch=None)
124 124
125 125 vcs_repo = repo.scm_instance()
126 126 rest_commit_ids = [c.raw_id for c in vcs_repo.get_changesets()]
127 127 assert len(rest_commit_ids) == 2
128 128 assert commit_ids['b'] not in rest_commit_ids
129 129
130 130
131 131 def test_get_nodes_returns_unicode_flat(backend_random):
132 132 repo = backend_random.repo
133 133 directories, files = scm.ScmModel().get_nodes(
134 134 repo.repo_name, repo.get_commit(commit_idx=0).raw_id,
135 135 flat=True)
136 136 assert_contains_only_unicode(directories)
137 137 assert_contains_only_unicode(files)
138 138
139 139
140 140 def test_get_nodes_returns_unicode_non_flat(backend_random):
141 141 repo = backend_random.repo
142 142 directories, files = scm.ScmModel().get_nodes(
143 143 repo.repo_name, repo.get_commit(commit_idx=0).raw_id,
144 144 flat=False)
145 145 # johbo: Checking only the names for now, since that is the critical
146 146 # part.
147 147 assert_contains_only_unicode([d['name'] for d in directories])
148 148 assert_contains_only_unicode([f['name'] for f in files])
149 149
150 150
151 def test_get_nodes_max_file_bytes(backend_random):
152 repo = backend_random.repo
153 max_file_bytes = 10
154 directories, files = scm.ScmModel().get_nodes(
155 repo.repo_name, repo.get_commit(commit_idx=0).raw_id, content=True,
156 extended_info=True, flat=False)
157 assert any(file['content'] and len(file['content']) > max_file_bytes
158 for file in files)
159
160 directories, files = scm.ScmModel().get_nodes(
161 repo.repo_name, repo.get_commit(commit_idx=0).raw_id, content=True,
162 extended_info=True, flat=False, max_file_bytes=max_file_bytes)
163 assert all(
164 file['content'] is None if file['size'] > max_file_bytes else True
165 for file in files)
166
167
151 168 def assert_contains_only_unicode(structure):
152 169 assert structure
153 170 for value in structure:
154 171 assert isinstance(value, unicode)
155 172
156 173
157 174 @pytest.mark.backends("hg", "git")
158 175 def test_get_non_unicode_reference(backend):
159 176 model = scm.ScmModel()
160 177 non_unicode_list = ["AdΔ±nΔ±".decode("cp1254")]
161 178
162 179 def scm_instance():
163 180 return Mock(
164 181 branches=non_unicode_list, bookmarks=non_unicode_list,
165 182 tags=non_unicode_list, alias=backend.alias)
166 183
167 184 repo = Mock(__class__=db.Repository, scm_instance=scm_instance)
168 185 choices, __ = model.get_repo_landing_revs(repo=repo)
169 186 if backend.alias == 'hg':
170 187 valid_choices = [
171 188 'rev:tip', u'branch:Ad\xc4\xb1n\xc4\xb1',
172 189 u'book:Ad\xc4\xb1n\xc4\xb1', u'tag:Ad\xc4\xb1n\xc4\xb1']
173 190 else:
174 191 valid_choices = [
175 192 'rev:tip', u'branch:Ad\xc4\xb1n\xc4\xb1',
176 193 u'tag:Ad\xc4\xb1n\xc4\xb1']
177 194
178 195 assert choices == valid_choices
179 196
180 197
181 198 class TestInstallSvnHooks(object):
182 199 HOOK_FILES = ('pre-commit', 'post-commit')
183 200
184 201 def test_new_hooks_are_created(self, backend_svn):
185 202 model = scm.ScmModel()
186 203 repo = backend_svn.create_repo()
187 204 vcs_repo = repo.scm_instance()
188 205 model.install_svn_hooks(vcs_repo)
189 206
190 207 hooks_path = os.path.join(vcs_repo.path, 'hooks')
191 208 assert os.path.isdir(hooks_path)
192 209 for file_name in self.HOOK_FILES:
193 210 file_path = os.path.join(hooks_path, file_name)
194 211 self._check_hook_file_mode(file_path)
195 212 self._check_hook_file_content(file_path)
196 213
197 214 def test_rc_hooks_are_replaced(self, backend_svn):
198 215 model = scm.ScmModel()
199 216 repo = backend_svn.create_repo()
200 217 vcs_repo = repo.scm_instance()
201 218 hooks_path = os.path.join(vcs_repo.path, 'hooks')
202 219 file_paths = [os.path.join(hooks_path, f) for f in self.HOOK_FILES]
203 220
204 221 for file_path in file_paths:
205 222 self._create_fake_hook(
206 223 file_path, content="RC_HOOK_VER = 'abcde'\n")
207 224
208 225 model.install_svn_hooks(vcs_repo)
209 226
210 227 for file_path in file_paths:
211 228 self._check_hook_file_content(file_path)
212 229
213 230 def test_non_rc_hooks_are_not_replaced_without_force_create(
214 231 self, backend_svn):
215 232 model = scm.ScmModel()
216 233 repo = backend_svn.create_repo()
217 234 vcs_repo = repo.scm_instance()
218 235 hooks_path = os.path.join(vcs_repo.path, 'hooks')
219 236 file_paths = [os.path.join(hooks_path, f) for f in self.HOOK_FILES]
220 237 non_rc_content = "exit 0\n"
221 238
222 239 for file_path in file_paths:
223 240 self._create_fake_hook(file_path, content=non_rc_content)
224 241
225 242 model.install_svn_hooks(vcs_repo)
226 243
227 244 for file_path in file_paths:
228 245 with open(file_path, 'rt') as hook_file:
229 246 content = hook_file.read()
230 247 assert content == non_rc_content
231 248
232 249 def test_non_rc_hooks_are_replaced_with_force_create(self, backend_svn):
233 250 model = scm.ScmModel()
234 251 repo = backend_svn.create_repo()
235 252 vcs_repo = repo.scm_instance()
236 253 hooks_path = os.path.join(vcs_repo.path, 'hooks')
237 254 file_paths = [os.path.join(hooks_path, f) for f in self.HOOK_FILES]
238 255 non_rc_content = "exit 0\n"
239 256
240 257 for file_path in file_paths:
241 258 self._create_fake_hook(file_path, content=non_rc_content)
242 259
243 260 model.install_svn_hooks(vcs_repo, force_create=True)
244 261
245 262 for file_path in file_paths:
246 263 self._check_hook_file_content(file_path)
247 264
248 265 def _check_hook_file_mode(self, file_path):
249 266 assert os.path.exists(file_path)
250 267 stat_info = os.stat(file_path)
251 268
252 269 file_mode = stat.S_IMODE(stat_info.st_mode)
253 270 expected_mode = int('755', 8)
254 271 assert expected_mode == file_mode
255 272
256 273 def _check_hook_file_content(self, file_path):
257 274 with open(file_path, 'rt') as hook_file:
258 275 content = hook_file.read()
259 276
260 277 expected_env = '#!{}'.format(sys.executable)
261 278 expected_rc_version = "\nRC_HOOK_VER = '{}'\n".format(
262 279 rhodecode.__version__)
263 280 assert content.strip().startswith(expected_env)
264 281 assert expected_rc_version in content
265 282
266 283 def _create_fake_hook(self, file_path, content):
267 284 with open(file_path, 'w') as hook_file:
268 285 hook_file.write(content)
269 286
270 287
271 288 class TestCheckRhodecodeHook(object):
272 289
273 290 @patch('os.path.exists', Mock(return_value=False))
274 291 def test_returns_true_when_no_hook_found(self):
275 292 result = scm._check_rhodecode_hook('/tmp/fake_hook_file.py')
276 293 assert result
277 294
278 295 @pytest.mark.parametrize("file_content, expected_result", [
279 296 ("RC_HOOK_VER = '3.3.3'\n", True),
280 297 ("RC_HOOK = '3.3.3'\n", False),
281 298 ])
282 299 @patch('os.path.exists', Mock(return_value=True))
283 300 def test_signatures(self, file_content, expected_result):
284 301 hook_content_patcher = patch.object(
285 302 scm, '_read_hook', return_value=file_content)
286 303 with hook_content_patcher:
287 304 result = scm._check_rhodecode_hook('/tmp/fake_hook_file.py')
288 305
289 306 assert result is expected_result
290 307
291 308
292 309 class TestInstallHooks(object):
293 310 def test_hooks_are_installed_for_git_repo(self, backend_git):
294 311 repo = backend_git.create_repo()
295 312 model = scm.ScmModel()
296 313 scm_repo = repo.scm_instance()
297 314 with patch.object(model, 'install_git_hook') as hooks_mock:
298 315 model.install_hooks(scm_repo, repo_type='git')
299 316 hooks_mock.assert_called_once_with(scm_repo)
300 317
301 318 def test_hooks_are_installed_for_svn_repo(self, backend_svn):
302 319 repo = backend_svn.create_repo()
303 320 scm_repo = repo.scm_instance()
304 321 model = scm.ScmModel()
305 322 with patch.object(scm.ScmModel, 'install_svn_hooks') as hooks_mock:
306 323 model.install_hooks(scm_repo, repo_type='svn')
307 324 hooks_mock.assert_called_once_with(scm_repo)
308 325
309 326 @pytest.mark.parametrize('hook_method', [
310 327 'install_svn_hooks',
311 328 'install_git_hook'])
312 329 def test_mercurial_doesnt_trigger_hooks(self, backend_hg, hook_method):
313 330 repo = backend_hg.create_repo()
314 331 scm_repo = repo.scm_instance()
315 332 model = scm.ScmModel()
316 333 with patch.object(scm.ScmModel, hook_method) as hooks_mock:
317 334 model.install_hooks(scm_repo, repo_type='hg')
318 335 assert hooks_mock.call_count == 0
General Comments 0
You need to be logged in to leave comments. Login now