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