##// END OF EJS Templates
api: allow uncached content fetching....
marcink -
r3479:58288c09 default
parent child
Show More
@@ -1,2305 +1,2309
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 40 from rhodecode.model.changeset_status import ChangesetStatusModel
41 41 from rhodecode.model.comment import CommentsModel
42 42 from rhodecode.model.db import (
43 43 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
44 44 ChangesetComment)
45 45 from rhodecode.model.repo import RepoModel
46 46 from rhodecode.model.scm import ScmModel, RepoList
47 47 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
48 48 from rhodecode.model import validation_schema
49 49 from rhodecode.model.validation_schema.schemas import repo_schema
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 @jsonrpc_method()
55 55 def get_repo(request, apiuser, repoid, cache=Optional(True)):
56 56 """
57 57 Gets an existing repository by its name or repository_id.
58 58
59 59 The members section so the output returns users groups or users
60 60 associated with that repository.
61 61
62 62 This command can only be run using an |authtoken| with admin rights,
63 63 or users with at least read rights to the |repo|.
64 64
65 65 :param apiuser: This is filled automatically from the |authtoken|.
66 66 :type apiuser: AuthUser
67 67 :param repoid: The repository name or repository id.
68 68 :type repoid: str or int
69 69 :param cache: use the cached value for last changeset
70 70 :type: cache: Optional(bool)
71 71
72 72 Example output:
73 73
74 74 .. code-block:: bash
75 75
76 76 {
77 77 "error": null,
78 78 "id": <repo_id>,
79 79 "result": {
80 80 "clone_uri": null,
81 81 "created_on": "timestamp",
82 82 "description": "repo description",
83 83 "enable_downloads": false,
84 84 "enable_locking": false,
85 85 "enable_statistics": false,
86 86 "followers": [
87 87 {
88 88 "active": true,
89 89 "admin": false,
90 90 "api_key": "****************************************",
91 91 "api_keys": [
92 92 "****************************************"
93 93 ],
94 94 "email": "user@example.com",
95 95 "emails": [
96 96 "user@example.com"
97 97 ],
98 98 "extern_name": "rhodecode",
99 99 "extern_type": "rhodecode",
100 100 "firstname": "username",
101 101 "ip_addresses": [],
102 102 "language": null,
103 103 "last_login": "2015-09-16T17:16:35.854",
104 104 "lastname": "surname",
105 105 "user_id": <user_id>,
106 106 "username": "name"
107 107 }
108 108 ],
109 109 "fork_of": "parent-repo",
110 110 "landing_rev": [
111 111 "rev",
112 112 "tip"
113 113 ],
114 114 "last_changeset": {
115 115 "author": "User <user@example.com>",
116 116 "branch": "default",
117 117 "date": "timestamp",
118 118 "message": "last commit message",
119 119 "parents": [
120 120 {
121 121 "raw_id": "commit-id"
122 122 }
123 123 ],
124 124 "raw_id": "commit-id",
125 125 "revision": <revision number>,
126 126 "short_id": "short id"
127 127 },
128 128 "lock_reason": null,
129 129 "locked_by": null,
130 130 "locked_date": null,
131 131 "owner": "owner-name",
132 132 "permissions": [
133 133 {
134 134 "name": "super-admin-name",
135 135 "origin": "super-admin",
136 136 "permission": "repository.admin",
137 137 "type": "user"
138 138 },
139 139 {
140 140 "name": "owner-name",
141 141 "origin": "owner",
142 142 "permission": "repository.admin",
143 143 "type": "user"
144 144 },
145 145 {
146 146 "name": "user-group-name",
147 147 "origin": "permission",
148 148 "permission": "repository.write",
149 149 "type": "user_group"
150 150 }
151 151 ],
152 152 "private": true,
153 153 "repo_id": 676,
154 154 "repo_name": "user-group/repo-name",
155 155 "repo_type": "hg"
156 156 }
157 157 }
158 158 """
159 159
160 160 repo = get_repo_or_error(repoid)
161 161 cache = Optional.extract(cache)
162 162
163 163 include_secrets = False
164 164 if has_superadmin_permission(apiuser):
165 165 include_secrets = True
166 166 else:
167 167 # check if we have at least read permission for this repo !
168 168 _perms = (
169 169 'repository.admin', 'repository.write', 'repository.read',)
170 170 validate_repo_permissions(apiuser, repoid, repo, _perms)
171 171
172 172 permissions = []
173 173 for _user in repo.permissions():
174 174 user_data = {
175 175 'name': _user.username,
176 176 'permission': _user.permission,
177 177 'origin': get_origin(_user),
178 178 'type': "user",
179 179 }
180 180 permissions.append(user_data)
181 181
182 182 for _user_group in repo.permission_user_groups():
183 183 user_group_data = {
184 184 'name': _user_group.users_group_name,
185 185 'permission': _user_group.permission,
186 186 'origin': get_origin(_user_group),
187 187 'type': "user_group",
188 188 }
189 189 permissions.append(user_group_data)
190 190
191 191 following_users = [
192 192 user.user.get_api_data(include_secrets=include_secrets)
193 193 for user in repo.followers]
194 194
195 195 if not cache:
196 196 repo.update_commit_cache()
197 197 data = repo.get_api_data(include_secrets=include_secrets)
198 198 data['permissions'] = permissions
199 199 data['followers'] = following_users
200 200 return data
201 201
202 202
203 203 @jsonrpc_method()
204 204 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
205 205 """
206 206 Lists all existing repositories.
207 207
208 208 This command can only be run using an |authtoken| with admin rights,
209 209 or users with at least read rights to |repos|.
210 210
211 211 :param apiuser: This is filled automatically from the |authtoken|.
212 212 :type apiuser: AuthUser
213 213 :param root: specify root repository group to fetch repositories.
214 214 filters the returned repositories to be members of given root group.
215 215 :type root: Optional(None)
216 216 :param traverse: traverse given root into subrepositories. With this flag
217 217 set to False, it will only return top-level repositories from `root`.
218 218 if root is empty it will return just top-level repositories.
219 219 :type traverse: Optional(True)
220 220
221 221
222 222 Example output:
223 223
224 224 .. code-block:: bash
225 225
226 226 id : <id_given_in_input>
227 227 result: [
228 228 {
229 229 "repo_id" : "<repo_id>",
230 230 "repo_name" : "<reponame>"
231 231 "repo_type" : "<repo_type>",
232 232 "clone_uri" : "<clone_uri>",
233 233 "private": : "<bool>",
234 234 "created_on" : "<datetimecreated>",
235 235 "description" : "<description>",
236 236 "landing_rev": "<landing_rev>",
237 237 "owner": "<repo_owner>",
238 238 "fork_of": "<name_of_fork_parent>",
239 239 "enable_downloads": "<bool>",
240 240 "enable_locking": "<bool>",
241 241 "enable_statistics": "<bool>",
242 242 },
243 243 ...
244 244 ]
245 245 error: null
246 246 """
247 247
248 248 include_secrets = has_superadmin_permission(apiuser)
249 249 _perms = ('repository.read', 'repository.write', 'repository.admin',)
250 250 extras = {'user': apiuser}
251 251
252 252 root = Optional.extract(root)
253 253 traverse = Optional.extract(traverse, binary=True)
254 254
255 255 if root:
256 256 # verify parent existance, if it's empty return an error
257 257 parent = RepoGroup.get_by_group_name(root)
258 258 if not parent:
259 259 raise JSONRPCError(
260 260 'Root repository group `{}` does not exist'.format(root))
261 261
262 262 if traverse:
263 263 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
264 264 else:
265 265 repos = RepoModel().get_repos_for_root(root=parent)
266 266 else:
267 267 if traverse:
268 268 repos = RepoModel().get_all()
269 269 else:
270 270 # return just top-level
271 271 repos = RepoModel().get_repos_for_root(root=None)
272 272
273 273 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
274 274 return [repo.get_api_data(include_secrets=include_secrets)
275 275 for repo in repo_list]
276 276
277 277
278 278 @jsonrpc_method()
279 279 def get_repo_changeset(request, apiuser, repoid, revision,
280 280 details=Optional('basic')):
281 281 """
282 282 Returns information about a changeset.
283 283
284 284 Additionally parameters define the amount of details returned by
285 285 this function.
286 286
287 287 This command can only be run using an |authtoken| with admin rights,
288 288 or users with at least read rights to the |repo|.
289 289
290 290 :param apiuser: This is filled automatically from the |authtoken|.
291 291 :type apiuser: AuthUser
292 292 :param repoid: The repository name or repository id
293 293 :type repoid: str or int
294 294 :param revision: revision for which listing should be done
295 295 :type revision: str
296 296 :param details: details can be 'basic|extended|full' full gives diff
297 297 info details like the diff itself, and number of changed files etc.
298 298 :type details: Optional(str)
299 299
300 300 """
301 301 repo = get_repo_or_error(repoid)
302 302 if not has_superadmin_permission(apiuser):
303 303 _perms = (
304 304 'repository.admin', 'repository.write', 'repository.read',)
305 305 validate_repo_permissions(apiuser, repoid, repo, _perms)
306 306
307 307 changes_details = Optional.extract(details)
308 308 _changes_details_types = ['basic', 'extended', 'full']
309 309 if changes_details not in _changes_details_types:
310 310 raise JSONRPCError(
311 311 'ret_type must be one of %s' % (
312 312 ','.join(_changes_details_types)))
313 313
314 314 pre_load = ['author', 'branch', 'date', 'message', 'parents',
315 315 'status', '_commit', '_file_paths']
316 316
317 317 try:
318 318 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
319 319 except TypeError as e:
320 320 raise JSONRPCError(safe_str(e))
321 321 _cs_json = cs.__json__()
322 322 _cs_json['diff'] = build_commit_data(cs, changes_details)
323 323 if changes_details == 'full':
324 324 _cs_json['refs'] = cs._get_refs()
325 325 return _cs_json
326 326
327 327
328 328 @jsonrpc_method()
329 329 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
330 330 details=Optional('basic')):
331 331 """
332 332 Returns a set of commits limited by the number starting
333 333 from the `start_rev` option.
334 334
335 335 Additional parameters define the amount of details returned by this
336 336 function.
337 337
338 338 This command can only be run using an |authtoken| with admin rights,
339 339 or users with at least read rights to |repos|.
340 340
341 341 :param apiuser: This is filled automatically from the |authtoken|.
342 342 :type apiuser: AuthUser
343 343 :param repoid: The repository name or repository ID.
344 344 :type repoid: str or int
345 345 :param start_rev: The starting revision from where to get changesets.
346 346 :type start_rev: str
347 347 :param limit: Limit the number of commits to this amount
348 348 :type limit: str or int
349 349 :param details: Set the level of detail returned. Valid option are:
350 350 ``basic``, ``extended`` and ``full``.
351 351 :type details: Optional(str)
352 352
353 353 .. note::
354 354
355 355 Setting the parameter `details` to the value ``full`` is extensive
356 356 and returns details like the diff itself, and the number
357 357 of changed files.
358 358
359 359 """
360 360 repo = get_repo_or_error(repoid)
361 361 if not has_superadmin_permission(apiuser):
362 362 _perms = (
363 363 'repository.admin', 'repository.write', 'repository.read',)
364 364 validate_repo_permissions(apiuser, repoid, repo, _perms)
365 365
366 366 changes_details = Optional.extract(details)
367 367 _changes_details_types = ['basic', 'extended', 'full']
368 368 if changes_details not in _changes_details_types:
369 369 raise JSONRPCError(
370 370 'ret_type must be one of %s' % (
371 371 ','.join(_changes_details_types)))
372 372
373 373 limit = int(limit)
374 374 pre_load = ['author', 'branch', 'date', 'message', 'parents',
375 375 'status', '_commit', '_file_paths']
376 376
377 377 vcs_repo = repo.scm_instance()
378 378 # SVN needs a special case to distinguish its index and commit id
379 379 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
380 380 start_rev = vcs_repo.commit_ids[0]
381 381
382 382 try:
383 383 commits = vcs_repo.get_commits(
384 384 start_id=start_rev, pre_load=pre_load, translate_tags=False)
385 385 except TypeError as e:
386 386 raise JSONRPCError(safe_str(e))
387 387 except Exception:
388 388 log.exception('Fetching of commits failed')
389 389 raise JSONRPCError('Error occurred during commit fetching')
390 390
391 391 ret = []
392 392 for cnt, commit in enumerate(commits):
393 393 if cnt >= limit != -1:
394 394 break
395 395 _cs_json = commit.__json__()
396 396 _cs_json['diff'] = build_commit_data(commit, changes_details)
397 397 if changes_details == 'full':
398 398 _cs_json['refs'] = {
399 399 'branches': [commit.branch],
400 400 'bookmarks': getattr(commit, 'bookmarks', []),
401 401 'tags': commit.tags
402 402 }
403 403 ret.append(_cs_json)
404 404 return ret
405 405
406 406
407 407 @jsonrpc_method()
408 408 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
409 409 ret_type=Optional('all'), details=Optional('basic'),
410 410 max_file_bytes=Optional(None)):
411 411 """
412 412 Returns a list of nodes and children in a flat list for a given
413 413 path at given revision.
414 414
415 415 It's possible to specify ret_type to show only `files` or `dirs`.
416 416
417 417 This command can only be run using an |authtoken| with admin rights,
418 418 or users with at least read rights to |repos|.
419 419
420 420 :param apiuser: This is filled automatically from the |authtoken|.
421 421 :type apiuser: AuthUser
422 422 :param repoid: The repository name or repository ID.
423 423 :type repoid: str or int
424 424 :param revision: The revision for which listing should be done.
425 425 :type revision: str
426 426 :param root_path: The path from which to start displaying.
427 427 :type root_path: str
428 428 :param ret_type: Set the return type. Valid options are
429 429 ``all`` (default), ``files`` and ``dirs``.
430 430 :type ret_type: Optional(str)
431 431 :param details: Returns extended information about nodes, such as
432 432 md5, binary, and or content.
433 433 The valid options are ``basic`` and ``full``.
434 434 :type details: Optional(str)
435 435 :param max_file_bytes: Only return file content under this file size bytes
436 436 :type details: Optional(int)
437 437
438 438 Example output:
439 439
440 440 .. code-block:: bash
441 441
442 442 id : <id_given_in_input>
443 443 result: [
444 444 {
445 445 "binary": false,
446 446 "content": "File line\nLine2\n",
447 447 "extension": "md",
448 448 "lines": 2,
449 449 "md5": "059fa5d29b19c0657e384749480f6422",
450 450 "mimetype": "text/x-minidsrc",
451 451 "name": "file.md",
452 452 "size": 580,
453 453 "type": "file"
454 454 },
455 455 ...
456 456 ]
457 457 error: null
458 458 """
459 459
460 460 repo = get_repo_or_error(repoid)
461 461 if not has_superadmin_permission(apiuser):
462 462 _perms = ('repository.admin', 'repository.write', 'repository.read',)
463 463 validate_repo_permissions(apiuser, repoid, repo, _perms)
464 464
465 465 ret_type = Optional.extract(ret_type)
466 466 details = Optional.extract(details)
467 467 _extended_types = ['basic', 'full']
468 468 if details not in _extended_types:
469 469 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
470 470 extended_info = False
471 471 content = False
472 472 if details == 'basic':
473 473 extended_info = True
474 474
475 475 if details == 'full':
476 476 extended_info = content = True
477 477
478 478 _map = {}
479 479 try:
480 480 # check if repo is not empty by any chance, skip quicker if it is.
481 481 _scm = repo.scm_instance()
482 482 if _scm.is_empty():
483 483 return []
484 484
485 485 _d, _f = ScmModel().get_nodes(
486 486 repo, revision, root_path, flat=False,
487 487 extended_info=extended_info, content=content,
488 488 max_file_bytes=max_file_bytes)
489 489 _map = {
490 490 'all': _d + _f,
491 491 'files': _f,
492 492 'dirs': _d,
493 493 }
494 494 return _map[ret_type]
495 495 except KeyError:
496 496 raise JSONRPCError(
497 497 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
498 498 except Exception:
499 499 log.exception("Exception occurred while trying to get repo nodes")
500 500 raise JSONRPCError(
501 501 'failed to get repo: `%s` nodes' % repo.repo_name
502 502 )
503 503
504 504
505 505 @jsonrpc_method()
506 506 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
507 max_file_bytes=Optional(None), details=Optional('basic')):
507 max_file_bytes=Optional(None), details=Optional('basic'),
508 cache=Optional(True)):
508 509 """
509 510 Returns a single file from repository at given revision.
510 511
511 512 This command can only be run using an |authtoken| with admin rights,
512 513 or users with at least read rights to |repos|.
513 514
514 515 :param apiuser: This is filled automatically from the |authtoken|.
515 516 :type apiuser: AuthUser
516 517 :param repoid: The repository name or repository ID.
517 518 :type repoid: str or int
518 519 :param commit_id: The revision for which listing should be done.
519 520 :type commit_id: str
520 521 :param file_path: The path from which to start displaying.
521 522 :type file_path: str
522 523 :param details: Returns different set of information about nodes.
523 524 The valid options are ``minimal`` ``basic`` and ``full``.
524 525 :type details: Optional(str)
525 526 :param max_file_bytes: Only return file content under this file size bytes
526 :type details: Optional(int)
527
527 :type max_file_bytes: Optional(int)
528 :param cache: Use internal caches for fetching files. If disabled fetching
529 files is slower but more memory efficient
530 :type cache: Optional(bool)
528 531 Example output:
529 532
530 533 .. code-block:: bash
531 534
532 535 id : <id_given_in_input>
533 536 result: {
534 537 "binary": false,
535 538 "extension": "py",
536 539 "lines": 35,
537 540 "content": "....",
538 541 "md5": "76318336366b0f17ee249e11b0c99c41",
539 542 "mimetype": "text/x-python",
540 543 "name": "python.py",
541 544 "size": 817,
542 545 "type": "file",
543 546 }
544 547 error: null
545 548 """
546 549
547 550 repo = get_repo_or_error(repoid)
548 551 if not has_superadmin_permission(apiuser):
549 552 _perms = ('repository.admin', 'repository.write', 'repository.read',)
550 553 validate_repo_permissions(apiuser, repoid, repo, _perms)
551 554
555 cache = Optional.extract(cache, binary=True)
552 556 details = Optional.extract(details)
553 557 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
554 558 if details not in _extended_types:
555 559 raise JSONRPCError(
556 560 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
557 561 extended_info = False
558 562 content = False
559 563
560 564 if details == 'minimal':
561 565 extended_info = False
562 566
563 567 elif details == 'basic':
564 568 extended_info = True
565 569
566 570 elif details == 'full':
567 571 extended_info = content = True
568 572
569 573 try:
570 574 # check if repo is not empty by any chance, skip quicker if it is.
571 575 _scm = repo.scm_instance()
572 576 if _scm.is_empty():
573 577 return None
574 578
575 579 node = ScmModel().get_node(
576 580 repo, commit_id, file_path, extended_info=extended_info,
577 content=content, max_file_bytes=max_file_bytes)
581 content=content, max_file_bytes=max_file_bytes, cache=cache)
578 582
579 583 except Exception:
580 584 log.exception("Exception occurred while trying to get repo node")
581 585 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
582 586
583 587 return node
584 588
585 589
586 590 @jsonrpc_method()
587 591 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
588 592 """
589 593 Returns a list of tree nodes for path at given revision. This api is built
590 594 strictly for usage in full text search building, and shouldn't be consumed
591 595
592 596 This command can only be run using an |authtoken| with admin rights,
593 597 or users with at least read rights to |repos|.
594 598
595 599 """
596 600
597 601 repo = get_repo_or_error(repoid)
598 602 if not has_superadmin_permission(apiuser):
599 603 _perms = ('repository.admin', 'repository.write', 'repository.read',)
600 604 validate_repo_permissions(apiuser, repoid, repo, _perms)
601 605
602 606 repo_id = repo.repo_id
603 607 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
604 608 cache_on = cache_seconds > 0
605 609
606 610 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
607 611 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
608 612
609 613 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
610 614 condition=cache_on)
611 615 def compute_fts_tree(repo_id, commit_id, root_path, cache_ver):
612 616 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
613 617
614 618 try:
615 619 # check if repo is not empty by any chance, skip quicker if it is.
616 620 _scm = repo.scm_instance()
617 621 if _scm.is_empty():
618 622 return []
619 623 except RepositoryError:
620 624 log.exception("Exception occurred while trying to get repo nodes")
621 625 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
622 626
623 627 try:
624 628 # we need to resolve commit_id to a FULL sha for cache to work correctly.
625 629 # sending 'master' is a pointer that needs to be translated to current commit.
626 630 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
627 631 log.debug(
628 632 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
629 633 'with caching: %s[TTL: %ss]' % (
630 634 repo_id, commit_id, cache_on, cache_seconds or 0))
631 635
632 636 tree_files = compute_fts_tree(repo_id, commit_id, root_path, 'v1')
633 637 return tree_files
634 638
635 639 except Exception:
636 640 log.exception("Exception occurred while trying to get repo nodes")
637 641 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
638 642
639 643
640 644 @jsonrpc_method()
641 645 def get_repo_refs(request, apiuser, repoid):
642 646 """
643 647 Returns a dictionary of current references. It returns
644 648 bookmarks, branches, closed_branches, and tags for given repository
645 649
646 650 It's possible to specify ret_type to show only `files` or `dirs`.
647 651
648 652 This command can only be run using an |authtoken| with admin rights,
649 653 or users with at least read rights to |repos|.
650 654
651 655 :param apiuser: This is filled automatically from the |authtoken|.
652 656 :type apiuser: AuthUser
653 657 :param repoid: The repository name or repository ID.
654 658 :type repoid: str or int
655 659
656 660 Example output:
657 661
658 662 .. code-block:: bash
659 663
660 664 id : <id_given_in_input>
661 665 "result": {
662 666 "bookmarks": {
663 667 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
664 668 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
665 669 },
666 670 "branches": {
667 671 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
668 672 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
669 673 },
670 674 "branches_closed": {},
671 675 "tags": {
672 676 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
673 677 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
674 678 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
675 679 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
676 680 }
677 681 }
678 682 error: null
679 683 """
680 684
681 685 repo = get_repo_or_error(repoid)
682 686 if not has_superadmin_permission(apiuser):
683 687 _perms = ('repository.admin', 'repository.write', 'repository.read',)
684 688 validate_repo_permissions(apiuser, repoid, repo, _perms)
685 689
686 690 try:
687 691 # check if repo is not empty by any chance, skip quicker if it is.
688 692 vcs_instance = repo.scm_instance()
689 693 refs = vcs_instance.refs()
690 694 return refs
691 695 except Exception:
692 696 log.exception("Exception occurred while trying to get repo refs")
693 697 raise JSONRPCError(
694 698 'failed to get repo: `%s` references' % repo.repo_name
695 699 )
696 700
697 701
698 702 @jsonrpc_method()
699 703 def create_repo(
700 704 request, apiuser, repo_name, repo_type,
701 705 owner=Optional(OAttr('apiuser')),
702 706 description=Optional(''),
703 707 private=Optional(False),
704 708 clone_uri=Optional(None),
705 709 push_uri=Optional(None),
706 710 landing_rev=Optional('rev:tip'),
707 711 enable_statistics=Optional(False),
708 712 enable_locking=Optional(False),
709 713 enable_downloads=Optional(False),
710 714 copy_permissions=Optional(False)):
711 715 """
712 716 Creates a repository.
713 717
714 718 * If the repository name contains "/", repository will be created inside
715 719 a repository group or nested repository groups
716 720
717 721 For example "foo/bar/repo1" will create |repo| called "repo1" inside
718 722 group "foo/bar". You have to have permissions to access and write to
719 723 the last repository group ("bar" in this example)
720 724
721 725 This command can only be run using an |authtoken| with at least
722 726 permissions to create repositories, or write permissions to
723 727 parent repository groups.
724 728
725 729 :param apiuser: This is filled automatically from the |authtoken|.
726 730 :type apiuser: AuthUser
727 731 :param repo_name: Set the repository name.
728 732 :type repo_name: str
729 733 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
730 734 :type repo_type: str
731 735 :param owner: user_id or username
732 736 :type owner: Optional(str)
733 737 :param description: Set the repository description.
734 738 :type description: Optional(str)
735 739 :param private: set repository as private
736 740 :type private: bool
737 741 :param clone_uri: set clone_uri
738 742 :type clone_uri: str
739 743 :param push_uri: set push_uri
740 744 :type push_uri: str
741 745 :param landing_rev: <rev_type>:<rev>
742 746 :type landing_rev: str
743 747 :param enable_locking:
744 748 :type enable_locking: bool
745 749 :param enable_downloads:
746 750 :type enable_downloads: bool
747 751 :param enable_statistics:
748 752 :type enable_statistics: bool
749 753 :param copy_permissions: Copy permission from group in which the
750 754 repository is being created.
751 755 :type copy_permissions: bool
752 756
753 757
754 758 Example output:
755 759
756 760 .. code-block:: bash
757 761
758 762 id : <id_given_in_input>
759 763 result: {
760 764 "msg": "Created new repository `<reponame>`",
761 765 "success": true,
762 766 "task": "<celery task id or None if done sync>"
763 767 }
764 768 error: null
765 769
766 770
767 771 Example error output:
768 772
769 773 .. code-block:: bash
770 774
771 775 id : <id_given_in_input>
772 776 result : null
773 777 error : {
774 778 'failed to create repository `<repo_name>`'
775 779 }
776 780
777 781 """
778 782
779 783 owner = validate_set_owner_permissions(apiuser, owner)
780 784
781 785 description = Optional.extract(description)
782 786 copy_permissions = Optional.extract(copy_permissions)
783 787 clone_uri = Optional.extract(clone_uri)
784 788 push_uri = Optional.extract(push_uri)
785 789 landing_commit_ref = Optional.extract(landing_rev)
786 790
787 791 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
788 792 if isinstance(private, Optional):
789 793 private = defs.get('repo_private') or Optional.extract(private)
790 794 if isinstance(repo_type, Optional):
791 795 repo_type = defs.get('repo_type')
792 796 if isinstance(enable_statistics, Optional):
793 797 enable_statistics = defs.get('repo_enable_statistics')
794 798 if isinstance(enable_locking, Optional):
795 799 enable_locking = defs.get('repo_enable_locking')
796 800 if isinstance(enable_downloads, Optional):
797 801 enable_downloads = defs.get('repo_enable_downloads')
798 802
799 803 schema = repo_schema.RepoSchema().bind(
800 804 repo_type_options=rhodecode.BACKENDS.keys(),
801 805 repo_type=repo_type,
802 806 # user caller
803 807 user=apiuser)
804 808
805 809 try:
806 810 schema_data = schema.deserialize(dict(
807 811 repo_name=repo_name,
808 812 repo_type=repo_type,
809 813 repo_owner=owner.username,
810 814 repo_description=description,
811 815 repo_landing_commit_ref=landing_commit_ref,
812 816 repo_clone_uri=clone_uri,
813 817 repo_push_uri=push_uri,
814 818 repo_private=private,
815 819 repo_copy_permissions=copy_permissions,
816 820 repo_enable_statistics=enable_statistics,
817 821 repo_enable_downloads=enable_downloads,
818 822 repo_enable_locking=enable_locking))
819 823 except validation_schema.Invalid as err:
820 824 raise JSONRPCValidationError(colander_exc=err)
821 825
822 826 try:
823 827 data = {
824 828 'owner': owner,
825 829 'repo_name': schema_data['repo_group']['repo_name_without_group'],
826 830 'repo_name_full': schema_data['repo_name'],
827 831 'repo_group': schema_data['repo_group']['repo_group_id'],
828 832 'repo_type': schema_data['repo_type'],
829 833 'repo_description': schema_data['repo_description'],
830 834 'repo_private': schema_data['repo_private'],
831 835 'clone_uri': schema_data['repo_clone_uri'],
832 836 'push_uri': schema_data['repo_push_uri'],
833 837 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
834 838 'enable_statistics': schema_data['repo_enable_statistics'],
835 839 'enable_locking': schema_data['repo_enable_locking'],
836 840 'enable_downloads': schema_data['repo_enable_downloads'],
837 841 'repo_copy_permissions': schema_data['repo_copy_permissions'],
838 842 }
839 843
840 844 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
841 845 task_id = get_task_id(task)
842 846 # no commit, it's done in RepoModel, or async via celery
843 847 return {
844 848 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
845 849 'success': True, # cannot return the repo data here since fork
846 850 # can be done async
847 851 'task': task_id
848 852 }
849 853 except Exception:
850 854 log.exception(
851 855 u"Exception while trying to create the repository %s",
852 856 schema_data['repo_name'])
853 857 raise JSONRPCError(
854 858 'failed to create repository `%s`' % (schema_data['repo_name'],))
855 859
856 860
857 861 @jsonrpc_method()
858 862 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
859 863 description=Optional('')):
860 864 """
861 865 Adds an extra field to a repository.
862 866
863 867 This command can only be run using an |authtoken| with at least
864 868 write permissions to the |repo|.
865 869
866 870 :param apiuser: This is filled automatically from the |authtoken|.
867 871 :type apiuser: AuthUser
868 872 :param repoid: Set the repository name or repository id.
869 873 :type repoid: str or int
870 874 :param key: Create a unique field key for this repository.
871 875 :type key: str
872 876 :param label:
873 877 :type label: Optional(str)
874 878 :param description:
875 879 :type description: Optional(str)
876 880 """
877 881 repo = get_repo_or_error(repoid)
878 882 if not has_superadmin_permission(apiuser):
879 883 _perms = ('repository.admin',)
880 884 validate_repo_permissions(apiuser, repoid, repo, _perms)
881 885
882 886 label = Optional.extract(label) or key
883 887 description = Optional.extract(description)
884 888
885 889 field = RepositoryField.get_by_key_name(key, repo)
886 890 if field:
887 891 raise JSONRPCError('Field with key '
888 892 '`%s` exists for repo `%s`' % (key, repoid))
889 893
890 894 try:
891 895 RepoModel().add_repo_field(repo, key, field_label=label,
892 896 field_desc=description)
893 897 Session().commit()
894 898 return {
895 899 'msg': "Added new repository field `%s`" % (key,),
896 900 'success': True,
897 901 }
898 902 except Exception:
899 903 log.exception("Exception occurred while trying to add field to repo")
900 904 raise JSONRPCError(
901 905 'failed to create new field for repository `%s`' % (repoid,))
902 906
903 907
904 908 @jsonrpc_method()
905 909 def remove_field_from_repo(request, apiuser, repoid, key):
906 910 """
907 911 Removes an extra field from a repository.
908 912
909 913 This command can only be run using an |authtoken| with at least
910 914 write permissions to the |repo|.
911 915
912 916 :param apiuser: This is filled automatically from the |authtoken|.
913 917 :type apiuser: AuthUser
914 918 :param repoid: Set the repository name or repository ID.
915 919 :type repoid: str or int
916 920 :param key: Set the unique field key for this repository.
917 921 :type key: str
918 922 """
919 923
920 924 repo = get_repo_or_error(repoid)
921 925 if not has_superadmin_permission(apiuser):
922 926 _perms = ('repository.admin',)
923 927 validate_repo_permissions(apiuser, repoid, repo, _perms)
924 928
925 929 field = RepositoryField.get_by_key_name(key, repo)
926 930 if not field:
927 931 raise JSONRPCError('Field with key `%s` does not '
928 932 'exists for repo `%s`' % (key, repoid))
929 933
930 934 try:
931 935