##// END OF EJS Templates
api: report better error when fetching a node that doesn't exist.
marcink -
r3490:dcd7ad80 default
parent child
Show More
@@ -1,2311 +1,2314
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