##// END OF EJS Templates
api: add get_changesets
domruf -
r6619:19bc05bd default
parent child Browse files
Show More
@@ -1,1262 +1,1304 b''
1 1 .. _api:
2 2
3 3 ===
4 4 API
5 5 ===
6 6
7 7 Kallithea has a simple JSON RPC API with a single schema for calling all API
8 8 methods. Everything is available by sending JSON encoded http(s) requests to
9 9 ``<your_server>/_admin/api``.
10 10
11 11
12 12 API keys
13 13 --------
14 14
15 15 Every Kallithea user automatically receives an API key, which they can
16 16 view under "My Account". On this page, API keys can also be revoked, and
17 17 additional API keys can be generated.
18 18
19 19
20 20 API access
21 21 ----------
22 22
23 23 Clients must send JSON encoded JSON-RPC requests::
24 24
25 25 {
26 26 "id: "<id>",
27 27 "api_key": "<api_key>",
28 28 "method": "<method_name>",
29 29 "args": {"<arg_key>": "<arg_val>"}
30 30 }
31 31
32 32 For example, to pull to a local "CPython" mirror using curl::
33 33
34 34 curl https://kallithea.example.com/_admin/api -X POST -H 'content-type:text/plain' \
35 35 --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
36 36
37 37 In general, provide
38 38 - *id*, a value of any type, can be used to match the response with the request that it is replying to.
39 39 - *api_key*, for authentication and permission validation.
40 40 - *method*, the name of the method to call -- a list of available methods can be found below.
41 41 - *args*, the arguments to pass to the method.
42 42
43 43 .. note::
44 44
45 45 api_key can be found or set on the user account page.
46 46
47 47 The response to the JSON-RPC API call will always be a JSON structure::
48 48
49 49 {
50 50 "id": <id>, # the id that was used in the request
51 51 "result": <result>|null, # JSON formatted result (null on error)
52 52 "error": null|<error_message> # JSON formatted error (null on success)
53 53 }
54 54
55 55 All responses from the API will be ``HTTP/1.0 200 OK``. If an error occurs,
56 56 the reponse will have a failure description in *error* and
57 57 *result* will be null.
58 58
59 59
60 60 API client
61 61 ----------
62 62
63 63 Kallithea comes with a ``kallithea-api`` command line tool, providing a convenient
64 64 way to call the JSON-RPC API.
65 65
66 66 For example, to call ``get_repo``::
67 67
68 68 kallithea-api --apihost=<Kallithea URL> --apikey=<API key> get_repo
69 69
70 70 Calling method get_repo => <Kallithea URL>
71 71 Server response
72 72 ERROR:"Missing non optional `repoid` arg in JSON DATA"
73 73
74 74 Oops, looks like we forgot to add an argument. Let's try again, now
75 75 providing the ``repoid`` as a parameter::
76 76
77 77 kallithea-api --apihost=<Kallithea URL> --apikey=<API key> get_repo repoid:myrepo
78 78
79 79 Calling method get_repo => <Kallithea URL>
80 80 Server response
81 81 {
82 82 "clone_uri": null,
83 83 "created_on": "2015-08-31T14:55:19.042",
84 84 ...
85 85
86 86 To avoid specifying ``apihost`` and ``apikey`` every time, run::
87 87
88 88 kallithea-api --save-config --apihost=<Kallithea URL> --apikey=<API key>
89 89
90 90 This will create a ``~/.config/kallithea`` with the specified URL and API key
91 91 so you don't have to specify them every time.
92 92
93 93
94 94 API methods
95 95 -----------
96 96
97 97
98 98 pull
99 99 ^^^^
100 100
101 101 Pull the given repo from remote location. Can be used to automatically keep
102 102 remote repos up to date.
103 103 This command can only be executed using the api_key of a user with admin rights.
104 104
105 105 INPUT::
106 106
107 107 id : <id_for_response>
108 108 api_key : "<api_key>"
109 109 method : "pull"
110 110 args : {
111 111 "repoid" : "<reponame or repo_id>"
112 112 }
113 113
114 114 OUTPUT::
115 115
116 116 id : <id_given_in_input>
117 117 result : "Pulled from `<reponame>`"
118 118 error : null
119 119
120 120 rescan_repos
121 121 ^^^^^^^^^^^^
122 122
123 123 Rescan repositories. If ``remove_obsolete`` is set,
124 124 Kallithea will delete repos that are in the database but not in the filesystem.
125 125 This command can only be executed using the api_key of a user with admin rights.
126 126
127 127 INPUT::
128 128
129 129 id : <id_for_response>
130 130 api_key : "<api_key>"
131 131 method : "rescan_repos"
132 132 args : {
133 133 "remove_obsolete" : "<boolean = Optional(False)>"
134 134 }
135 135
136 136 OUTPUT::
137 137
138 138 id : <id_given_in_input>
139 139 result : "{'added': [<list of names of added repos>],
140 140 'removed': [<list of names of removed repos>]}"
141 141 error : null
142 142
143 143 invalidate_cache
144 144 ^^^^^^^^^^^^^^^^
145 145
146 146 Invalidate the cache for a repository.
147 147 This command can only be executed using the api_key of a user with admin rights,
148 148 or that of a regular user with admin or write access to the repository.
149 149
150 150 INPUT::
151 151
152 152 id : <id_for_response>
153 153 api_key : "<api_key>"
154 154 method : "invalidate_cache"
155 155 args : {
156 156 "repoid" : "<reponame or repo_id>"
157 157 }
158 158
159 159 OUTPUT::
160 160
161 161 id : <id_given_in_input>
162 162 result : "Caches of repository `<reponame>`"
163 163 error : null
164 164
165 165 lock
166 166 ^^^^
167 167
168 168 Set the locking state on the given repository by the given user.
169 169 If the param ``userid`` is skipped, it is set to the ID of the user who is calling this method.
170 170 If param ``locked`` is skipped, the current lock state of the repository is returned.
171 171 This command can only be executed using the api_key of a user with admin rights, or that of a regular user with admin or write access to the repository.
172 172
173 173 INPUT::
174 174
175 175 id : <id_for_response>
176 176 api_key : "<api_key>"
177 177 method : "lock"
178 178 args : {
179 179 "repoid" : "<reponame or repo_id>"
180 180 "userid" : "<user_id or username = Optional(=apiuser)>",
181 181 "locked" : "<bool true|false = Optional(=None)>"
182 182 }
183 183
184 184 OUTPUT::
185 185
186 186 id : <id_given_in_input>
187 187 result : {
188 188 "repo": "<reponame>",
189 189 "locked": "<bool true|false>",
190 190 "locked_since": "<float lock_time>",
191 191 "locked_by": "<username>",
192 192 "msg": "User `<username>` set lock state for repo `<reponame>` to `<false|true>`"
193 193 }
194 194 error : null
195 195
196 196 get_ip
197 197 ^^^^^^
198 198
199 199 Return IP address as seen from Kallithea server, together with all
200 200 defined IP addresses for given user.
201 201 This command can only be executed using the api_key of a user with admin rights.
202 202
203 203 INPUT::
204 204
205 205 id : <id_for_response>
206 206 api_key : "<api_key>"
207 207 method : "get_ip"
208 208 args : {
209 209 "userid" : "<user_id or username>",
210 210 }
211 211
212 212 OUTPUT::
213 213
214 214 id : <id_given_in_input>
215 215 result : {
216 216 "ip_addr_server": <ip_from_clien>",
217 217 "user_ips": [
218 218 {
219 219 "ip_addr": "<ip_with_mask>",
220 220 "ip_range": ["<start_ip>", "<end_ip>"],
221 221 },
222 222 ...
223 223 ]
224 224 }
225 225
226 226 error : null
227 227
228 228 get_user
229 229 ^^^^^^^^
230 230
231 231 Get a user by username or userid. The result is empty if user can't be found.
232 232 If userid param is skipped, it is set to id of user who is calling this method.
233 233 Any userid can be specified when the command is executed using the api_key of a user with admin rights.
234 234 Regular users can only specify their own userid.
235 235
236 236 INPUT::
237 237
238 238 id : <id_for_response>
239 239 api_key : "<api_key>"
240 240 method : "get_user"
241 241 args : {
242 242 "userid" : "<username or user_id Optional(=apiuser)>"
243 243 }
244 244
245 245 OUTPUT::
246 246
247 247 id : <id_given_in_input>
248 248 result: None if user does not exist or
249 249 {
250 250 "user_id" : "<user_id>",
251 251 "api_key" : "<api_key>",
252 252 "username" : "<username>",
253 253 "firstname": "<firstname>",
254 254 "lastname" : "<lastname>",
255 255 "email" : "<email>",
256 256 "emails": "<list_of_all_additional_emails>",
257 257 "ip_addresses": "<list_of_ip_addresses_for_user>",
258 258 "active" : "<bool>",
259 259 "admin" :Β  "<bool>",
260 260 "ldap_dn" : "<ldap_dn>",
261 261 "last_login": "<last_login>",
262 262 "permissions": {
263 263 "global": ["hg.create.repository",
264 264 "repository.read",
265 265 "hg.register.manual_activate"],
266 266 "repositories": {"repo1": "repository.none"},
267 267 "repositories_groups": {"Group1": "group.read"}
268 268 },
269 269 }
270 270 error: null
271 271
272 272 get_users
273 273 ^^^^^^^^^
274 274
275 275 List all existing users.
276 276 This command can only be executed using the api_key of a user with admin rights.
277 277
278 278 INPUT::
279 279
280 280 id : <id_for_response>
281 281 api_key : "<api_key>"
282 282 method : "get_users"
283 283 args : { }
284 284
285 285 OUTPUT::
286 286
287 287 id : <id_given_in_input>
288 288 result: [
289 289 {
290 290 "user_id" : "<user_id>",
291 291 "api_key" : "<api_key>",
292 292 "username" : "<username>",
293 293 "firstname": "<firstname>",
294 294 "lastname" : "<lastname>",
295 295 "email" : "<email>",
296 296 "emails": "<list_of_all_additional_emails>",
297 297 "ip_addresses": "<list_of_ip_addresses_for_user>",
298 298 "active" : "<bool>",
299 299 "admin" :Β  "<bool>",
300 300 "ldap_dn" : "<ldap_dn>",
301 301 "last_login": "<last_login>",
302 302 },
303 303 …
304 304 ]
305 305 error: null
306 306
307 307 .. _create-user:
308 308
309 309 create_user
310 310 ^^^^^^^^^^^
311 311
312 312 Create new user.
313 313 This command can only be executed using the api_key of a user with admin rights.
314 314
315 315 INPUT::
316 316
317 317 id : <id_for_response>
318 318 api_key : "<api_key>"
319 319 method : "create_user"
320 320 args : {
321 321 "username" : "<username>",
322 322 "email" : "<useremail>",
323 323 "password" : "<password = Optional(None)>",
324 324 "firstname" : "<firstname> = Optional(None)",
325 325 "lastname" : "<lastname> = Optional(None)",
326 326 "active" : "<bool> = Optional(True)",
327 327 "admin" : "<bool> = Optional(False)",
328 328 "ldap_dn" : "<ldap_dn> = Optional(None)"
329 329 }
330 330
331 331 OUTPUT::
332 332
333 333 id : <id_given_in_input>
334 334 result: {
335 335 "msg" : "created new user `<username>`",
336 336 "user": {
337 337 "user_id" : "<user_id>",
338 338 "username" : "<username>",
339 339 "firstname": "<firstname>",
340 340 "lastname" : "<lastname>",
341 341 "email" : "<email>",
342 342 "emails": "<list_of_all_additional_emails>",
343 343 "active" : "<bool>",
344 344 "admin" :Β  "<bool>",
345 345 "ldap_dn" : "<ldap_dn>",
346 346 "last_login": "<last_login>",
347 347 },
348 348 }
349 349 error: null
350 350
351 351 Example::
352 352
353 353 kallithea-api create_user username:bent email:bent@example.com firstname:Bent lastname:Bentsen extern_type:ldap extern_name:uid=bent,dc=example,dc=com
354 354
355 355 update_user
356 356 ^^^^^^^^^^^
357 357
358 358 Update the given user if such user exists.
359 359 This command can only be executed using the api_key of a user with admin rights.
360 360
361 361 INPUT::
362 362
363 363 id : <id_for_response>
364 364 api_key : "<api_key>"
365 365 method : "update_user"
366 366 args : {
367 367 "userid" : "<user_id or username>",
368 368 "username" : "<username> = Optional(None)",
369 369 "email" : "<useremail> = Optional(None)",
370 370 "password" : "<password> = Optional(None)",
371 371 "firstname" : "<firstname> = Optional(None)",
372 372 "lastname" : "<lastname> = Optional(None)",
373 373 "active" : "<bool> = Optional(None)",
374 374 "admin" : "<bool> = Optional(None)",
375 375 "ldap_dn" : "<ldap_dn> = Optional(None)"
376 376 }
377 377
378 378 OUTPUT::
379 379
380 380 id : <id_given_in_input>
381 381 result: {
382 382 "msg" : "updated user ID:<userid> <username>",
383 383 "user": {
384 384 "user_id" : "<user_id>",
385 385 "api_key" : "<api_key>",
386 386 "username" : "<username>",
387 387 "firstname": "<firstname>",
388 388 "lastname" : "<lastname>",
389 389 "email" : "<email>",
390 390 "emails": "<list_of_all_additional_emails>",
391 391 "active" : "<bool>",
392 392 "admin" :Β  "<bool>",
393 393 "ldap_dn" : "<ldap_dn>",
394 394 "last_login": "<last_login>",
395 395 },
396 396 }
397 397 error: null
398 398
399 399 delete_user
400 400 ^^^^^^^^^^^
401 401
402 402 Delete the given user if such a user exists.
403 403 This command can only be executed using the api_key of a user with admin rights.
404 404
405 405 INPUT::
406 406
407 407 id : <id_for_response>
408 408 api_key : "<api_key>"
409 409 method : "delete_user"
410 410 args : {
411 411 "userid" : "<user_id or username>",
412 412 }
413 413
414 414 OUTPUT::
415 415
416 416 id : <id_given_in_input>
417 417 result: {
418 418 "msg" : "deleted user ID:<userid> <username>",
419 419 "user": null
420 420 }
421 421 error: null
422 422
423 423 get_user_group
424 424 ^^^^^^^^^^^^^^
425 425
426 426 Get an existing user group.
427 427 This command can only be executed using the api_key of a user with admin rights.
428 428
429 429 INPUT::
430 430
431 431 id : <id_for_response>
432 432 api_key : "<api_key>"
433 433 method : "get_user_group"
434 434 args : {
435 435 "usergroupid" : "<user group id or name>"
436 436 }
437 437
438 438 OUTPUT::
439 439
440 440 id : <id_given_in_input>
441 441 result : None if group not exist
442 442 {
443 443 "users_group_id" : "<id>",
444 444 "group_name" : "<groupname>",
445 445 "active": "<bool>",
446 446 "members" : [
447 447 {
448 448 "user_id" : "<user_id>",
449 449 "api_key" : "<api_key>",
450 450 "username" : "<username>",
451 451 "firstname": "<firstname>",
452 452 "lastname" : "<lastname>",
453 453 "email" : "<email>",
454 454 "emails": "<list_of_all_additional_emails>",
455 455 "active" : "<bool>",
456 456 "admin" :Β  "<bool>",
457 457 "ldap_dn" : "<ldap_dn>",
458 458 "last_login": "<last_login>",
459 459 },
460 460 …
461 461 ]
462 462 }
463 463 error : null
464 464
465 465 get_user_groups
466 466 ^^^^^^^^^^^^^^^
467 467
468 468 List all existing user groups.
469 469 This command can only be executed using the api_key of a user with admin rights.
470 470
471 471 INPUT::
472 472
473 473 id : <id_for_response>
474 474 api_key : "<api_key>"
475 475 method : "get_user_groups"
476 476 args : { }
477 477
478 478 OUTPUT::
479 479
480 480 id : <id_given_in_input>
481 481 result : [
482 482 {
483 483 "users_group_id" : "<id>",
484 484 "group_name" : "<groupname>",
485 485 "active": "<bool>",
486 486 },
487 487 …
488 488 ]
489 489 error : null
490 490
491 491 create_user_group
492 492 ^^^^^^^^^^^^^^^^^
493 493
494 494 Create a new user group.
495 495 This command can only be executed using the api_key of a user with admin rights.
496 496
497 497 INPUT::
498 498
499 499 id : <id_for_response>
500 500 api_key : "<api_key>"
501 501 method : "create_user_group"
502 502 args: {
503 503 "group_name": "<groupname>",
504 504 "owner" : "<owner_name_or_id = Optional(=apiuser)>",
505 505 "active": "<bool> = Optional(True)"
506 506 }
507 507
508 508 OUTPUT::
509 509
510 510 id : <id_given_in_input>
511 511 result: {
512 512 "msg": "created new user group `<groupname>`",
513 513 "users_group": {
514 514 "users_group_id" : "<id>",
515 515 "group_name" : "<groupname>",
516 516 "active": "<bool>",
517 517 },
518 518 }
519 519 error: null
520 520
521 521 add_user_to_user_group
522 522 ^^^^^^^^^^^^^^^^^^^^^^
523 523
524 524 Adds a user to a user group. If the user already is in that group, success will be
525 525 ``false``.
526 526 This command can only be executed using the api_key of a user with admin rights.
527 527
528 528 INPUT::
529 529
530 530 id : <id_for_response>
531 531 api_key : "<api_key>"
532 532 method : "add_user_user_group"
533 533 args: {
534 534 "usersgroupid" : "<user group id or name>",
535 535 "userid" : "<user_id or username>",
536 536 }
537 537
538 538 OUTPUT::
539 539
540 540 id : <id_given_in_input>
541 541 result: {
542 542 "success": True|False # depends on if member is in group
543 543 "msg": "added member `<username>` to a user group `<groupname>` |
544 544 User is already in that group"
545 545 }
546 546 error: null
547 547
548 548 remove_user_from_user_group
549 549 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
550 550
551 551 Remove a user from a user group. If the user isn't in the given group, success will
552 552 be ``false``.
553 553 This command can only be executed using the api_key of a user with admin rights.
554 554
555 555 INPUT::
556 556
557 557 id : <id_for_response>
558 558 api_key : "<api_key>"
559 559 method : "remove_user_from_user_group"
560 560 args: {
561 561 "usersgroupid" : "<user group id or name>",
562 562 "userid" : "<user_id or username>",
563 563 }
564 564
565 565 OUTPUT::
566 566
567 567 id : <id_given_in_input>
568 568 result: {
569 569 "success": True|False, # depends on if member is in group
570 570 "msg": "removed member <username> from user group <groupname> |
571 571 User wasn't in group"
572 572 }
573 573 error: null
574 574
575 575 get_repo
576 576 ^^^^^^^^
577 577
578 578 Get an existing repository by its name or repository_id. Members will contain
579 579 either users_group or users associated to that repository.
580 580 This command can only be executed using the api_key of a user with admin rights,
581 581 or that of a regular user with at least read access to the repository.
582 582
583 583 INPUT::
584 584
585 585 id : <id_for_response>
586 586 api_key : "<api_key>"
587 587 method : "get_repo"
588 588 args: {
589 589 "repoid" : "<reponame or repo_id>",
590 590 "with_revision_names": "<bool> = Optional(False)",
591 591 "with_pullrequests": "<bool> = Optional(False)",
592 592 }
593 593
594 594 OUTPUT::
595 595
596 596 id : <id_given_in_input>
597 597 result: None if repository does not exist or
598 598 {
599 599 "repo_id" : "<repo_id>",
600 600 "repo_name" : "<reponame>"
601 601 "repo_type" : "<repo_type>",
602 602 "clone_uri" : "<clone_uri>",
603 603 "enable_downloads": "<bool>",
604 604 "enable_locking": "<bool>",
605 605 "enable_statistics": "<bool>",
606 606 "private": "<bool>",
607 607 "created_on" : "<date_time_created>",
608 608 "description" : "<description>",
609 609 "landing_rev": "<landing_rev>",
610 610 "last_changeset": {
611 611 "author": "<full_author>",
612 612 "date": "<date_time_of_commit>",
613 613 "message": "<commit_message>",
614 614 "raw_id": "<raw_id>",
615 615 "revision": "<numeric_revision>",
616 616 "short_id": "<short_id>"
617 617 },
618 618 "owner": "<repo_owner>",
619 619 "fork_of": "<name_of_fork_parent>",
620 620 "members" : [
621 621 {
622 622 "type": "user",
623 623 "user_id" : "<user_id>",
624 624 "api_key" : "<api_key>",
625 625 "username" : "<username>",
626 626 "firstname": "<firstname>",
627 627 "lastname" : "<lastname>",
628 628 "email" : "<email>",
629 629 "emails": "<list_of_all_additional_emails>",
630 630 "active" : "<bool>",
631 631 "admin" :Β  "<bool>",
632 632 "ldap_dn" : "<ldap_dn>",
633 633 "last_login": "<last_login>",
634 634 "permission" : "repository.(read|write|admin)"
635 635 },
636 636 …
637 637 {
638 638 "type": "users_group",
639 639 "id" : "<usersgroupid>",
640 640 "name" : "<usersgroupname>",
641 641 "active": "<bool>",
642 642 "permission" : "repository.(read|write|admin)"
643 643 },
644 644 …
645 645 ],
646 646 "followers": [
647 647 {
648 648 "user_id" : "<user_id>",
649 649 "username" : "<username>",
650 650 "api_key" : "<api_key>",
651 651 "firstname": "<firstname>",
652 652 "lastname" : "<lastname>",
653 653 "email" : "<email>",
654 654 "emails": "<list_of_all_additional_emails>",
655 655 "ip_addresses": "<list_of_ip_addresses_for_user>",
656 656 "active" : "<bool>",
657 657 "admin" :Β  "<bool>",
658 658 "ldap_dn" : "<ldap_dn>",
659 659 "last_login": "<last_login>",
660 660 },
661 661 …
662 662 ],
663 663 <if with_revision_names == True>
664 664 "tags": {
665 665 "<tagname>": "<raw_id>",
666 666 ...
667 667 },
668 668 "branches": {
669 669 "<branchname>": "<raw_id>",
670 670 ...
671 671 },
672 672 "bookmarks": {
673 673 "<bookmarkname>": "<raw_id>",
674 674 ...
675 675 },
676 676 <if with_pullrequests == True>
677 677 "pull_requests": [
678 678 {
679 679 "status": "<pull_request_status>",
680 680 "pull_request_id": <pull_request_id>,
681 681 "description": "<pull_request_description>",
682 682 "title": "<pull_request_title>",
683 683 "url": "<pull_request_url>",
684 684 "reviewers": [
685 685 {
686 686 "username": "<user_id>",
687 687 },
688 688 ...
689 689 ],
690 690 "org_repo_url": "<repo_url>",
691 691 "org_ref_parts": [
692 692 "<ref_type>",
693 693 "<ref_name>",
694 694 "<raw_id>"
695 695 ],
696 696 "other_ref_parts": [
697 697 "<ref_type>",
698 698 "<ref_name>",
699 699 "<raw_id>"
700 700 ],
701 701 "comments": [
702 702 {
703 703 "username": "<user_id>",
704 704 "text": "<comment text>",
705 705 "comment_id": "<comment_id>",
706 706 },
707 707 ...
708 708 ],
709 709 "owner": "<username>",
710 710 "statuses": [
711 711 {
712 712 "status": "<status_of_review>", # "under_review", "approved" or "rejected"
713 713 "reviewer": "<user_id>",
714 714 "modified_at": "<date_time_of_review>" # iso 8601 date, server's timezone
715 715 },
716 716 ...
717 717 ],
718 718 "revisions": [
719 719 "<raw_id>",
720 720 ...
721 721 ]
722 722 },
723 723 ...
724 724 ]
725 725 }
726 726 error: null
727 727
728 728 get_repos
729 729 ^^^^^^^^^
730 730
731 731 List all existing repositories.
732 732 This command can only be executed using the api_key of a user with admin rights,
733 733 or that of a regular user with at least read access to the repository.
734 734
735 735 INPUT::
736 736
737 737 id : <id_for_response>
738 738 api_key : "<api_key>"
739 739 method : "get_repos"
740 740 args: { }
741 741
742 742 OUTPUT::
743 743
744 744 id : <id_given_in_input>
745 745 result: [
746 746 {
747 747 "repo_id" : "<repo_id>",
748 748 "repo_name" : "<reponame>"
749 749 "repo_type" : "<repo_type>",
750 750 "clone_uri" : "<clone_uri>",
751 751 "private" : "<bool>",
752 752 "created_on" : "<datetimecreated>",
753 753 "description" : "<description>",
754 754 "landing_rev": "<landing_rev>",
755 755 "owner": "<repo_owner>",
756 756 "fork_of": "<name_of_fork_parent>",
757 757 "enable_downloads": "<bool>",
758 758 "enable_locking": "<bool>",
759 759 "enable_statistics": "<bool>",
760 760 },
761 761 …
762 762 ]
763 763 error: null
764 764
765 765 get_repo_nodes
766 766 ^^^^^^^^^^^^^^
767 767
768 768 Return a list of files and directories for a given path at the given revision.
769 769 It is possible to specify ret_type to show only ``files`` or ``dirs``.
770 770 This command can only be executed using the api_key of a user with admin rights.
771 771
772 772 INPUT::
773 773
774 774 id : <id_for_response>
775 775 api_key : "<api_key>"
776 776 method : "get_repo_nodes"
777 777 args: {
778 778 "repoid" : "<reponame or repo_id>"
779 779 "revision" : "<revision>",
780 780 "root_path" : "<root_path>",
781 781 "ret_type" : "<ret_type> = Optional('all')"
782 782 }
783 783
784 784 OUTPUT::
785 785
786 786 id : <id_given_in_input>
787 787 result: [
788 788 {
789 789 "name" : "<name>"
790 790 "type" : "<type>",
791 791 },
792 792 …
793 793 ]
794 794 error: null
795 795
796 796 create_repo
797 797 ^^^^^^^^^^^
798 798
799 799 Create a repository. If the repository name contains "/", all needed repository
800 800 groups will be created. For example "foo/bar/baz" will create repository groups
801 801 "foo", "bar" (with "foo" as parent), and create "baz" repository with
802 802 "bar" as group.
803 803 This command can only be executed using the api_key of a user with admin rights,
804 804 or that of a regular user with create repository permission.
805 805 Regular users cannot specify owner parameter.
806 806
807 807 INPUT::
808 808
809 809 id : <id_for_response>
810 810 api_key : "<api_key>"
811 811 method : "create_repo"
812 812 args: {
813 813 "repo_name" : "<reponame>",
814 814 "owner" : "<owner_name_or_id = Optional(=apiuser)>",
815 815 "repo_type" : "<repo_type> = Optional('hg')",
816 816 "description" : "<description> = Optional('')",
817 817 "private" : "<bool> = Optional(False)",
818 818 "clone_uri" : "<clone_uri> = Optional(None)",
819 819 "landing_rev" : "<landing_rev> = Optional('tip')",
820 820 "enable_downloads": "<bool> = Optional(False)",
821 821 "enable_locking": "<bool> = Optional(False)",
822 822 "enable_statistics": "<bool> = Optional(False)",
823 823 }
824 824
825 825 OUTPUT::
826 826
827 827 id : <id_given_in_input>
828 828 result: {
829 829 "msg": "Created new repository `<reponame>`",
830 830 "repo": {
831 831 "repo_id" : "<repo_id>",
832 832 "repo_name" : "<reponame>"
833 833 "repo_type" : "<repo_type>",
834 834 "clone_uri" : "<clone_uri>",
835 835 "private" : "<bool>",
836 836 "created_on" : "<datetimecreated>",
837 837 "description" : "<description>",
838 838 "landing_rev": "<landing_rev>",
839 839 "owner": "<username or user_id>",
840 840 "fork_of": "<name_of_fork_parent>",
841 841 "enable_downloads": "<bool>",
842 842 "enable_locking": "<bool>",
843 843 "enable_statistics": "<bool>",
844 844 },
845 845 }
846 846 error: null
847 847
848 848 update_repo
849 849 ^^^^^^^^^^^
850 850
851 851 Update a repository.
852 852 This command can only be executed using the api_key of a user with admin rights,
853 853 or that of a regular user with create repository permission.
854 854 Regular users cannot specify owner parameter.
855 855
856 856 INPUT::
857 857
858 858 id : <id_for_response>
859 859 api_key : "<api_key>"
860 860 method : "update_repo"
861 861 args: {
862 862 "repoid" : "<reponame or repo_id>"
863 863 "name" : "<reponame> = Optional('')",
864 864 "group" : "<group_id> = Optional(None)",
865 865 "owner" : "<owner_name_or_id = Optional(=apiuser)>",
866 866 "description" : "<description> = Optional('')",
867 867 "private" : "<bool> = Optional(False)",
868 868 "clone_uri" : "<clone_uri> = Optional(None)",
869 869 "landing_rev" : "<landing_rev> = Optional('tip')",
870 870 "enable_downloads": "<bool> = Optional(False)",
871 871 "enable_locking": "<bool> = Optional(False)",
872 872 "enable_statistics": "<bool> = Optional(False)",
873 873 }
874 874
875 875 OUTPUT::
876 876
877 877 id : <id_given_in_input>
878 878 result: {
879 879 "msg": "updated repo ID:repo_id `<reponame>`",
880 880 "repository": {
881 881 "repo_id" : "<repo_id>",
882 882 "repo_name" : "<reponame>"
883 883 "repo_type" : "<repo_type>",
884 884 "clone_uri" : "<clone_uri>",
885 885 "private": "<bool>",
886 886 "created_on" : "<datetimecreated>",
887 887 "description" : "<description>",
888 888 "landing_rev": "<landing_rev>",
889 889 "owner": "<username or user_id>",
890 890 "fork_of": "<name_of_fork_parent>",
891 891 "enable_downloads": "<bool>",
892 892 "enable_locking": "<bool>",
893 893 "enable_statistics": "<bool>",
894 894 "last_changeset": {
895 895 "author": "<full_author>",
896 896 "date": "<date_time_of_commit>",
897 897 "message": "<commit_message>",
898 898 "raw_id": "<raw_id>",
899 899 "revision": "<numeric_revision>",
900 900 "short_id": "<short_id>"
901 901 }
902 902 "locked_by": "<username>",
903 903 "locked_date": "<float lock_time>",
904 904 },
905 905 }
906 906 error: null
907 907
908 908 fork_repo
909 909 ^^^^^^^^^
910 910
911 911 Create a fork of the given repo. If using Celery, this will
912 912 return success message immediately and a fork will be created
913 913 asynchronously.
914 914 This command can only be executed using the api_key of a user with admin
915 915 rights, or with the global fork permission, by a regular user with create
916 916 repository permission and at least read access to the repository.
917 917 Regular users cannot specify owner parameter.
918 918
919 919 INPUT::
920 920
921 921 id : <id_for_response>
922 922 api_key : "<api_key>"
923 923 method : "fork_repo"
924 924 args: {
925 925 "repoid" : "<reponame or repo_id>",
926 926 "fork_name": "<forkname>",
927 927 "owner": "<username or user_id = Optional(=apiuser)>",
928 928 "description": "<description>",
929 929 "copy_permissions": "<bool>",
930 930 "private": "<bool>",
931 931 "landing_rev": "<landing_rev>"
932 932
933 933 }
934 934
935 935 OUTPUT::
936 936
937 937 id : <id_given_in_input>
938 938 result: {
939 939 "msg": "Created fork of `<reponame>` as `<forkname>`",
940 940 "success": true
941 941 }
942 942 error: null
943 943
944 944 delete_repo
945 945 ^^^^^^^^^^^
946 946
947 947 Delete a repository.
948 948 This command can only be executed using the api_key of a user with admin rights,
949 949 or that of a regular user with admin access to the repository.
950 950 When ``forks`` param is set it is possible to detach or delete forks of the deleted repository.
951 951
952 952 INPUT::
953 953
954 954 id : <id_for_response>
955 955 api_key : "<api_key>"
956 956 method : "delete_repo"
957 957 args: {
958 958 "repoid" : "<reponame or repo_id>",
959 959 "forks" : "`delete` or `detach` = Optional(None)"
960 960 }
961 961
962 962 OUTPUT::
963 963
964 964 id : <id_given_in_input>
965 965 result: {
966 966 "msg": "Deleted repository `<reponame>`",
967 967 "success": true
968 968 }
969 969 error: null
970 970
971 971 grant_user_permission
972 972 ^^^^^^^^^^^^^^^^^^^^^
973 973
974 974 Grant permission for a user on the given repository, or update the existing one if found.
975 975 This command can only be executed using the api_key of a user with admin rights.
976 976
977 977 INPUT::
978 978
979 979 id : <id_for_response>
980 980 api_key : "<api_key>"
981 981 method : "grant_user_permission"
982 982 args: {
983 983 "repoid" : "<reponame or repo_id>"
984 984 "userid" : "<username or user_id>"
985 985 "perm" : "(repository.(none|read|write|admin))",
986 986 }
987 987
988 988 OUTPUT::
989 989
990 990 id : <id_given_in_input>
991 991 result: {
992 992 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
993 993 "success": true
994 994 }
995 995 error: null
996 996
997 997 revoke_user_permission
998 998 ^^^^^^^^^^^^^^^^^^^^^^
999 999
1000 1000 Revoke permission for a user on the given repository.
1001 1001 This command can only be executed using the api_key of a user with admin rights.
1002 1002
1003 1003 INPUT::
1004 1004
1005 1005 id : <id_for_response>
1006 1006 api_key : "<api_key>"
1007 1007 method : "revoke_user_permission"
1008 1008 args: {
1009 1009 "repoid" : "<reponame or repo_id>"
1010 1010 "userid" : "<username or user_id>"
1011 1011 }
1012 1012
1013 1013 OUTPUT::
1014 1014
1015 1015 id : <id_given_in_input>
1016 1016 result: {
1017 1017 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1018 1018 "success": true
1019 1019 }
1020 1020 error: null
1021 1021
1022 1022 grant_user_group_permission
1023 1023 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
1024 1024
1025 1025 Grant permission for a user group on the given repository, or update the
1026 1026 existing one if found.
1027 1027 This command can only be executed using the api_key of a user with admin rights.
1028 1028
1029 1029 INPUT::
1030 1030
1031 1031 id : <id_for_response>
1032 1032 api_key : "<api_key>"
1033 1033 method : "grant_user_group_permission"
1034 1034 args: {
1035 1035 "repoid" : "<reponame or repo_id>"
1036 1036 "usersgroupid" : "<user group id or name>"
1037 1037 "perm" : "(repository.(none|read|write|admin))",
1038 1038 }
1039 1039
1040 1040 OUTPUT::
1041 1041
1042 1042 id : <id_given_in_input>
1043 1043 result: {
1044 1044 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1045 1045 "success": true
1046 1046 }
1047 1047 error: null
1048 1048
1049 1049 revoke_user_group_permission
1050 1050 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1051 1051
1052 1052 Revoke permission for a user group on the given repository.
1053 1053 This command can only be executed using the api_key of a user with admin rights.
1054 1054
1055 1055 INPUT::
1056 1056
1057 1057 id : <id_for_response>
1058 1058 api_key : "<api_key>"
1059 1059 method : "revoke_user_group_permission"
1060 1060 args: {
1061 1061 "repoid" : "<reponame or repo_id>"
1062 1062 "usersgroupid" : "<user group id or name>"
1063 1063 }
1064 1064
1065 1065 OUTPUT::
1066 1066
1067 1067 id : <id_given_in_input>
1068 1068 result: {
1069 1069 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1070 1070 "success": true
1071 1071 }
1072 1072 error: null
1073 1073
1074 get_changesets
1075 ^^^^^^^^^^^^^^
1076
1077 Get changesets of a given repository. This command can only be executed using the api_key
1078 of a user with read permissions to the repository.
1079
1080 INPUT::
1081
1082 id : <id_for_response>
1083 api_key : "<api_key>"
1084 method : "get_changesets"
1085 args: {
1086 "repoid" : "<reponame or repo_id>",
1087 "start": "<revision number> = Optional(None)",
1088 "end": "<revision number> = Optional(None)",
1089 "start_date": "<date> = Optional(None)", # in "%Y-%m-%dT%H:%M:%S" format
1090 "end_date": "<date> = Optional(None)", # in "%Y-%m-%dT%H:%M:%S" format
1091 "branch_name": "<branch name filter> = Optional(None)",
1092 "reverse": "<bool> = Optional(False)",
1093 "with_file_list": "<bool> = Optional(False)"
1094 }
1095
1096 OUTPUT::
1097
1098 id : <id_given_in_input>
1099 result: [
1100 {
1101 "raw_id": "<raw_id>",
1102 "short_id": "short_id": "<short_id>",
1103 "author": "<full_author>",
1104 "date": "<date_time_of_commit>",
1105 "message": "<commit_message>",
1106 "revision": "<numeric_revision>",
1107 <if with_file_list == True>
1108 "added": [<list of added files>],
1109 "changed": [<list of changed files>],
1110 "removed": [<list of removed files>]
1111 },
1112 ...
1113 ]
1114 error: null
1115
1074 1116 get_changeset
1075 1117 ^^^^^^^^^^^^^
1076 1118
1077 1119 Get information and review status for a given changeset. This command can only
1078 1120 be executed using the api_key of a user with read permissions to the
1079 1121 repository.
1080 1122
1081 1123 INPUT::
1082 1124
1083 1125 id : <id_for_response>
1084 1126 api_key : "<api_key>"
1085 1127 method : "get_changeset"
1086 1128 args: {
1087 1129 "repoid" : "<reponame or repo_id>",
1088 1130 "raw_id" : "<raw_id>",
1089 1131 "with_reviews": "<bool> = Optional(False)"
1090 1132 }
1091 1133
1092 1134 OUTPUT::
1093 1135
1094 1136 id : <id_given_in_input>
1095 1137 result: {
1096 1138 "author": "<full_author>",
1097 1139 "date": "<date_time_of_commit>",
1098 1140 "message": "<commit_message>",
1099 1141 "raw_id": "<raw_id>",
1100 1142 "revision": "<numeric_revision>",
1101 1143 "short_id": "<short_id>",
1102 1144 "reviews": [{
1103 1145 "reviewer": "<username>",
1104 1146 "modified_at": "<date_time_of_review>", # iso 8601 date, server's timezone
1105 1147 "status": "<status_of_review>", # "under_review", "approved" or "rejected"
1106 1148 },
1107 1149 ...
1108 1150 ]
1109 1151 }
1110 1152 error: null
1111 1153
1112 1154 Example output::
1113 1155
1114 1156 {
1115 1157 "id" : 1,
1116 1158 "error" : null,
1117 1159 "result" : {
1118 1160 "author" : {
1119 1161 "email" : "user@example.com",
1120 1162 "name" : "Kallithea Admin"
1121 1163 },
1122 1164 "changed" : [],
1123 1165 "short_id" : "e1022d3d28df",
1124 1166 "date" : "2017-03-28T09:09:03",
1125 1167 "added" : [
1126 1168 "README.rst"
1127 1169 ],
1128 1170 "removed" : [],
1129 1171 "revision" : 0,
1130 1172 "raw_id" : "e1022d3d28dfba02f626cde65dbe08f4ceb0e4e7",
1131 1173 "message" : "Added file via Kallithea",
1132 1174 "id" : "e1022d3d28dfba02f626cde65dbe08f4ceb0e4e7",
1133 1175 "reviews" : [
1134 1176 {
1135 1177 "status" : "under_review",
1136 1178 "modified_at" : "2017-03-28T09:17:08.618",
1137 1179 "reviewer" : "user"
1138 1180 }
1139 1181 ]
1140 1182 }
1141 1183 }
1142 1184
1143 1185 get_pullrequest
1144 1186 ^^^^^^^^^^^^^^^
1145 1187
1146 1188 Get information and review status for a given pull request. This command can only be executed
1147 1189 using the api_key of a user with read permissions to the original repository.
1148 1190
1149 1191 INPUT::
1150 1192
1151 1193 id : <id_for_response>
1152 1194 api_key : "<api_key>"
1153 1195 method : "get_pullrequest"
1154 1196 args: {
1155 1197 "pullrequest_id" : "<pullrequest_id>",
1156 1198 }
1157 1199
1158 1200 OUTPUT::
1159 1201
1160 1202 id : <id_given_in_input>
1161 1203 result: {
1162 1204 "status": "<pull_request_status>",
1163 1205 "pull_request_id": <pull_request_id>,
1164 1206 "description": "<pull_request_description>",
1165 1207 "title": "<pull_request_title>",
1166 1208 "url": "<pull_request_url>",
1167 1209 "reviewers": [
1168 1210 {
1169 1211 "username": "<user_name>",
1170 1212 },
1171 1213 ...
1172 1214 ],
1173 1215 "org_repo_url": "<repo_url>",
1174 1216 "org_ref_parts": [
1175 1217 "<ref_type>",
1176 1218 "<ref_name>",
1177 1219 "<raw_id>"
1178 1220 ],
1179 1221 "other_ref_parts": [
1180 1222 "<ref_type>",
1181 1223 "<ref_name>",
1182 1224 "<raw_id>"
1183 1225 ],
1184 1226 "comments": [
1185 1227 {
1186 1228 "username": "<user_name>",
1187 1229 "text": "<comment text>",
1188 1230 "comment_id": "<comment_id>",
1189 1231 },
1190 1232 ...
1191 1233 ],
1192 1234 "owner": "<username>",
1193 1235 "statuses": [
1194 1236 {
1195 1237 "status": "<status_of_review>", # "under_review", "approved" or "rejected"
1196 1238 "reviewer": "<user_name>",
1197 1239 "modified_at": "<date_time_of_review>" # iso 8601 date, server's timezone
1198 1240 },
1199 1241 ...
1200 1242 ],
1201 1243 "revisions": [
1202 1244 "<raw_id>",
1203 1245 ...
1204 1246 ]
1205 1247 },
1206 1248 error: null
1207 1249
1208 1250 comment_pullrequest
1209 1251 ^^^^^^^^^^^^^^^^^^^
1210 1252
1211 1253 Add comment, change status or close a given pull request. This command can only be executed
1212 1254 using the api_key of a user with read permissions to the original repository.
1213 1255
1214 1256 INPUT::
1215 1257
1216 1258 id : <id_for_response>
1217 1259 api_key : "<api_key>"
1218 1260 method : "comment_pullrequest"
1219 1261 args: {
1220 1262 "pull_request_id": "<pull_request_id>",
1221 1263 "comment_msg": Optional(''),
1222 1264 "status": Optional(None), # "under_review", "approved" or "rejected"
1223 1265 "close_pr": Optional(False)",
1224 1266 }
1225 1267
1226 1268 OUTPUT::
1227 1269
1228 1270 id : <id_given_in_input>
1229 1271 result: True
1230 1272 error: null
1231 1273
1232 1274
1233 1275 API access for web views
1234 1276 ------------------------
1235 1277
1236 1278 API access can also be turned on for each web view in Kallithea that is
1237 1279 decorated with the ``@LoginRequired`` decorator. Some views use
1238 1280 ``@LoginRequired(api_access=True)`` and are always available. By default only
1239 1281 RSS/Atom feed views are enabled. Other views are
1240 1282 only available if they have been whitelisted. Edit the
1241 1283 ``api_access_controllers_whitelist`` option in your .ini file and define views
1242 1284 that should have API access enabled.
1243 1285
1244 1286 For example, to enable API access to patch/diff, raw file and archive::
1245 1287
1246 1288 api_access_controllers_whitelist =
1247 1289 ChangesetController:changeset_patch,
1248 1290 ChangesetController:changeset_raw,
1249 1291 FilesController:raw,
1250 1292 FilesController:archivefile
1251 1293
1252 1294 After this change, a Kallithea view can be accessed without login using
1253 1295 bearer authentication, by including this header with the request::
1254 1296
1255 1297 Authentication: Bearer <api_key>
1256 1298
1257 1299 Alternatively, the API key can be passed in the URL query string using
1258 1300 ``?api_key=<api_key>``, though this is not recommended due to the increased
1259 1301 risk of API key leaks, and support will likely be removed in the future.
1260 1302
1261 1303 Exposing raw diffs is a good way to integrate with
1262 1304 third-party services like code review, or build farms that can download archives.
@@ -1,2573 +1,2594 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.api.api
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 API controller for Kallithea
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Aug 20, 2011
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28 import time
29 29 import traceback
30 30 import logging
31
32 from datetime import datetime
31 33 from sqlalchemy import or_
32 34
33 35 from tg import request
34 36
35 37 from kallithea.controllers.api import JSONRPCController, JSONRPCError
36 38 from kallithea.lib.auth import (
37 39 PasswordGenerator, AuthUser, HasPermissionAnyDecorator,
38 40 HasPermissionAnyDecorator, HasPermissionAny, HasRepoPermissionLevel,
39 41 HasRepoGroupPermissionLevel, HasUserGroupPermissionLevel)
40 42 from kallithea.lib.utils import map_groups, repo2db_mapper
41 43 from kallithea.lib.utils2 import (
42 44 str2bool, time_to_datetime, safe_int, Optional, OAttr)
43 45 from kallithea.model.meta import Session
44 46 from kallithea.model.repo_group import RepoGroupModel
45 47 from kallithea.model.scm import ScmModel, UserGroupList
46 48 from kallithea.model.repo import RepoModel
47 49 from kallithea.model.user import UserModel
48 50 from kallithea.model.user_group import UserGroupModel
49 51 from kallithea.model.gist import GistModel
50 52 from kallithea.model.changeset_status import ChangesetStatusModel
51 53 from kallithea.model.comment import ChangesetCommentsModel
52 54 from kallithea.model.pull_request import PullRequestModel
53 55 from kallithea.model.db import (
54 56 Repository, Setting, UserIpMap, Permission, User, Gist,
55 57 RepoGroup, UserGroup, PullRequest, ChangesetStatus)
56 58 from kallithea.lib.compat import json
57 59 from kallithea.lib.exceptions import (
58 60 DefaultUserException, UserGroupsAssignedException)
59 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
61 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, EmptyRepositoryError
60 62 from kallithea.lib.vcs.backends.base import EmptyChangeset
61 63 from kallithea.lib.utils import action_logger
62 64
63 65 log = logging.getLogger(__name__)
64 66
65 67
66 68 def store_update(updates, attr, name):
67 69 """
68 70 Stores param in updates dict if it's not instance of Optional
69 71 allows easy updates of passed in params
70 72 """
71 73 if not isinstance(attr, Optional):
72 74 updates[name] = attr
73 75
74 76
75 77 def get_user_or_error(userid):
76 78 """
77 79 Get user by id or name or return JsonRPCError if not found
78 80
79 81 :param userid:
80 82 """
81 83 user = UserModel().get_user(userid)
82 84 if user is None:
83 85 raise JSONRPCError("user `%s` does not exist" % (userid,))
84 86 return user
85 87
86 88
87 89 def get_repo_or_error(repoid):
88 90 """
89 91 Get repo by id or name or return JsonRPCError if not found
90 92
91 93 :param repoid:
92 94 """
93 95 repo = RepoModel().get_repo(repoid)
94 96 if repo is None:
95 97 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
96 98 return repo
97 99
98 100
99 101 def get_repo_group_or_error(repogroupid):
100 102 """
101 103 Get repo group by id or name or return JsonRPCError if not found
102 104
103 105 :param repogroupid:
104 106 """
105 107 repo_group = RepoGroup.guess_instance(repogroupid)
106 108 if repo_group is None:
107 109 raise JSONRPCError(
108 110 'repository group `%s` does not exist' % (repogroupid,))
109 111 return repo_group
110 112
111 113
112 114 def get_user_group_or_error(usergroupid):
113 115 """
114 116 Get user group by id or name or return JsonRPCError if not found
115 117
116 118 :param usergroupid:
117 119 """
118 120 user_group = UserGroupModel().get_group(usergroupid)
119 121 if user_group is None:
120 122 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
121 123 return user_group
122 124
123 125
124 126 def get_perm_or_error(permid, prefix=None):
125 127 """
126 128 Get permission by id or name or return JsonRPCError if not found
127 129
128 130 :param permid:
129 131 """
130 132 perm = Permission.get_by_key(permid)
131 133 if perm is None:
132 134 raise JSONRPCError('permission `%s` does not exist' % (permid,))
133 135 if prefix:
134 136 if not perm.permission_name.startswith(prefix):
135 137 raise JSONRPCError('permission `%s` is invalid, '
136 138 'should start with %s' % (permid, prefix))
137 139 return perm
138 140
139 141
140 142 def get_gist_or_error(gistid):
141 143 """
142 144 Get gist by id or gist_access_id or return JsonRPCError if not found
143 145
144 146 :param gistid:
145 147 """
146 148 gist = GistModel().get_gist(gistid)
147 149 if gist is None:
148 150 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
149 151 return gist
150 152
151 153
152 154 class ApiController(JSONRPCController):
153 155 """
154 156 API Controller
155 157
156 158 The authenticated user can be found as request.authuser.
157 159
158 160 Example function::
159 161
160 162 def func(arg1, arg2,...):
161 163 pass
162 164
163 165 Each function should also **raise** JSONRPCError for any
164 166 errors that happens.
165 167 """
166 168
167 169 @HasPermissionAnyDecorator('hg.admin')
168 170 def test(self, args):
169 171 return args
170 172
171 173 @HasPermissionAnyDecorator('hg.admin')
172 174 def pull(self, repoid):
173 175 """
174 176 Triggers a pull from remote location on given repo. Can be used to
175 177 automatically keep remote repos up to date. This command can be executed
176 178 only using api_key belonging to user with admin rights
177 179
178 180 :param repoid: repository name or repository id
179 181 :type repoid: str or int
180 182
181 183 OUTPUT::
182 184
183 185 id : <id_given_in_input>
184 186 result : {
185 187 "msg": "Pulled from `<repository name>`"
186 188 "repository": "<repository name>"
187 189 }
188 190 error : null
189 191
190 192 ERROR OUTPUT::
191 193
192 194 id : <id_given_in_input>
193 195 result : null
194 196 error : {
195 197 "Unable to pull changes from `<reponame>`"
196 198 }
197 199
198 200 """
199 201
200 202 repo = get_repo_or_error(repoid)
201 203
202 204 try:
203 205 ScmModel().pull_changes(repo.repo_name,
204 206 request.authuser.username)
205 207 return dict(
206 208 msg='Pulled from `%s`' % repo.repo_name,
207 209 repository=repo.repo_name
208 210 )
209 211 except Exception:
210 212 log.error(traceback.format_exc())
211 213 raise JSONRPCError(
212 214 'Unable to pull changes from `%s`' % repo.repo_name
213 215 )
214 216
215 217 @HasPermissionAnyDecorator('hg.admin')
216 218 def rescan_repos(self, remove_obsolete=Optional(False)):
217 219 """
218 220 Triggers rescan repositories action. If remove_obsolete is set
219 221 than also delete repos that are in database but not in the filesystem.
220 222 aka "clean zombies". This command can be executed only using api_key
221 223 belonging to user with admin rights.
222 224
223 225 :param remove_obsolete: deletes repositories from
224 226 database that are not found on the filesystem
225 227 :type remove_obsolete: Optional(bool)
226 228
227 229 OUTPUT::
228 230
229 231 id : <id_given_in_input>
230 232 result : {
231 233 'added': [<added repository name>,...]
232 234 'removed': [<removed repository name>,...]
233 235 }
234 236 error : null
235 237
236 238 ERROR OUTPUT::
237 239
238 240 id : <id_given_in_input>
239 241 result : null
240 242 error : {
241 243 'Error occurred during rescan repositories action'
242 244 }
243 245
244 246 """
245 247
246 248 try:
247 249 rm_obsolete = Optional.extract(remove_obsolete)
248 250 added, removed = repo2db_mapper(ScmModel().repo_scan(),
249 251 remove_obsolete=rm_obsolete)
250 252 return {'added': added, 'removed': removed}
251 253 except Exception:
252 254 log.error(traceback.format_exc())
253 255 raise JSONRPCError(
254 256 'Error occurred during rescan repositories action'
255 257 )
256 258
257 259 def invalidate_cache(self, repoid):
258 260 """
259 261 Invalidate cache for repository.
260 262 This command can be executed only using api_key belonging to user with admin
261 263 rights or regular user that have write or admin or write access to repository.
262 264
263 265 :param repoid: repository name or repository id
264 266 :type repoid: str or int
265 267
266 268 OUTPUT::
267 269
268 270 id : <id_given_in_input>
269 271 result : {
270 272 'msg': Cache for repository `<repository name>` was invalidated,
271 273 'repository': <repository name>
272 274 }
273 275 error : null
274 276
275 277 ERROR OUTPUT::
276 278
277 279 id : <id_given_in_input>
278 280 result : null
279 281 error : {
280 282 'Error occurred during cache invalidation action'
281 283 }
282 284
283 285 """
284 286 repo = get_repo_or_error(repoid)
285 287 if not HasPermissionAny('hg.admin')():
286 288 if not HasRepoPermissionLevel('write')(repo.repo_name):
287 289 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
288 290
289 291 try:
290 292 ScmModel().mark_for_invalidation(repo.repo_name)
291 293 return dict(
292 294 msg='Cache for repository `%s` was invalidated' % (repoid,),
293 295 repository=repo.repo_name
294 296 )
295 297 except Exception:
296 298 log.error(traceback.format_exc())
297 299 raise JSONRPCError(
298 300 'Error occurred during cache invalidation action'
299 301 )
300 302
301 303 # permission check inside
302 304 def lock(self, repoid, locked=Optional(None),
303 305 userid=Optional(OAttr('apiuser'))):
304 306 """
305 307 Set locking state on given repository by given user. If userid param
306 308 is skipped, then it is set to id of user who is calling this method.
307 309 If locked param is skipped then function shows current lock state of
308 310 given repo. This command can be executed only using api_key belonging
309 311 to user with admin rights or regular user that have admin or write
310 312 access to repository.
311 313
312 314 :param repoid: repository name or repository id
313 315 :type repoid: str or int
314 316 :param locked: lock state to be set
315 317 :type locked: Optional(bool)
316 318 :param userid: set lock as user
317 319 :type userid: Optional(str or int)
318 320
319 321 OUTPUT::
320 322
321 323 id : <id_given_in_input>
322 324 result : {
323 325 'repo': '<reponame>',
324 326 'locked': <bool: lock state>,
325 327 'locked_since': <int: lock timestamp>,
326 328 'locked_by': <username of person who made the lock>,
327 329 'lock_state_changed': <bool: True if lock state has been changed in this request>,
328 330 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
329 331 or
330 332 'msg': 'Repo `<repository name>` not locked.'
331 333 or
332 334 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
333 335 }
334 336 error : null
335 337
336 338 ERROR OUTPUT::
337 339
338 340 id : <id_given_in_input>
339 341 result : null
340 342 error : {
341 343 'Error occurred locking repository `<reponame>`
342 344 }
343 345
344 346 """
345 347 repo = get_repo_or_error(repoid)
346 348 if HasPermissionAny('hg.admin')():
347 349 pass
348 350 elif HasRepoPermissionLevel('write')(repo.repo_name):
349 351 # make sure normal user does not pass someone else userid,
350 352 # he is not allowed to do that
351 353 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
352 354 raise JSONRPCError(
353 355 'userid is not the same as your user'
354 356 )
355 357 else:
356 358 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
357 359
358 360 if isinstance(userid, Optional):
359 361 userid = request.authuser.user_id
360 362
361 363 user = get_user_or_error(userid)
362 364
363 365 if isinstance(locked, Optional):
364 366 lockobj = Repository.getlock(repo)
365 367
366 368 if lockobj[0] is None:
367 369 _d = {
368 370 'repo': repo.repo_name,
369 371 'locked': False,
370 372 'locked_since': None,
371 373 'locked_by': None,
372 374 'lock_state_changed': False,
373 375 'msg': 'Repo `%s` not locked.' % repo.repo_name
374 376 }
375 377 return _d
376 378 else:
377 379 userid, time_ = lockobj
378 380 lock_user = get_user_or_error(userid)
379 381 _d = {
380 382 'repo': repo.repo_name,
381 383 'locked': True,
382 384 'locked_since': time_,
383 385 'locked_by': lock_user.username,
384 386 'lock_state_changed': False,
385 387 'msg': ('Repo `%s` locked by `%s` on `%s`.'
386 388 % (repo.repo_name, lock_user.username,
387 389 json.dumps(time_to_datetime(time_))))
388 390 }
389 391 return _d
390 392
391 393 # force locked state through a flag
392 394 else:
393 395 locked = str2bool(locked)
394 396 try:
395 397 if locked:
396 398 lock_time = time.time()
397 399 Repository.lock(repo, user.user_id, lock_time)
398 400 else:
399 401 lock_time = None
400 402 Repository.unlock(repo)
401 403 _d = {
402 404 'repo': repo.repo_name,
403 405 'locked': locked,
404 406 'locked_since': lock_time,
405 407 'locked_by': user.username,
406 408 'lock_state_changed': True,
407 409 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
408 410 % (user.username, repo.repo_name, locked))
409 411 }
410 412 return _d
411 413 except Exception:
412 414 log.error(traceback.format_exc())
413 415 raise JSONRPCError(
414 416 'Error occurred locking repository `%s`' % repo.repo_name
415 417 )
416 418
417 419 def get_locks(self, userid=Optional(OAttr('apiuser'))):
418 420 """
419 421 Get all repositories with locks for given userid, if
420 422 this command is run by non-admin account userid is set to user
421 423 who is calling this method, thus returning locks for himself.
422 424
423 425 :param userid: User to get locks for
424 426 :type userid: Optional(str or int)
425 427
426 428 OUTPUT::
427 429
428 430 id : <id_given_in_input>
429 431 result : {
430 432 [repo_object, repo_object,...]
431 433 }
432 434 error : null
433 435 """
434 436
435 437 if not HasPermissionAny('hg.admin')():
436 438 # make sure normal user does not pass someone else userid,
437 439 # he is not allowed to do that
438 440 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
439 441 raise JSONRPCError(
440 442 'userid is not the same as your user'
441 443 )
442 444
443 445 ret = []
444 446 if isinstance(userid, Optional):
445 447 user = None
446 448 else:
447 449 user = get_user_or_error(userid)
448 450
449 451 # show all locks
450 452 for r in Repository.query():
451 453 userid, time_ = r.locked
452 454 if time_:
453 455 _api_data = r.get_api_data()
454 456 # if we use userfilter just show the locks for this user
455 457 if user is not None:
456 458 if safe_int(userid) == user.user_id:
457 459 ret.append(_api_data)
458 460 else:
459 461 ret.append(_api_data)
460 462
461 463 return ret
462 464
463 465 @HasPermissionAnyDecorator('hg.admin')
464 466 def get_ip(self, userid=Optional(OAttr('apiuser'))):
465 467 """
466 468 Shows IP address as seen from Kallithea server, together with all
467 469 defined IP addresses for given user. If userid is not passed data is
468 470 returned for user who's calling this function.
469 471 This command can be executed only using api_key belonging to user with
470 472 admin rights.
471 473
472 474 :param userid: username to show ips for
473 475 :type userid: Optional(str or int)
474 476
475 477 OUTPUT::
476 478
477 479 id : <id_given_in_input>
478 480 result : {
479 481 "server_ip_addr": "<ip_from_clien>",
480 482 "user_ips": [
481 483 {
482 484 "ip_addr": "<ip_with_mask>",
483 485 "ip_range": ["<start_ip>", "<end_ip>"],
484 486 },
485 487 ...
486 488 ]
487 489 }
488 490
489 491 """
490 492 if isinstance(userid, Optional):
491 493 userid = request.authuser.user_id
492 494 user = get_user_or_error(userid)
493 495 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
494 496 return dict(
495 497 server_ip_addr=request.ip_addr,
496 498 user_ips=ips
497 499 )
498 500
499 501 # alias for old
500 502 show_ip = get_ip
501 503
502 504 @HasPermissionAnyDecorator('hg.admin')
503 505 def get_server_info(self):
504 506 """
505 507 return server info, including Kallithea version and installed packages
506 508
507 509
508 510 OUTPUT::
509 511
510 512 id : <id_given_in_input>
511 513 result : {
512 514 'modules': [<module name>,...]
513 515 'py_version': <python version>,
514 516 'platform': <platform type>,
515 517 'kallithea_version': <kallithea version>
516 518 }
517 519 error : null
518 520 """
519 521 return Setting.get_server_info()
520 522
521 523 def get_user(self, userid=Optional(OAttr('apiuser'))):
522 524 """
523 525 Gets a user by username or user_id, Returns empty result if user is
524 526 not found. If userid param is skipped it is set to id of user who is
525 527 calling this method. This command can be executed only using api_key
526 528 belonging to user with admin rights, or regular users that cannot
527 529 specify different userid than theirs
528 530
529 531 :param userid: user to get data for
530 532 :type userid: Optional(str or int)
531 533
532 534 OUTPUT::
533 535
534 536 id : <id_given_in_input>
535 537 result: None if user does not exist or
536 538 {
537 539 "user_id" : "<user_id>",
538 540 "api_key" : "<api_key>",
539 541 "api_keys": "[<list of all API keys including additional ones>]"
540 542 "username" : "<username>",
541 543 "firstname": "<firstname>",
542 544 "lastname" : "<lastname>",
543 545 "email" : "<email>",
544 546 "emails": "[<list of all emails including additional ones>]",
545 547 "ip_addresses": "[<ip_address_for_user>,...]",
546 548 "active" : "<bool: user active>",
547 549 "admin" :Β  "<bool: user is admin>",
548 550 "extern_name" : "<extern_name>",
549 551 "extern_type" : "<extern type>
550 552 "last_login": "<last_login>",
551 553 "permissions": {
552 554 "global": ["hg.create.repository",
553 555 "repository.read",
554 556 "hg.register.manual_activate"],
555 557 "repositories": {"repo1": "repository.none"},
556 558 "repositories_groups": {"Group1": "group.read"}
557 559 },
558 560 }
559 561
560 562 error: null
561 563
562 564 """
563 565 if not HasPermissionAny('hg.admin')():
564 566 # make sure normal user does not pass someone else userid,
565 567 # he is not allowed to do that
566 568 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
567 569 raise JSONRPCError(
568 570 'userid is not the same as your user'
569 571 )
570 572
571 573 if isinstance(userid, Optional):
572 574 userid = request.authuser.user_id
573 575
574 576 user = get_user_or_error(userid)
575 577 data = user.get_api_data()
576 578 data['permissions'] = AuthUser(user_id=user.user_id).permissions
577 579 return data
578 580
579 581 @HasPermissionAnyDecorator('hg.admin')
580 582 def get_users(self):
581 583 """
582 584 Lists all existing users. This command can be executed only using api_key
583 585 belonging to user with admin rights.
584 586
585 587
586 588 OUTPUT::
587 589
588 590 id : <id_given_in_input>
589 591 result: [<user_object>, ...]
590 592 error: null
591 593 """
592 594
593 595 return [
594 596 user.get_api_data()
595 597 for user in User.query()
596 598 .order_by(User.username)
597 599 .filter_by(is_default_user=False)
598 600 ]
599 601
600 602 @HasPermissionAnyDecorator('hg.admin')
601 603 def create_user(self, username, email, password=Optional(''),
602 604 firstname=Optional(u''), lastname=Optional(u''),
603 605 active=Optional(True), admin=Optional(False),
604 606 extern_type=Optional(User.DEFAULT_AUTH_TYPE),
605 607 extern_name=Optional('')):
606 608 """
607 609 Creates new user. Returns new user object. This command can
608 610 be executed only using api_key belonging to user with admin rights.
609 611
610 612 :param username: new username
611 613 :type username: str or int
612 614 :param email: email
613 615 :type email: str
614 616 :param password: password
615 617 :type password: Optional(str)
616 618 :param firstname: firstname
617 619 :type firstname: Optional(str)
618 620 :param lastname: lastname
619 621 :type lastname: Optional(str)
620 622 :param active: active
621 623 :type active: Optional(bool)
622 624 :param admin: admin
623 625 :type admin: Optional(bool)
624 626 :param extern_name: name of extern
625 627 :type extern_name: Optional(str)
626 628 :param extern_type: extern_type
627 629 :type extern_type: Optional(str)
628 630
629 631
630 632 OUTPUT::
631 633
632 634 id : <id_given_in_input>
633 635 result: {
634 636 "msg" : "created new user `<username>`",
635 637 "user": <user_obj>
636 638 }
637 639 error: null
638 640
639 641 ERROR OUTPUT::
640 642
641 643 id : <id_given_in_input>
642 644 result : null
643 645 error : {
644 646 "user `<username>` already exist"
645 647 or
646 648 "email `<email>` already exist"
647 649 or
648 650 "failed to create user `<username>`"
649 651 }
650 652
651 653 """
652 654
653 655 if User.get_by_username(username):
654 656 raise JSONRPCError("user `%s` already exist" % (username,))
655 657
656 658 if User.get_by_email(email):
657 659 raise JSONRPCError("email `%s` already exist" % (email,))
658 660
659 661 try:
660 662 user = UserModel().create_or_update(
661 663 username=Optional.extract(username),
662 664 password=Optional.extract(password),
663 665 email=Optional.extract(email),
664 666 firstname=Optional.extract(firstname),
665 667 lastname=Optional.extract(lastname),
666 668 active=Optional.extract(active),
667 669 admin=Optional.extract(admin),
668 670 extern_type=Optional.extract(extern_type),
669 671 extern_name=Optional.extract(extern_name)
670 672 )
671 673 Session().commit()
672 674 return dict(
673 675 msg='created new user `%s`' % username,
674 676 user=user.get_api_data()
675 677 )
676 678 except Exception:
677 679 log.error(traceback.format_exc())
678 680 raise JSONRPCError('failed to create user `%s`' % (username,))
679 681
680 682 @HasPermissionAnyDecorator('hg.admin')
681 683 def update_user(self, userid, username=Optional(None),
682 684 email=Optional(None), password=Optional(None),
683 685 firstname=Optional(None), lastname=Optional(None),
684 686 active=Optional(None), admin=Optional(None),
685 687 extern_type=Optional(None), extern_name=Optional(None)):
686 688 """
687 689 updates given user if such user exists. This command can
688 690 be executed only using api_key belonging to user with admin rights.
689 691
690 692 :param userid: userid to update
691 693 :type userid: str or int
692 694 :param username: new username
693 695 :type username: str or int
694 696 :param email: email
695 697 :type email: str
696 698 :param password: password
697 699 :type password: Optional(str)
698 700 :param firstname: firstname
699 701 :type firstname: Optional(str)
700 702 :param lastname: lastname
701 703 :type lastname: Optional(str)
702 704 :param active: active
703 705 :type active: Optional(bool)
704 706 :param admin: admin
705 707 :type admin: Optional(bool)
706 708 :param extern_name:
707 709 :type extern_name: Optional(str)
708 710 :param extern_type:
709 711 :type extern_type: Optional(str)
710 712
711 713
712 714 OUTPUT::
713 715
714 716 id : <id_given_in_input>
715 717 result: {
716 718 "msg" : "updated user ID:<userid> <username>",
717 719 "user": <user_object>,
718 720 }
719 721 error: null
720 722
721 723 ERROR OUTPUT::
722 724
723 725 id : <id_given_in_input>
724 726 result : null
725 727 error : {
726 728 "failed to update user `<username>`"
727 729 }
728 730
729 731 """
730 732
731 733 user = get_user_or_error(userid)
732 734
733 735 # only non optional arguments will be stored in updates
734 736 updates = {}
735 737
736 738 try:
737 739
738 740 store_update(updates, username, 'username')
739 741 store_update(updates, password, 'password')
740 742 store_update(updates, email, 'email')
741 743 store_update(updates, firstname, 'name')
742 744 store_update(updates, lastname, 'lastname')
743 745 store_update(updates, active, 'active')
744 746 store_update(updates, admin, 'admin')
745 747 store_update(updates, extern_name, 'extern_name')
746 748 store_update(updates, extern_type, 'extern_type')
747 749
748 750 user = UserModel().update_user(user, **updates)
749 751 Session().commit()
750 752 return dict(
751 753 msg='updated user ID:%s %s' % (user.user_id, user.username),
752 754 user=user.get_api_data()
753 755 )
754 756 except DefaultUserException:
755 757 log.error(traceback.format_exc())
756 758 raise JSONRPCError('editing default user is forbidden')
757 759 except Exception:
758 760 log.error(traceback.format_exc())
759 761 raise JSONRPCError('failed to update user `%s`' % (userid,))
760 762
761 763 @HasPermissionAnyDecorator('hg.admin')
762 764 def delete_user(self, userid):
763 765 """
764 766 deletes given user if such user exists. This command can
765 767 be executed only using api_key belonging to user with admin rights.
766 768
767 769 :param userid: user to delete
768 770 :type userid: str or int
769 771
770 772 OUTPUT::
771 773
772 774 id : <id_given_in_input>
773 775 result: {
774 776 "msg" : "deleted user ID:<userid> <username>",
775 777 "user": null
776 778 }
777 779 error: null
778 780
779 781 ERROR OUTPUT::
780 782
781 783 id : <id_given_in_input>
782 784 result : null
783 785 error : {
784 786 "failed to delete user ID:<userid> <username>"
785 787 }
786 788
787 789 """
788 790 user = get_user_or_error(userid)
789 791
790 792 try:
791 793 UserModel().delete(userid)
792 794 Session().commit()
793 795 return dict(
794 796 msg='deleted user ID:%s %s' % (user.user_id, user.username),
795 797 user=None
796 798 )
797 799 except Exception:
798 800
799 801 log.error(traceback.format_exc())
800 802 raise JSONRPCError('failed to delete user ID:%s %s'
801 803 % (user.user_id, user.username))
802 804
803 805 # permission check inside
804 806 def get_user_group(self, usergroupid):
805 807 """
806 808 Gets an existing user group. This command can be executed only using api_key
807 809 belonging to user with admin rights or user who has at least
808 810 read access to user group.
809 811
810 812 :param usergroupid: id of user_group to edit
811 813 :type usergroupid: str or int
812 814
813 815 OUTPUT::
814 816
815 817 id : <id_given_in_input>
816 818 result : None if group not exist
817 819 {
818 820 "users_group_id" : "<id>",
819 821 "group_name" : "<groupname>",
820 822 "active": "<bool>",
821 823 "members" : [<user_obj>,...]
822 824 }
823 825 error : null
824 826
825 827 """
826 828 user_group = get_user_group_or_error(usergroupid)
827 829 if not HasPermissionAny('hg.admin')():
828 830 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
829 831 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
830 832
831 833 data = user_group.get_api_data()
832 834 return data
833 835
834 836 # permission check inside
835 837 def get_user_groups(self):
836 838 """
837 839 Lists all existing user groups. This command can be executed only using
838 840 api_key belonging to user with admin rights or user who has at least
839 841 read access to user group.
840 842
841 843
842 844 OUTPUT::
843 845
844 846 id : <id_given_in_input>
845 847 result : [<user_group_obj>,...]
846 848 error : null
847 849 """
848 850
849 851 return [
850 852 user_group.get_api_data()
851 853 for user_group in UserGroupList(UserGroup.query().all(), perm_level='read')
852 854 ]
853 855
854 856 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
855 857 def create_user_group(self, group_name, description=Optional(u''),
856 858 owner=Optional(OAttr('apiuser')), active=Optional(True)):
857 859 """
858 860 Creates new user group. This command can be executed only using api_key
859 861 belonging to user with admin rights or an user who has create user group
860 862 permission
861 863
862 864 :param group_name: name of new user group
863 865 :type group_name: str
864 866 :param description: group description
865 867 :type description: str
866 868 :param owner: owner of group. If not passed apiuser is the owner
867 869 :type owner: Optional(str or int)
868 870 :param active: group is active
869 871 :type active: Optional(bool)
870 872
871 873 OUTPUT::
872 874
873 875 id : <id_given_in_input>
874 876 result: {
875 877 "msg": "created new user group `<groupname>`",
876 878 "user_group": <user_group_object>
877 879 }
878 880 error: null
879 881
880 882 ERROR OUTPUT::
881 883
882 884 id : <id_given_in_input>
883 885 result : null
884 886 error : {
885 887 "user group `<group name>` already exist"
886 888 or
887 889 "failed to create group `<group name>`"
888 890 }
889 891
890 892 """
891 893
892 894 if UserGroupModel().get_by_name(group_name):
893 895 raise JSONRPCError("user group `%s` already exist" % (group_name,))
894 896
895 897 try:
896 898 if isinstance(owner, Optional):
897 899 owner = request.authuser.user_id
898 900
899 901 owner = get_user_or_error(owner)
900 902 active = Optional.extract(active)
901 903 description = Optional.extract(description)
902 904 ug = UserGroupModel().create(name=group_name, description=description,
903 905 owner=owner, active=active)
904 906 Session().commit()
905 907 return dict(
906 908 msg='created new user group `%s`' % group_name,
907 909 user_group=ug.get_api_data()
908 910 )
909 911 except Exception:
910 912 log.error(traceback.format_exc())
911 913 raise JSONRPCError('failed to create group `%s`' % (group_name,))
912 914
913 915 # permission check inside
914 916 def update_user_group(self, usergroupid, group_name=Optional(''),
915 917 description=Optional(''), owner=Optional(None),
916 918 active=Optional(True)):
917 919 """
918 920 Updates given usergroup. This command can be executed only using api_key
919 921 belonging to user with admin rights or an admin of given user group
920 922
921 923 :param usergroupid: id of user group to update
922 924 :type usergroupid: str or int
923 925 :param group_name: name of new user group
924 926 :type group_name: str
925 927 :param description: group description
926 928 :type description: str
927 929 :param owner: owner of group.
928 930 :type owner: Optional(str or int)
929 931 :param active: group is active
930 932 :type active: Optional(bool)
931 933
932 934 OUTPUT::
933 935
934 936 id : <id_given_in_input>
935 937 result : {
936 938 "msg": 'updated user group ID:<user group id> <user group name>',
937 939 "user_group": <user_group_object>
938 940 }
939 941 error : null
940 942
941 943 ERROR OUTPUT::
942 944
943 945 id : <id_given_in_input>
944 946 result : null
945 947 error : {
946 948 "failed to update user group `<user group name>`"
947 949 }
948 950
949 951 """
950 952 user_group = get_user_group_or_error(usergroupid)
951 953 if not HasPermissionAny('hg.admin')():
952 954 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
953 955 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
954 956
955 957 if not isinstance(owner, Optional):
956 958 owner = get_user_or_error(owner)
957 959
958 960 updates = {}
959 961 store_update(updates, group_name, 'users_group_name')
960 962 store_update(updates, description, 'user_group_description')
961 963 store_update(updates, owner, 'owner')
962 964 store_update(updates, active, 'users_group_active')
963 965 try:
964 966 UserGroupModel().update(user_group, updates)
965 967 Session().commit()
966 968 return dict(
967 969 msg='updated user group ID:%s %s' % (user_group.users_group_id,
968 970 user_group.users_group_name),
969 971 user_group=user_group.get_api_data()
970 972 )
971 973 except Exception:
972 974 log.error(traceback.format_exc())
973 975 raise JSONRPCError('failed to update user group `%s`' % (usergroupid,))
974 976
975 977 # permission check inside
976 978 def delete_user_group(self, usergroupid):
977 979 """
978 980 Delete given user group by user group id or name.
979 981 This command can be executed only using api_key
980 982 belonging to user with admin rights or an admin of given user group
981 983
982 984 :param usergroupid:
983 985 :type usergroupid: int
984 986
985 987 OUTPUT::
986 988
987 989 id : <id_given_in_input>
988 990 result : {
989 991 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
990 992 }
991 993 error : null
992 994
993 995 ERROR OUTPUT::
994 996
995 997 id : <id_given_in_input>
996 998 result : null
997 999 error : {
998 1000 "failed to delete user group ID:<user_group_id> <user_group_name>"
999 1001 or
1000 1002 "RepoGroup assigned to <repo_groups_list>"
1001 1003 }
1002 1004
1003 1005 """
1004 1006 user_group = get_user_group_or_error(usergroupid)
1005 1007 if not HasPermissionAny('hg.admin')():
1006 1008 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
1007 1009 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1008 1010
1009 1011 try:
1010 1012 UserGroupModel().delete(user_group)
1011 1013 Session().commit()
1012 1014 return dict(
1013 1015 msg='deleted user group ID:%s %s' %
1014 1016 (user_group.users_group_id, user_group.users_group_name),
1015 1017 user_group=None
1016 1018 )
1017 1019 except UserGroupsAssignedException as e:
1018 1020 log.error(traceback.format_exc())
1019 1021 raise JSONRPCError(str(e))
1020 1022 except Exception:
1021 1023 log.error(traceback.format_exc())
1022 1024 raise JSONRPCError('failed to delete user group ID:%s %s' %
1023 1025 (user_group.users_group_id,
1024 1026 user_group.users_group_name)
1025 1027 )
1026 1028
1027 1029 # permission check inside
1028 1030 def add_user_to_user_group(self, usergroupid, userid):
1029 1031 """
1030 1032 Adds a user to a user group. If user exists in that group success will be
1031 1033 `false`. This command can be executed only using api_key
1032 1034 belonging to user with admin rights or an admin of given user group
1033 1035
1034 1036 :param usergroupid:
1035 1037 :type usergroupid: int
1036 1038 :param userid:
1037 1039 :type userid: int
1038 1040
1039 1041 OUTPUT::
1040 1042
1041 1043 id : <id_given_in_input>
1042 1044 result : {
1043 1045 "success": True|False # depends on if member is in group
1044 1046 "msg": "added member `<username>` to user group `<groupname>` |
1045 1047 User is already in that group"
1046 1048
1047 1049 }
1048 1050 error : null
1049 1051
1050 1052 ERROR OUTPUT::
1051 1053
1052 1054 id : <id_given_in_input>
1053 1055 result : null
1054 1056 error : {
1055 1057 "failed to add member to user group `<user_group_name>`"
1056 1058 }
1057 1059
1058 1060 """
1059 1061 user = get_user_or_error(userid)
1060 1062 user_group = get_user_group_or_error(usergroupid)
1061 1063 if not HasPermissionAny('hg.admin')():
1062 1064 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
1063 1065 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1064 1066
1065 1067 try:
1066 1068 ugm = UserGroupModel().add_user_to_group(user_group, user)
1067 1069 success = True if ugm != True else False
1068 1070 msg = 'added member `%s` to user group `%s`' % (
1069 1071 user.username, user_group.users_group_name
1070 1072 )
1071 1073 msg = msg if success else 'User is already in that group'
1072 1074 Session().commit()
1073 1075
1074 1076 return dict(
1075 1077 success=success,
1076 1078 msg=msg
1077 1079 )
1078 1080 except Exception:
1079 1081 log.error(traceback.format_exc())
1080 1082 raise JSONRPCError(
1081 1083 'failed to add member to user group `%s`' % (
1082 1084 user_group.users_group_name,
1083 1085 )
1084 1086 )
1085 1087
1086 1088 # permission check inside
1087 1089 def remove_user_from_user_group(self, usergroupid, userid):
1088 1090 """
1089 1091 Removes a user from a user group. If user is not in given group success will
1090 1092 be `false`. This command can be executed only
1091 1093 using api_key belonging to user with admin rights or an admin of given user group
1092 1094
1093 1095 :param usergroupid:
1094 1096 :param userid:
1095 1097
1096 1098
1097 1099 OUTPUT::
1098 1100
1099 1101 id : <id_given_in_input>
1100 1102 result: {
1101 1103 "success": True|False, # depends on if member is in group
1102 1104 "msg": "removed member <username> from user group <groupname> |
1103 1105 User wasn't in group"
1104 1106 }
1105 1107 error: null
1106 1108
1107 1109 """
1108 1110 user = get_user_or_error(userid)
1109 1111 user_group = get_user_group_or_error(usergroupid)
1110 1112 if not HasPermissionAny('hg.admin')():
1111 1113 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
1112 1114 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1113 1115
1114 1116 try:
1115 1117 success = UserGroupModel().remove_user_from_group(user_group, user)
1116 1118 msg = 'removed member `%s` from user group `%s`' % (
1117 1119 user.username, user_group.users_group_name
1118 1120 )
1119 1121 msg = msg if success else "User wasn't in group"
1120 1122 Session().commit()
1121 1123 return dict(success=success, msg=msg)
1122 1124 except Exception:
1123 1125 log.error(traceback.format_exc())
1124 1126 raise JSONRPCError(
1125 1127 'failed to remove member from user group `%s`' % (
1126 1128 user_group.users_group_name,
1127 1129 )
1128 1130 )
1129 1131
1130 1132 # permission check inside
1131 1133 def get_repo(self, repoid,
1132 1134 with_revision_names=Optional(False),
1133 1135 with_pullrequests=Optional(False)):
1134 1136 """
1135 1137 Gets an existing repository by it's name or repository_id. Members will return
1136 1138 either users_group or user associated to that repository. This command can be
1137 1139 executed only using api_key belonging to user with admin
1138 1140 rights or regular user that have at least read access to repository.
1139 1141
1140 1142 :param repoid: repository name or repository id
1141 1143 :type repoid: str or int
1142 1144
1143 1145 OUTPUT::
1144 1146
1145 1147 id : <id_given_in_input>
1146 1148 result : {
1147 1149 {
1148 1150 "repo_id" : "<repo_id>",
1149 1151 "repo_name" : "<reponame>"
1150 1152 "repo_type" : "<repo_type>",
1151 1153 "clone_uri" : "<clone_uri>",
1152 1154 "enable_downloads": "<bool>",
1153 1155 "enable_locking": "<bool>",
1154 1156 "enable_statistics": "<bool>",
1155 1157 "private": "<bool>",
1156 1158 "created_on" : "<date_time_created>",
1157 1159 "description" : "<description>",
1158 1160 "landing_rev": "<landing_rev>",
1159 1161 "last_changeset": {
1160 1162 "author": "<full_author>",
1161 1163 "date": "<date_time_of_commit>",
1162 1164 "message": "<commit_message>",
1163 1165 "raw_id": "<raw_id>",
1164 1166 "revision": "<numeric_revision>",
1165 1167 "short_id": "<short_id>"
1166 1168 }
1167 1169 "owner": "<repo_owner>",
1168 1170 "fork_of": "<name_of_fork_parent>",
1169 1171 "members" : [
1170 1172 {
1171 1173 "name": "<username>",
1172 1174 "type" : "user",
1173 1175 "permission" : "repository.(read|write|admin)"
1174 1176 },
1175 1177 …
1176 1178 {
1177 1179 "name": "<usergroup name>",
1178 1180 "type" : "user_group",
1179 1181 "permission" : "usergroup.(read|write|admin)"
1180 1182 },
1181 1183 …
1182 1184 ]
1183 1185 "followers": [<user_obj>, ...],
1184 1186 <if with_revision_names == True>
1185 1187 "tags": {
1186 1188 "<tagname>": "<raw_id>",
1187 1189 ...
1188 1190 },
1189 1191 "branches": {
1190 1192 "<branchname>": "<raw_id>",
1191 1193 ...
1192 1194 },
1193 1195 "bookmarks": {
1194 1196 "<bookmarkname>": "<raw_id>",
1195 1197 ...
1196 1198 },
1197 1199 }
1198 1200 }
1199 1201 error : null
1200 1202
1201 1203 """
1202 1204 repo = get_repo_or_error(repoid)
1203 1205
1204 1206 if not HasPermissionAny('hg.admin')():
1205 1207 if not HasRepoPermissionLevel('read')(repo.repo_name):
1206 1208 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1207 1209
1208 1210 members = []
1209 1211 for user in repo.repo_to_perm:
1210 1212 perm = user.permission.permission_name
1211 1213 user = user.user
1212 1214 user_data = {
1213 1215 'name': user.username,
1214 1216 'type': "user",
1215 1217 'permission': perm
1216 1218 }
1217 1219 members.append(user_data)
1218 1220
1219 1221 for user_group in repo.users_group_to_perm:
1220 1222 perm = user_group.permission.permission_name
1221 1223 user_group = user_group.users_group
1222 1224 user_group_data = {
1223 1225 'name': user_group.users_group_name,
1224 1226 'type': "user_group",
1225 1227 'permission': perm
1226 1228 }
1227 1229 members.append(user_group_data)
1228 1230
1229 1231 followers = [
1230 1232 uf.user.get_api_data()
1231 1233 for uf in repo.followers
1232 1234 ]
1233 1235
1234 1236 data = repo.get_api_data(with_revision_names=Optional.extract(with_revision_names),
1235 1237 with_pullrequests=Optional.extract(with_pullrequests))
1236 1238 data['members'] = members
1237 1239 data['followers'] = followers
1238 1240 return data
1239 1241
1240 1242 # permission check inside
1241 1243 def get_repos(self):
1242 1244 """
1243 1245 Lists all existing repositories. This command can be executed only using
1244 1246 api_key belonging to user with admin rights or regular user that have
1245 1247 admin, write or read access to repository.
1246 1248
1247 1249
1248 1250 OUTPUT::
1249 1251
1250 1252 id : <id_given_in_input>
1251 1253 result: [
1252 1254 {
1253 1255 "repo_id" : "<repo_id>",
1254 1256 "repo_name" : "<reponame>"
1255 1257 "repo_type" : "<repo_type>",
1256 1258 "clone_uri" : "<clone_uri>",
1257 1259 "private": : "<bool>",
1258 1260 "created_on" : "<datetimecreated>",
1259 1261 "description" : "<description>",
1260 1262 "landing_rev": "<landing_rev>",
1261 1263 "owner": "<repo_owner>",
1262 1264 "fork_of": "<name_of_fork_parent>",
1263 1265 "enable_downloads": "<bool>",
1264 1266 "enable_locking": "<bool>",
1265 1267 "enable_statistics": "<bool>",
1266 1268 },
1267 1269 …
1268 1270 ]
1269 1271 error: null
1270 1272 """
1271 1273 if not HasPermissionAny('hg.admin')():
1272 1274 repos = RepoModel().get_all_user_repos(user=request.authuser.user_id)
1273 1275 else:
1274 1276 repos = Repository.query()
1275 1277
1276 1278 return [
1277 1279 repo.get_api_data()
1278 1280 for repo in repos
1279 1281 ]
1280 1282
1281 1283 # permission check inside
1282 1284 def get_repo_nodes(self, repoid, revision, root_path,
1283 1285 ret_type=Optional('all')):
1284 1286 """
1285 1287 returns a list of nodes and it's children in a flat list for a given path
1286 1288 at given revision. It's possible to specify ret_type to show only `files` or
1287 1289 `dirs`. This command can be executed only using api_key belonging to
1288 1290 user with admin rights or regular user that have at least read access to repository.
1289 1291
1290 1292 :param repoid: repository name or repository id
1291 1293 :type repoid: str or int
1292 1294 :param revision: revision for which listing should be done
1293 1295 :type revision: str
1294 1296 :param root_path: path from which start displaying
1295 1297 :type root_path: str
1296 1298 :param ret_type: return type 'all|files|dirs' nodes
1297 1299 :type ret_type: Optional(str)
1298 1300
1299 1301
1300 1302 OUTPUT::
1301 1303
1302 1304 id : <id_given_in_input>
1303 1305 result: [
1304 1306 {
1305 1307 "name" : "<name>"
1306 1308 "type" : "<type>",
1307 1309 },
1308 1310 …
1309 1311 ]
1310 1312 error: null
1311 1313 """
1312 1314 repo = get_repo_or_error(repoid)
1313 1315
1314 1316 if not HasPermissionAny('hg.admin')():
1315 1317 if not HasRepoPermissionLevel('read')(repo.repo_name):
1316 1318 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1317 1319
1318 1320 ret_type = Optional.extract(ret_type)
1319 1321 _map = {}
1320 1322 try:
1321 1323 _d, _f = ScmModel().get_nodes(repo, revision, root_path,
1322 1324 flat=False)
1323 1325 _map = {
1324 1326 'all': _d + _f,
1325 1327 'files': _f,
1326 1328 'dirs': _d,
1327 1329 }
1328 1330 return _map[ret_type]
1329 1331 except KeyError:
1330 1332 raise JSONRPCError('ret_type must be one of %s'
1331 1333 % (','.join(_map.keys())))
1332 1334 except Exception:
1333 1335 log.error(traceback.format_exc())
1334 1336 raise JSONRPCError(
1335 1337 'failed to get repo: `%s` nodes' % repo.repo_name
1336 1338 )
1337 1339
1338 1340 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
1339 1341 def create_repo(self, repo_name, owner=Optional(OAttr('apiuser')),
1340 1342 repo_type=Optional('hg'), description=Optional(''),
1341 1343 private=Optional(False), clone_uri=Optional(None),
1342 1344 landing_rev=Optional('rev:tip'),
1343 1345 enable_statistics=Optional(False),
1344 1346 enable_locking=Optional(False),
1345 1347 enable_downloads=Optional(False),
1346 1348 copy_permissions=Optional(False)):
1347 1349 """
1348 1350 Creates a repository. If repository name contains "/", all needed repository
1349 1351 groups will be created. For example "foo/bar/baz" will create groups
1350 1352 "foo", "bar" (with "foo" as parent), and create "baz" repository with
1351 1353 "bar" as group. This command can be executed only using api_key
1352 1354 belonging to user with admin rights or regular user that have create
1353 1355 repository permission. Regular users cannot specify owner parameter
1354 1356
1355 1357 :param repo_name: repository name
1356 1358 :type repo_name: str
1357 1359 :param owner: user_id or username
1358 1360 :type owner: Optional(str)
1359 1361 :param repo_type: 'hg' or 'git'
1360 1362 :type repo_type: Optional(str)
1361 1363 :param description: repository description
1362 1364 :type description: Optional(str)
1363 1365 :param private:
1364 1366 :type private: bool
1365 1367 :param clone_uri:
1366 1368 :type clone_uri: str
1367 1369 :param landing_rev: <rev_type>:<rev>
1368 1370 :type landing_rev: str
1369 1371 :param enable_locking:
1370 1372 :type enable_locking: bool
1371 1373 :param enable_downloads:
1372 1374 :type enable_downloads: bool
1373 1375 :param enable_statistics:
1374 1376 :type enable_statistics: bool
1375 1377 :param copy_permissions: Copy permission from group that repository is
1376 1378 being created.
1377 1379 :type copy_permissions: bool
1378 1380
1379 1381 OUTPUT::
1380 1382
1381 1383 id : <id_given_in_input>
1382 1384 result: {
1383 1385 "msg": "Created new repository `<reponame>`",
1384 1386 "success": true,
1385 1387 "task": "<celery task id or None if done sync>"
1386 1388 }
1387 1389 error: null
1388 1390
1389 1391 ERROR OUTPUT::
1390 1392
1391 1393 id : <id_given_in_input>
1392 1394 result : null
1393 1395 error : {
1394 1396 'failed to create repository `<repo_name>`
1395 1397 }
1396 1398
1397 1399 """
1398 1400 if not HasPermissionAny('hg.admin')():
1399 1401 if not isinstance(owner, Optional):
1400 1402 # forbid setting owner for non-admins
1401 1403 raise JSONRPCError(
1402 1404 'Only Kallithea admin can specify `owner` param'
1403 1405 )
1404 1406 if isinstance(owner, Optional):
1405 1407 owner = request.authuser.user_id
1406 1408
1407 1409 owner = get_user_or_error(owner)
1408 1410
1409 1411 if RepoModel().get_by_repo_name(repo_name):
1410 1412 raise JSONRPCError("repo `%s` already exist" % repo_name)
1411 1413
1412 1414 defs = Setting.get_default_repo_settings(strip_prefix=True)
1413 1415 if isinstance(private, Optional):
1414 1416 private = defs.get('repo_private') or Optional.extract(private)
1415 1417 if isinstance(repo_type, Optional):
1416 1418 repo_type = defs.get('repo_type')
1417 1419 if isinstance(enable_statistics, Optional):
1418 1420 enable_statistics = defs.get('repo_enable_statistics')
1419 1421 if isinstance(enable_locking, Optional):
1420 1422 enable_locking = defs.get('repo_enable_locking')
1421 1423 if isinstance(enable_downloads, Optional):
1422 1424 enable_downloads = defs.get('repo_enable_downloads')
1423 1425
1424 1426 clone_uri = Optional.extract(clone_uri)
1425 1427 description = Optional.extract(description)
1426 1428 landing_rev = Optional.extract(landing_rev)
1427 1429 copy_permissions = Optional.extract(copy_permissions)
1428 1430
1429 1431 try:
1430 1432 repo_name_cleaned = repo_name.split('/')[-1]
1431 1433 # create structure of groups and return the last group
1432 1434 repo_group = map_groups(repo_name)
1433 1435 data = dict(
1434 1436 repo_name=repo_name_cleaned,
1435 1437 repo_name_full=repo_name,
1436 1438 repo_type=repo_type,
1437 1439 repo_description=description,
1438 1440 owner=owner,
1439 1441 repo_private=private,
1440 1442 clone_uri=clone_uri,
1441 1443 repo_group=repo_group,
1442 1444 repo_landing_rev=landing_rev,
1443 1445 enable_statistics=enable_statistics,
1444 1446 enable_locking=enable_locking,
1445 1447 enable_downloads=enable_downloads,
1446 1448 repo_copy_permissions=copy_permissions,
1447 1449 )
1448 1450
1449 1451 task = RepoModel().create(form_data=data, cur_user=owner)
1450 1452 task_id = task.task_id
1451 1453 # no commit, it's done in RepoModel, or async via celery
1452 1454 return dict(
1453 1455 msg="Created new repository `%s`" % (repo_name,),
1454 1456 success=True, # cannot return the repo data here since fork
1455 1457 # can be done async
1456 1458 task=task_id
1457 1459 )
1458 1460 except Exception:
1459 1461 log.error(traceback.format_exc())
1460 1462 raise JSONRPCError(
1461 1463 'failed to create repository `%s`' % (repo_name,))
1462 1464
1463 1465 # permission check inside
1464 1466 def update_repo(self, repoid, name=Optional(None),
1465 1467 owner=Optional(OAttr('apiuser')),
1466 1468 group=Optional(None),
1467 1469 description=Optional(''), private=Optional(False),
1468 1470 clone_uri=Optional(None), landing_rev=Optional('rev:tip'),
1469 1471 enable_statistics=Optional(False),
1470 1472 enable_locking=Optional(False),
1471 1473 enable_downloads=Optional(False)):
1472 1474
1473 1475 """
1474 1476 Updates repo
1475 1477
1476 1478 :param repoid: repository name or repository id
1477 1479 :type repoid: str or int
1478 1480 :param name:
1479 1481 :param owner:
1480 1482 :param group:
1481 1483 :param description:
1482 1484 :param private:
1483 1485 :param clone_uri:
1484 1486 :param landing_rev:
1485 1487 :param enable_statistics:
1486 1488 :param enable_locking:
1487 1489 :param enable_downloads:
1488 1490 """
1489 1491 repo = get_repo_or_error(repoid)
1490 1492 if not HasPermissionAny('hg.admin')():
1491 1493 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1492 1494 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1493 1495
1494 1496 if (name != repo.repo_name and
1495 1497 not HasPermissionAny('hg.create.repository')()
1496 1498 ):
1497 1499 raise JSONRPCError('no permission to create (or move) repositories')
1498 1500
1499 1501 if not isinstance(owner, Optional):
1500 1502 # forbid setting owner for non-admins
1501 1503 raise JSONRPCError(
1502 1504 'Only Kallithea admin can specify `owner` param'
1503 1505 )
1504 1506
1505 1507 updates = {}
1506 1508 repo_group = group
1507 1509 if not isinstance(repo_group, Optional):
1508 1510 repo_group = get_repo_group_or_error(repo_group)
1509 1511 repo_group = repo_group.group_id
1510 1512 try:
1511 1513 store_update(updates, name, 'repo_name')
1512 1514 store_update(updates, repo_group, 'repo_group')
1513 1515 store_update(updates, owner, 'owner')
1514 1516 store_update(updates, description, 'repo_description')
1515 1517 store_update(updates, private, 'repo_private')
1516 1518 store_update(updates, clone_uri, 'clone_uri')
1517 1519 store_update(updates, landing_rev, 'repo_landing_rev')
1518 1520 store_update(updates, enable_statistics, 'repo_enable_statistics')
1519 1521 store_update(updates, enable_locking, 'repo_enable_locking')
1520 1522 store_update(updates, enable_downloads, 'repo_enable_downloads')
1521 1523
1522 1524 RepoModel().update(repo, **updates)
1523 1525 Session().commit()
1524 1526 return dict(
1525 1527 msg='updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1526 1528 repository=repo.get_api_data()
1527 1529 )
1528 1530 except Exception:
1529 1531 log.error(traceback.format_exc())
1530 1532 raise JSONRPCError('failed to update repo `%s`' % repoid)
1531 1533
1532 1534 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
1533 1535 def fork_repo(self, repoid, fork_name,
1534 1536 owner=Optional(OAttr('apiuser')),
1535 1537 description=Optional(''), copy_permissions=Optional(False),
1536 1538 private=Optional(False), landing_rev=Optional('rev:tip')):
1537 1539 """
1538 1540 Creates a fork of given repo. In case of using celery this will
1539 1541 immediately return success message, while fork is going to be created
1540 1542 asynchronous. This command can be executed only using api_key belonging to
1541 1543 user with admin rights or regular user that have fork permission, and at least
1542 1544 read access to forking repository. Regular users cannot specify owner parameter.
1543 1545
1544 1546 :param repoid: repository name or repository id
1545 1547 :type repoid: str or int
1546 1548 :param fork_name:
1547 1549 :param owner:
1548 1550 :param description:
1549 1551 :param copy_permissions:
1550 1552 :param private:
1551 1553 :param landing_rev:
1552 1554
1553 1555 INPUT::
1554 1556
1555 1557 id : <id_for_response>
1556 1558 api_key : "<api_key>"
1557 1559 args: {
1558 1560 "repoid" : "<reponame or repo_id>",
1559 1561 "fork_name": "<forkname>",
1560 1562 "owner": "<username or user_id = Optional(=apiuser)>",
1561 1563 "description": "<description>",
1562 1564 "copy_permissions": "<bool>",
1563 1565 "private": "<bool>",
1564 1566 "landing_rev": "<landing_rev>"
1565 1567 }
1566 1568
1567 1569 OUTPUT::
1568 1570
1569 1571 id : <id_given_in_input>
1570 1572 result: {
1571 1573 "msg": "Created fork of `<reponame>` as `<forkname>`",
1572 1574 "success": true,
1573 1575 "task": "<celery task id or None if done sync>"
1574 1576 }
1575 1577 error: null
1576 1578
1577 1579 """
1578 1580 repo = get_repo_or_error(repoid)
1579 1581 repo_name = repo.repo_name
1580 1582
1581 1583 _repo = RepoModel().get_by_repo_name(fork_name)
1582 1584 if _repo:
1583 1585 type_ = 'fork' if _repo.fork else 'repo'
1584 1586 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
1585 1587
1586 1588 if HasPermissionAny('hg.admin')():
1587 1589 pass
1588 1590 elif HasRepoPermissionLevel('read')(repo.repo_name):
1589 1591 if not isinstance(owner, Optional):
1590 1592 # forbid setting owner for non-admins
1591 1593 raise JSONRPCError(
1592 1594 'Only Kallithea admin can specify `owner` param'
1593 1595 )
1594 1596
1595 1597 if not HasPermissionAny('hg.create.repository')():
1596 1598 raise JSONRPCError('no permission to create repositories')
1597 1599 else:
1598 1600 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1599 1601
1600 1602 if isinstance(owner, Optional):
1601 1603 owner = request.authuser.user_id
1602 1604
1603 1605 owner = get_user_or_error(owner)
1604 1606
1605 1607 try:
1606 1608 # create structure of groups and return the last group
1607 1609 group = map_groups(fork_name)
1608 1610 fork_base_name = fork_name.rsplit('/', 1)[-1]
1609 1611
1610 1612 form_data = dict(
1611 1613 repo_name=fork_base_name,
1612 1614 repo_name_full=fork_name,
1613 1615 repo_group=group,
1614 1616 repo_type=repo.repo_type,
1615 1617 description=Optional.extract(description),
1616 1618 private=Optional.extract(private),
1617 1619 copy_permissions=Optional.extract(copy_permissions),
1618 1620 landing_rev=Optional.extract(landing_rev),
1619 1621 update_after_clone=False,
1620 1622 fork_parent_id=repo.repo_id,
1621 1623 )
1622 1624 task = RepoModel().create_fork(form_data, cur_user=owner)
1623 1625 # no commit, it's done in RepoModel, or async via celery
1624 1626 task_id = task.task_id
1625 1627 return dict(
1626 1628 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
1627 1629 fork_name),
1628 1630 success=True, # cannot return the repo data here since fork
1629 1631 # can be done async
1630 1632 task=task_id
1631 1633 )
1632 1634 except Exception:
1633 1635 log.error(traceback.format_exc())
1634 1636 raise JSONRPCError(
1635 1637 'failed to fork repository `%s` as `%s`' % (repo_name,
1636 1638 fork_name)
1637 1639 )
1638 1640
1639 1641 # permission check inside
1640 1642 def delete_repo(self, repoid, forks=Optional('')):
1641 1643 """
1642 1644 Deletes a repository. This command can be executed only using api_key belonging
1643 1645 to user with admin rights or regular user that have admin access to repository.
1644 1646 When `forks` param is set it's possible to detach or delete forks of deleting
1645 1647 repository
1646 1648
1647 1649 :param repoid: repository name or repository id
1648 1650 :type repoid: str or int
1649 1651 :param forks: `detach` or `delete`, what do do with attached forks for repo
1650 1652 :type forks: Optional(str)
1651 1653
1652 1654 OUTPUT::
1653 1655
1654 1656 id : <id_given_in_input>
1655 1657 result: {
1656 1658 "msg": "Deleted repository `<reponame>`",
1657 1659 "success": true
1658 1660 }
1659 1661 error: null
1660 1662
1661 1663 """
1662 1664 repo = get_repo_or_error(repoid)
1663 1665
1664 1666 if not HasPermissionAny('hg.admin')():
1665 1667 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1666 1668 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1667 1669
1668 1670 try:
1669 1671 handle_forks = Optional.extract(forks)
1670 1672 _forks_msg = ''
1671 1673 _forks = [f for f in repo.forks]
1672 1674 if handle_forks == 'detach':
1673 1675 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1674 1676 elif handle_forks == 'delete':
1675 1677 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1676 1678 elif _forks:
1677 1679 raise JSONRPCError(
1678 1680 'Cannot delete `%s` it still contains attached forks' %
1679 1681 (repo.repo_name,)
1680 1682 )
1681 1683
1682 1684 RepoModel().delete(repo, forks=forks)
1683 1685 Session().commit()
1684 1686 return dict(
1685 1687 msg='Deleted repository `%s`%s' % (repo.repo_name, _forks_msg),
1686 1688 success=True
1687 1689 )
1688 1690 except Exception:
1689 1691 log.error(traceback.format_exc())
1690 1692 raise JSONRPCError(
1691 1693 'failed to delete repository `%s`' % (repo.repo_name,)
1692 1694 )
1693 1695
1694 1696 @HasPermissionAnyDecorator('hg.admin')
1695 1697 def grant_user_permission(self, repoid, userid, perm):
1696 1698 """
1697 1699 Grant permission for user on given repository, or update existing one
1698 1700 if found. This command can be executed only using api_key belonging to user
1699 1701 with admin rights.
1700 1702
1701 1703 :param repoid: repository name or repository id
1702 1704 :type repoid: str or int
1703 1705 :param userid:
1704 1706 :param perm: (repository.(none|read|write|admin))
1705 1707 :type perm: str
1706 1708
1707 1709 OUTPUT::
1708 1710
1709 1711 id : <id_given_in_input>
1710 1712 result: {
1711 1713 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1712 1714 "success": true
1713 1715 }
1714 1716 error: null
1715 1717 """
1716 1718 repo = get_repo_or_error(repoid)
1717 1719 user = get_user_or_error(userid)
1718 1720 perm = get_perm_or_error(perm)
1719 1721
1720 1722 try:
1721 1723
1722 1724 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1723 1725
1724 1726 Session().commit()
1725 1727 return dict(
1726 1728 msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1727 1729 perm.permission_name, user.username, repo.repo_name
1728 1730 ),
1729 1731 success=True
1730 1732 )
1731 1733 except Exception:
1732 1734 log.error(traceback.format_exc())
1733 1735 raise JSONRPCError(
1734 1736 'failed to edit permission for user: `%s` in repo: `%s`' % (
1735 1737 userid, repoid
1736 1738 )
1737 1739 )
1738 1740
1739 1741 @HasPermissionAnyDecorator('hg.admin')
1740 1742 def revoke_user_permission(self, repoid, userid):
1741 1743 """
1742 1744 Revoke permission for user on given repository. This command can be executed
1743 1745 only using api_key belonging to user with admin rights.
1744 1746
1745 1747 :param repoid: repository name or repository id
1746 1748 :type repoid: str or int
1747 1749 :param userid:
1748 1750
1749 1751 OUTPUT::
1750 1752
1751 1753 id : <id_given_in_input>
1752 1754 result: {
1753 1755 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1754 1756 "success": true
1755 1757 }
1756 1758 error: null
1757 1759
1758 1760 """
1759 1761
1760 1762 repo = get_repo_or_error(repoid)
1761 1763 user = get_user_or_error(userid)
1762 1764 try:
1763 1765 RepoModel().revoke_user_permission(repo=repo, user=user)
1764 1766 Session().commit()
1765 1767 return dict(
1766 1768 msg='Revoked perm for user: `%s` in repo: `%s`' % (
1767 1769 user.username, repo.repo_name
1768 1770 ),
1769 1771 success=True
1770 1772 )
1771 1773 except Exception:
1772 1774 log.error(traceback.format_exc())
1773 1775 raise JSONRPCError(
1774 1776 'failed to edit permission for user: `%s` in repo: `%s`' % (
1775 1777 userid, repoid
1776 1778 )
1777 1779 )
1778 1780
1779 1781 # permission check inside
1780 1782 def grant_user_group_permission(self, repoid, usergroupid, perm):
1781 1783 """
1782 1784 Grant permission for user group on given repository, or update
1783 1785 existing one if found. This command can be executed only using
1784 1786 api_key belonging to user with admin rights.
1785 1787
1786 1788 :param repoid: repository name or repository id
1787 1789 :type repoid: str or int
1788 1790 :param usergroupid: id of usergroup
1789 1791 :type usergroupid: str or int
1790 1792 :param perm: (repository.(none|read|write|admin))
1791 1793 :type perm: str
1792 1794
1793 1795 OUTPUT::
1794 1796
1795 1797 id : <id_given_in_input>
1796 1798 result : {
1797 1799 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1798 1800 "success": true
1799 1801
1800 1802 }
1801 1803 error : null
1802 1804
1803 1805 ERROR OUTPUT::
1804 1806
1805 1807 id : <id_given_in_input>
1806 1808 result : null
1807 1809 error : {
1808 1810 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1809 1811 }
1810 1812
1811 1813 """
1812 1814 repo = get_repo_or_error(repoid)
1813 1815 perm = get_perm_or_error(perm)
1814 1816 user_group = get_user_group_or_error(usergroupid)
1815 1817 if not HasPermissionAny('hg.admin')():
1816 1818 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1817 1819 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1818 1820
1819 1821 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
1820 1822 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1821 1823
1822 1824 try:
1823 1825 RepoModel().grant_user_group_permission(
1824 1826 repo=repo, group_name=user_group, perm=perm)
1825 1827
1826 1828 Session().commit()
1827 1829 return dict(
1828 1830 msg='Granted perm: `%s` for user group: `%s` in '
1829 1831 'repo: `%s`' % (
1830 1832 perm.permission_name, user_group.users_group_name,
1831 1833 repo.repo_name
1832 1834 ),
1833 1835 success=True
1834 1836 )
1835 1837 except Exception:
1836 1838 log.error(traceback.format_exc())
1837 1839 raise JSONRPCError(
1838 1840 'failed to edit permission for user group: `%s` in '
1839 1841 'repo: `%s`' % (
1840 1842 usergroupid, repo.repo_name
1841 1843 )
1842 1844 )
1843 1845
1844 1846 # permission check inside
1845 1847 def revoke_user_group_permission(self, repoid, usergroupid):
1846 1848 """
1847 1849 Revoke permission for user group on given repository. This command can be
1848 1850 executed only using api_key belonging to user with admin rights.
1849 1851
1850 1852 :param repoid: repository name or repository id
1851 1853 :type repoid: str or int
1852 1854 :param usergroupid:
1853 1855
1854 1856 OUTPUT::
1855 1857
1856 1858 id : <id_given_in_input>
1857 1859 result: {
1858 1860 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1859 1861 "success": true
1860 1862 }
1861 1863 error: null
1862 1864 """
1863 1865 repo = get_repo_or_error(repoid)
1864 1866 user_group = get_user_group_or_error(usergroupid)
1865 1867 if not HasPermissionAny('hg.admin')():
1866 1868 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1867 1869 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1868 1870
1869 1871 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
1870 1872 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1871 1873
1872 1874 try:
1873 1875 RepoModel().revoke_user_group_permission(
1874 1876 repo=repo, group_name=user_group)
1875 1877
1876 1878 Session().commit()
1877 1879 return dict(
1878 1880 msg='Revoked perm for user group: `%s` in repo: `%s`' % (
1879 1881 user_group.users_group_name, repo.repo_name
1880 1882 ),
1881 1883 success=True
1882 1884 )
1883 1885 except Exception:
1884 1886 log.error(traceback.format_exc())
1885 1887 raise JSONRPCError(
1886 1888 'failed to edit permission for user group: `%s` in '
1887 1889 'repo: `%s`' % (
1888 1890 user_group.users_group_name, repo.repo_name
1889 1891 )
1890 1892 )
1891 1893
1892 1894 @HasPermissionAnyDecorator('hg.admin')
1893 1895 def get_repo_group(self, repogroupid):
1894 1896 """
1895 1897 Returns given repo group together with permissions, and repositories
1896 1898 inside the group
1897 1899
1898 1900 :param repogroupid: id/name of repository group
1899 1901 :type repogroupid: str or int
1900 1902 """
1901 1903 repo_group = get_repo_group_or_error(repogroupid)
1902 1904
1903 1905 members = []
1904 1906 for user in repo_group.repo_group_to_perm:
1905 1907 perm = user.permission.permission_name
1906 1908 user = user.user
1907 1909 user_data = {
1908 1910 'name': user.username,
1909 1911 'type': "user",
1910 1912 'permission': perm
1911 1913 }
1912 1914 members.append(user_data)
1913 1915
1914 1916 for user_group in repo_group.users_group_to_perm:
1915 1917 perm = user_group.permission.permission_name
1916 1918 user_group = user_group.users_group
1917 1919 user_group_data = {
1918 1920 'name': user_group.users_group_name,
1919 1921 'type': "user_group",
1920 1922 'permission': perm
1921 1923 }
1922 1924 members.append(user_group_data)
1923 1925
1924 1926 data = repo_group.get_api_data()
1925 1927 data["members"] = members
1926 1928 return data
1927 1929
1928 1930 @HasPermissionAnyDecorator('hg.admin')
1929 1931 def get_repo_groups(self):
1930 1932 """
1931 1933 Returns all repository groups
1932 1934
1933 1935 """
1934 1936 return [
1935 1937 repo_group.get_api_data()
1936 1938 for repo_group in RepoGroup.query()
1937 1939 ]
1938 1940
1939 1941 @HasPermissionAnyDecorator('hg.admin')
1940 1942 def create_repo_group(self, group_name, description=Optional(''),
1941 1943 owner=Optional(OAttr('apiuser')),
1942 1944 parent=Optional(None),
1943 1945 copy_permissions=Optional(False)):
1944 1946 """
1945 1947 Creates a repository group. This command can be executed only using
1946 1948 api_key belonging to user with admin rights.
1947 1949
1948 1950 :param group_name:
1949 1951 :type group_name:
1950 1952 :param description:
1951 1953 :type description:
1952 1954 :param owner:
1953 1955 :type owner:
1954 1956 :param parent:
1955 1957 :type parent:
1956 1958 :param copy_permissions:
1957 1959 :type copy_permissions:
1958 1960
1959 1961 OUTPUT::
1960 1962
1961 1963 id : <id_given_in_input>
1962 1964 result : {
1963 1965 "msg": "created new repo group `<repo_group_name>`"
1964 1966 "repo_group": <repogroup_object>
1965 1967 }
1966 1968 error : null
1967 1969
1968 1970 ERROR OUTPUT::
1969 1971
1970 1972 id : <id_given_in_input>
1971 1973 result : null
1972 1974 error : {
1973 1975 failed to create repo group `<repogroupid>`
1974 1976 }
1975 1977
1976 1978 """
1977 1979 if RepoGroup.get_by_group_name(group_name):
1978 1980 raise JSONRPCError("repo group `%s` already exist" % (group_name,))
1979 1981
1980 1982 if isinstance(owner, Optional):
1981 1983 owner = request.authuser.user_id
1982 1984 group_description = Optional.extract(description)
1983 1985 parent_group = Optional.extract(parent)
1984 1986 if not isinstance(parent, Optional):
1985 1987 parent_group = get_repo_group_or_error(parent_group)
1986 1988
1987 1989 copy_permissions = Optional.extract(copy_permissions)
1988 1990 try:
1989 1991 repo_group = RepoGroupModel().create(
1990 1992 group_name=group_name,
1991 1993 group_description=group_description,
1992 1994 owner=owner,
1993 1995 parent=parent_group,
1994 1996 copy_permissions=copy_permissions
1995 1997 )
1996 1998 Session().commit()
1997 1999 return dict(
1998 2000 msg='created new repo group `%s`' % group_name,
1999 2001 repo_group=repo_group.get_api_data()
2000 2002 )
2001 2003 except Exception:
2002 2004
2003 2005 log.error(traceback.format_exc())
2004 2006 raise JSONRPCError('failed to create repo group `%s`' % (group_name,))
2005 2007
2006 2008 @HasPermissionAnyDecorator('hg.admin')
2007 2009 def update_repo_group(self, repogroupid, group_name=Optional(''),
2008 2010 description=Optional(''),
2009 2011 owner=Optional(OAttr('apiuser')),
2010 2012 parent=Optional(None), enable_locking=Optional(False)):
2011 2013 repo_group = get_repo_group_or_error(repogroupid)
2012 2014
2013 2015 updates = {}
2014 2016 try:
2015 2017 store_update(updates, group_name, 'group_name')
2016 2018 store_update(updates, description, 'group_description')
2017 2019 store_update(updates, owner, 'owner')
2018 2020 store_update(updates, parent, 'parent_group')
2019 2021 store_update(updates, enable_locking, 'enable_locking')
2020 2022 repo_group = RepoGroupModel().update(repo_group, updates)
2021 2023 Session().commit()
2022 2024 return dict(
2023 2025 msg='updated repository group ID:%s %s' % (repo_group.group_id,
2024 2026 repo_group.group_name),
2025 2027 repo_group=repo_group.get_api_data()
2026 2028 )
2027 2029 except Exception:
2028 2030 log.error(traceback.format_exc())
2029 2031 raise JSONRPCError('failed to update repository group `%s`'
2030 2032 % (repogroupid,))
2031 2033
2032 2034 @HasPermissionAnyDecorator('hg.admin')
2033 2035 def delete_repo_group(self, repogroupid):
2034 2036 """
2035 2037
2036 2038 :param repogroupid: name or id of repository group
2037 2039 :type repogroupid: str or int
2038 2040
2039 2041 OUTPUT::
2040 2042
2041 2043 id : <id_given_in_input>
2042 2044 result : {
2043 2045 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>
2044 2046 'repo_group': null
2045 2047 }
2046 2048 error : null
2047 2049
2048 2050 ERROR OUTPUT::
2049 2051
2050 2052 id : <id_given_in_input>
2051 2053 result : null
2052 2054 error : {
2053 2055 "failed to delete repo group ID:<repogroupid> <repogroupname>"
2054 2056 }
2055 2057
2056 2058 """
2057 2059 repo_group = get_repo_group_or_error(repogroupid)
2058 2060
2059 2061 try:
2060 2062 RepoGroupModel().delete(repo_group)
2061 2063 Session().commit()
2062 2064 return dict(
2063 2065 msg='deleted repo group ID:%s %s' %
2064 2066 (repo_group.group_id, repo_group.group_name),
2065 2067 repo_group=None
2066 2068 )
2067 2069 except Exception:
2068 2070 log.error(traceback.format_exc())
2069 2071 raise JSONRPCError('failed to delete repo group ID:%s %s' %
2070 2072 (repo_group.group_id, repo_group.group_name)
2071 2073 )
2072 2074
2073 2075 # permission check inside
2074 2076 def grant_user_permission_to_repo_group(self, repogroupid, userid,
2075 2077 perm, apply_to_children=Optional('none')):
2076 2078 """
2077 2079 Grant permission for user on given repository group, or update existing
2078 2080 one if found. This command can be executed only using api_key belonging
2079 2081 to user with admin rights, or user who has admin right to given repository
2080 2082 group.
2081 2083
2082 2084 :param repogroupid: name or id of repository group
2083 2085 :type repogroupid: str or int
2084 2086 :param userid:
2085 2087 :param perm: (group.(none|read|write|admin))
2086 2088 :type perm: str
2087 2089 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2088 2090 :type apply_to_children: str
2089 2091
2090 2092 OUTPUT::
2091 2093
2092 2094 id : <id_given_in_input>
2093 2095 result: {
2094 2096 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
2095 2097 "success": true
2096 2098 }
2097 2099 error: null
2098 2100
2099 2101 ERROR OUTPUT::
2100 2102
2101 2103 id : <id_given_in_input>
2102 2104 result : null
2103 2105 error : {
2104 2106 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
2105 2107 }
2106 2108
2107 2109 """
2108 2110
2109 2111 repo_group = get_repo_group_or_error(repogroupid)
2110 2112
2111 2113 if not HasPermissionAny('hg.admin')():
2112 2114 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2113 2115 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
2114 2116
2115 2117 user = get_user_or_error(userid)
2116 2118 perm = get_perm_or_error(perm, prefix='group.')
2117 2119 apply_to_children = Optional.extract(apply_to_children)
2118 2120
2119 2121 try:
2120 2122 RepoGroupModel().add_permission(repo_group=repo_group,
2121 2123 obj=user,
2122 2124 obj_type="user",
2123 2125 perm=perm,
2124 2126 recursive=apply_to_children)
2125 2127 Session().commit()
2126 2128 return dict(
2127 2129 msg='Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
2128 2130 perm.permission_name, apply_to_children, user.username, repo_group.name
2129 2131 ),
2130 2132 success=True
2131 2133 )
2132 2134 except Exception:
2133 2135 log.error(traceback.format_exc())
2134 2136 raise JSONRPCError(
2135 2137 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2136 2138 userid, repo_group.name))
2137 2139
2138 2140 # permission check inside
2139 2141 def revoke_user_permission_from_repo_group(self, repogroupid, userid,
2140 2142 apply_to_children=Optional('none')):
2141 2143 """
2142 2144 Revoke permission for user on given repository group. This command can
2143 2145 be executed only using api_key belonging to user with admin rights, or
2144 2146 user who has admin right to given repository group.
2145 2147
2146 2148 :param repogroupid: name or id of repository group
2147 2149 :type repogroupid: str or int
2148 2150 :param userid:
2149 2151 :type userid:
2150 2152 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2151 2153 :type apply_to_children: str
2152 2154
2153 2155 OUTPUT::
2154 2156
2155 2157 id : <id_given_in_input>
2156 2158 result: {
2157 2159 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
2158 2160 "success": true
2159 2161 }
2160 2162 error: null
2161 2163
2162 2164 ERROR OUTPUT::
2163 2165
2164 2166 id : <id_given_in_input>
2165 2167 result : null
2166 2168 error : {
2167 2169 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
2168 2170 }
2169 2171
2170 2172 """
2171 2173
2172 2174 repo_group = get_repo_group_or_error(repogroupid)
2173 2175
2174 2176 if not HasPermissionAny('hg.admin')():
2175 2177 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2176 2178 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
2177 2179
2178 2180 user = get_user_or_error(userid)
2179 2181 apply_to_children = Optional.extract(apply_to_children)
2180 2182
2181 2183 try:
2182 2184 RepoGroupModel().delete_permission(repo_group=repo_group,
2183 2185 obj=user,
2184 2186 obj_type="user",
2185 2187 recursive=apply_to_children)
2186 2188
2187 2189 Session().commit()
2188 2190 return dict(
2189 2191 msg='Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2190 2192 apply_to_children, user.username, repo_group.name
2191 2193 ),
2192 2194 success=True
2193 2195 )
2194 2196 except Exception:
2195 2197 log.error(traceback.format_exc())
2196 2198 raise JSONRPCError(
2197 2199 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2198 2200 userid, repo_group.name))
2199 2201
2200 2202 # permission check inside
2201 2203 def grant_user_group_permission_to_repo_group(
2202 2204 self, repogroupid, usergroupid, perm,
2203 2205 apply_to_children=Optional('none')):
2204 2206 """
2205 2207 Grant permission for user group on given repository group, or update
2206 2208 existing one if found. This command can be executed only using
2207 2209 api_key belonging to user with admin rights, or user who has admin
2208 2210 right to given repository group.
2209 2211
2210 2212 :param repogroupid: name or id of repository group
2211 2213 :type repogroupid: str or int
2212 2214 :param usergroupid: id of usergroup
2213 2215 :type usergroupid: str or int
2214 2216 :param perm: (group.(none|read|write|admin))
2215 2217 :type perm: str
2216 2218 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2217 2219 :type apply_to_children: str
2218 2220
2219 2221 OUTPUT::
2220 2222
2221 2223 id : <id_given_in_input>
2222 2224 result : {
2223 2225 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2224 2226 "success": true
2225 2227
2226 2228 }
2227 2229 error : null
2228 2230
2229 2231 ERROR OUTPUT::
2230 2232
2231 2233 id : <id_given_in_input>
2232 2234 result : null
2233 2235 error : {
2234 2236 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2235 2237 }
2236 2238
2237 2239 """
2238 2240 repo_group = get_repo_group_or_error(repogroupid)
2239 2241 perm = get_perm_or_error(perm, prefix='group.')
2240 2242 user_group = get_user_group_or_error(usergroupid)
2241 2243 if not HasPermissionAny('hg.admin')():
2242 2244 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2243 2245 raise JSONRPCError(
2244 2246 'repository group `%s` does not exist' % (repogroupid,))
2245 2247
2246 2248 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
2247 2249 raise JSONRPCError(
2248 2250 'user group `%s` does not exist' % (usergroupid,))
2249 2251
2250 2252 apply_to_children = Optional.extract(apply_to_children)
2251 2253
2252 2254 try:
2253 2255 RepoGroupModel().add_permission(repo_group=repo_group,
2254 2256 obj=user_group,
2255 2257 obj_type="user_group",
2256 2258 perm=perm,
2257 2259 recursive=apply_to_children)
2258 2260 Session().commit()
2259 2261 return dict(
2260 2262 msg='Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2261 2263 perm.permission_name, apply_to_children,
2262 2264 user_group.users_group_name, repo_group.name
2263 2265 ),
2264 2266 success=True
2265 2267 )
2266 2268 except Exception:
2267 2269 log.error(traceback.format_exc())
2268 2270 raise JSONRPCError(
2269 2271 'failed to edit permission for user group: `%s` in '
2270 2272 'repo group: `%s`' % (
2271 2273 usergroupid, repo_group.name
2272 2274 )
2273 2275 )
2274 2276
2275 2277 # permission check inside
2276 2278 def revoke_user_group_permission_from_repo_group(
2277 2279 self, repogroupid, usergroupid,
2278 2280 apply_to_children=Optional('none')):
2279 2281 """
2280 2282 Revoke permission for user group on given repository. This command can be
2281 2283 executed only using api_key belonging to user with admin rights, or
2282 2284 user who has admin right to given repository group.
2283 2285
2284 2286 :param repogroupid: name or id of repository group
2285 2287 :type repogroupid: str or int
2286 2288 :param usergroupid:
2287 2289 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2288 2290 :type apply_to_children: str
2289 2291
2290 2292 OUTPUT::
2291 2293
2292 2294 id : <id_given_in_input>
2293 2295 result: {
2294 2296 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2295 2297 "success": true
2296 2298 }
2297 2299 error: null
2298 2300
2299 2301 ERROR OUTPUT::
2300 2302
2301 2303 id : <id_given_in_input>
2302 2304 result : null
2303 2305 error : {
2304 2306 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2305 2307 }
2306 2308
2307 2309
2308 2310 """
2309 2311 repo_group = get_repo_group_or_error(repogroupid)
2310 2312 user_group = get_user_group_or_error(usergroupid)
2311 2313 if not HasPermissionAny('hg.admin')():
2312 2314 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2313 2315 raise JSONRPCError(
2314 2316 'repository group `%s` does not exist' % (repogroupid,))
2315 2317
2316 2318 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
2317 2319 raise JSONRPCError(
2318 2320 'user group `%s` does not exist' % (usergroupid,))
2319 2321
2320 2322 apply_to_children = Optional.extract(apply_to_children)
2321 2323
2322 2324 try:
2323 2325 RepoGroupModel().delete_permission(repo_group=repo_group,
2324 2326 obj=user_group,
2325 2327 obj_type="user_group",
2326 2328 recursive=apply_to_children)
2327 2329 Session().commit()
2328 2330 return dict(
2329 2331 msg='Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2330 2332 apply_to_children, user_group.users_group_name, repo_group.name
2331 2333 ),
2332 2334 success=True
2333 2335 )
2334 2336 except Exception:
2335 2337 log.error(traceback.format_exc())
2336 2338 raise JSONRPCError(
2337 2339 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2338 2340 user_group.users_group_name, repo_group.name
2339 2341 )
2340 2342 )
2341 2343
2342 2344 def get_gist(self, gistid):
2343 2345 """
2344 2346 Get given gist by id
2345 2347
2346 2348 :param gistid: id of private or public gist
2347 2349 :type gistid: str
2348 2350 """
2349 2351 gist = get_gist_or_error(gistid)
2350 2352 if not HasPermissionAny('hg.admin')():
2351 2353 if gist.owner_id != request.authuser.user_id:
2352 2354 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
2353 2355 return gist.get_api_data()
2354 2356
2355 2357 def get_gists(self, userid=Optional(OAttr('apiuser'))):
2356 2358 """
2357 2359 Get all gists for given user. If userid is empty returned gists
2358 2360 are for user who called the api
2359 2361
2360 2362 :param userid: user to get gists for
2361 2363 :type userid: Optional(str or int)
2362 2364 """
2363 2365 if not HasPermissionAny('hg.admin')():
2364 2366 # make sure normal user does not pass someone else userid,
2365 2367 # he is not allowed to do that
2366 2368 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
2367 2369 raise JSONRPCError(
2368 2370 'userid is not the same as your user'
2369 2371 )
2370 2372
2371 2373 if isinstance(userid, Optional):
2372 2374 user_id = request.authuser.user_id
2373 2375 else:
2374 2376 user_id = get_user_or_error(userid).user_id
2375 2377
2376 2378 return [
2377 2379 gist.get_api_data()
2378 2380 for gist in Gist().query()
2379 2381 .filter_by(is_expired=False)
2380 2382 .filter(Gist.owner_id == user_id)
2381 2383 .order_by(Gist.created_on.desc())
2382 2384 ]
2383 2385
2384 2386 def create_gist(self, files, owner=Optional(OAttr('apiuser')),
2385 2387 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
2386 2388 description=Optional('')):
2387 2389
2388 2390 """
2389 2391 Creates new Gist
2390 2392
2391 2393 :param files: files to be added to gist
2392 2394 {'filename': {'content':'...', 'lexer': null},
2393 2395 'filename2': {'content':'...', 'lexer': null}}
2394 2396 :type files: dict
2395 2397 :param owner: gist owner, defaults to api method caller
2396 2398 :type owner: Optional(str or int)
2397 2399 :param gist_type: type of gist 'public' or 'private'
2398 2400 :type gist_type: Optional(str)
2399 2401 :param lifetime: time in minutes of gist lifetime
2400 2402 :type lifetime: Optional(int)
2401 2403 :param description: gist description
2402 2404 :type description: Optional(str)
2403 2405
2404 2406 OUTPUT::
2405 2407
2406 2408 id : <id_given_in_input>
2407 2409 result : {
2408 2410 "msg": "created new gist",
2409 2411 "gist": {}
2410 2412 }
2411 2413 error : null
2412 2414
2413 2415 ERROR OUTPUT::
2414 2416
2415 2417 id : <id_given_in_input>
2416 2418 result : null
2417 2419 error : {
2418 2420 "failed to create gist"
2419 2421 }
2420 2422
2421 2423 """
2422 2424 try:
2423 2425 if isinstance(owner, Optional):
2424 2426 owner = request.authuser.user_id
2425 2427
2426 2428 owner = get_user_or_error(owner)
2427 2429 description = Optional.extract(description)
2428 2430 gist_type = Optional.extract(gist_type)
2429 2431 lifetime = Optional.extract(lifetime)
2430 2432
2431 2433 gist = GistModel().create(description=description,
2432 2434 owner=owner,
2433 2435 gist_mapping=files,
2434 2436 gist_type=gist_type,
2435 2437 lifetime=lifetime)
2436 2438 Session().commit()
2437 2439 return dict(
2438 2440 msg='created new gist',
2439 2441 gist=gist.get_api_data()
2440 2442 )
2441 2443 except Exception:
2442 2444 log.error(traceback.format_exc())
2443 2445 raise JSONRPCError('failed to create gist')
2444 2446
2445 2447 # def update_gist(self, gistid, files, owner=Optional(OAttr('apiuser')),
2446 2448 # gist_type=Optional(Gist.GIST_PUBLIC),
2447 2449 # gist_lifetime=Optional(-1), gist_description=Optional('')):
2448 2450 # gist = get_gist_or_error(gistid)
2449 2451 # updates = {}
2450 2452
2451 2453 # permission check inside
2452 2454 def delete_gist(self, gistid):
2453 2455 """
2454 2456 Deletes existing gist
2455 2457
2456 2458 :param gistid: id of gist to delete
2457 2459 :type gistid: str
2458 2460
2459 2461 OUTPUT::
2460 2462
2461 2463 id : <id_given_in_input>
2462 2464 result : {
2463 2465 "deleted gist ID: <gist_id>",
2464 2466 "gist": null
2465 2467 }
2466 2468 error : null
2467 2469
2468 2470 ERROR OUTPUT::
2469 2471
2470 2472 id : <id_given_in_input>
2471 2473 result : null
2472 2474 error : {
2473 2475 "failed to delete gist ID:<gist_id>"
2474 2476 }
2475 2477
2476 2478 """
2477 2479 gist = get_gist_or_error(gistid)
2478 2480 if not HasPermissionAny('hg.admin')():
2479 2481 if gist.owner_id != request.authuser.user_id:
2480 2482 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
2481 2483
2482 2484 try:
2483 2485 GistModel().delete(gist)
2484 2486 Session().commit()
2485 2487 return dict(
2486 2488 msg='deleted gist ID:%s' % (gist.gist_access_id,),
2487 2489 gist=None
2488 2490 )
2489 2491 except Exception:
2490 2492 log.error(traceback.format_exc())
2491 2493 raise JSONRPCError('failed to delete gist ID:%s'
2492 2494 % (gist.gist_access_id,))
2493 2495
2494 2496 # permission check inside
2497 def get_changesets(self, repoid, start=None, end=None, start_date=None,
2498 end_date=None, branch_name=None, reverse=False, with_file_list=False):
2499 repo = get_repo_or_error(repoid)
2500 if not HasRepoPermissionLevel('read')(repo.repo_name):
2501 raise JSONRPCError('Access denied to repo %s' % repo.repo_name)
2502
2503 format = "%Y-%m-%dT%H:%M:%S"
2504 try:
2505 return [e.__json__(with_file_list) for e in
2506 repo.scm_instance.get_changesets(start,
2507 end,
2508 datetime.strptime(start_date, format) if start_date else None,
2509 datetime.strptime(end_date, format) if end_date else None,
2510 branch_name,
2511 reverse)]
2512 except EmptyRepositoryError as e:
2513 raise JSONRPCError(e.message)
2514
2515 # permission check inside
2495 2516 def get_changeset(self, repoid, raw_id, with_reviews=Optional(False)):
2496 2517 repo = get_repo_or_error(repoid)
2497 2518 if not HasRepoPermissionLevel('read')(repo.repo_name):
2498 2519 raise JSONRPCError('Access denied to repo %s' % repo.repo_name)
2499 2520 changeset = repo.get_changeset(raw_id)
2500 2521 if isinstance(changeset, EmptyChangeset):
2501 2522 raise JSONRPCError('Changeset %s does not exist' % raw_id)
2502 2523
2503 2524 info = dict(changeset.as_dict())
2504 2525
2505 2526 with_reviews = Optional.extract(with_reviews)
2506 2527 if with_reviews:
2507 2528 reviews = ChangesetStatusModel().get_statuses(
2508 2529 repo.repo_name, raw_id)
2509 2530 info["reviews"] = reviews
2510 2531
2511 2532 return info
2512 2533
2513 2534 # permission check inside
2514 2535 def get_pullrequest(self, pullrequest_id):
2515 2536 """
2516 2537 Get given pull request by id
2517 2538 """
2518 2539 pull_request = PullRequest.get(pullrequest_id)
2519 2540 if pull_request is None:
2520 2541 raise JSONRPCError('pull request `%s` does not exist' % (pullrequest_id,))
2521 2542 if not HasRepoPermissionLevel('read')(pull_request.org_repo.repo_name):
2522 2543 raise JSONRPCError('not allowed')
2523 2544 return pull_request.get_api_data()
2524 2545
2525 2546 # permission check inside
2526 2547 def comment_pullrequest(self, pull_request_id, comment_msg=u'', status=None, close_pr=False):
2527 2548 """
2528 2549 Add comment, close and change status of pull request.
2529 2550 """
2530 2551 apiuser = get_user_or_error(request.authuser.user_id)
2531 2552 pull_request = PullRequest.get(pull_request_id)
2532 2553 if pull_request is None:
2533 2554 raise JSONRPCError('pull request `%s` does not exist' % (pull_request_id,))
2534 2555 if (not HasRepoPermissionLevel('read')(pull_request.org_repo.repo_name)):
2535 2556 raise JSONRPCError('No permission to add comment. User needs at least reading permissions'
2536 2557 ' to the source repository.')
2537 2558 owner = apiuser.user_id == pull_request.owner_id
2538 2559 reviewer = apiuser.user_id in [reviewer.user_id for reviewer in pull_request.reviewers]
2539 2560 if close_pr and not (apiuser.admin or owner):
2540 2561 raise JSONRPCError('No permission to close pull request. User needs to be admin or owner.')
2541 2562 if status and not (apiuser.admin or owner or reviewer):
2542 2563 raise JSONRPCError('No permission to change pull request status. User needs to be admin, owner or reviewer.')
2543 2564 if pull_request.is_closed():
2544 2565 raise JSONRPCError('pull request is already closed')
2545 2566
2546 2567 comment = ChangesetCommentsModel().create(
2547 2568 text=comment_msg,
2548 2569 repo=pull_request.org_repo.repo_id,
2549 2570 author=apiuser.user_id,
2550 2571 pull_request=pull_request.pull_request_id,
2551 2572 f_path=None,
2552 2573 line_no=None,
2553 2574 status_change=(ChangesetStatus.get_status_lbl(status)),
2554 2575 closing_pr=close_pr
2555 2576 )
2556 2577 action_logger(apiuser,
2557 2578 'user_commented_pull_request:%s' % pull_request_id,
2558 2579 pull_request.org_repo, request.ip_addr)
2559 2580 if status:
2560 2581 ChangesetStatusModel().set_status(
2561 2582 pull_request.org_repo_id,
2562 2583 status,
2563 2584 apiuser.user_id,
2564 2585 comment,
2565 2586 pull_request=pull_request_id
2566 2587 )
2567 2588 if close_pr:
2568 2589 PullRequestModel().close_pull_request(pull_request_id)
2569 2590 action_logger(apiuser,
2570 2591 'user_closed_pull_request:%s' % pull_request_id,
2571 2592 pull_request.org_repo, request.ip_addr)
2572 2593 Session().commit()
2573 2594 return True
@@ -1,1062 +1,1075 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 vcs.backends.base
4 4 ~~~~~~~~~~~~~~~~~
5 5
6 6 Base for all available scm backends
7 7
8 8 :created_on: Apr 8, 2010
9 9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 10 """
11 11
12 12 import datetime
13 13 import itertools
14 14
15 15 from kallithea.lib.vcs.utils import author_name, author_email, safe_unicode
16 16 from kallithea.lib.vcs.utils.lazy import LazyProperty
17 17 from kallithea.lib.vcs.utils.helpers import get_dict_for_attrs
18 18 from kallithea.lib.vcs.conf import settings
19 19
20 20 from kallithea.lib.vcs.exceptions import (
21 21 ChangesetError, EmptyRepositoryError, NodeAlreadyAddedError,
22 22 NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError,
23 23 NodeDoesNotExistError, NodeNotChangedError, RepositoryError
24 24 )
25 25
26 26
27 27 class BaseRepository(object):
28 28 """
29 29 Base Repository for final backends
30 30
31 31 **Attributes**
32 32
33 33 ``DEFAULT_BRANCH_NAME``
34 34 name of default branch (i.e. "trunk" for svn, "master" for git etc.
35 35
36 36 ``scm``
37 37 alias of scm, i.e. *git* or *hg*
38 38
39 39 ``repo``
40 40 object from external api
41 41
42 42 ``revisions``
43 43 list of all available revisions' ids, in ascending order
44 44
45 45 ``changesets``
46 46 storage dict caching returned changesets
47 47
48 48 ``path``
49 49 absolute path to the repository
50 50
51 51 ``branches``
52 52 branches as list of changesets
53 53
54 54 ``tags``
55 55 tags as list of changesets
56 56 """
57 57 scm = None
58 58 DEFAULT_BRANCH_NAME = None
59 59 EMPTY_CHANGESET = '0' * 40
60 60
61 61 def __init__(self, repo_path, create=False, **kwargs):
62 62 """
63 63 Initializes repository. Raises RepositoryError if repository could
64 64 not be find at the given ``repo_path`` or directory at ``repo_path``
65 65 exists and ``create`` is set to True.
66 66
67 67 :param repo_path: local path of the repository
68 68 :param create=False: if set to True, would try to create repository.
69 69 :param src_url=None: if set, should be proper url from which repository
70 70 would be cloned; requires ``create`` parameter to be set to True -
71 71 raises RepositoryError if src_url is set and create evaluates to
72 72 False
73 73 """
74 74 raise NotImplementedError
75 75
76 76 def __str__(self):
77 77 return '<%s at %s>' % (self.__class__.__name__, self.path)
78 78
79 79 def __repr__(self):
80 80 return self.__str__()
81 81
82 82 def __len__(self):
83 83 return self.count()
84 84
85 85 def __eq__(self, other):
86 86 same_instance = isinstance(other, self.__class__)
87 87 return same_instance and getattr(other, 'path', None) == self.path
88 88
89 89 def __ne__(self, other):
90 90 return not self.__eq__(other)
91 91
92 92 @LazyProperty
93 93 def alias(self):
94 94 for k, v in settings.BACKENDS.items():
95 95 if v.split('.')[-1] == str(self.__class__.__name__):
96 96 return k
97 97
98 98 @LazyProperty
99 99 def name(self):
100 100 """
101 101 Return repository name (without group name)
102 102 """
103 103 raise NotImplementedError
104 104
105 105 @property
106 106 def name_unicode(self):
107 107 return safe_unicode(self.name)
108 108
109 109 @LazyProperty
110 110 def owner(self):
111 111 raise NotImplementedError
112 112
113 113 @LazyProperty
114 114 def description(self):
115 115 raise NotImplementedError
116 116
117 117 @LazyProperty
118 118 def size(self):
119 119 """
120 120 Returns combined size in bytes for all repository files
121 121 """
122 122
123 123 size = 0
124 124 try:
125 125 tip = self.get_changeset()
126 126 for topnode, dirs, files in tip.walk('/'):
127 127 for f in files:
128 128 size += tip.get_file_size(f.path)
129 129
130 130 except RepositoryError as e:
131 131 pass
132 132 return size
133 133
134 134 def is_valid(self):
135 135 """
136 136 Validates repository.
137 137 """
138 138 raise NotImplementedError
139 139
140 140 def is_empty(self):
141 141 return self._empty
142 142
143 143 #==========================================================================
144 144 # CHANGESETS
145 145 #==========================================================================
146 146
147 147 def get_changeset(self, revision=None):
148 148 """
149 149 Returns instance of ``Changeset`` class. If ``revision`` is None, most
150 150 recent changeset is returned.
151 151
152 152 :raises ``EmptyRepositoryError``: if there are no revisions
153 153 """
154 154 raise NotImplementedError
155 155
156 156 def __iter__(self):
157 157 """
158 158 Allows Repository objects to be iterated.
159 159
160 160 *Requires* implementation of ``__getitem__`` method.
161 161 """
162 162 for revision in self.revisions:
163 163 yield self.get_changeset(revision)
164 164
165 165 def get_changesets(self, start=None, end=None, start_date=None,
166 166 end_date=None, branch_name=None, reverse=False):
167 167 """
168 168 Returns iterator of ``BaseChangeset`` objects from start to end,
169 169 both inclusive.
170 170
171 171 :param start: None or str
172 172 :param end: None or str
173 173 :param start_date:
174 174 :param end_date:
175 175 :param branch_name:
176 176 :param reversed:
177 177 """
178 178 raise NotImplementedError
179 179
180 180 def __getslice__(self, i, j):
181 181 """
182 182 Returns a iterator of sliced repository
183 183 """
184 184 for rev in self.revisions[i:j]:
185 185 yield self.get_changeset(rev)
186 186
187 187 def __getitem__(self, key):
188 188 return self.get_changeset(key)
189 189
190 190 def count(self):
191 191 return len(self.revisions)
192 192
193 193 def tag(self, name, user, revision=None, message=None, date=None, **opts):
194 194 """
195 195 Creates and returns a tag for the given ``revision``.
196 196
197 197 :param name: name for new tag
198 198 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
199 199 :param revision: changeset id for which new tag would be created
200 200 :param message: message of the tag's commit
201 201 :param date: date of tag's commit
202 202
203 203 :raises TagAlreadyExistError: if tag with same name already exists
204 204 """
205 205 raise NotImplementedError
206 206
207 207 def remove_tag(self, name, user, message=None, date=None):
208 208 """
209 209 Removes tag with the given ``name``.
210 210
211 211 :param name: name of the tag to be removed
212 212 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
213 213 :param message: message of the tag's removal commit
214 214 :param date: date of tag's removal commit
215 215
216 216 :raises TagDoesNotExistError: if tag with given name does not exists
217 217 """
218 218 raise NotImplementedError
219 219
220 220 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
221 221 context=3):
222 222 """
223 223 Returns (git like) *diff*, as plain text. Shows changes introduced by
224 224 ``rev2`` since ``rev1``.
225 225
226 226 :param rev1: Entry point from which diff is shown. Can be
227 227 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
228 228 the changes since empty state of the repository until ``rev2``
229 229 :param rev2: Until which revision changes should be shown.
230 230 :param ignore_whitespace: If set to ``True``, would not show whitespace
231 231 changes. Defaults to ``False``.
232 232 :param context: How many lines before/after changed lines should be
233 233 shown. Defaults to ``3``.
234 234 """
235 235 raise NotImplementedError
236 236
237 237 # ========== #
238 238 # COMMIT API #
239 239 # ========== #
240 240
241 241 @LazyProperty
242 242 def in_memory_changeset(self):
243 243 """
244 244 Returns ``InMemoryChangeset`` object for this repository.
245 245 """
246 246 raise NotImplementedError
247 247
248 248 def add(self, filenode, **kwargs):
249 249 """
250 250 Commit api function that will add given ``FileNode`` into this
251 251 repository.
252 252
253 253 :raises ``NodeAlreadyExistsError``: if there is a file with same path
254 254 already in repository
255 255 :raises ``NodeAlreadyAddedError``: if given node is already marked as
256 256 *added*
257 257 """
258 258 raise NotImplementedError
259 259
260 260 def remove(self, filenode, **kwargs):
261 261 """
262 262 Commit api function that will remove given ``FileNode`` into this
263 263 repository.
264 264
265 265 :raises ``EmptyRepositoryError``: if there are no changesets yet
266 266 :raises ``NodeDoesNotExistError``: if there is no file with given path
267 267 """
268 268 raise NotImplementedError
269 269
270 270 def commit(self, message, **kwargs):
271 271 """
272 272 Persists current changes made on this repository and returns newly
273 273 created changeset.
274 274
275 275 :raises ``NothingChangedError``: if no changes has been made
276 276 """
277 277 raise NotImplementedError
278 278
279 279 def get_state(self):
280 280 """
281 281 Returns dictionary with ``added``, ``changed`` and ``removed`` lists
282 282 containing ``FileNode`` objects.
283 283 """
284 284 raise NotImplementedError
285 285
286 286 def get_config_value(self, section, name, config_file=None):
287 287 """
288 288 Returns configuration value for a given [``section``] and ``name``.
289 289
290 290 :param section: Section we want to retrieve value from
291 291 :param name: Name of configuration we want to retrieve
292 292 :param config_file: A path to file which should be used to retrieve
293 293 configuration from (might also be a list of file paths)
294 294 """
295 295 raise NotImplementedError
296 296
297 297 def get_user_name(self, config_file=None):
298 298 """
299 299 Returns user's name from global configuration file.
300 300
301 301 :param config_file: A path to file which should be used to retrieve
302 302 configuration from (might also be a list of file paths)
303 303 """
304 304 raise NotImplementedError
305 305
306 306 def get_user_email(self, config_file=None):
307 307 """
308 308 Returns user's email from global configuration file.
309 309
310 310 :param config_file: A path to file which should be used to retrieve
311 311 configuration from (might also be a list of file paths)
312 312 """
313 313 raise NotImplementedError
314 314
315 315 # =========== #
316 316 # WORKDIR API #
317 317 # =========== #
318 318
319 319 @LazyProperty
320 320 def workdir(self):
321 321 """
322 322 Returns ``Workdir`` instance for this repository.
323 323 """
324 324 raise NotImplementedError
325 325
326 326
327 327 class BaseChangeset(object):
328 328 """
329 329 Each backend should implement it's changeset representation.
330 330
331 331 **Attributes**
332 332
333 333 ``repository``
334 334 repository object within which changeset exists
335 335
336 336 ``id``
337 337 may be ``raw_id`` or i.e. for mercurial's tip just ``tip``
338 338
339 339 ``raw_id``
340 340 raw changeset representation (i.e. full 40 length sha for git
341 341 backend)
342 342
343 343 ``short_id``
344 344 shortened (if apply) version of ``raw_id``; it would be simple
345 345 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
346 346 as ``raw_id`` for subversion
347 347
348 348 ``revision``
349 349 revision number as integer
350 350
351 351 ``files``
352 352 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
353 353
354 354 ``dirs``
355 355 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
356 356
357 357 ``nodes``
358 358 combined list of ``Node`` objects
359 359
360 360 ``author``
361 361 author of the changeset, as unicode
362 362
363 363 ``message``
364 364 message of the changeset, as unicode
365 365
366 366 ``parents``
367 367 list of parent changesets
368 368
369 369 ``last``
370 370 ``True`` if this is last changeset in repository, ``False``
371 371 otherwise; trying to access this attribute while there is no
372 372 changesets would raise ``EmptyRepositoryError``
373 373 """
374 374 def __str__(self):
375 375 return '<%s at %s:%s>' % (self.__class__.__name__, self.revision,
376 376 self.short_id)
377 377
378 378 def __repr__(self):
379 379 return self.__str__()
380 380
381 381 def __unicode__(self):
382 382 return u'%s:%s' % (self.revision, self.short_id)
383 383
384 384 def __eq__(self, other):
385 385 return self.raw_id == other.raw_id
386 386
387 def __json__(self):
387 def __json__(self, with_file_list=False):
388 if with_file_list:
389 return dict(
390 short_id=self.short_id,
391 raw_id=self.raw_id,
392 revision=self.revision,
393 message=self.message,
394 date=self.date,
395 author=self.author,
396 added=[el.path for el in self.added],
397 changed=[el.path for el in self.changed],
398 removed=[el.path for el in self.removed],
399 )
400 else:
388 401 return dict(
389 402 short_id=self.short_id,
390 403 raw_id=self.raw_id,
391 404 revision=self.revision,
392 405 message=self.message,
393 406 date=self.date,
394 407 author=self.author,
395 408 )
396 409
397 410 @LazyProperty
398 411 def last(self):
399 412 if self.repository is None:
400 413 raise ChangesetError("Cannot check if it's most recent revision")
401 414 return self.raw_id == self.repository.revisions[-1]
402 415
403 416 @LazyProperty
404 417 def parents(self):
405 418 """
406 419 Returns list of parents changesets.
407 420 """
408 421 raise NotImplementedError
409 422
410 423 @LazyProperty
411 424 def children(self):
412 425 """
413 426 Returns list of children changesets.
414 427 """
415 428 raise NotImplementedError
416 429
417 430 @LazyProperty
418 431 def id(self):
419 432 """
420 433 Returns string identifying this changeset.
421 434 """
422 435 raise NotImplementedError
423 436
424 437 @LazyProperty
425 438 def raw_id(self):
426 439 """
427 440 Returns raw string identifying this changeset.
428 441 """
429 442 raise NotImplementedError
430 443
431 444 @LazyProperty
432 445 def short_id(self):
433 446 """
434 447 Returns shortened version of ``raw_id`` attribute, as string,
435 448 identifying this changeset, useful for web representation.
436 449 """
437 450 raise NotImplementedError
438 451
439 452 @LazyProperty
440 453 def revision(self):
441 454 """
442 455 Returns integer identifying this changeset.
443 456
444 457 """
445 458 raise NotImplementedError
446 459
447 460 @LazyProperty
448 461 def committer(self):
449 462 """
450 463 Returns Committer for given commit
451 464 """
452 465
453 466 raise NotImplementedError
454 467
455 468 @LazyProperty
456 469 def committer_name(self):
457 470 """
458 471 Returns Author name for given commit
459 472 """
460 473
461 474 return author_name(self.committer)
462 475
463 476 @LazyProperty
464 477 def committer_email(self):
465 478 """
466 479 Returns Author email address for given commit
467 480 """
468 481
469 482 return author_email(self.committer)
470 483
471 484 @LazyProperty
472 485 def author(self):
473 486 """
474 487 Returns Author for given commit
475 488 """
476 489
477 490 raise NotImplementedError
478 491
479 492 @LazyProperty
480 493 def author_name(self):
481 494 """
482 495 Returns Author name for given commit
483 496 """
484 497
485 498 return author_name(self.author)
486 499
487 500 @LazyProperty
488 501 def author_email(self):
489 502 """
490 503 Returns Author email address for given commit
491 504 """
492 505
493 506 return author_email(self.author)
494 507
495 508 def get_file_mode(self, path):
496 509 """
497 510 Returns stat mode of the file at the given ``path``.
498 511 """
499 512 raise NotImplementedError
500 513
501 514 def get_file_content(self, path):
502 515 """
503 516 Returns content of the file at the given ``path``.
504 517 """
505 518 raise NotImplementedError
506 519
507 520 def get_file_size(self, path):
508 521 """
509 522 Returns size of the file at the given ``path``.
510 523 """
511 524 raise NotImplementedError
512 525
513 526 def get_file_changeset(self, path):
514 527 """
515 528 Returns last commit of the file at the given ``path``.
516 529 """
517 530 raise NotImplementedError
518 531
519 532 def get_file_history(self, path):
520 533 """
521 534 Returns history of file as reversed list of ``Changeset`` objects for
522 535 which file at given ``path`` has been modified.
523 536 """
524 537 raise NotImplementedError
525 538
526 539 def get_nodes(self, path):
527 540 """
528 541 Returns combined ``DirNode`` and ``FileNode`` objects list representing
529 542 state of changeset at the given ``path``.
530 543
531 544 :raises ``ChangesetError``: if node at the given ``path`` is not
532 545 instance of ``DirNode``
533 546 """
534 547 raise NotImplementedError
535 548
536 549 def get_node(self, path):
537 550 """
538 551 Returns ``Node`` object from the given ``path``.
539 552
540 553 :raises ``NodeDoesNotExistError``: if there is no node at the given
541 554 ``path``
542 555 """
543 556 raise NotImplementedError
544 557
545 558 def fill_archive(self, stream=None, kind='tgz', prefix=None):
546 559 """
547 560 Fills up given stream.
548 561
549 562 :param stream: file like object.
550 563 :param kind: one of following: ``zip``, ``tar``, ``tgz``
551 564 or ``tbz2``. Default: ``tgz``.
552 565 :param prefix: name of root directory in archive.
553 566 Default is repository name and changeset's raw_id joined with dash.
554 567
555 568 repo-tip.<kind>
556 569 """
557 570
558 571 raise NotImplementedError
559 572
560 573 def get_chunked_archive(self, **kwargs):
561 574 """
562 575 Returns iterable archive. Tiny wrapper around ``fill_archive`` method.
563 576
564 577 :param chunk_size: extra parameter which controls size of returned
565 578 chunks. Default:8k.
566 579 """
567 580
568 581 chunk_size = kwargs.pop('chunk_size', 8192)
569 582 stream = kwargs.get('stream')
570 583 self.fill_archive(**kwargs)
571 584 while True:
572 585 data = stream.read(chunk_size)
573 586 if not data:
574 587 break
575 588 yield data
576 589
577 590 @LazyProperty
578 591 def root(self):
579 592 """
580 593 Returns ``RootNode`` object for this changeset.
581 594 """
582 595 return self.get_node('')
583 596
584 597 def next(self, branch=None):
585 598 """
586 599 Returns next changeset from current, if branch is gives it will return
587 600 next changeset belonging to this branch
588 601
589 602 :param branch: show changesets within the given named branch
590 603 """
591 604 raise NotImplementedError
592 605
593 606 def prev(self, branch=None):
594 607 """
595 608 Returns previous changeset from current, if branch is gives it will
596 609 return previous changeset belonging to this branch
597 610
598 611 :param branch: show changesets within the given named branch
599 612 """
600 613 raise NotImplementedError
601 614
602 615 @LazyProperty
603 616 def added(self):
604 617 """
605 618 Returns list of added ``FileNode`` objects.
606 619 """
607 620 raise NotImplementedError
608 621
609 622 @LazyProperty
610 623 def changed(self):
611 624 """
612 625 Returns list of modified ``FileNode`` objects.
613 626 """
614 627 raise NotImplementedError
615 628
616 629 @LazyProperty
617 630 def removed(self):
618 631 """
619 632 Returns list of removed ``FileNode`` objects.
620 633 """
621 634 raise NotImplementedError
622 635
623 636 @LazyProperty
624 637 def size(self):
625 638 """
626 639 Returns total number of bytes from contents of all filenodes.
627 640 """
628 641 return sum((node.size for node in self.get_filenodes_generator()))
629 642
630 643 def walk(self, topurl=''):
631 644 """
632 645 Similar to os.walk method. Instead of filesystem it walks through
633 646 changeset starting at given ``topurl``. Returns generator of tuples
634 647 (topnode, dirnodes, filenodes).
635 648 """
636 649 topnode = self.get_node(topurl)
637 650 yield (topnode, topnode.dirs, topnode.files)
638 651 for dirnode in topnode.dirs:
639 652 for tup in self.walk(dirnode.path):
640 653 yield tup
641 654
642 655 def get_filenodes_generator(self):
643 656 """
644 657 Returns generator that yields *all* file nodes.
645 658 """
646 659 for topnode, dirs, files in self.walk():
647 660 for node in files:
648 661 yield node
649 662
650 663 def as_dict(self):
651 664 """
652 665 Returns dictionary with changeset's attributes and their values.
653 666 """
654 667 data = get_dict_for_attrs(self, ['id', 'raw_id', 'short_id',
655 668 'revision', 'date', 'message'])
656 669 data['author'] = {'name': self.author_name, 'email': self.author_email}
657 670 data['added'] = [node.path for node in self.added]
658 671 data['changed'] = [node.path for node in self.changed]
659 672 data['removed'] = [node.path for node in self.removed]
660 673 return data
661 674
662 675 @LazyProperty
663 676 def closesbranch(self):
664 677 return False
665 678
666 679 @LazyProperty
667 680 def obsolete(self):
668 681 return False
669 682
670 683 @LazyProperty
671 684 def bumped(self):
672 685 return False
673 686
674 687 @LazyProperty
675 688 def divergent(self):
676 689 return False
677 690
678 691 @LazyProperty
679 692 def extinct(self):
680 693 return False
681 694
682 695 @LazyProperty
683 696 def unstable(self):
684 697 return False
685 698
686 699 @LazyProperty
687 700 def phase(self):
688 701 return ''
689 702
690 703 class BaseWorkdir(object):
691 704 """
692 705 Working directory representation of single repository.
693 706
694 707 :attribute: repository: repository object of working directory
695 708 """
696 709
697 710 def __init__(self, repository):
698 711 self.repository = repository
699 712
700 713 def get_branch(self):
701 714 """
702 715 Returns name of current branch.
703 716 """
704 717 raise NotImplementedError
705 718
706 719 def get_changeset(self):
707 720 """
708 721 Returns current changeset.
709 722 """
710 723 raise NotImplementedError
711 724
712 725 def get_added(self):
713 726 """
714 727 Returns list of ``FileNode`` objects marked as *new* in working
715 728 directory.
716 729 """
717 730 raise NotImplementedError
718 731
719 732 def get_changed(self):
720 733 """
721 734 Returns list of ``FileNode`` objects *changed* in working directory.
722 735 """
723 736 raise NotImplementedError
724 737
725 738 def get_removed(self):
726 739 """
727 740 Returns list of ``RemovedFileNode`` objects marked as *removed* in
728 741 working directory.
729 742 """
730 743 raise NotImplementedError
731 744
732 745 def get_untracked(self):
733 746 """
734 747 Returns list of ``FileNode`` objects which are present within working
735 748 directory however are not tracked by repository.
736 749 """
737 750 raise NotImplementedError
738 751
739 752 def get_status(self):
740 753 """
741 754 Returns dict with ``added``, ``changed``, ``removed`` and ``untracked``
742 755 lists.
743 756 """
744 757 raise NotImplementedError
745 758
746 759 def commit(self, message, **kwargs):
747 760 """
748 761 Commits local (from working directory) changes and returns newly
749 762 created
750 763 ``Changeset``. Updates repository's ``revisions`` list.
751 764
752 765 :raises ``CommitError``: if any error occurs while committing
753 766 """
754 767 raise NotImplementedError
755 768
756 769 def update(self, revision=None):
757 770 """
758 771 Fetches content of the given revision and populates it within working
759 772 directory.
760 773 """
761 774 raise NotImplementedError
762 775
763 776 def checkout_branch(self, branch=None):
764 777 """
765 778 Checks out ``branch`` or the backend's default branch.
766 779
767 780 Raises ``BranchDoesNotExistError`` if the branch does not exist.
768 781 """
769 782 raise NotImplementedError
770 783
771 784
772 785 class BaseInMemoryChangeset(object):
773 786 """
774 787 Represents differences between repository's state (most recent head) and
775 788 changes made *in place*.
776 789
777 790 **Attributes**
778 791
779 792 ``repository``
780 793 repository object for this in-memory-changeset
781 794
782 795 ``added``
783 796 list of ``FileNode`` objects marked as *added*
784 797
785 798 ``changed``
786 799 list of ``FileNode`` objects marked as *changed*
787 800
788 801 ``removed``
789 802 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
790 803 *removed*
791 804
792 805 ``parents``
793 806 list of ``Changeset`` representing parents of in-memory changeset.
794 807 Should always be 2-element sequence.
795 808
796 809 """
797 810
798 811 def __init__(self, repository):
799 812 self.repository = repository
800 813 self.added = []
801 814 self.changed = []
802 815 self.removed = []
803 816 self.parents = []
804 817
805 818 def add(self, *filenodes):
806 819 """
807 820 Marks given ``FileNode`` objects as *to be committed*.
808 821
809 822 :raises ``NodeAlreadyExistsError``: if node with same path exists at
810 823 latest changeset
811 824 :raises ``NodeAlreadyAddedError``: if node with same path is already
812 825 marked as *added*
813 826 """
814 827 # Check if not already marked as *added* first
815 828 for node in filenodes:
816 829 if node.path in (n.path for n in self.added):
817 830 raise NodeAlreadyAddedError("Such FileNode %s is already "
818 831 "marked for addition" % node.path)
819 832 for node in filenodes:
820 833 self.added.append(node)
821 834
822 835 def change(self, *filenodes):
823 836 """
824 837 Marks given ``FileNode`` objects to be *changed* in next commit.
825 838
826 839 :raises ``EmptyRepositoryError``: if there are no changesets yet
827 840 :raises ``NodeAlreadyExistsError``: if node with same path is already
828 841 marked to be *changed*
829 842 :raises ``NodeAlreadyRemovedError``: if node with same path is already
830 843 marked to be *removed*
831 844 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
832 845 changeset
833 846 :raises ``NodeNotChangedError``: if node hasn't really be changed
834 847 """
835 848 for node in filenodes:
836 849 if node.path in (n.path for n in self.removed):
837 850 raise NodeAlreadyRemovedError("Node at %s is already marked "
838 851 "as removed" % node.path)
839 852 try:
840 853 self.repository.get_changeset()
841 854 except EmptyRepositoryError:
842 855 raise EmptyRepositoryError("Nothing to change - try to *add* new "
843 856 "nodes rather than changing them")
844 857 for node in filenodes:
845 858 if node.path in (n.path for n in self.changed):
846 859 raise NodeAlreadyChangedError("Node at '%s' is already "
847 860 "marked as changed" % node.path)
848 861 self.changed.append(node)
849 862
850 863 def remove(self, *filenodes):
851 864 """
852 865 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
853 866 *removed* in next commit.
854 867
855 868 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
856 869 be *removed*
857 870 :raises ``NodeAlreadyChangedError``: if node has been already marked to
858 871 be *changed*
859 872 """
860 873 for node in filenodes:
861 874 if node.path in (n.path for n in self.removed):
862 875 raise NodeAlreadyRemovedError("Node is already marked to "
863 876 "for removal at %s" % node.path)
864 877 if node.path in (n.path for n in self.changed):
865 878 raise NodeAlreadyChangedError("Node is already marked to "
866 879 "be changed at %s" % node.path)
867 880 # We only mark node as *removed* - real removal is done by
868 881 # commit method
869 882 self.removed.append(node)
870 883
871 884 def reset(self):
872 885 """
873 886 Resets this instance to initial state (cleans ``added``, ``changed``
874 887 and ``removed`` lists).
875 888 """
876 889 self.added = []
877 890 self.changed = []
878 891 self.removed = []
879 892 self.parents = []
880 893
881 894 def get_ipaths(self):
882 895 """
883 896 Returns generator of paths from nodes marked as added, changed or
884 897 removed.
885 898 """
886 899 for node in itertools.chain(self.added, self.changed, self.removed):
887 900 yield node.path
888 901
889 902 def get_paths(self):
890 903 """
891 904 Returns list of paths from nodes marked as added, changed or removed.
892 905 """
893 906 return list(self.get_ipaths())
894 907
895 908 def check_integrity(self, parents=None):
896 909 """
897 910 Checks in-memory changeset's integrity. Also, sets parents if not
898 911 already set.
899 912
900 913 :raises CommitError: if any error occurs (i.e.
901 914 ``NodeDoesNotExistError``).
902 915 """
903 916 if not self.parents:
904 917 parents = parents or []
905 918 if len(parents) == 0:
906 919 try:
907 920 parents = [self.repository.get_changeset(), None]
908 921 except EmptyRepositoryError:
909 922 parents = [None, None]
910 923 elif len(parents) == 1:
911 924 parents += [None]
912 925 self.parents = parents
913 926
914 927 # Local parents, only if not None
915 928 parents = [p for p in self.parents if p]
916 929
917 930 # Check nodes marked as added
918 931 for p in parents:
919 932 for node in self.added:
920 933 try:
921 934 p.get_node(node.path)
922 935 except NodeDoesNotExistError:
923 936 pass
924 937 else:
925 938 raise NodeAlreadyExistsError("Node at %s already exists "
926 939 "at %s" % (node.path, p))
927 940
928 941 # Check nodes marked as changed
929 942 missing = set(self.changed)
930 943 not_changed = set(self.changed)
931 944 if self.changed and not parents:
932 945 raise NodeDoesNotExistError(str(self.changed[0].path))
933 946 for p in parents:
934 947 for node in self.changed:
935 948 try:
936 949 old = p.get_node(node.path)
937 950 missing.remove(node)
938 951 # if content actually changed, remove node from unchanged
939 952 if old.content != node.content:
940 953 not_changed.remove(node)
941 954 except NodeDoesNotExistError:
942 955 pass
943 956 if self.changed and missing:
944 957 raise NodeDoesNotExistError("Node at %s is missing "
945 958 "(parents: %s)" % (node.path, parents))
946 959
947 960 if self.changed and not_changed:
948 961 raise NodeNotChangedError("Node at %s wasn't actually changed "
949 962 "since parents' changesets: %s" % (not_changed.pop().path,
950 963 parents)
951 964 )
952 965
953 966 # Check nodes marked as removed
954 967 if self.removed and not parents:
955 968 raise NodeDoesNotExistError("Cannot remove node at %s as there "
956 969 "were no parents specified" % self.removed[0].path)
957 970 really_removed = set()
958 971 for p in parents:
959 972 for node in self.removed:
960 973 try:
961 974 p.get_node(node.path)
962 975 really_removed.add(node)
963 976 except ChangesetError:
964 977 pass
965 978 not_removed = set(self.removed) - really_removed
966 979 if not_removed:
967 980 raise NodeDoesNotExistError("Cannot remove node at %s from "
968 981 "following parents: %s" % (not_removed[0], parents))
969 982
970 983 def commit(self, message, author, parents=None, branch=None, date=None,
971 984 **kwargs):
972 985 """
973 986 Performs in-memory commit (doesn't check workdir in any way) and
974 987 returns newly created ``Changeset``. Updates repository's
975 988 ``revisions``.
976 989
977 990 .. note::
978 991 While overriding this method each backend's should call
979 992 ``self.check_integrity(parents)`` in the first place.
980 993
981 994 :param message: message of the commit
982 995 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
983 996 :param parents: single parent or sequence of parents from which commit
984 997 would be derived
985 998 :param date: ``datetime.datetime`` instance. Defaults to
986 999 ``datetime.datetime.now()``.
987 1000 :param branch: branch name, as string. If none given, default backend's
988 1001 branch would be used.
989 1002
990 1003 :raises ``CommitError``: if any error occurs while committing
991 1004 """
992 1005 raise NotImplementedError
993 1006
994 1007
995 1008 class EmptyChangeset(BaseChangeset):
996 1009 """
997 1010 An dummy empty changeset. It's possible to pass hash when creating
998 1011 an EmptyChangeset
999 1012 """
1000 1013
1001 1014 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
1002 1015 alias=None, revision=-1, message='', author='', date=None):
1003 1016 self._empty_cs = cs
1004 1017 self.revision = revision
1005 1018 self.message = message
1006 1019 self.author = author
1007 1020 self.date = date or datetime.datetime.fromtimestamp(0)
1008 1021 self.repository = repo
1009 1022 self.requested_revision = requested_revision
1010 1023 self.alias = alias
1011 1024
1012 1025 @LazyProperty
1013 1026 def raw_id(self):
1014 1027 """
1015 1028 Returns raw string identifying this changeset, useful for web
1016 1029 representation.
1017 1030 """
1018 1031
1019 1032 return self._empty_cs
1020 1033
1021 1034 @LazyProperty
1022 1035 def branch(self):
1023 1036 from kallithea.lib.vcs.backends import get_backend
1024 1037 return get_backend(self.alias).DEFAULT_BRANCH_NAME
1025 1038
1026 1039 @LazyProperty
1027 1040 def short_id(self):
1028 1041 return self.raw_id[:12]
1029 1042
1030 1043 def get_file_changeset(self, path):
1031 1044 return self
1032 1045
1033 1046 def get_file_content(self, path):
1034 1047 return u''
1035 1048
1036 1049 def get_file_size(self, path):
1037 1050 return 0
1038 1051
1039 1052
1040 1053 class CollectionGenerator(object):
1041 1054
1042 1055 def __init__(self, repo, revs):
1043 1056 self.repo = repo
1044 1057 self.revs = revs
1045 1058
1046 1059 def __len__(self):
1047 1060 return len(self.revs)
1048 1061
1049 1062 def __iter__(self):
1050 1063 for rev in self.revs:
1051 1064 yield self.repo.get_changeset(rev)
1052 1065
1053 1066 def __getitem__(self, what):
1054 1067 """Return either a single element by index, or a sliced collection."""
1055 1068 if isinstance(what, slice):
1056 1069 return CollectionGenerator(self.repo, self.revs[what])
1057 1070 else:
1058 1071 # single item
1059 1072 return self.repo.get_changeset(self.revs[what])
1060 1073
1061 1074 def __repr__(self):
1062 1075 return '<CollectionGenerator[len:%s]>' % (len(self))
@@ -1,2596 +1,2614 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14
15 15 """
16 16 Tests for the JSON-RPC web api.
17 17 """
18 18
19 19 import os
20 20 import random
21 21 import mock
22 22 import re
23 23
24 24 from kallithea.tests.base import *
25 25 from kallithea.tests.fixture import Fixture
26 26 from kallithea.lib.compat import json
27 27 from kallithea.lib.auth import AuthUser
28 28 from kallithea.model.user import UserModel
29 29 from kallithea.model.user_group import UserGroupModel
30 30 from kallithea.model.repo import RepoModel
31 31 from kallithea.model.repo_group import RepoGroupModel
32 32 from kallithea.model.meta import Session
33 33 from kallithea.model.scm import ScmModel
34 34 from kallithea.model.gist import GistModel
35 35 from kallithea.model.changeset_status import ChangesetStatusModel
36 36 from kallithea.model.db import Repository, User, Setting, Ui, PullRequest, ChangesetStatus
37 37 from kallithea.lib.utils2 import time_to_datetime
38 38
39 39
40 40 API_URL = '/_admin/api'
41 41 TEST_USER_GROUP = u'test_user_group'
42 42 TEST_REPO_GROUP = u'test_repo_group'
43 43
44 44 fixture = Fixture()
45 45
46 46
47 47 def _build_data(apikey, method, **kw):
48 48 """
49 49 Builds API data with given random ID
50 50
51 51 :param random_id:
52 52 """
53 53 random_id = random.randrange(1, 9999)
54 54 return random_id, json.dumps({
55 55 "id": random_id,
56 56 "api_key": apikey,
57 57 "method": method,
58 58 "args": kw
59 59 })
60 60
61 61
62 62 jsonify = lambda obj: json.loads(json.dumps(obj))
63 63
64 64
65 65 def crash(*args, **kwargs):
66 66 raise Exception('Total Crash !')
67 67
68 68
69 69 def api_call(test_obj, params):
70 70 response = test_obj.app.post(API_URL, content_type='application/json',
71 71 params=params)
72 72 return response
73 73
74 74
75 75 ## helpers
76 76 def make_user_group(name=TEST_USER_GROUP):
77 77 gr = fixture.create_user_group(name, cur_user=TEST_USER_ADMIN_LOGIN)
78 78 UserGroupModel().add_user_to_group(user_group=gr,
79 79 user=TEST_USER_ADMIN_LOGIN)
80 80 Session().commit()
81 81 return gr
82 82
83 83
84 84 def make_repo_group(name=TEST_REPO_GROUP):
85 85 gr = fixture.create_repo_group(name, cur_user=TEST_USER_ADMIN_LOGIN)
86 86 Session().commit()
87 87 return gr
88 88
89 89
90 90 class _BaseTestApi(object):
91 91 REPO = None
92 92 REPO_TYPE = None
93 93
94 94 @classmethod
95 95 def setup_class(cls):
96 96 cls.usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
97 97 cls.apikey = cls.usr.api_key
98 98 cls.test_user = UserModel().create_or_update(
99 99 username='test-api',
100 100 password='test',
101 101 email='test@example.com',
102 102 firstname=u'first',
103 103 lastname=u'last'
104 104 )
105 105 Session().commit()
106 106 cls.TEST_USER_LOGIN = cls.test_user.username
107 107 cls.apikey_regular = cls.test_user.api_key
108 108
109 109 @classmethod
110 110 def teardown_class(cls):
111 111 pass
112 112
113 113 def setup_method(self, method):
114 114 make_user_group()
115 115 make_repo_group()
116 116
117 117 def teardown_method(self, method):
118 118 fixture.destroy_user_group(TEST_USER_GROUP)
119 119 fixture.destroy_gists()
120 120 fixture.destroy_repo_group(TEST_REPO_GROUP)
121 121
122 122 def _compare_ok(self, id_, expected, given):
123 123 expected = jsonify({
124 124 'id': id_,
125 125 'error': None,
126 126 'result': expected
127 127 })
128 128 given = json.loads(given)
129 129 assert expected == given
130 130
131 131 def _compare_error(self, id_, expected, given):
132 132 expected = jsonify({
133 133 'id': id_,
134 134 'error': expected,
135 135 'result': None
136 136 })
137 137 given = json.loads(given)
138 138 assert expected == given
139 139
140 140 def test_Optional_object(self):
141 141 from kallithea.controllers.api.api import Optional
142 142
143 143 option1 = Optional(None)
144 144 assert '<Optional:%s>' % None == repr(option1)
145 145 assert option1() == None
146 146
147 147 assert 1 == Optional.extract(Optional(1))
148 148 assert 'trololo' == Optional.extract('trololo')
149 149
150 150 def test_Optional_OAttr(self):
151 151 from kallithea.controllers.api.api import Optional, OAttr
152 152
153 153 option1 = Optional(OAttr('apiuser'))
154 154 assert 'apiuser' == Optional.extract(option1)
155 155
156 156 def test_OAttr_object(self):
157 157 from kallithea.controllers.api.api import OAttr
158 158
159 159 oattr1 = OAttr('apiuser')
160 160 assert '<OptionalAttr:apiuser>' == repr(oattr1)
161 161 assert oattr1() == oattr1
162 162
163 163 def test_api_wrong_key(self):
164 164 id_, params = _build_data('trololo', 'get_user')
165 165 response = api_call(self, params)
166 166
167 167 expected = 'Invalid API key'
168 168 self._compare_error(id_, expected, given=response.body)
169 169
170 170 def test_api_missing_non_optional_param(self):
171 171 id_, params = _build_data(self.apikey, 'get_repo')
172 172 response = api_call(self, params)
173 173
174 174 expected = 'Missing non optional `repoid` arg in JSON DATA'
175 175 self._compare_error(id_, expected, given=response.body)
176 176
177 177 def test_api_missing_non_optional_param_args_null(self):
178 178 id_, params = _build_data(self.apikey, 'get_repo')
179 179 params = params.replace('"args": {}', '"args": null')
180 180 response = api_call(self, params)
181 181
182 182 expected = 'Missing non optional `repoid` arg in JSON DATA'
183 183 self._compare_error(id_, expected, given=response.body)
184 184
185 185 def test_api_missing_non_optional_param_args_bad(self):
186 186 id_, params = _build_data(self.apikey, 'get_repo')
187 187 params = params.replace('"args": {}', '"args": 1')
188 188 response = api_call(self, params)
189 189
190 190 expected = 'Missing non optional `repoid` arg in JSON DATA'
191 191 self._compare_error(id_, expected, given=response.body)
192 192
193 193 def test_api_args_is_null(self):
194 194 id_, params = _build_data(self.apikey, 'get_users', )
195 195 params = params.replace('"args": {}', '"args": null')
196 196 response = api_call(self, params)
197 197 assert response.status == '200 OK'
198 198
199 199 def test_api_args_is_bad(self):
200 200 id_, params = _build_data(self.apikey, 'get_users', )
201 201 params = params.replace('"args": {}', '"args": 1')
202 202 response = api_call(self, params)
203 203 assert response.status == '200 OK'
204 204
205 205 def test_api_args_different_args(self):
206 206 import string
207 207 expected = {
208 208 'ascii_letters': string.ascii_letters,
209 209 'ws': string.whitespace,
210 210 'printables': string.printable
211 211 }
212 212 id_, params = _build_data(self.apikey, 'test', args=expected)
213 213 response = api_call(self, params)
214 214 assert response.status == '200 OK'
215 215 self._compare_ok(id_, expected, response.body)
216 216
217 217 def test_api_get_users(self):
218 218 id_, params = _build_data(self.apikey, 'get_users', )
219 219 response = api_call(self, params)
220 220 ret_all = []
221 221 _users = User.query().filter_by(is_default_user=False) \
222 222 .order_by(User.username).all()
223 223 for usr in _users:
224 224 ret = usr.get_api_data()
225 225 ret_all.append(jsonify(ret))
226 226 expected = ret_all
227 227 self._compare_ok(id_, expected, given=response.body)
228 228
229 229 def test_api_get_user(self):
230 230 id_, params = _build_data(self.apikey, 'get_user',
231 231 userid=TEST_USER_ADMIN_LOGIN)
232 232 response = api_call(self, params)
233 233
234 234 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
235 235 ret = usr.get_api_data()
236 236 ret['permissions'] = AuthUser(dbuser=usr).permissions
237 237
238 238 expected = ret
239 239 self._compare_ok(id_, expected, given=response.body)
240 240
241 241 def test_api_get_user_that_does_not_exist(self):
242 242 id_, params = _build_data(self.apikey, 'get_user',
243 243 userid='trololo')
244 244 response = api_call(self, params)
245 245
246 246 expected = "user `%s` does not exist" % 'trololo'
247 247 self._compare_error(id_, expected, given=response.body)
248 248
249 249 def test_api_get_user_without_giving_userid(self):
250 250 id_, params = _build_data(self.apikey, 'get_user')
251 251 response = api_call(self, params)
252 252
253 253 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
254 254 ret = usr.get_api_data()
255 255 ret['permissions'] = AuthUser(dbuser=usr).permissions
256 256
257 257 expected = ret
258 258 self._compare_ok(id_, expected, given=response.body)
259 259
260 260 def test_api_get_user_without_giving_userid_non_admin(self):
261 261 id_, params = _build_data(self.apikey_regular, 'get_user')
262 262 response = api_call(self, params)
263 263
264 264 usr = User.get_by_username(self.TEST_USER_LOGIN)
265 265 ret = usr.get_api_data()
266 266 ret['permissions'] = AuthUser(dbuser=usr).permissions
267 267
268 268 expected = ret
269 269 self._compare_ok(id_, expected, given=response.body)
270 270
271 271 def test_api_get_user_with_giving_userid_non_admin(self):
272 272 id_, params = _build_data(self.apikey_regular, 'get_user',
273 273 userid=self.TEST_USER_LOGIN)
274 274 response = api_call(self, params)
275 275
276 276 expected = 'userid is not the same as your user'
277 277 self._compare_error(id_, expected, given=response.body)
278 278
279 279 def test_api_pull(self):
280 280 repo_name = u'test_pull'
281 281 r = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
282 282 r.clone_uri = os.path.join(Ui.get_by_key('paths', '/').ui_value, self.REPO)
283 283 Session().commit()
284 284
285 285 id_, params = _build_data(self.apikey, 'pull',
286 286 repoid=repo_name,)
287 287 response = api_call(self, params)
288 288
289 289 expected = {'msg': 'Pulled from `%s`' % repo_name,
290 290 'repository': repo_name}
291 291 self._compare_ok(id_, expected, given=response.body)
292 292
293 293 fixture.destroy_repo(repo_name)
294 294
295 295 def test_api_pull_error(self):
296 296 id_, params = _build_data(self.apikey, 'pull',
297 297 repoid=self.REPO, )
298 298 response = api_call(self, params)
299 299
300 300 expected = 'Unable to pull changes from `%s`' % self.REPO
301 301 self._compare_error(id_, expected, given=response.body)
302 302
303 303 def test_api_rescan_repos(self):
304 304 id_, params = _build_data(self.apikey, 'rescan_repos')
305 305 response = api_call(self, params)
306 306
307 307 expected = {'added': [], 'removed': []}
308 308 self._compare_ok(id_, expected, given=response.body)
309 309
310 310 @mock.patch.object(ScmModel, 'repo_scan', crash)
311 311 def test_api_rescann_error(self):
312 312 id_, params = _build_data(self.apikey, 'rescan_repos', )
313 313 response = api_call(self, params)
314 314
315 315 expected = 'Error occurred during rescan repositories action'
316 316 self._compare_error(id_, expected, given=response.body)
317 317
318 318 def test_api_invalidate_cache(self):
319 319 repo = RepoModel().get_by_repo_name(self.REPO)
320 320 repo.scm_instance_cached() # seed cache
321 321
322 322 id_, params = _build_data(self.apikey, 'invalidate_cache',
323 323 repoid=self.REPO)
324 324 response = api_call(self, params)
325 325
326 326 expected = {
327 327 'msg': "Cache for repository `%s` was invalidated" % (self.REPO,),
328 328 'repository': self.REPO
329 329 }
330 330 self._compare_ok(id_, expected, given=response.body)
331 331
332 332 @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
333 333 def test_api_invalidate_cache_error(self):
334 334 id_, params = _build_data(self.apikey, 'invalidate_cache',
335 335 repoid=self.REPO)
336 336 response = api_call(self, params)
337 337
338 338 expected = 'Error occurred during cache invalidation action'
339 339 self._compare_error(id_, expected, given=response.body)
340 340
341 341 def test_api_invalidate_cache_regular_user_no_permission(self):
342 342 repo = RepoModel().get_by_repo_name(self.REPO)
343 343 repo.scm_instance_cached() # seed cache
344 344
345 345 id_, params = _build_data(self.apikey_regular, 'invalidate_cache',
346 346 repoid=self.REPO)
347 347 response = api_call(self, params)
348 348
349 349 expected = "repository `%s` does not exist" % (self.REPO,)
350 350 self._compare_error(id_, expected, given=response.body)
351 351
352 352 def test_api_lock_repo_lock_acquire(self):
353 353 id_, params = _build_data(self.apikey, 'lock',
354 354 userid=TEST_USER_ADMIN_LOGIN,
355 355 repoid=self.REPO,
356 356 locked=True)
357 357 response = api_call(self, params)
358 358 expected = {
359 359 'repo': self.REPO, 'locked': True,
360 360 'locked_since': response.json['result']['locked_since'],
361 361 'locked_by': TEST_USER_ADMIN_LOGIN,
362 362 'lock_state_changed': True,
363 363 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
364 364 % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
365 365 }
366 366 self._compare_ok(id_, expected, given=response.body)
367 367
368 368 def test_api_lock_repo_lock_acquire_by_non_admin(self):
369 369 repo_name = u'api_delete_me'
370 370 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
371 371 cur_user=self.TEST_USER_LOGIN)
372 372 try:
373 373 id_, params = _build_data(self.apikey_regular, 'lock',
374 374 repoid=repo_name,
375 375 locked=True)
376 376 response = api_call(self, params)
377 377 expected = {
378 378 'repo': repo_name,
379 379 'locked': True,
380 380 'locked_since': response.json['result']['locked_since'],
381 381 'locked_by': self.TEST_USER_LOGIN,
382 382 'lock_state_changed': True,
383 383 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
384 384 % (self.TEST_USER_LOGIN, repo_name, True))
385 385 }
386 386 self._compare_ok(id_, expected, given=response.body)
387 387 finally:
388 388 fixture.destroy_repo(repo_name)
389 389
390 390 def test_api_lock_repo_lock_acquire_non_admin_with_userid(self):
391 391 repo_name = u'api_delete_me'
392 392 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
393 393 cur_user=self.TEST_USER_LOGIN)
394 394 try:
395 395 id_, params = _build_data(self.apikey_regular, 'lock',
396 396 userid=TEST_USER_ADMIN_LOGIN,
397 397 repoid=repo_name,
398 398 locked=True)
399 399 response = api_call(self, params)
400 400 expected = 'userid is not the same as your user'
401 401 self._compare_error(id_, expected, given=response.body)
402 402 finally:
403 403 fixture.destroy_repo(repo_name)
404 404
405 405 def test_api_lock_repo_lock_acquire_non_admin_not_his_repo(self):
406 406 id_, params = _build_data(self.apikey_regular, 'lock',
407 407 repoid=self.REPO,
408 408 locked=True)
409 409 response = api_call(self, params)
410 410 expected = 'repository `%s` does not exist' % (self.REPO)
411 411 self._compare_error(id_, expected, given=response.body)
412 412
413 413 def test_api_lock_repo_lock_release(self):
414 414 id_, params = _build_data(self.apikey, 'lock',
415 415 userid=TEST_USER_ADMIN_LOGIN,
416 416 repoid=self.REPO,
417 417 locked=False)
418 418 response = api_call(self, params)
419 419 expected = {
420 420 'repo': self.REPO,
421 421 'locked': False,
422 422 'locked_since': None,
423 423 'locked_by': TEST_USER_ADMIN_LOGIN,
424 424 'lock_state_changed': True,
425 425 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
426 426 % (TEST_USER_ADMIN_LOGIN, self.REPO, False))
427 427 }
428 428 self._compare_ok(id_, expected, given=response.body)
429 429
430 430 def test_api_lock_repo_lock_acquire_optional_userid(self):
431 431 id_, params = _build_data(self.apikey, 'lock',
432 432 repoid=self.REPO,
433 433 locked=True)
434 434 response = api_call(self, params)
435 435 time_ = response.json['result']['locked_since']
436 436 expected = {
437 437 'repo': self.REPO,
438 438 'locked': True,
439 439 'locked_since': time_,
440 440 'locked_by': TEST_USER_ADMIN_LOGIN,
441 441 'lock_state_changed': True,
442 442 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
443 443 % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
444 444 }
445 445
446 446 self._compare_ok(id_, expected, given=response.body)
447 447
448 448 def test_api_lock_repo_lock_optional_locked(self):
449 449 try:
450 450 id_, params = _build_data(self.apikey, 'lock',
451 451 repoid=self.REPO)
452 452 response = api_call(self, params)
453 453 time_ = response.json['result']['locked_since']
454 454 expected = {
455 455 'repo': self.REPO,
456 456 'locked': True,
457 457 'locked_since': time_,
458 458 'locked_by': TEST_USER_ADMIN_LOGIN,
459 459 'lock_state_changed': False,
460 460 'msg': ('Repo `%s` locked by `%s` on `%s`.'
461 461 % (self.REPO, TEST_USER_ADMIN_LOGIN,
462 462 json.dumps(time_to_datetime(time_))))
463 463 }
464 464 self._compare_ok(id_, expected, given=response.body)
465 465 finally:
466 466 # cleanup
467 467 Repository.unlock(RepoModel().get_by_repo_name(self.REPO))
468 468
469 469 def test_api_lock_repo_lock_optional_not_locked(self):
470 470 repo_name = u'api_not_locked'
471 471 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
472 472 cur_user=self.TEST_USER_LOGIN)
473 473 assert repo.locked == [None, None]
474 474 try:
475 475 id_, params = _build_data(self.apikey, 'lock',
476 476 repoid=repo.repo_id)
477 477 response = api_call(self, params)
478 478 expected = {
479 479 'repo': repo_name,
480 480 'locked': False,
481 481 'locked_since': None,
482 482 'locked_by': None,
483 483 'lock_state_changed': False,
484 484 'msg': ('Repo `%s` not locked.' % (repo_name,))
485 485 }
486 486 self._compare_ok(id_, expected, given=response.body)
487 487 finally:
488 488 fixture.destroy_repo(repo_name)
489 489
490 490 @mock.patch.object(Repository, 'lock', crash)
491 491 def test_api_lock_error(self):
492 492 id_, params = _build_data(self.apikey, 'lock',
493 493 userid=TEST_USER_ADMIN_LOGIN,
494 494 repoid=self.REPO,
495 495 locked=True)
496 496 response = api_call(self, params)
497 497
498 498 expected = 'Error occurred locking repository `%s`' % self.REPO
499 499 self._compare_error(id_, expected, given=response.body)
500 500
501 501 def test_api_get_locks_regular_user(self):
502 502 id_, params = _build_data(self.apikey_regular, 'get_locks')
503 503 response = api_call(self, params)
504 504 expected = []
505 505 self._compare_ok(id_, expected, given=response.body)
506 506
507 507 def test_api_get_locks_with_userid_regular_user(self):
508 508 id_, params = _build_data(self.apikey_regular, 'get_locks',
509 509 userid=TEST_USER_ADMIN_LOGIN)
510 510 response = api_call(self, params)
511 511 expected = 'userid is not the same as your user'
512 512 self._compare_error(id_, expected, given=response.body)
513 513
514 514 def test_api_get_locks(self):
515 515 id_, params = _build_data(self.apikey, 'get_locks')
516 516 response = api_call(self, params)
517 517 expected = []
518 518 self._compare_ok(id_, expected, given=response.body)
519 519
520 520 def test_api_get_locks_with_one_locked_repo(self):
521 521 repo_name = u'api_delete_me'
522 522 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
523 523 cur_user=self.TEST_USER_LOGIN)
524 524 Repository.lock(repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
525 525 try:
526 526 id_, params = _build_data(self.apikey, 'get_locks')
527 527 response = api_call(self, params)
528 528 expected = [repo.get_api_data()]
529 529 self._compare_ok(id_, expected, given=response.body)
530 530 finally:
531 531 fixture.destroy_repo(repo_name)
532 532
533 533 def test_api_get_locks_with_one_locked_repo_for_specific_user(self):
534 534 repo_name = u'api_delete_me'
535 535 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
536 536 cur_user=self.TEST_USER_LOGIN)
537 537 Repository.lock(repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
538 538 try:
539 539 id_, params = _build_data(self.apikey, 'get_locks',
540 540 userid=self.TEST_USER_LOGIN)
541 541 response = api_call(self, params)
542 542 expected = [repo.get_api_data()]
543 543 self._compare_ok(id_, expected, given=response.body)
544 544 finally:
545 545 fixture.destroy_repo(repo_name)
546 546
547 547 def test_api_get_locks_with_userid(self):
548 548 id_, params = _build_data(self.apikey, 'get_locks',
549 549 userid=TEST_USER_REGULAR_LOGIN)
550 550 response = api_call(self, params)
551 551 expected = []
552 552 self._compare_ok(id_, expected, given=response.body)
553 553
554 554 def test_api_create_existing_user(self):
555 555 id_, params = _build_data(self.apikey, 'create_user',
556 556 username=TEST_USER_ADMIN_LOGIN,
557 557 email='test@example.com',
558 558 password='trololo')
559 559 response = api_call(self, params)
560 560
561 561 expected = "user `%s` already exist" % TEST_USER_ADMIN_LOGIN
562 562 self._compare_error(id_, expected, given=response.body)
563 563
564 564 def test_api_create_user_with_existing_email(self):
565 565 id_, params = _build_data(self.apikey, 'create_user',
566 566 username=TEST_USER_ADMIN_LOGIN + 'new',
567 567 email=TEST_USER_REGULAR_EMAIL,
568 568 password='trololo')
569 569 response = api_call(self, params)
570 570
571 571 expected = "email `%s` already exist" % TEST_USER_REGULAR_EMAIL
572 572 self._compare_error(id_, expected, given=response.body)
573 573
574 574 def test_api_create_user(self):
575 575 username = 'test_new_api_user'
576 576 email = username + "@example.com"
577 577
578 578 id_, params = _build_data(self.apikey, 'create_user',
579 579 username=username,
580 580 email=email,
581 581 password='trololo')
582 582 response = api_call(self, params)
583 583
584 584 usr = User.get_by_username(username)
585 585 ret = dict(
586 586 msg='created new user `%s`' % username,
587 587 user=jsonify(usr.get_api_data())
588 588 )
589 589
590 590 try:
591 591 expected = ret
592 592 self._compare_ok(id_, expected, given=response.body)
593 593 finally:
594 594 fixture.destroy_user(usr.user_id)
595 595
596 596 def test_api_create_user_without_password(self):
597 597 username = 'test_new_api_user_passwordless'
598 598 email = username + "@example.com"
599 599
600 600 id_, params = _build_data(self.apikey, 'create_user',
601 601 username=username,
602 602 email=email)
603 603 response = api_call(self, params)
604 604
605 605 usr = User.get_by_username(username)
606 606 ret = dict(
607 607 msg='created new user `%s`' % username,
608 608 user=jsonify(usr.get_api_data())
609 609 )
610 610 try:
611 611 expected = ret
612 612 self._compare_ok(id_, expected, given=response.body)
613 613 finally:
614 614 fixture.destroy_user(usr.user_id)
615 615
616 616 def test_api_create_user_with_extern_name(self):
617 617 username = 'test_new_api_user_passwordless'
618 618 email = username + "@example.com"
619 619
620 620 id_, params = _build_data(self.apikey, 'create_user',
621 621 username=username,
622 622 email=email, extern_name='internal')
623 623 response = api_call(self, params)
624 624
625 625 usr = User.get_by_username(username)
626 626 ret = dict(
627 627 msg='created new user `%s`' % username,
628 628 user=jsonify(usr.get_api_data())
629 629 )
630 630 try:
631 631 expected = ret
632 632 self._compare_ok(id_, expected, given=response.body)
633 633 finally:
634 634 fixture.destroy_user(usr.user_id)
635 635
636 636 @mock.patch.object(UserModel, 'create_or_update', crash)
637 637 def test_api_create_user_when_exception_happened(self):
638 638
639 639 username = 'test_new_api_user'
640 640 email = username + "@example.com"
641 641
642 642 id_, params = _build_data(self.apikey, 'create_user',
643 643 username=username,
644 644 email=email,
645 645 password='trololo')
646 646 response = api_call(self, params)
647 647 expected = 'failed to create user `%s`' % username
648 648 self._compare_error(id_, expected, given=response.body)
649 649
650 650 def test_api_delete_user(self):
651 651 usr = UserModel().create_or_update(username=u'test_user',
652 652 password=u'qweqwe',
653 653 email=u'u232@example.com',
654 654 firstname=u'u1', lastname=u'u1')
655 655 Session().commit()
656 656 username = usr.username
657 657 email = usr.email
658 658 usr_id = usr.user_id
659 659 ## DELETE THIS USER NOW
660 660
661 661 id_, params = _build_data(self.apikey, 'delete_user',
662 662 userid=username, )
663 663 response = api_call(self, params)
664 664
665 665 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
666 666 'user': None}
667 667 expected = ret
668 668 self._compare_ok(id_, expected, given=response.body)
669 669
670 670 @mock.patch.object(UserModel, 'delete', crash)
671 671 def test_api_delete_user_when_exception_happened(self):
672 672 usr = UserModel().create_or_update(username=u'test_user',
673 673 password=u'qweqwe',
674 674 email=u'u232@example.com',
675 675 firstname=u'u1', lastname=u'u1')
676 676 Session().commit()
677 677 username = usr.username
678 678
679 679 id_, params = _build_data(self.apikey, 'delete_user',
680 680 userid=username, )
681 681 response = api_call(self, params)
682 682 ret = 'failed to delete user ID:%s %s' % (usr.user_id,
683 683 usr.username)
684 684 expected = ret
685 685 self._compare_error(id_, expected, given=response.body)
686 686
687 687 @parametrize('name,expected', [
688 688 ('firstname', 'new_username'),
689 689 ('lastname', 'new_username'),
690 690 ('email', 'new_username'),
691 691 ('admin', True),
692 692 ('admin', False),
693 693 ('extern_type', 'ldap'),
694 694 ('extern_type', None),
695 695 ('extern_name', 'test'),
696 696 ('extern_name', None),
697 697 ('active', False),
698 698 ('active', True),
699 699 ('password', 'newpass'),
700 700 ])
701 701 def test_api_update_user(self, name, expected):
702 702 usr = User.get_by_username(self.TEST_USER_LOGIN)
703 703 kw = {name: expected,
704 704 'userid': usr.user_id}
705 705 id_, params = _build_data(self.apikey, 'update_user', **kw)
706 706 response = api_call(self, params)
707 707
708 708 ret = {
709 709 'msg': 'updated user ID:%s %s' % (
710 710 usr.user_id, self.TEST_USER_LOGIN),
711 711 'user': jsonify(User \
712 712 .get_by_username(self.TEST_USER_LOGIN) \
713 713 .get_api_data())
714 714 }
715 715
716 716 expected = ret
717 717 self._compare_ok(id_, expected, given=response.body)
718 718
719 719 def test_api_update_user_no_changed_params(self):
720 720 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
721 721 ret = jsonify(usr.get_api_data())
722 722 id_, params = _build_data(self.apikey, 'update_user',
723 723 userid=TEST_USER_ADMIN_LOGIN)
724 724
725 725 response = api_call(self, params)
726 726 ret = {
727 727 'msg': 'updated user ID:%s %s' % (
728 728 usr.user_id, TEST_USER_ADMIN_LOGIN),
729 729 'user': ret
730 730 }
731 731 expected = ret
732 732 self._compare_ok(id_, expected, given=response.body)
733 733
734 734 def test_api_update_user_by_user_id(self):
735 735 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
736 736 ret = jsonify(usr.get_api_data())
737 737 id_, params = _build_data(self.apikey, 'update_user',
738 738 userid=usr.user_id)
739 739
740 740 response = api_call(self, params)
741 741 ret = {
742 742 'msg': 'updated user ID:%s %s' % (
743 743 usr.user_id, TEST_USER_ADMIN_LOGIN),
744 744 'user': ret
745 745 }
746 746 expected = ret
747 747 self._compare_ok(id_, expected, given=response.body)
748 748
749 749 def test_api_update_user_default_user(self):
750 750 usr = User.get_default_user()
751 751 id_, params = _build_data(self.apikey, 'update_user',
752 752 userid=usr.user_id)
753 753
754 754 response = api_call(self, params)
755 755 expected = 'editing default user is forbidden'
756 756 self._compare_error(id_, expected, given=response.body)
757 757
758 758 @mock.patch.object(UserModel, 'update_user', crash)
759 759 def test_api_update_user_when_exception_happens(self):
760 760 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
761 761 ret = jsonify(usr.get_api_data())
762 762 id_, params = _build_data(self.apikey, 'update_user',
763 763 userid=usr.user_id)
764 764
765 765 response = api_call(self, params)
766 766 ret = 'failed to update user `%s`' % usr.user_id
767 767
768 768 expected = ret
769 769 self._compare_error(id_, expected, given=response.body)
770 770
771 771 def test_api_get_repo(self):
772 772 new_group = u'some_new_group'
773 773 make_user_group(new_group)
774 774 RepoModel().grant_user_group_permission(repo=self.REPO,
775 775 group_name=new_group,
776 776 perm='repository.read')
777 777 Session().commit()
778 778 id_, params = _build_data(self.apikey, 'get_repo',
779 779 repoid=self.REPO)
780 780 response = api_call(self, params)
781 781 assert u"tags" not in response.json[u'result']
782 782 assert u'pull_requests' not in response.json[u'result']
783 783
784 784 repo = RepoModel().get_by_repo_name(self.REPO)
785 785 ret = repo.get_api_data()
786 786
787 787 members = []
788 788 followers = []
789 789 for user in repo.repo_to_perm:
790 790 perm = user.permission.permission_name
791 791 user = user.user
792 792 user_data = {'name': user.username, 'type': "user",
793 793 'permission': perm}
794 794 members.append(user_data)
795 795
796 796 for user_group in repo.users_group_to_perm:
797 797 perm = user_group.permission.permission_name
798 798 user_group = user_group.users_group
799 799 user_group_data = {'name': user_group.users_group_name,
800 800 'type': "user_group", 'permission': perm}
801 801 members.append(user_group_data)
802 802
803 803 for user in repo.followers:
804 804 followers.append(user.user.get_api_data())
805 805
806 806 ret['members'] = members
807 807 ret['followers'] = followers
808 808
809 809 expected = ret
810 810 self._compare_ok(id_, expected, given=response.body)
811 811 fixture.destroy_user_group(new_group)
812 812
813 813 id_, params = _build_data(self.apikey, 'get_repo', repoid=self.REPO,
814 814 with_revision_names=True,
815 815 with_pullrequests=True)
816 816 response = api_call(self, params)
817 817 assert u"v0.2.0" in response.json[u'result'][u'tags']
818 818 assert u'pull_requests' in response.json[u'result']
819 819
820 820 @parametrize('grant_perm', [
821 821 ('repository.admin'),
822 822 ('repository.write'),
823 823 ('repository.read'),
824 824 ])
825 825 def test_api_get_repo_by_non_admin(self, grant_perm):
826 826 RepoModel().grant_user_permission(repo=self.REPO,
827 827 user=self.TEST_USER_LOGIN,
828 828 perm=grant_perm)
829 829 Session().commit()
830 830 id_, params = _build_data(self.apikey_regular, 'get_repo',
831 831 repoid=self.REPO)
832 832 response = api_call(self, params)
833 833
834 834 repo = RepoModel().get_by_repo_name(self.REPO)
835 835 ret = repo.get_api_data()
836 836
837 837 members = []
838 838 followers = []
839 839 assert 2 == len(repo.repo_to_perm)
840 840 for user in repo.repo_to_perm:
841 841 perm = user.permission.permission_name
842 842 user_obj = user.user
843 843 user_data = {'name': user_obj.username, 'type': "user",
844 844 'permission': perm}
845 845 members.append(user_data)
846 846
847 847 for user_group in repo.users_group_to_perm:
848 848 perm = user_group.permission.permission_name
849 849 user_group_obj = user_group.users_group
850 850 user_group_data = {'name': user_group_obj.users_group_name,
851 851 'type': "user_group", 'permission': perm}
852 852 members.append(user_group_data)
853 853
854 854 for user in repo.followers:
855 855 followers.append(user.user.get_api_data())
856 856
857 857 ret['members'] = members
858 858 ret['followers'] = followers
859 859
860 860 expected = ret
861 861 try:
862 862 self._compare_ok(id_, expected, given=response.body)
863 863 finally:
864 864 RepoModel().revoke_user_permission(self.REPO, self.TEST_USER_LOGIN)
865 865
866 866 def test_api_get_repo_by_non_admin_no_permission_to_repo(self):
867 867 RepoModel().grant_user_permission(repo=self.REPO,
868 868 user=self.TEST_USER_LOGIN,
869 869 perm='repository.none')
870 870
871 871 id_, params = _build_data(self.apikey_regular, 'get_repo',
872 872 repoid=self.REPO)
873 873 response = api_call(self, params)
874 874
875 875 expected = 'repository `%s` does not exist' % (self.REPO)
876 876 self._compare_error(id_, expected, given=response.body)
877 877
878 878 def test_api_get_repo_that_doesn_not_exist(self):
879 879 id_, params = _build_data(self.apikey, 'get_repo',
880 880 repoid='no-such-repo')
881 881 response = api_call(self, params)
882 882
883 883 ret = 'repository `%s` does not exist' % 'no-such-repo'
884 884 expected = ret
885 885 self._compare_error(id_, expected, given=response.body)
886 886
887 887 def test_api_get_repos(self):
888 888 id_, params = _build_data(self.apikey, 'get_repos')
889 889 response = api_call(self, params)
890 890
891 891 expected = jsonify([
892 892 repo.get_api_data()
893 893 for repo in Repository.query()
894 894 ])
895 895
896 896 self._compare_ok(id_, expected, given=response.body)
897 897
898 898 def test_api_get_repos_non_admin(self):
899 899 id_, params = _build_data(self.apikey_regular, 'get_repos')
900 900 response = api_call(self, params)
901 901
902 902 expected = jsonify([
903 903 repo.get_api_data()
904 904 for repo in RepoModel().get_all_user_repos(self.TEST_USER_LOGIN)
905 905 ])
906 906
907 907 self._compare_ok(id_, expected, given=response.body)
908 908
909 909 @parametrize('name,ret_type', [
910 910 ('all', 'all'),
911 911 ('dirs', 'dirs'),
912 912 ('files', 'files'),
913 913 ])
914 914 def test_api_get_repo_nodes(self, name, ret_type):
915 915 rev = 'tip'
916 916 path = '/'
917 917 id_, params = _build_data(self.apikey, 'get_repo_nodes',
918 918 repoid=self.REPO, revision=rev,
919 919 root_path=path,
920 920 ret_type=ret_type)
921 921 response = api_call(self, params)
922 922
923 923 # we don't the actual return types here since it's tested somewhere
924 924 # else
925 925 expected = response.json['result']
926 926 self._compare_ok(id_, expected, given=response.body)
927 927
928 928 def test_api_get_repo_nodes_bad_revisions(self):
929 929 rev = 'i-dont-exist'
930 930 path = '/'
931 931 id_, params = _build_data(self.apikey, 'get_repo_nodes',
932 932 repoid=self.REPO, revision=rev,
933 933 root_path=path, )
934 934 response = api_call(self, params)
935 935
936 936 expected = 'failed to get repo: `%s` nodes' % self.REPO
937 937 self._compare_error(id_, expected, given=response.body)
938 938
939 939 def test_api_get_repo_nodes_bad_path(self):
940 940 rev = 'tip'
941 941 path = '/idontexits'
942 942 id_, params = _build_data(self.apikey, 'get_repo_nodes',
943 943 repoid=self.REPO, revision=rev,
944 944 root_path=path, )
945 945 response = api_call(self, params)
946 946
947 947 expected = 'failed to get repo: `%s` nodes' % self.REPO
948 948 self._compare_error(id_, expected, given=response.body)
949 949
950 950 def test_api_get_repo_nodes_bad_ret_type(self):
951 951 rev = 'tip'
952 952 path = '/'
953 953 ret_type = 'error'
954 954 id_, params = _build_data(self.apikey, 'get_repo_nodes',
955 955 repoid=self.REPO, revision=rev,
956 956 root_path=path,
957 957 ret_type=ret_type)
958 958 response = api_call(self, params)
959 959
960 960 expected = ('ret_type must be one of %s'
961 961 % (','.join(['files', 'dirs', 'all'])))
962 962 self._compare_error(id_, expected, given=response.body)
963 963
964 964 @parametrize('name,ret_type,grant_perm', [
965 965 ('all', 'all', 'repository.write'),
966 966 ('dirs', 'dirs', 'repository.admin'),
967 967 ('files', 'files', 'repository.read'),
968 968 ])
969 969 def test_api_get_repo_nodes_by_regular_user(self, name, ret_type, grant_perm):
970 970 RepoModel().grant_user_permission(repo=self.REPO,
971 971 user=self.TEST_USER_LOGIN,
972 972 perm=grant_perm)
973 973 Session().commit()
974 974
975 975 rev = 'tip'
976 976 path = '/'
977 977 id_, params = _build_data(self.apikey_regular, 'get_repo_nodes',
978 978 repoid=self.REPO, revision=rev,
979 979 root_path=path,
980 980 ret_type=ret_type)
981 981 response = api_call(self, params)
982 982
983 983 # we don't the actual return types here since it's tested somewhere
984 984 # else
985 985 expected = response.json['result']
986 986 try:
987 987 self._compare_ok(id_, expected, given=response.body)
988 988 finally:
989 989 RepoModel().revoke_user_permission(self.REPO, self.TEST_USER_LOGIN)
990 990
991 991 def test_api_create_repo(self):
992 992 repo_name = u'api-repo'
993 993 id_, params = _build_data(self.apikey, 'create_repo',
994 994 repo_name=repo_name,
995 995 owner=TEST_USER_ADMIN_LOGIN,
996 996 repo_type=self.REPO_TYPE,
997 997 )
998 998 response = api_call(self, params)
999 999
1000 1000 repo = RepoModel().get_by_repo_name(repo_name)
1001 1001 assert repo != None
1002 1002 ret = {
1003 1003 'msg': 'Created new repository `%s`' % repo_name,
1004 1004 'success': True,
1005 1005 'task': None,
1006 1006 }
1007 1007 expected = ret
1008 1008 self._compare_ok(id_, expected, given=response.body)
1009 1009 fixture.destroy_repo(repo_name)
1010 1010
1011 1011 def test_api_create_repo_and_repo_group(self):
1012 1012 repo_name = u'my_gr/api-repo'
1013 1013 id_, params = _build_data(self.apikey, 'create_repo',
1014 1014 repo_name=repo_name,
1015 1015 owner=TEST_USER_ADMIN_LOGIN,
1016 1016 repo_type=self.REPO_TYPE,)
1017 1017 response = api_call(self, params)
1018 1018 print params
1019 1019 repo = RepoModel().get_by_repo_name(repo_name)
1020 1020 assert repo != None
1021 1021 ret = {
1022 1022 'msg': 'Created new repository `%s`' % repo_name,
1023 1023 'success': True,
1024 1024 'task': None,
1025 1025 }
1026 1026 expected = ret
1027 1027 self._compare_ok(id_, expected, given=response.body)
1028 1028 fixture.destroy_repo(repo_name)
1029 1029 fixture.destroy_repo_group(u'my_gr')
1030 1030
1031 1031 def test_api_create_repo_in_repo_group_without_permission(self):
1032 1032 repo_group_name = u'%s/api-repo-repo' % TEST_REPO_GROUP
1033 1033 repo_name = u'%s/api-repo' % repo_group_name
1034 1034
1035 1035 rg = fixture.create_repo_group(repo_group_name)
1036 1036 Session().commit()
1037 1037 RepoGroupModel().grant_user_permission(repo_group_name,
1038 1038 self.TEST_USER_LOGIN,
1039 1039 'group.none')
1040 1040 Session().commit()
1041 1041
1042 1042 id_, params = _build_data(self.apikey_regular, 'create_repo',
1043 1043 repo_name=repo_name,
1044 1044 repo_type=self.REPO_TYPE,
1045 1045 )
1046 1046 response = api_call(self, params)
1047 1047
1048 1048 # Current result when API access control is different from Web:
1049 1049 ret = {
1050 1050 'msg': 'Created new repository `%s`' % repo_name,
1051 1051 'success': True,
1052 1052 'task': None,
1053 1053 }
1054 1054 expected = ret
1055 1055 self._compare_ok(id_, expected, given=response.body)
1056 1056 fixture.destroy_repo(repo_name)
1057 1057
1058 1058 # Expected and arguably more correct result:
1059 1059 #expected = 'failed to create repository `%s`' % repo_name
1060 1060 #self._compare_error(id_, expected, given=response.body)
1061 1061
1062 1062 fixture.destroy_repo_group(repo_group_name)
1063 1063
1064 1064 def test_api_create_repo_unknown_owner(self):
1065 1065 repo_name = u'api-repo'
1066 1066 owner = 'i-dont-exist'
1067 1067 id_, params = _build_data(self.apikey, 'create_repo',
1068 1068 repo_name=repo_name,
1069 1069 owner=owner,
1070 1070 repo_type=self.REPO_TYPE,
1071 1071 )
1072 1072 response = api_call(self, params)
1073 1073 expected = 'user `%s` does not exist' % owner
1074 1074 self._compare_error(id_, expected, given=response.body)
1075 1075
1076 1076 def test_api_create_repo_dont_specify_owner(self):
1077 1077 repo_name = u'api-repo'
1078 1078 owner = 'i-dont-exist'
1079 1079 id_, params = _build_data(self.apikey, 'create_repo',
1080 1080 repo_name=repo_name,
1081 1081 repo_type=self.REPO_TYPE,
1082 1082 )
1083 1083 response = api_call(self, params)
1084 1084
1085 1085 repo = RepoModel().get_by_repo_name(repo_name)
1086 1086 assert repo != None
1087 1087 ret = {
1088 1088 'msg': 'Created new repository `%s`' % repo_name,
1089 1089 'success': True,
1090 1090 'task': None,
1091 1091 }
1092 1092 expected = ret
1093 1093 self._compare_ok(id_, expected, given=response.body)
1094 1094 fixture.destroy_repo(repo_name)
1095 1095
1096 1096 def test_api_create_repo_by_non_admin(self):
1097 1097 repo_name = u'api-repo'
1098 1098 owner = 'i-dont-exist'
1099 1099 id_, params = _build_data(self.apikey_regular, 'create_repo',
1100 1100 repo_name=repo_name,
1101 1101 repo_type=self.REPO_TYPE,
1102 1102 )
1103 1103 response = api_call(self, params)
1104 1104
1105 1105 repo = RepoModel().get_by_repo_name(repo_name)
1106 1106 assert repo != None
1107 1107 ret = {
1108 1108 'msg': 'Created new repository `%s`' % repo_name,
1109 1109 'success': True,
1110 1110 'task': None,
1111 1111 }
1112 1112 expected = ret
1113 1113 self._compare_ok(id_, expected, given=response.body)
1114 1114 fixture.destroy_repo(repo_name)
1115 1115
1116 1116 def test_api_create_repo_by_non_admin_specify_owner(self):
1117 1117 repo_name = u'api-repo'
1118 1118 owner = 'i-dont-exist'
1119 1119 id_, params = _build_data(self.apikey_regular, 'create_repo',
1120 1120 repo_name=repo_name,
1121 1121 repo_type=self.REPO_TYPE,
1122 1122 owner=owner)
1123 1123 response = api_call(self, params)
1124 1124
1125 1125 expected = 'Only Kallithea admin can specify `owner` param'
1126 1126 self._compare_error(id_, expected, given=response.body)
1127 1127 fixture.destroy_repo(repo_name)
1128 1128
1129 1129 def test_api_create_repo_exists(self):
1130 1130 repo_name = self.REPO
1131 1131 id_, params = _build_data(self.apikey, 'create_repo',
1132 1132 repo_name=repo_name,
1133 1133 owner=TEST_USER_ADMIN_LOGIN,
1134 1134 repo_type=self.REPO_TYPE,)
1135 1135 response = api_call(self, params)
1136 1136 expected = "repo `%s` already exist" % repo_name
1137 1137 self._compare_error(id_, expected, given=response.body)
1138 1138
1139 1139 @mock.patch.object(RepoModel, 'create', crash)
1140 1140 def test_api_create_repo_exception_occurred(self):
1141 1141 repo_name = u'api-repo'
1142 1142 id_, params = _build_data(self.apikey, 'create_repo',
1143 1143 repo_name=repo_name,
1144 1144 owner=TEST_USER_ADMIN_LOGIN,
1145 1145 repo_type=self.REPO_TYPE,)
1146 1146 response = api_call(self, params)
1147 1147 expected = 'failed to create repository `%s`' % repo_name
1148 1148 self._compare_error(id_, expected, given=response.body)
1149 1149
1150 1150 @parametrize('changing_attr,updates', [
1151 1151 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
1152 1152 ('description', {'description': u'new description'}),
1153 1153 ('clone_uri', {'clone_uri': 'http://example.com/repo'}),
1154 1154 ('clone_uri', {'clone_uri': None}),
1155 1155 ('landing_rev', {'landing_rev': 'branch:master'}),
1156 1156 ('enable_statistics', {'enable_statistics': True}),
1157 1157 ('enable_locking', {'enable_locking': True}),
1158 1158 ('enable_downloads', {'enable_downloads': True}),
1159 1159 ('name', {'name': u'new_repo_name'}),
1160 1160 ('repo_group', {'group': u'test_group_for_update'}),
1161 1161 ])
1162 1162 def test_api_update_repo(self, changing_attr, updates):
1163 1163 repo_name = u'api_update_me'
1164 1164 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1165 1165 if changing_attr == 'repo_group':
1166 1166 fixture.create_repo_group(updates['group'])
1167 1167
1168 1168 id_, params = _build_data(self.apikey, 'update_repo',
1169 1169 repoid=repo_name, **updates)
1170 1170 response = api_call(self, params)
1171 1171 if changing_attr == 'name':
1172 1172 repo_name = updates['name']
1173 1173 if changing_attr == 'repo_group':
1174 1174 repo_name = u'/'.join([updates['group'], repo_name])
1175 1175 try:
1176 1176 expected = {
1177 1177 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
1178 1178 'repository': repo.get_api_data()
1179 1179 }
1180 1180 self._compare_ok(id_, expected, given=response.body)
1181 1181 finally:
1182 1182 fixture.destroy_repo(repo_name)
1183 1183 if changing_attr == 'repo_group':
1184 1184 fixture.destroy_repo_group(updates['group'])
1185 1185
1186 1186 @parametrize('changing_attr,updates', [
1187 1187 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
1188 1188 ('description', {'description': u'new description'}),
1189 1189 ('clone_uri', {'clone_uri': 'http://example.com/repo'}),
1190 1190 ('clone_uri', {'clone_uri': None}),
1191 1191 ('landing_rev', {'landing_rev': 'branch:master'}),
1192 1192 ('enable_statistics', {'enable_statistics': True}),
1193 1193 ('enable_locking', {'enable_locking': True}),
1194 1194 ('enable_downloads', {'enable_downloads': True}),
1195 1195 ('name', {'name': u'new_repo_name'}),
1196 1196 ('repo_group', {'group': u'test_group_for_update'}),
1197 1197 ])
1198 1198 def test_api_update_group_repo(self, changing_attr, updates):
1199 1199 group_name = u'lololo'
1200 1200 fixture.create_repo_group(group_name)
1201 1201 repo_name = u'%s/api_update_me' % group_name
1202 1202 repo = fixture.create_repo(repo_name, repo_group=group_name, repo_type=self.REPO_TYPE)
1203 1203 if changing_attr == 'repo_group':
1204 1204 fixture.create_repo_group(updates['group'])
1205 1205
1206 1206 id_, params = _build_data(self.apikey, 'update_repo',
1207 1207 repoid=repo_name, **updates)
1208 1208 response = api_call(self, params)
1209 1209 if changing_attr == 'name':
1210 1210 repo_name = u'%s/%s' % (group_name, updates['name'])
1211 1211 if changing_attr == 'repo_group':
1212 1212 repo_name = u'/'.join([updates['group'], repo_name.rsplit('/', 1)[-1]])
1213 1213 try:
1214 1214 expected = {
1215 1215 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
1216 1216 'repository': repo.get_api_data()
1217 1217 }
1218 1218 self._compare_ok(id_, expected, given=response.body)
1219 1219 finally:
1220 1220 fixture.destroy_repo(repo_name)
1221 1221 if changing_attr == 'repo_group':
1222 1222 fixture.destroy_repo_group(updates['group'])
1223 1223 fixture.destroy_repo_group(group_name)
1224 1224
1225 1225 def test_api_update_repo_repo_group_does_not_exist(self):
1226 1226 repo_name = u'admin_owned'
1227 1227 fixture.create_repo(repo_name)
1228 1228 updates = {'group': 'test_group_for_update'}
1229 1229 id_, params = _build_data(self.apikey, 'update_repo',
1230 1230 repoid=repo_name, **updates)
1231 1231 response = api_call(self, params)
1232 1232 try:
1233 1233 expected = 'repository group `%s` does not exist' % updates['group']
1234 1234 self._compare_error(id_, expected, given=response.body)
1235 1235 finally:
1236 1236 fixture.destroy_repo(repo_name)
1237 1237
1238 1238 def test_api_update_repo_regular_user_not_allowed(self):
1239 1239 repo_name = u'admin_owned'
1240 1240 fixture.create_repo(repo_name)
1241 1241 updates = {'description': 'something else'}
1242 1242 id_, params = _build_data(self.apikey_regular, 'update_repo',
1243 1243 repoid=repo_name, **updates)
1244 1244 response = api_call(self, params)
1245 1245 try:
1246 1246 expected = 'repository `%s` does not exist' % repo_name
1247 1247 self._compare_error(id_, expected, given=response.body)
1248 1248 finally:
1249 1249 fixture.destroy_repo(repo_name)
1250 1250
1251 1251 @mock.patch.object(RepoModel, 'update', crash)
1252 1252 def test_api_update_repo_exception_occurred(self):
1253 1253 repo_name = u'api_update_me'
1254 1254 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1255 1255 id_, params = _build_data(self.apikey, 'update_repo',
1256 1256 repoid=repo_name, owner=TEST_USER_ADMIN_LOGIN,)
1257 1257 response = api_call(self, params)
1258 1258 try:
1259 1259 expected = 'failed to update repo `%s`' % repo_name
1260 1260 self._compare_error(id_, expected, given=response.body)
1261 1261 finally:
1262 1262 fixture.destroy_repo(repo_name)
1263 1263
1264 1264 def test_api_update_repo_regular_user_change_repo_name(self):
1265 1265 repo_name = u'admin_owned'
1266 1266 new_repo_name = u'new_repo_name'
1267 1267 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1268 1268 RepoModel().grant_user_permission(repo=repo_name,
1269 1269 user=self.TEST_USER_LOGIN,
1270 1270 perm='repository.admin')
1271 1271 UserModel().revoke_perm('default', 'hg.create.repository')
1272 1272 UserModel().grant_perm('default', 'hg.create.none')
1273 1273 updates = {'name': new_repo_name}
1274 1274 id_, params = _build_data(self.apikey_regular, 'update_repo',
1275 1275 repoid=repo_name, **updates)
1276 1276 response = api_call(self, params)
1277 1277 try:
1278 1278 expected = 'no permission to create (or move) repositories'
1279 1279 self._compare_error(id_, expected, given=response.body)
1280 1280 finally:
1281 1281 fixture.destroy_repo(repo_name)
1282 1282 fixture.destroy_repo(new_repo_name)
1283 1283
1284 1284 def test_api_update_repo_regular_user_change_repo_name_allowed(self):
1285 1285 repo_name = u'admin_owned'
1286 1286 new_repo_name = u'new_repo_name'
1287 1287 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1288 1288 RepoModel().grant_user_permission(repo=repo_name,
1289 1289 user=self.TEST_USER_LOGIN,
1290 1290 perm='repository.admin')
1291 1291 UserModel().revoke_perm('default', 'hg.create.none')
1292 1292 UserModel().grant_perm('default', 'hg.create.repository')
1293 1293 updates = {'name': new_repo_name}
1294 1294 id_, params = _build_data(self.apikey_regular, 'update_repo',
1295 1295 repoid=repo_name, **updates)
1296 1296 response = api_call(self, params)
1297 1297 try:
1298 1298 expected = {
1299 1299 'msg': 'updated repo ID:%s %s' % (repo.repo_id, new_repo_name),
1300 1300 'repository': repo.get_api_data()
1301 1301 }
1302 1302 self._compare_ok(id_, expected, given=response.body)
1303 1303 finally:
1304 1304 fixture.destroy_repo(repo_name)
1305 1305 fixture.destroy_repo(new_repo_name)
1306 1306
1307 1307 def test_api_update_repo_regular_user_change_owner(self):
1308 1308 repo_name = u'admin_owned'
1309 1309 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1310 1310 RepoModel().grant_user_permission(repo=repo_name,
1311 1311 user=self.TEST_USER_LOGIN,
1312 1312 perm='repository.admin')
1313 1313 updates = {'owner': TEST_USER_ADMIN_LOGIN}
1314 1314 id_, params = _build_data(self.apikey_regular, 'update_repo',
1315 1315 repoid=repo_name, **updates)
1316 1316 response = api_call(self, params)
1317 1317 try:
1318 1318 expected = 'Only Kallithea admin can specify `owner` param'
1319 1319 self._compare_error(id_, expected, given=response.body)
1320 1320 finally:
1321 1321 fixture.destroy_repo(repo_name)
1322 1322
1323 1323 def test_api_delete_repo(self):
1324 1324 repo_name = u'api_delete_me'
1325 1325 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1326 1326
1327 1327 id_, params = _build_data(self.apikey, 'delete_repo',
1328 1328 repoid=repo_name, )
1329 1329 response = api_call(self, params)
1330 1330
1331 1331 ret = {
1332 1332 'msg': 'Deleted repository `%s`' % repo_name,
1333 1333 'success': True
1334 1334 }
1335 1335 try:
1336 1336 expected = ret
1337 1337 self._compare_ok(id_, expected, given=response.body)
1338 1338 finally:
1339 1339 fixture.destroy_repo(repo_name)
1340 1340
1341 1341 def test_api_delete_repo_by_non_admin(self):
1342 1342 repo_name = u'api_delete_me'
1343 1343 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
1344 1344 cur_user=self.TEST_USER_LOGIN)
1345 1345 id_, params = _build_data(self.apikey_regular, 'delete_repo',
1346 1346 repoid=repo_name, )
1347 1347 response = api_call(self, params)
1348 1348
1349 1349 ret = {
1350 1350 'msg': 'Deleted repository `%s`' % repo_name,
1351 1351 'success': True
1352 1352 }
1353 1353 try:
1354 1354 expected = ret
1355 1355 self._compare_ok(id_, expected, given=response.body)
1356 1356 finally:
1357 1357 fixture.destroy_repo(repo_name)
1358 1358
1359 1359 def test_api_delete_repo_by_non_admin_no_permission(self):
1360 1360 repo_name = u'api_delete_me'
1361 1361 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1362 1362 try:
1363 1363 id_, params = _build_data(self.apikey_regular, 'delete_repo',
1364 1364 repoid=repo_name, )
1365 1365 response = api_call(self, params)
1366 1366 expected = 'repository `%s` does not exist' % (repo_name)
1367 1367 self._compare_error(id_, expected, given=response.body)
1368 1368 finally:
1369 1369 fixture.destroy_repo(repo_name)
1370 1370
1371 1371 def test_api_delete_repo_exception_occurred(self):
1372 1372 repo_name = u'api_delete_me'
1373 1373 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1374 1374 try:
1375 1375 with mock.patch.object(RepoModel, 'delete', crash):
1376 1376 id_, params = _build_data(self.apikey, 'delete_repo',
1377 1377 repoid=repo_name, )
1378 1378 response = api_call(self, params)
1379 1379
1380 1380 expected = 'failed to delete repository `%s`' % repo_name
1381 1381 self._compare_error(id_, expected, given=response.body)
1382 1382 finally:
1383 1383 fixture.destroy_repo(repo_name)
1384 1384
1385 1385 def test_api_fork_repo(self):
1386 1386 fork_name = u'api-repo-fork'
1387 1387 id_, params = _build_data(self.apikey, 'fork_repo',
1388 1388 repoid=self.REPO,
1389 1389 fork_name=fork_name,
1390 1390 owner=TEST_USER_ADMIN_LOGIN,
1391 1391 )
1392 1392 response = api_call(self, params)
1393 1393
1394 1394 ret = {
1395 1395 'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
1396 1396 fork_name),
1397 1397 'success': True,
1398 1398 'task': None,
1399 1399 }
1400 1400 expected = ret
1401 1401 self._compare_ok(id_, expected, given=response.body)
1402 1402 fixture.destroy_repo(fork_name)
1403 1403
1404 1404 @parametrize('fork_name', [
1405 1405 u'api-repo-fork',
1406 1406 u'%s/api-repo-fork' % TEST_REPO_GROUP,
1407 1407 ])
1408 1408 def test_api_fork_repo_non_admin(self, fork_name):
1409 1409 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1410 1410 repoid=self.REPO,
1411 1411 fork_name=fork_name,
1412 1412 )
1413 1413 response = api_call(self, params)
1414 1414
1415 1415 ret = {
1416 1416 'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
1417 1417 fork_name),
1418 1418 'success': True,
1419 1419 'task': None,
1420 1420 }
1421 1421 expected = ret
1422 1422 self._compare_ok(id_, expected, given=response.body)
1423 1423 fixture.destroy_repo(fork_name)
1424 1424
1425 1425 def test_api_fork_repo_non_admin_specify_owner(self):
1426 1426 fork_name = u'api-repo-fork'
1427 1427 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1428 1428 repoid=self.REPO,
1429 1429 fork_name=fork_name,
1430 1430 owner=TEST_USER_ADMIN_LOGIN,
1431 1431 )
1432 1432 response = api_call(self, params)
1433 1433 expected = 'Only Kallithea admin can specify `owner` param'
1434 1434 self._compare_error(id_, expected, given=response.body)
1435 1435 fixture.destroy_repo(fork_name)
1436 1436
1437 1437 def test_api_fork_repo_non_admin_no_permission_to_fork(self):
1438 1438 RepoModel().grant_user_permission(repo=self.REPO,
1439 1439 user=self.TEST_USER_LOGIN,
1440 1440 perm='repository.none')
1441 1441 fork_name = u'api-repo-fork'
1442 1442 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1443 1443 repoid=self.REPO,
1444 1444 fork_name=fork_name,
1445 1445 )
1446 1446 response = api_call(self, params)
1447 1447 expected = 'repository `%s` does not exist' % (self.REPO)
1448 1448 self._compare_error(id_, expected, given=response.body)
1449 1449 fixture.destroy_repo(fork_name)
1450 1450
1451 1451 @parametrize('name,perm', [
1452 1452 ('read', 'repository.read'),
1453 1453 ('write', 'repository.write'),
1454 1454 ('admin', 'repository.admin'),
1455 1455 ])
1456 1456 def test_api_fork_repo_non_admin_no_create_repo_permission(self, name, perm):
1457 1457 fork_name = u'api-repo-fork'
1458 1458 # regardless of base repository permission, forking is disallowed
1459 1459 # when repository creation is disabled
1460 1460 RepoModel().grant_user_permission(repo=self.REPO,
1461 1461 user=self.TEST_USER_LOGIN,
1462 1462 perm=perm)
1463 1463 UserModel().revoke_perm('default', 'hg.create.repository')
1464 1464 UserModel().grant_perm('default', 'hg.create.none')
1465 1465 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1466 1466 repoid=self.REPO,
1467 1467 fork_name=fork_name,
1468 1468 )
1469 1469 response = api_call(self, params)
1470 1470 expected = 'no permission to create repositories'
1471 1471 self._compare_error(id_, expected, given=response.body)
1472 1472 fixture.destroy_repo(fork_name)
1473 1473
1474 1474 def test_api_fork_repo_unknown_owner(self):
1475 1475 fork_name = u'api-repo-fork'
1476 1476 owner = 'i-dont-exist'
1477 1477 id_, params = _build_data(self.apikey, 'fork_repo',
1478 1478 repoid=self.REPO,
1479 1479 fork_name=fork_name,
1480 1480 owner=owner,
1481 1481 )
1482 1482 response = api_call(self, params)
1483 1483 expected = 'user `%s` does not exist' % owner
1484 1484 self._compare_error(id_, expected, given=response.body)
1485 1485
1486 1486 def test_api_fork_repo_fork_exists(self):
1487 1487 fork_name = u'api-repo-fork'
1488 1488 fixture.create_fork(self.REPO, fork_name)
1489 1489
1490 1490 try:
1491 1491 fork_name = u'api-repo-fork'
1492 1492
1493 1493 id_, params = _build_data(self.apikey, 'fork_repo',
1494 1494 repoid=self.REPO,
1495 1495 fork_name=fork_name,
1496 1496 owner=TEST_USER_ADMIN_LOGIN,
1497 1497 )
1498 1498 response = api_call(self, params)
1499 1499
1500 1500 expected = "fork `%s` already exist" % fork_name
1501 1501 self._compare_error(id_, expected, given=response.body)
1502 1502 finally:
1503 1503 fixture.destroy_repo(fork_name)
1504 1504
1505 1505 def test_api_fork_repo_repo_exists(self):
1506 1506 fork_name = self.REPO
1507 1507
1508 1508 id_, params = _build_data(self.apikey, 'fork_repo',
1509 1509 repoid=self.REPO,
1510 1510 fork_name=fork_name,
1511 1511 owner=TEST_USER_ADMIN_LOGIN,
1512 1512 )
1513 1513 response = api_call(self, params)
1514 1514
1515 1515 expected = "repo `%s` already exist" % fork_name
1516 1516 self._compare_error(id_, expected, given=response.body)
1517 1517
1518 1518 @mock.patch.object(RepoModel, 'create_fork', crash)
1519 1519 def test_api_fork_repo_exception_occurred(self):
1520 1520 fork_name = u'api-repo-fork'
1521 1521 id_, params = _build_data(self.apikey, 'fork_repo',
1522 1522 repoid=self.REPO,
1523 1523 fork_name=fork_name,
1524 1524 owner=TEST_USER_ADMIN_LOGIN,
1525 1525 )
1526 1526 response = api_call(self, params)
1527 1527
1528 1528 expected = 'failed to fork repository `%s` as `%s`' % (self.REPO,
1529 1529 fork_name)
1530 1530 self._compare_error(id_, expected, given=response.body)
1531 1531
1532 1532 def test_api_get_user_group(self):
1533 1533 id_, params = _build_data(self.apikey, 'get_user_group',
1534 1534 usergroupid=TEST_USER_GROUP)
1535 1535 response = api_call(self, params)
1536 1536
1537 1537 user_group = UserGroupModel().get_group(TEST_USER_GROUP)
1538 1538 members = []
1539 1539 for user in user_group.members:
1540 1540 user = user.user
1541 1541 members.append(user.get_api_data())
1542 1542
1543 1543 ret = user_group.get_api_data()
1544 1544 ret['members'] = members
1545 1545 expected = ret
1546 1546 self._compare_ok(id_, expected, given=response.body)
1547 1547
1548 1548 def test_api_get_user_groups(self):
1549 1549 gr_name = u'test_user_group2'
1550 1550 make_user_group(gr_name)
1551 1551
1552 1552 try:
1553 1553 id_, params = _build_data(self.apikey, 'get_user_groups', )
1554 1554 response = api_call(self, params)
1555 1555
1556 1556 expected = []
1557 1557 for gr_name in [TEST_USER_GROUP, u'test_user_group2']:
1558 1558 user_group = UserGroupModel().get_group(gr_name)
1559 1559 ret = user_group.get_api_data()
1560 1560 expected.append(ret)
1561 1561 self._compare_ok(id_, expected, given=response.body)
1562 1562 finally:
1563 1563 fixture.destroy_user_group(gr_name)
1564 1564
1565 1565 def test_api_create_user_group(self):
1566 1566 group_name = u'some_new_group'
1567 1567 id_, params = _build_data(self.apikey, 'create_user_group',
1568 1568 group_name=group_name)
1569 1569 response = api_call(self, params)
1570 1570
1571 1571 ret = {
1572 1572 'msg': 'created new user group `%s`' % group_name,
1573 1573 'user_group': jsonify(UserGroupModel() \
1574 1574 .get_by_name(group_name) \
1575 1575 .get_api_data())
1576 1576 }
1577 1577 expected = ret
1578 1578 self._compare_ok(id_, expected, given=response.body)
1579 1579
1580 1580 fixture.destroy_user_group(group_name)
1581 1581
1582 1582 def test_api_get_user_group_that_exist(self):
1583 1583 id_, params = _build_data(self.apikey, 'create_user_group',
1584 1584 group_name=TEST_USER_GROUP)
1585 1585 response = api_call(self, params)
1586 1586
1587 1587 expected = "user group `%s` already exist" % TEST_USER_GROUP
1588 1588 self._compare_error(id_, expected, given=response.body)
1589 1589
1590 1590 @mock.patch.object(UserGroupModel, 'create', crash)
1591 1591 def test_api_get_user_group_exception_occurred(self):
1592 1592 group_name = u'exception_happens'
1593 1593 id_, params = _build_data(self.apikey, 'create_user_group',
1594 1594 group_name=group_name)
1595 1595 response = api_call(self, params)
1596 1596
1597 1597 expected = 'failed to create group `%s`' % group_name
1598 1598 self._compare_error(id_, expected, given=response.body)
1599 1599
1600 1600 @parametrize('changing_attr,updates', [
1601 1601 ('group_name', {'group_name': u'new_group_name'}),
1602 1602 ('group_name', {'group_name': u'test_group_for_update'}),
1603 1603 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
1604 1604 ('active', {'active': False}),
1605 1605 ('active', {'active': True}),
1606 1606 ])
1607 1607 def test_api_update_user_group(self, changing_attr, updates):
1608 1608 gr_name = u'test_group_for_update'
1609 1609 user_group = fixture.create_user_group(gr_name)
1610 1610 try:
1611 1611 id_, params = _build_data(self.apikey, 'update_user_group',
1612 1612 usergroupid=gr_name, **updates)
1613 1613 response = api_call(self, params)
1614 1614 expected = {
1615 1615 'msg': 'updated user group ID:%s %s' % (user_group.users_group_id,
1616 1616 user_group.users_group_name),
1617 1617 'user_group': user_group.get_api_data()
1618 1618 }
1619 1619 self._compare_ok(id_, expected, given=response.body)
1620 1620 finally:
1621 1621 if changing_attr == 'group_name':
1622 1622 # switch to updated name for proper cleanup
1623 1623 gr_name = updates['group_name']
1624 1624 fixture.destroy_user_group(gr_name)
1625 1625
1626 1626 @mock.patch.object(UserGroupModel, 'update', crash)
1627 1627 def test_api_update_user_group_exception_occurred(self):
1628 1628 gr_name = u'test_group'
1629 1629 fixture.create_user_group(gr_name)
1630 1630 try:
1631 1631 id_, params = _build_data(self.apikey, 'update_user_group',
1632 1632 usergroupid=gr_name)
1633 1633 response = api_call(self, params)
1634 1634 expected = 'failed to update user group `%s`' % gr_name
1635 1635 self._compare_error(id_, expected, given=response.body)
1636 1636 finally:
1637 1637 fixture.destroy_user_group(gr_name)
1638 1638
1639 1639 def test_api_add_user_to_user_group(self):
1640 1640 gr_name = u'test_group'
1641 1641 fixture.create_user_group(gr_name)
1642 1642 try:
1643 1643 id_, params = _build_data(self.apikey, 'add_user_to_user_group',
1644 1644 usergroupid=gr_name,
1645 1645 userid=TEST_USER_ADMIN_LOGIN)
1646 1646 response = api_call(self, params)
1647 1647 expected = {
1648 1648 'msg': 'added member `%s` to user group `%s`' % (
1649 1649 TEST_USER_ADMIN_LOGIN, gr_name),
1650 1650 'success': True
1651 1651 }
1652 1652 self._compare_ok(id_, expected, given=response.body)
1653 1653 finally:
1654 1654 fixture.destroy_user_group(gr_name)
1655 1655
1656 1656 def test_api_add_user_to_user_group_that_doesnt_exist(self):
1657 1657 id_, params = _build_data(self.apikey, 'add_user_to_user_group',
1658 1658 usergroupid='false-group',
1659 1659 userid=TEST_USER_ADMIN_LOGIN)
1660 1660 response = api_call(self, params)
1661 1661
1662 1662 expected = 'user group `%s` does not exist' % 'false-group'
1663 1663 self._compare_error(id_, expected, given=response.body)
1664 1664
1665 1665 @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
1666 1666 def test_api_add_user_to_user_group_exception_occurred(self):
1667 1667 gr_name = u'test_group'
1668 1668 fixture.create_user_group(gr_name)
1669 1669 try:
1670 1670 id_, params = _build_data(self.apikey, 'add_user_to_user_group',
1671 1671 usergroupid=gr_name,
1672 1672 userid=TEST_USER_ADMIN_LOGIN)
1673 1673 response = api_call(self, params)
1674 1674 expected = 'failed to add member to user group `%s`' % gr_name
1675 1675 self._compare_error(id_, expected, given=response.body)
1676 1676 finally:
1677 1677 fixture.destroy_user_group(gr_name)
1678 1678
1679 1679 def test_api_remove_user_from_user_group(self):
1680 1680 gr_name = u'test_group_3'
1681 1681 gr = fixture.create_user_group(gr_name)
1682 1682 UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
1683 1683 Session().commit()
1684 1684 try:
1685 1685 id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
1686 1686 usergroupid=gr_name,
1687 1687 userid=TEST_USER_ADMIN_LOGIN)
1688 1688 response = api_call(self, params)
1689 1689 expected = {
1690 1690 'msg': 'removed member `%s` from user group `%s`' % (
1691 1691 TEST_USER_ADMIN_LOGIN, gr_name
1692 1692 ),
1693 1693 'success': True}
1694 1694 self._compare_ok(id_, expected, given=response.body)
1695 1695 finally:
1696 1696 fixture.destroy_user_group(gr_name)
1697 1697
1698 1698 @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
1699 1699 def test_api_remove_user_from_user_group_exception_occurred(self):
1700 1700 gr_name = u'test_group_3'
1701 1701 gr = fixture.create_user_group(gr_name)
1702 1702 UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
1703 1703 Session().commit()
1704 1704 try:
1705 1705 id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
1706 1706 usergroupid=gr_name,
1707 1707 userid=TEST_USER_ADMIN_LOGIN)
1708 1708 response = api_call(self, params)
1709 1709 expected = 'failed to remove member from user group `%s`' % gr_name
1710 1710 self._compare_error(id_, expected, given=response.body)
1711 1711 finally:
1712 1712 fixture.destroy_user_group(gr_name)
1713 1713
1714 1714 def test_api_delete_user_group(self):
1715 1715 gr_name = u'test_group'
1716 1716 ugroup = fixture.create_user_group(gr_name)
1717 1717 gr_id = ugroup.users_group_id
1718 1718 try:
1719 1719 id_, params = _build_data(self.apikey, 'delete_user_group',
1720 1720 usergroupid=gr_name)
1721 1721 response = api_call(self, params)
1722 1722 expected = {
1723 1723 'user_group': None,
1724 1724 'msg': 'deleted user group ID:%s %s' % (gr_id, gr_name)
1725 1725 }
1726 1726 self._compare_ok(id_, expected, given=response.body)
1727 1727 finally:
1728 1728 if UserGroupModel().get_by_name(gr_name):
1729 1729 fixture.destroy_user_group(gr_name)
1730 1730
1731 1731 def test_api_delete_user_group_that_is_assigned(self):
1732 1732 gr_name = u'test_group'
1733 1733 ugroup = fixture.create_user_group(gr_name)
1734 1734 gr_id = ugroup.users_group_id
1735 1735
1736 1736 ugr_to_perm = RepoModel().grant_user_group_permission(self.REPO, gr_name, 'repository.write')
1737 1737 msg = 'User Group assigned to %s' % ugr_to_perm.repository.repo_name
1738 1738
1739 1739 try:
1740 1740 id_, params = _build_data(self.apikey, 'delete_user_group',
1741 1741 usergroupid=gr_name)
1742 1742 response = api_call(self, params)
1743 1743 expected = msg
1744 1744 self._compare_error(id_, expected, given=response.body)
1745 1745 finally:
1746 1746 if UserGroupModel().get_by_name(gr_name):
1747 1747 fixture.destroy_user_group(gr_name)
1748 1748
1749 1749 def test_api_delete_user_group_exception_occurred(self):
1750 1750 gr_name = u'test_group'
1751 1751 ugroup = fixture.create_user_group(gr_name)
1752 1752 gr_id = ugroup.users_group_id
1753 1753 id_, params = _build_data(self.apikey, 'delete_user_group',
1754 1754 usergroupid=gr_name)
1755 1755
1756 1756 try:
1757 1757 with mock.patch.object(UserGroupModel, 'delete', crash):
1758 1758 response = api_call(self, params)
1759 1759 expected = 'failed to delete user group ID:%s %s' % (gr_id, gr_name)
1760 1760 self._compare_error(id_, expected, given=response.body)
1761 1761 finally:
1762 1762 fixture.destroy_user_group(gr_name)
1763 1763
1764 1764 @parametrize('name,perm', [
1765 1765 ('none', 'repository.none'),
1766 1766 ('read', 'repository.read'),
1767 1767 ('write', 'repository.write'),
1768 1768 ('admin', 'repository.admin'),
1769 1769 ])
1770 1770 def test_api_grant_user_permission(self, name, perm):
1771 1771 id_, params = _build_data(self.apikey,
1772 1772 'grant_user_permission',
1773 1773 repoid=self.REPO,
1774 1774 userid=TEST_USER_ADMIN_LOGIN,
1775 1775 perm=perm)
1776 1776 response = api_call(self, params)
1777 1777
1778 1778 ret = {
1779 1779 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1780 1780 perm, TEST_USER_ADMIN_LOGIN, self.REPO
1781 1781 ),
1782 1782 'success': True
1783 1783 }
1784 1784 expected = ret
1785 1785 self._compare_ok(id_, expected, given=response.body)
1786 1786
1787 1787 def test_api_grant_user_permission_wrong_permission(self):
1788 1788 perm = 'haha.no.permission'
1789 1789 id_, params = _build_data(self.apikey,
1790 1790 'grant_user_permission',
1791 1791 repoid=self.REPO,
1792 1792 userid=TEST_USER_ADMIN_LOGIN,
1793 1793 perm=perm)
1794 1794 response = api_call(self, params)
1795 1795
1796 1796 expected = 'permission `%s` does not exist' % perm
1797 1797 self._compare_error(id_, expected, given=response.body)
1798 1798
1799 1799 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
1800 1800 def test_api_grant_user_permission_exception_when_adding(self):
1801 1801 perm = 'repository.read'
1802 1802 id_, params = _build_data(self.apikey,
1803 1803 'grant_user_permission',
1804 1804 repoid=self.REPO,
1805 1805 userid=TEST_USER_ADMIN_LOGIN,
1806 1806 perm=perm)
1807 1807 response = api_call(self, params)
1808 1808
1809 1809 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
1810 1810 TEST_USER_ADMIN_LOGIN, self.REPO
1811 1811 )
1812 1812 self._compare_error(id_, expected, given=response.body)
1813 1813
1814 1814 def test_api_revoke_user_permission(self):
1815 1815 id_, params = _build_data(self.apikey,
1816 1816 'revoke_user_permission',
1817 1817 repoid=self.REPO,
1818 1818 userid=TEST_USER_ADMIN_LOGIN, )
1819 1819 response = api_call(self, params)
1820 1820
1821 1821 expected = {
1822 1822 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1823 1823 TEST_USER_ADMIN_LOGIN, self.REPO
1824 1824 ),
1825 1825 'success': True
1826 1826 }
1827 1827 self._compare_ok(id_, expected, given=response.body)
1828 1828
1829 1829 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
1830 1830 def test_api_revoke_user_permission_exception_when_adding(self):
1831 1831 id_, params = _build_data(self.apikey,
1832 1832 'revoke_user_permission',
1833 1833 repoid=self.REPO,
1834 1834 userid=TEST_USER_ADMIN_LOGIN, )
1835 1835 response = api_call(self, params)
1836 1836
1837 1837 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
1838 1838 TEST_USER_ADMIN_LOGIN, self.REPO
1839 1839 )
1840 1840 self._compare_error(id_, expected, given=response.body)
1841 1841
1842 1842 @parametrize('name,perm', [
1843 1843 ('none', 'repository.none'),
1844 1844 ('read', 'repository.read'),
1845 1845 ('write', 'repository.write'),
1846 1846 ('admin', 'repository.admin'),
1847 1847 ])
1848 1848 def test_api_grant_user_group_permission(self, name, perm):
1849 1849 id_, params = _build_data(self.apikey,
1850 1850 'grant_user_group_permission',
1851 1851 repoid=self.REPO,
1852 1852 usergroupid=TEST_USER_GROUP,
1853 1853 perm=perm)
1854 1854 response = api_call(self, params)
1855 1855
1856 1856 ret = {
1857 1857 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
1858 1858 perm, TEST_USER_GROUP, self.REPO
1859 1859 ),
1860 1860 'success': True
1861 1861 }
1862 1862 expected = ret
1863 1863 self._compare_ok(id_, expected, given=response.body)
1864 1864
1865 1865 def test_api_grant_user_group_permission_wrong_permission(self):
1866 1866 perm = 'haha.no.permission'
1867 1867 id_, params = _build_data(self.apikey,
1868 1868 'grant_user_group_permission',
1869 1869 repoid=self.REPO,
1870 1870 usergroupid=TEST_USER_GROUP,
1871 1871 perm=perm)
1872 1872 response = api_call(self, params)
1873 1873
1874 1874 expected = 'permission `%s` does not exist' % perm
1875 1875 self._compare_error(id_, expected, given=response.body)
1876 1876
1877 1877 @mock.patch.object(RepoModel, 'grant_user_group_permission', crash)
1878 1878 def test_api_grant_user_group_permission_exception_when_adding(self):
1879 1879 perm = 'repository.read'
1880 1880 id_, params = _build_data(self.apikey,
1881 1881 'grant_user_group_permission',
1882 1882 repoid=self.REPO,
1883 1883 usergroupid=TEST_USER_GROUP,
1884 1884 perm=perm)
1885 1885 response = api_call(self, params)
1886 1886
1887 1887 expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
1888 1888 TEST_USER_GROUP, self.REPO
1889 1889 )
1890 1890 self._compare_error(id_, expected, given=response.body)
1891 1891
1892 1892 def test_api_revoke_user_group_permission(self):
1893 1893 RepoModel().grant_user_group_permission(repo=self.REPO,
1894 1894 group_name=TEST_USER_GROUP,
1895 1895 perm='repository.read')
1896 1896 Session().commit()
1897 1897 id_, params = _build_data(self.apikey,
1898 1898 'revoke_user_group_permission',
1899 1899 repoid=self.REPO,
1900 1900 usergroupid=TEST_USER_GROUP, )
1901 1901 response = api_call(self, params)
1902 1902
1903 1903 expected = {
1904 1904 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1905 1905 TEST_USER_GROUP, self.REPO
1906 1906 ),
1907 1907 'success': True
1908 1908 }
1909 1909 self._compare_ok(id_, expected, given=response.body)
1910 1910
1911 1911 @mock.patch.object(RepoModel, 'revoke_user_group_permission', crash)
1912 1912 def test_api_revoke_user_group_permission_exception_when_adding(self):
1913 1913 id_, params = _build_data(self.apikey,
1914 1914 'revoke_user_group_permission',
1915 1915 repoid=self.REPO,
1916 1916 usergroupid=TEST_USER_GROUP, )
1917 1917 response = api_call(self, params)
1918 1918
1919 1919 expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
1920 1920 TEST_USER_GROUP, self.REPO
1921 1921 )
1922 1922 self._compare_error(id_, expected, given=response.body)
1923 1923
1924 1924 @parametrize('name,perm,apply_to_children', [
1925 1925 ('none', 'group.none', 'none'),
1926 1926 ('read', 'group.read', 'none'),
1927 1927 ('write', 'group.write', 'none'),
1928 1928 ('admin', 'group.admin', 'none'),
1929 1929
1930 1930 ('none', 'group.none', 'all'),
1931 1931 ('read', 'group.read', 'all'),
1932 1932 ('write', 'group.write', 'all'),
1933 1933 ('admin', 'group.admin', 'all'),
1934 1934
1935 1935 ('none', 'group.none', 'repos'),
1936 1936 ('read', 'group.read', 'repos'),
1937 1937 ('write', 'group.write', 'repos'),
1938 1938 ('admin', 'group.admin', 'repos'),
1939 1939
1940 1940 ('none', 'group.none', 'groups'),
1941 1941 ('read', 'group.read', 'groups'),
1942 1942 ('write', 'group.write', 'groups'),
1943 1943 ('admin', 'group.admin', 'groups'),
1944 1944 ])
1945 1945 def test_api_grant_user_permission_to_repo_group(self, name, perm, apply_to_children):
1946 1946 id_, params = _build_data(self.apikey,
1947 1947 'grant_user_permission_to_repo_group',
1948 1948 repogroupid=TEST_REPO_GROUP,
1949 1949 userid=TEST_USER_ADMIN_LOGIN,
1950 1950 perm=perm, apply_to_children=apply_to_children)
1951 1951 response = api_call(self, params)
1952 1952
1953 1953 ret = {
1954 1954 'msg': 'Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
1955 1955 perm, apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
1956 1956 ),
1957 1957 'success': True
1958 1958 }
1959 1959 expected = ret
1960 1960 self._compare_ok(id_, expected, given=response.body)
1961 1961
1962 1962 @parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
1963 1963 ('none_fails', 'group.none', 'none', False, False),
1964 1964 ('read_fails', 'group.read', 'none', False, False),
1965 1965 ('write_fails', 'group.write', 'none', False, False),
1966 1966 ('admin_fails', 'group.admin', 'none', False, False),
1967 1967
1968 1968 # with granted perms
1969 1969 ('none_ok', 'group.none', 'none', True, True),
1970 1970 ('read_ok', 'group.read', 'none', True, True),
1971 1971 ('write_ok', 'group.write', 'none', True, True),
1972 1972 ('admin_ok', 'group.admin', 'none', True, True),
1973 1973 ])
1974 1974 def test_api_grant_user_permission_to_repo_group_by_regular_user(
1975 1975 self, name, perm, apply_to_children, grant_admin, access_ok):
1976 1976 if grant_admin:
1977 1977 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
1978 1978 self.TEST_USER_LOGIN,
1979 1979 'group.admin')
1980 1980 Session().commit()
1981 1981
1982 1982 id_, params = _build_data(self.apikey_regular,
1983 1983 'grant_user_permission_to_repo_group',
1984 1984 repogroupid=TEST_REPO_GROUP,
1985 1985 userid=TEST_USER_ADMIN_LOGIN,
1986 1986 perm=perm, apply_to_children=apply_to_children)
1987 1987 response = api_call(self, params)
1988 1988 if access_ok:
1989 1989 ret = {
1990 1990 'msg': 'Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
1991 1991 perm, apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
1992 1992 ),
1993 1993 'success': True
1994 1994 }
1995 1995 expected = ret
1996 1996 self._compare_ok(id_, expected, given=response.body)
1997 1997 else:
1998 1998 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
1999 1999 self._compare_error(id_, expected, given=response.body)
2000 2000
2001 2001 def test_api_grant_user_permission_to_repo_group_wrong_permission(self):
2002 2002 perm = 'haha.no.permission'
2003 2003 id_, params = _build_data(self.apikey,
2004 2004 'grant_user_permission_to_repo_group',
2005 2005 repogroupid=TEST_REPO_GROUP,
2006 2006 userid=TEST_USER_ADMIN_LOGIN,
2007 2007 perm=perm)
2008 2008 response = api_call(self, params)
2009 2009
2010 2010 expected = 'permission `%s` does not exist' % perm
2011 2011 self._compare_error(id_, expected, given=response.body)
2012 2012
2013 2013 @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash)
2014 2014 def test_api_grant_user_permission_to_repo_group_exception_when_adding(self):
2015 2015 perm = 'group.read'
2016 2016 id_, params = _build_data(self.apikey,
2017 2017 'grant_user_permission_to_repo_group',
2018 2018 repogroupid=TEST_REPO_GROUP,
2019 2019 userid=TEST_USER_ADMIN_LOGIN,
2020 2020 perm=perm)
2021 2021 response = api_call(self, params)
2022 2022
2023 2023 expected = 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2024 2024 TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2025 2025 )
2026 2026 self._compare_error(id_, expected, given=response.body)
2027 2027
2028 2028 @parametrize('name,apply_to_children', [
2029 2029 ('none', 'none'),
2030 2030 ('all', 'all'),
2031 2031 ('repos', 'repos'),
2032 2032 ('groups', 'groups'),
2033 2033 ])
2034 2034 def test_api_revoke_user_permission_from_repo_group(self, name, apply_to_children):
2035 2035 RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
2036 2036 user=TEST_USER_ADMIN_LOGIN,
2037 2037 perm='group.read',)
2038 2038 Session().commit()
2039 2039
2040 2040 id_, params = _build_data(self.apikey,
2041 2041 'revoke_user_permission_from_repo_group',
2042 2042 repogroupid=TEST_REPO_GROUP,
2043 2043 userid=TEST_USER_ADMIN_LOGIN,
2044 2044 apply_to_children=apply_to_children,)
2045 2045 response = api_call(self, params)
2046 2046
2047 2047 expected = {
2048 2048 'msg': 'Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2049 2049 apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2050 2050 ),
2051 2051 'success': True
2052 2052 }
2053 2053 self._compare_ok(id_, expected, given=response.body)
2054 2054
2055 2055 @parametrize('name,apply_to_children,grant_admin,access_ok', [
2056 2056 ('none', 'none', False, False),
2057 2057 ('all', 'all', False, False),
2058 2058 ('repos', 'repos', False, False),
2059 2059 ('groups', 'groups', False, False),
2060 2060
2061 2061 # after granting admin rights
2062 2062 ('none', 'none', False, False),
2063 2063 ('all', 'all', False, False),
2064 2064 ('repos', 'repos', False, False),
2065 2065 ('groups', 'groups', False, False),
2066 2066 ])
2067 2067 def test_api_revoke_user_permission_from_repo_group_by_regular_user(
2068 2068 self, name, apply_to_children, grant_admin, access_ok):
2069 2069 RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
2070 2070 user=TEST_USER_ADMIN_LOGIN,
2071 2071 perm='group.read',)
2072 2072 Session().commit()
2073 2073
2074 2074 if grant_admin:
2075 2075 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
2076 2076 self.TEST_USER_LOGIN,
2077 2077 'group.admin')
2078 2078 Session().commit()
2079 2079
2080 2080 id_, params = _build_data(self.apikey_regular,
2081 2081 'revoke_user_permission_from_repo_group',
2082 2082 repogroupid=TEST_REPO_GROUP,
2083 2083 userid=TEST_USER_ADMIN_LOGIN,
2084 2084 apply_to_children=apply_to_children,)
2085 2085 response = api_call(self, params)
2086 2086 if access_ok:
2087 2087 expected = {
2088 2088 'msg': 'Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2089 2089 apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2090 2090 ),
2091 2091 'success': True
2092 2092 }
2093 2093 self._compare_ok(id_, expected, given=response.body)
2094 2094 else:
2095 2095 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
2096 2096 self._compare_error(id_, expected, given=response.body)
2097 2097
2098 2098 @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash)
2099 2099 def test_api_revoke_user_permission_from_repo_group_exception_when_adding(self):
2100 2100 id_, params = _build_data(self.apikey,
2101 2101 'revoke_user_permission_from_repo_group',
2102 2102 repogroupid=TEST_REPO_GROUP,
2103 2103 userid=TEST_USER_ADMIN_LOGIN, )
2104 2104 response = api_call(self, params)
2105 2105
2106 2106 expected = 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2107 2107 TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2108 2108 )
2109 2109 self._compare_error(id_, expected, given=response.body)
2110 2110
2111 2111 @parametrize('name,perm,apply_to_children', [
2112 2112 ('none', 'group.none', 'none'),
2113 2113 ('read', 'group.read', 'none'),
2114 2114 ('write', 'group.write', 'none'),
2115 2115 ('admin', 'group.admin', 'none'),
2116 2116
2117 2117 ('none', 'group.none', 'all'),
2118 2118 ('read', 'group.read', 'all'),
2119 2119 ('write', 'group.write', 'all'),
2120 2120 ('admin', 'group.admin', 'all'),
2121 2121
2122 2122 ('none', 'group.none', 'repos'),
2123 2123 ('read', 'group.read', 'repos'),
2124 2124 ('write', 'group.write', 'repos'),
2125 2125 ('admin', 'group.admin', 'repos'),
2126 2126
2127 2127 ('none', 'group.none', 'groups'),
2128 2128 ('read', 'group.read', 'groups'),
2129 2129 ('write', 'group.write', 'groups'),
2130 2130 ('admin', 'group.admin', 'groups'),
2131 2131 ])
2132 2132 def test_api_grant_user_group_permission_to_repo_group(self, name, perm, apply_to_children):
2133 2133 id_, params = _build_data(self.apikey,
2134 2134 'grant_user_group_permission_to_repo_group',
2135 2135 repogroupid=TEST_REPO_GROUP,
2136 2136 usergroupid=TEST_USER_GROUP,
2137 2137 perm=perm,
2138 2138 apply_to_children=apply_to_children,)
2139 2139 response = api_call(self, params)
2140 2140
2141 2141 ret = {
2142 2142 'msg': 'Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2143 2143 perm, apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
2144 2144 ),
2145 2145 'success': True
2146 2146 }
2147 2147 expected = ret
2148 2148 self._compare_ok(id_, expected, given=response.body)
2149 2149
2150 2150 @parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
2151 2151 ('none_fails', 'group.none', 'none', False, False),
2152 2152 ('read_fails', 'group.read', 'none', False, False),
2153 2153 ('write_fails', 'group.write', 'none', False, False),
2154 2154 ('admin_fails', 'group.admin', 'none', False, False),
2155 2155
2156 2156 # with granted perms
2157 2157 ('none_ok', 'group.none', 'none', True, True),
2158 2158 ('read_ok', 'group.read', 'none', True, True),
2159 2159 ('write_ok', 'group.write', 'none', True, True),
2160 2160 ('admin_ok', 'group.admin', 'none', True, True),
2161 2161 ])
2162 2162 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
2163 2163 self, name, perm, apply_to_children, grant_admin, access_ok):
2164 2164 if grant_admin:
2165 2165 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
2166 2166 self.TEST_USER_LOGIN,
2167 2167 'group.admin')
2168 2168 Session().commit()
2169 2169
2170 2170 id_, params = _build_data(self.apikey_regular,
2171 2171 'grant_user_group_permission_to_repo_group',
2172 2172 repogroupid=TEST_REPO_GROUP,
2173 2173 usergroupid=TEST_USER_GROUP,
2174 2174 perm=perm,
2175 2175 apply_to_children=apply_to_children,)
2176 2176 response = api_call(self, params)
2177 2177 if access_ok:
2178 2178 ret = {
2179 2179 'msg': 'Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2180 2180 perm, apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
2181 2181 ),
2182 2182 'success': True
2183 2183 }
2184 2184 expected = ret
2185 2185 self._compare_ok(id_, expected, given=response.body)
2186 2186 else:
2187 2187 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
2188 2188 self._compare_error(id_, expected, given=response.body)
2189 2189
2190 2190 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(self):
2191 2191 perm = 'haha.no.permission'
2192 2192 id_, params = _build_data(self.apikey,
2193 2193 'grant_user_group_permission_to_repo_group',
2194 2194 repogroupid=TEST_REPO_GROUP,
2195 2195 usergroupid=TEST_USER_GROUP,
2196 2196 perm=perm)
2197 2197 response = api_call(self, params)
2198 2198
2199 2199 expected = 'permission `%s` does not exist' % perm
2200 2200 self._compare_error(id_, expected, given=response.body)
2201 2201
2202 2202 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
2203 2203 def test_api_grant_user_group_permission_exception_when_adding_to_repo_group(self):
2204 2204 perm = 'group.read'
2205 2205 id_, params = _build_data(self.apikey,
2206 2206 'grant_user_group_permission_to_repo_group',
2207 2207 repogroupid=TEST_REPO_GROUP,
2208 2208 usergroupid=TEST_USER_GROUP,
2209 2209 perm=perm)
2210 2210 response = api_call(self, params)
2211 2211
2212 2212 expected = 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2213 2213 TEST_USER_GROUP, TEST_REPO_GROUP
2214 2214 )
2215 2215 self._compare_error(id_, expected, given=response.body)
2216 2216
2217 2217 @parametrize('name,apply_to_children', [
2218 2218 ('none', 'none'),
2219 2219 ('all', 'all'),
2220 2220 ('repos', 'repos'),
2221 2221 ('groups', 'groups'),
2222 2222 ])
2223 2223 def test_api_revoke_user_group_permission_from_repo_group(self, name, apply_to_children):
2224 2224 RepoGroupModel().grant_user_group_permission(repo_group=TEST_REPO_GROUP,
2225 2225 group_name=TEST_USER_GROUP,
2226 2226 perm='group.read',)
2227 2227 Session().commit()
2228 2228 id_, params = _build_data(self.apikey,
2229 2229 'revoke_user_group_permission_from_repo_group',
2230 2230 repogroupid=TEST_REPO_GROUP,
2231 2231 usergroupid=TEST_USER_GROUP,
2232 2232 apply_to_children=apply_to_children,)
2233 2233 response = api_call(self, params)
2234 2234
2235 2235 expected = {
2236 2236 'msg': 'Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2237 2237 apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
2238 2238 ),
2239 2239 'success': True
2240 2240 }
2241 2241 self._compare_ok(id_, expected, given=response.body)
2242 2242
2243 2243 @parametrize('name,apply_to_children,grant_admin,access_ok', [
2244 2244 ('none', 'none', False, False),
2245 2245 ('all', 'all', False, False),
2246 2246 ('repos', 'repos', False, False),
2247 2247 ('groups', 'groups', False, False),
2248 2248
2249 2249 # after granting admin rights
2250 2250 ('none', 'none', False, False),
2251 2251 ('all', 'all', False, False),
2252 2252 ('repos', 'repos', False, False),
2253 2253 ('groups', 'groups', False, False),
2254 2254 ])
2255 2255 def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
2256 2256 self, name, apply_to_children, grant_admin, access_ok):
2257 2257 RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
2258 2258 user=TEST_USER_ADMIN_LOGIN,
2259 2259 perm='group.read',)
2260 2260 Session().commit()
2261 2261
2262 2262 if grant_admin:
2263 2263 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
2264 2264 self.TEST_USER_LOGIN,
2265 2265 'group.admin')
2266 2266 Session().commit()
2267 2267
2268 2268 id_, params = _build_data(self.apikey_regular,
2269 2269 'revoke_user_group_permission_from_repo_group',
2270 2270 repogroupid=TEST_REPO_GROUP,
2271 2271 usergroupid=TEST_USER_GROUP,
2272 2272 apply_to_children=apply_to_children,)
2273 2273 response = api_call(self, params)
2274 2274 if access_ok:
2275 2275 expected = {
2276 2276 'msg': 'Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2277 2277 apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2278 2278 ),
2279 2279 'success': True
2280 2280 }
2281 2281 self._compare_ok(id_, expected, given=response.body)
2282 2282 else:
2283 2283 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
2284 2284 self._compare_error(id_, expected, given=response.body)
2285 2285
2286 2286 @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash)
2287 2287 def test_api_revoke_user_group_permission_from_repo_group_exception_when_adding(self):
2288 2288 id_, params = _build_data(self.apikey, 'revoke_user_group_permission_from_repo_group',
2289 2289 repogroupid=TEST_REPO_GROUP,
2290 2290 usergroupid=TEST_USER_GROUP,)
2291 2291 response = api_call(self, params)
2292 2292
2293 2293 expected = 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2294 2294 TEST_USER_GROUP, TEST_REPO_GROUP
2295 2295 )
2296 2296 self._compare_error(id_, expected, given=response.body)
2297 2297
2298 2298 def test_api_get_gist(self):
2299 2299 gist = fixture.create_gist()
2300 2300 gist_id = gist.gist_access_id
2301 2301 gist_created_on = gist.created_on
2302 2302 id_, params = _build_data(self.apikey, 'get_gist',
2303 2303 gistid=gist_id, )
2304 2304 response = api_call(self, params)
2305 2305
2306 2306 expected = {
2307 2307 'access_id': gist_id,
2308 2308 'created_on': gist_created_on,
2309 2309 'description': 'new-gist',
2310 2310 'expires': -1.0,
2311 2311 'gist_id': int(gist_id),
2312 2312 'type': 'public',
2313 2313 'url': 'http://localhost:80/_admin/gists/%s' % gist_id
2314 2314 }
2315 2315
2316 2316 self._compare_ok(id_, expected, given=response.body)
2317 2317
2318 2318 def test_api_get_gist_that_does_not_exist(self):
2319 2319 id_, params = _build_data(self.apikey_regular, 'get_gist',
2320 2320 gistid='12345', )
2321 2321 response = api_call(self, params)
2322 2322 expected = 'gist `%s` does not exist' % ('12345',)
2323 2323 self._compare_error(id_, expected, given=response.body)
2324 2324
2325 2325 def test_api_get_gist_private_gist_without_permission(self):
2326 2326 gist = fixture.create_gist()
2327 2327 gist_id = gist.gist_access_id
2328 2328 gist_created_on = gist.created_on
2329 2329 id_, params = _build_data(self.apikey_regular, 'get_gist',
2330 2330 gistid=gist_id, )
2331 2331 response = api_call(self, params)
2332 2332
2333 2333 expected = 'gist `%s` does not exist' % gist_id
2334 2334 self._compare_error(id_, expected, given=response.body)
2335 2335
2336 2336 def test_api_get_gists(self):
2337 2337 fixture.create_gist()
2338 2338 fixture.create_gist()
2339 2339
2340 2340 id_, params = _build_data(self.apikey, 'get_gists')
2341 2341 response = api_call(self, params)
2342 2342 expected = response.json
2343 2343 assert len(response.json['result']) == 2
2344 2344 #self._compare_ok(id_, expected, given=response.body)
2345 2345
2346 2346 def test_api_get_gists_regular_user(self):
2347 2347 # by admin
2348 2348 fixture.create_gist()
2349 2349 fixture.create_gist()
2350 2350
2351 2351 # by reg user
2352 2352 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2353 2353 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2354 2354 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2355 2355
2356 2356 id_, params = _build_data(self.apikey_regular, 'get_gists')
2357 2357 response = api_call(self, params)
2358 2358 expected = response.json
2359 2359 assert len(response.json['result']) == 3
2360 2360 #self._compare_ok(id_, expected, given=response.body)
2361 2361
2362 2362 def test_api_get_gists_only_for_regular_user(self):
2363 2363 # by admin
2364 2364 fixture.create_gist()
2365 2365 fixture.create_gist()
2366 2366
2367 2367 # by reg user
2368 2368 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2369 2369 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2370 2370 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2371 2371
2372 2372 id_, params = _build_data(self.apikey, 'get_gists',
2373 2373 userid=self.TEST_USER_LOGIN)
2374 2374 response = api_call(self, params)
2375 2375 expected = response.json
2376 2376 assert len(response.json['result']) == 3
2377 2377 #self._compare_ok(id_, expected, given=response.body)
2378 2378
2379 2379 def test_api_get_gists_regular_user_with_different_userid(self):
2380 2380 id_, params = _build_data(self.apikey_regular, 'get_gists',
2381 2381 userid=TEST_USER_ADMIN_LOGIN)
2382 2382 response = api_call(self, params)
2383 2383 expected = 'userid is not the same as your user'
2384 2384 self._compare_error(id_, expected, given=response.body)
2385 2385
2386 2386 def test_api_create_gist(self):
2387 2387 id_, params = _build_data(self.apikey_regular, 'create_gist',
2388 2388 lifetime=10,
2389 2389 description='foobar-gist',
2390 2390 gist_type='public',
2391 2391 files={'foobar': {'content': 'foo'}})
2392 2392 response = api_call(self, params)
2393 2393 response_json = response.json
2394 2394 expected = {
2395 2395 'gist': {
2396 2396 'access_id': response_json['result']['gist']['access_id'],
2397 2397 'created_on': response_json['result']['gist']['created_on'],
2398 2398 'description': 'foobar-gist',
2399 2399 'expires': response_json['result']['gist']['expires'],
2400 2400 'gist_id': response_json['result']['gist']['gist_id'],
2401 2401 'type': 'public',
2402 2402 'url': response_json['result']['gist']['url']
2403 2403 },
2404 2404 'msg': 'created new gist'
2405 2405 }
2406 2406 self._compare_ok(id_, expected, given=response.body)
2407 2407
2408 2408 @mock.patch.object(GistModel, 'create', crash)
2409 2409 def test_api_create_gist_exception_occurred(self):
2410 2410 id_, params = _build_data(self.apikey_regular, 'create_gist',
2411 2411 files={})
2412 2412 response = api_call(self, params)
2413 2413 expected = 'failed to create gist'
2414 2414 self._compare_error(id_, expected, given=response.body)
2415 2415
2416 2416 def test_api_delete_gist(self):
2417 2417 gist_id = fixture.create_gist().gist_access_id
2418 2418 id_, params = _build_data(self.apikey, 'delete_gist',
2419 2419 gistid=gist_id)
2420 2420 response = api_call(self, params)
2421 2421 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % gist_id}
2422 2422 self._compare_ok(id_, expected, given=response.body)
2423 2423
2424 2424 def test_api_delete_gist_regular_user(self):
2425 2425 gist_id = fixture.create_gist(owner=self.TEST_USER_LOGIN).gist_access_id
2426 2426 id_, params = _build_data(self.apikey_regular, 'delete_gist',
2427 2427 gistid=gist_id)
2428 2428 response = api_call(self, params)
2429 2429 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % gist_id}
2430 2430 self._compare_ok(id_, expected, given=response.body)
2431 2431
2432 2432 def test_api_delete_gist_regular_user_no_permission(self):
2433 2433 gist_id = fixture.create_gist().gist_access_id
2434 2434 id_, params = _build_data(self.apikey_regular, 'delete_gist',
2435 2435 gistid=gist_id)
2436 2436 response = api_call(self, params)
2437 2437 expected = 'gist `%s` does not exist' % (gist_id,)
2438 2438 self._compare_error(id_, expected, given=response.body)
2439 2439
2440 2440 @mock.patch.object(GistModel, 'delete', crash)
2441 2441 def test_api_delete_gist_exception_occurred(self):
2442 2442 gist_id = fixture.create_gist().gist_access_id
2443 2443 id_, params = _build_data(self.apikey, 'delete_gist',
2444 2444 gistid=gist_id)
2445 2445 response = api_call(self, params)
2446 2446 expected = 'failed to delete gist ID:%s' % (gist_id,)
2447 2447 self._compare_error(id_, expected, given=response.body)
2448 2448
2449 2449 def test_api_get_ip(self):
2450 2450 id_, params = _build_data(self.apikey, 'get_ip')
2451 2451 response = api_call(self, params)
2452 2452 expected = {
2453 2453 'server_ip_addr': '0.0.0.0',
2454 2454 'user_ips': []
2455 2455 }
2456 2456 self._compare_ok(id_, expected, given=response.body)
2457 2457
2458 2458 def test_api_get_server_info(self):
2459 2459 id_, params = _build_data(self.apikey, 'get_server_info')
2460 2460 response = api_call(self, params)
2461 2461 expected = Setting.get_server_info()
2462 2462 self._compare_ok(id_, expected, given=response.body)
2463 2463
2464 def test_api_get_changesets(self):
2465 id_, params = _build_data(self.apikey, 'get_changesets',
2466 repoid=self.REPO, start=0, end=2)
2467 response = api_call(self, params)
2468 result = json.loads(response.body)["result"]
2469 assert len(result) == 3
2470 assert result[0].has_key('message')
2471 assert not result[0].has_key('added')
2472
2473 def test_api_get_changesets_with_file_list(self):
2474 id_, params = _build_data(self.apikey, 'get_changesets',
2475 repoid=self.REPO, start_date="2010-04-07T23:30:30", end_date="2010-04-08T00:31:14", with_file_list=True)
2476 response = api_call(self, params)
2477 result = json.loads(response.body)["result"]
2478 assert len(result) == 3
2479 assert result[0].has_key('message')
2480 assert result[0].has_key('added')
2481
2464 2482 def test_api_get_changeset(self):
2465 2483 review = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
2466 2484 id_, params = _build_data(self.apikey, 'get_changeset',
2467 2485 repoid=self.REPO, raw_id = self.TEST_REVISION)
2468 2486 response = api_call(self, params)
2469 2487 result = json.loads(response.body)["result"]
2470 2488 assert result["raw_id"] == self.TEST_REVISION
2471 2489 assert not result.has_key("reviews")
2472 2490
2473 2491 def test_api_get_changeset_with_reviews(self):
2474 2492 reviewobjs = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
2475 2493 id_, params = _build_data(self.apikey, 'get_changeset',
2476 2494 repoid=self.REPO, raw_id = self.TEST_REVISION,
2477 2495 with_reviews = True)
2478 2496 response = api_call(self, params)
2479 2497 result = json.loads(response.body)["result"]
2480 2498 assert result["raw_id"] == self.TEST_REVISION
2481 2499 assert result.has_key("reviews")
2482 2500 assert len(result["reviews"]) == 1
2483 2501 review = result["reviews"][0]
2484 2502 expected = {
2485 2503 'status': 'approved',
2486 2504 'modified_at': reviewobjs[0].modified_at.isoformat()[:-3],
2487 2505 'reviewer': 'test_admin',
2488 2506 }
2489 2507 assert review == expected
2490 2508
2491 2509 def test_api_get_changeset_that_does_not_exist(self):
2492 2510 """ Fetch changeset status for non-existant changeset.
2493 2511 revision id is the above git hash used in the test above with the
2494 2512 last 3 nibbles replaced with 0xf. Should not exist for git _or_ hg.
2495 2513 """
2496 2514 id_, params = _build_data(self.apikey, 'get_changeset',
2497 2515 repoid=self.REPO, raw_id = '7ab37bc680b4aa72c34d07b230c866c28e9fcfff')
2498 2516 response = api_call(self, params)
2499 2517 expected = u'Changeset %s does not exist' % ('7ab37bc680b4aa72c34d07b230c866c28e9fcfff',)
2500 2518 self._compare_error(id_, expected, given=response.body)
2501 2519
2502 2520 def test_api_get_changeset_without_permission(self):
2503 2521 review = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
2504 2522 RepoModel().revoke_user_permission(repo=self.REPO, user=self.TEST_USER_LOGIN)
2505 2523 RepoModel().revoke_user_permission(repo=self.REPO, user="default")
2506 2524 id_, params = _build_data(self.apikey_regular, 'get_changeset',
2507 2525 repoid=self.REPO, raw_id = self.TEST_REVISION)
2508 2526 response = api_call(self, params)
2509 2527 expected = u'Access denied to repo %s' % self.REPO
2510 2528 self._compare_error(id_, expected, given=response.body)
2511 2529
2512 2530 def test_api_get_pullrequest(self):
2513 2531 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u'get test')
2514 2532 random_id = random.randrange(1, 9999)
2515 2533 params = json.dumps({
2516 2534 "id": random_id,
2517 2535 "api_key": self.apikey,
2518 2536 "method": 'get_pullrequest',
2519 2537 "args": {"pullrequest_id": pull_request_id},
2520 2538 })
2521 2539 response = api_call(self, params)
2522 2540 pullrequest = PullRequest().get(pull_request_id)
2523 2541 expected = {
2524 2542 "status": "new",
2525 2543 "pull_request_id": pull_request_id,
2526 2544 "description": "No description",
2527 2545 "url": "/%s/pull-request/%s/_/%s" % (self.REPO, pull_request_id, "stable"),
2528 2546 "reviewers": [{"username": "test_regular"}],
2529 2547 "org_repo_url": "http://localhost:80/%s" % self.REPO,
2530 2548 "org_ref_parts": ["branch", "stable", self.TEST_PR_SRC],
2531 2549 "other_ref_parts": ["branch", "default", self.TEST_PR_DST],
2532 2550 "comments": [{"username": TEST_USER_ADMIN_LOGIN, "text": "",
2533 2551 "comment_id": pullrequest.comments[0].comment_id}],
2534 2552 "owner": TEST_USER_ADMIN_LOGIN,
2535 2553 "statuses": [{"status": "under_review", "reviewer": TEST_USER_ADMIN_LOGIN, "modified_at": "2000-01-01T00:00:00.000"} for i in range(0, len(self.TEST_PR_REVISIONS))],
2536 2554 "title": "get test",
2537 2555 "revisions": self.TEST_PR_REVISIONS,
2538 2556 }
2539 2557 self._compare_ok(random_id, expected,
2540 2558 given=re.sub("\d\d\d\d\-\d\d\-\d\dT\d\d\:\d\d\:\d\d\.\d\d\d",
2541 2559 "2000-01-01T00:00:00.000", response.body))
2542 2560
2543 2561 def test_api_close_pullrequest(self):
2544 2562 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u'close test')
2545 2563 random_id = random.randrange(1, 9999)
2546 2564 params = json.dumps({
2547 2565 "id": random_id,
2548 2566 "api_key": self.apikey,
2549 2567 "method": "comment_pullrequest",
2550 2568 "args": {"pull_request_id": pull_request_id, "close_pr": True},
2551 2569 })
2552 2570 response = api_call(self, params)
2553 2571 self._compare_ok(random_id, True, given=response.body)
2554 2572 pullrequest = PullRequest().get(pull_request_id)
2555 2573 assert pullrequest.comments[-1].text == ''
2556 2574 assert pullrequest.status == PullRequest.STATUS_CLOSED
2557 2575 assert pullrequest.is_closed() == True
2558 2576
2559 2577 def test_api_status_pullrequest(self):
2560 2578 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u"status test")
2561 2579
2562 2580 random_id = random.randrange(1, 9999)
2563 2581 params = json.dumps({
2564 2582 "id": random_id,
2565 2583 "api_key": User.get_by_username(TEST_USER_REGULAR2_LOGIN).api_key,
2566 2584 "method": "comment_pullrequest",
2567 2585 "args": {"pull_request_id": pull_request_id, "status": ChangesetStatus.STATUS_APPROVED},
2568 2586 })
2569 2587 response = api_call(self, params)
2570 2588 pullrequest = PullRequest().get(pull_request_id)
2571 2589 self._compare_error(random_id, "No permission to change pull request status. User needs to be admin, owner or reviewer.", given=response.body)
2572 2590 assert ChangesetStatus.STATUS_UNDER_REVIEW == ChangesetStatusModel().calculate_pull_request_result(pullrequest)[2]
2573 2591 params = json.dumps({
2574 2592 "id": random_id,
2575 2593 "api_key": User.get_by_username(TEST_USER_REGULAR_LOGIN).api_key,
2576 2594 "method": "comment_pullrequest",
2577 2595 "args": {"pull_request_id": pull_request_id, "status": ChangesetStatus.STATUS_APPROVED},
2578 2596 })
2579 2597 response = api_call(self, params)
2580 2598 self._compare_ok(random_id, True, given=response.body)
2581 2599 pullrequest = PullRequest().get(pull_request_id)
2582 2600 assert ChangesetStatus.STATUS_APPROVED == ChangesetStatusModel().calculate_pull_request_result(pullrequest)[2]
2583 2601
2584 2602 def test_api_comment_pullrequest(self):
2585 2603 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u"comment test")
2586 2604 random_id = random.randrange(1, 9999)
2587 2605 params = json.dumps({
2588 2606 "id": random_id,
2589 2607 "api_key": self.apikey,
2590 2608 "method": "comment_pullrequest",
2591 2609 "args": {"pull_request_id": pull_request_id, "comment_msg": "Looks good to me"},
2592 2610 })
2593 2611 response = api_call(self, params)
2594 2612 self._compare_ok(random_id, True, given=response.body)
2595 2613 pullrequest = PullRequest().get(pull_request_id)
2596 2614 assert pullrequest.comments[-1].text == u'Looks good to me'
General Comments 0
You need to be logged in to leave comments. Login now