##// END OF EJS Templates
api: maked logging of get_repo_file API function tell a bit more info about errors.
marcink -
r3480:750f1746 default
parent child Browse files
Show More
@@ -1,2309 +1,2311 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 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 507 max_file_bytes=Optional(None), details=Optional('basic'),
508 508 cache=Optional(True)):
509 509 """
510 510 Returns a single file from repository at given revision.
511 511
512 512 This command can only be run using an |authtoken| with admin rights,
513 513 or users with at least read rights to |repos|.
514 514
515 515 :param apiuser: This is filled automatically from the |authtoken|.
516 516 :type apiuser: AuthUser
517 517 :param repoid: The repository name or repository ID.
518 518 :type repoid: str or int
519 519 :param commit_id: The revision for which listing should be done.
520 520 :type commit_id: str
521 521 :param file_path: The path from which to start displaying.
522 522 :type file_path: str
523 523 :param details: Returns different set of information about nodes.
524 524 The valid options are ``minimal`` ``basic`` and ``full``.
525 525 :type details: Optional(str)
526 526 :param max_file_bytes: Only return file content under this file size bytes
527 527 :type max_file_bytes: Optional(int)
528 528 :param cache: Use internal caches for fetching files. If disabled fetching
529 529 files is slower but more memory efficient
530 530 :type cache: Optional(bool)
531 531 Example output:
532 532
533 533 .. code-block:: bash
534 534
535 535 id : <id_given_in_input>
536 536 result: {
537 537 "binary": false,
538 538 "extension": "py",
539 539 "lines": 35,
540 540 "content": "....",
541 541 "md5": "76318336366b0f17ee249e11b0c99c41",
542 542 "mimetype": "text/x-python",
543 543 "name": "python.py",
544 544 "size": 817,
545 545 "type": "file",
546 546 }
547 547 error: null
548 548 """
549 549
550 550 repo = get_repo_or_error(repoid)
551 551 if not has_superadmin_permission(apiuser):
552 552 _perms = ('repository.admin', 'repository.write', 'repository.read',)
553 553 validate_repo_permissions(apiuser, repoid, repo, _perms)
554 554
555 555 cache = Optional.extract(cache, binary=True)
556 556 details = Optional.extract(details)
557 557 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
558 558 if details not in _extended_types:
559 559 raise JSONRPCError(
560 560 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
561 561 extended_info = False
562 562 content = False
563 563
564 564 if details == 'minimal':
565 565 extended_info = False
566 566
567 567 elif details == 'basic':
568 568 extended_info = True
569 569
570 570 elif details == 'full':
571 571 extended_info = content = True
572 572
573 573 try:
574 574 # check if repo is not empty by any chance, skip quicker if it is.
575 575 _scm = repo.scm_instance()
576 576 if _scm.is_empty():
577 577 return None
578 578
579 579 node = ScmModel().get_node(
580 580 repo, commit_id, file_path, extended_info=extended_info,
581 581 content=content, max_file_bytes=max_file_bytes, cache=cache)
582 582
583 583 except Exception:
584 log.exception("Exception occurred while trying to get repo node")
585 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
584 log.exception("Exception occurred while trying to get repo %s file",
585 repo.repo_name)
586 raise JSONRPCError('failed to get repo: `{}` file at path {}'.format(
587 repo.repo_name, file_path))
586 588
587 589 return node
588 590
589 591
590 592 @jsonrpc_method()
591 593 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
592 594 """
593 595 Returns a list of tree nodes for path at given revision. This api is built
594 596 strictly for usage in full text search building, and shouldn't be consumed
595 597
596 598 This command can only be run using an |authtoken| with admin rights,
597 599 or users with at least read rights to |repos|.
598 600
599 601 """
600 602
601 603 repo = get_repo_or_error(repoid)
602 604 if not has_superadmin_permission(apiuser):
603 605 _perms = ('repository.admin', 'repository.write', 'repository.read',)
604 606 validate_repo_permissions(apiuser, repoid, repo, _perms)
605 607
606 608 repo_id = repo.repo_id
607 609 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
608 610 cache_on = cache_seconds > 0
609 611
610 612 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
611 613 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
612 614
613 615 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
614 616 condition=cache_on)
615 617 def compute_fts_tree(repo_id, commit_id, root_path, cache_ver):
616 618 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
617 619
618 620 try:
619 621 # check if repo is not empty by any chance, skip quicker if it is.
620 622 _scm = repo.scm_instance()
621 623 if _scm.is_empty():
622 624 return []
623 625 except RepositoryError:
624 626 log.exception("Exception occurred while trying to get repo nodes")
625 627 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
626 628
627 629 try:
628 630 # we need to resolve commit_id to a FULL sha for cache to work correctly.
629 631 # sending 'master' is a pointer that needs to be translated to current commit.
630 632 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
631 633 log.debug(
632 634 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
633 635 'with caching: %s[TTL: %ss]' % (
634 636 repo_id, commit_id, cache_on, cache_seconds or 0))
635 637
636 638 tree_files = compute_fts_tree(repo_id, commit_id, root_path, 'v1')
637 639 return tree_files
638 640
639 641 except Exception:
640 642 log.exception("Exception occurred while trying to get repo nodes")
641 643 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
642 644
643 645
644 646 @jsonrpc_method()
645 647 def get_repo_refs(request, apiuser, repoid):
646 648 """
647 649 Returns a dictionary of current references. It returns
648 650 bookmarks, branches, closed_branches, and tags for given repository
649 651
650 652 It's possible to specify ret_type to show only `files` or `dirs`.
651 653
652 654 This command can only be run using an |authtoken| with admin rights,
653 655 or users with at least read rights to |repos|.
654 656
655 657 :param apiuser: This is filled automatically from the |authtoken|.
656 658 :type apiuser: AuthUser
657 659 :param repoid: The repository name or repository ID.
658 660 :type repoid: str or int
659 661
660 662 Example output:
661 663
662 664 .. code-block:: bash
663 665
664 666 id : <id_given_in_input>
665 667 "result": {
666 668 "bookmarks": {
667 669 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
668 670 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
669 671 },
670 672 "branches": {
671 673 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
672 674 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
673 675 },
674 676 "branches_closed": {},
675 677 "tags": {
676 678 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
677 679 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
678 680 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
679 681 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
680 682 }
681 683 }
682 684 error: null
683 685 """
684 686
685 687 repo = get_repo_or_error(repoid)
686 688 if not has_superadmin_permission(apiuser):
687 689 _perms = ('repository.admin', 'repository.write', 'repository.read',)
688 690 validate_repo_permissions(apiuser, repoid, repo, _perms)
689 691
690 692 try:
691 693 # check if repo is not empty by any chance, skip quicker if it is.
692 694 vcs_instance = repo.scm_instance()
693 695 refs = vcs_instance.refs()
694 696 return refs
695 697 except Exception:
696 698 log.exception("Exception occurred while trying to get repo refs")
697 699 raise JSONRPCError(
698 700 'failed to get repo: `%s` references' % repo.repo_name
699 701 )
700 702
701 703
702 704 @jsonrpc_method()
703 705 def create_repo(
704 706 request, apiuser, repo_name, repo_type,
705 707 owner=Optional(OAttr('apiuser')),
706 708 description=Optional(''),
707 709 private=Optional(False),
708 710 clone_uri=Optional(None),
709 711 push_uri=Optional(None),
710 712 landing_rev=Optional('rev:tip'),
711 713 enable_statistics=Optional(False),
712 714 enable_locking=Optional(False),
713 715 enable_downloads=Optional(False),
714 716 copy_permissions=Optional(False)):
715 717 """
716 718 Creates a repository.
717 719
718 720 * If the repository name contains "/", repository will be created inside
719 721 a repository group or nested repository groups
720 722
721 723 For example "foo/bar/repo1" will create |repo| called "repo1" inside
722 724 group "foo/bar". You have to have permissions to access and write to
723 725 the last repository group ("bar" in this example)
724 726
725 727 This command can only be run using an |authtoken| with at least
726 728 permissions to create repositories, or write permissions to
727 729 parent repository groups.
728 730
729 731 :param apiuser: This is filled automatically from the |authtoken|.
730 732 :type apiuser: AuthUser
731 733 :param repo_name: Set the repository name.
732 734 :type repo_name: str
733 735 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
734 736 :type repo_type: str
735 737 :param owner: user_id or username
736 738 :type owner: Optional(str)
737 739 :param description: Set the repository description.
738 740 :type description: Optional(str)
739 741 :param private: set repository as private
740 742 :type private: bool
741 743 :param clone_uri: set clone_uri
742 744 :type clone_uri: str
743 745 :param push_uri: set push_uri
744 746 :type push_uri: str
745 747 :param landing_rev: <rev_type>:<rev>
746 748 :type landing_rev: str
747 749 :param enable_locking:
748 750 :type enable_locking: bool
749 751 :param enable_downloads:
750 752 :type enable_downloads: bool
751 753 :param enable_statistics:
752 754 :type enable_statistics: bool
753 755 :param copy_permissions: Copy permission from group in which the
754 756 repository is being created.
755 757 :type copy_permissions: bool
756 758
757 759
758 760 Example output:
759 761
760 762 .. code-block:: bash
761 763
762 764 id : <id_given_in_input>
763 765 result: {
764 766 "msg": "Created new repository `<reponame>`",
765 767 "success": true,
766 768 "task": "<celery task id or None if done sync>"
767 769 }
768 770 error: null
769 771
770 772
771 773 Example error output:
772 774
773 775 .. code-block:: bash
774 776
775 777 id : <id_given_in_input>
776 778 result : null
777 779 error : {
778 780 'failed to create repository `<repo_name>`'
779 781 }
780 782
781 783 """
782 784
783 785 owner = validate_set_owner_permissions(apiuser, owner)
784 786
785 787 description = Optional.extract(description)
786 788 copy_permissions = Optional.extract(copy_permissions)
787 789 clone_uri = Optional.extract(clone_uri)
788 790 push_uri = Optional.extract(push_uri)
789 791 landing_commit_ref = Optional.extract(landing_rev)
790 792
791 793 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
792 794 if isinstance(private, Optional):
793 795 private = defs.get('repo_private') or Optional.extract(private)
794 796 if isinstance(repo_type, Optional):
795 797 repo_type = defs.get('repo_type')
796 798 if isinstance(enable_statistics, Optional):
797 799 enable_statistics = defs.get('repo_enable_statistics')
798 800 if isinstance(enable_locking, Optional):
799 801 enable_locking = defs.get('repo_enable_locking')
800 802 if isinstance(enable_downloads, Optional):
801 803 enable_downloads = defs.get('repo_enable_downloads')
802 804
803 805 schema = repo_schema.RepoSchema().bind(
804 806 repo_type_options=rhodecode.BACKENDS.keys(),
805 807 repo_type=repo_type,
806 808 # user caller
807 809 user=apiuser)
808 810
809 811 try:
810 812 schema_data = schema.deserialize(dict(
811 813 repo_name=repo_name,
812 814 repo_type=repo_type,
813 815 repo_owner=owner.username,
814 816 repo_description=description,
815 817 repo_landing_commit_ref=landing_commit_ref,
816 818 repo_clone_uri=clone_uri,
817 819 repo_push_uri=push_uri,
818 820 repo_private=private,
819 821 repo_copy_permissions=copy_permissions,
820 822 repo_enable_statistics=enable_statistics,
821 823 repo_enable_downloads=enable_downloads,
822 824 repo_enable_locking=enable_locking))
823 825 except validation_schema.Invalid as err:
824 826 raise JSONRPCValidationError(colander_exc=err)
825 827
826 828 try:
827 829 data = {
828 830 'owner': owner,
829 831 'repo_name': schema_data['repo_group']['repo_name_without_group'],
830 832 'repo_name_full': schema_data['repo_name'],
831 833 'repo_group': schema_data['repo_group']['repo_group_id'],
832 834 'repo_type': schema_data['repo_type'],
833 835 'repo_description': schema_data['repo_description'],
834 836 'repo_private': schema_data['repo_private'],
835 837 'clone_uri': schema_data['repo_clone_uri'],
836 838 'push_uri': schema_data['repo_push_uri'],
837 839 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
838 840 'enable_statistics': schema_data['repo_enable_statistics'],
839 841 'enable_locking': schema_data['repo_enable_locking'],
840 842 'enable_downloads': schema_data['repo_enable_downloads'],
841 843 'repo_copy_permissions': schema_data['repo_copy_permissions'],
842 844 }
843 845
844 846 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
845 847 task_id = get_task_id(task)
846 848 # no commit, it's done in RepoModel, or async via celery
847 849 return {
848 850 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
849 851 'success': True, # cannot return the repo data here since fork
850 852 # can be done async
851 853 'task': task_id
852 854 }
853 855 except Exception:
854 856 log.exception(
855 857 u"Exception while trying to create the repository %s",
856 858 schema_data['repo_name'])
857 859 raise JSONRPCError(
858 860 'failed to create repository `%s`' % (schema_data['repo_name'],))
859 861
860 862
861 863 @jsonrpc_method()
862 864 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
863 865 description=Optional('')):
864 866 """
865 867 Adds an extra field to a repository.
866 868
867 869 This command can only be run using an |authtoken| with at least
868 870 write permissions to the |repo|.
869 871
870 872 :param apiuser: This is filled automatically from the |authtoken|.
871 873 :type apiuser: AuthUser
872 874 :param repoid: Set the repository name or repository id.
873 875 :type repoid: str or int
874 876 :param key: Create a unique field key for this repository.
875 877 :type key: str
876 878 :param label:
877 879 :type label: Optional(str)
878 880 :param description:
879 881 :type description: Optional(str)
880 882 """
881 883 repo = get_repo_or_error(repoid)
882 884 if not has_superadmin_permission(apiuser):
883 885 _perms = ('repository.admin',)
884 886 validate_repo_permissions(apiuser, repoid, repo, _perms)
885 887
886 888 label = Optional.extract(label) or key
887 889 description = Optional.extract(description)
888 890
889 891 field = RepositoryField.get_by_key_name(key, repo)
890 892 if field:
891 893 raise JSONRPCError('Field with key '
892 894 '`%s` exists for repo `%s`' % (key, repoid))
893 895
894 896 try:
895 897 RepoModel().add_repo_field(repo, key, field_label=label,
896 898 field_desc=description)
897 899 Session().commit()
898 900 return {
899 901 'msg': "Added new repository field `%s`" % (key,),
900 902 'success': True,
901 903 }
902 904 except Exception:
903 905 log.exception("Exception occurred while trying to add field to repo")
904 906 raise JSONRPCError(
905 907 'failed to create new field for repository `%s`' % (repoid,))
906 908
907 909
908 910 @jsonrpc_method()
909 911 def remove_field_from_repo(request, apiuser, repoid, key):
910 912 """
911 913 Removes an extra field from a repository.
912 914
913 915 This command can only be run using an |authtoken| with at least
914 916 write permissions to the |repo|.
915 917
916 918 :param apiuser: This is filled automatically from the |authtoken|.
917 919 :type apiuser: AuthUser
918 920 :param repoid: Set the repository name or repository ID.
919 921 :type repoid: str or int
920 922 :param key: Set the unique field key for this repository.
921 923 :type key: str
922 924 """
923 925
924 926 repo = get_repo_or_error(repoid)
925 927 if not has_superadmin_permission(apiuser):
926 928 _perms = ('repository.admin',)
927 929 validate_repo_permissions(apiuser, repoid, repo, _perms)
928 930
929 931 field = RepositoryField.get_by_key_name(key, repo)
930 932 if not field:
931 933 raise JSONRPCError('Field with key `%s` does not '
932 934 'exists for repo `%s`' % (key, repoid))
933 935
934 936 try:
935 937 RepoModel().delete_repo_field(repo, field_key=key)
936 938 Session().commit()
937 939 return {
938 940 'msg': "Deleted repository field `%s`" % (key,),
939 941 'success': True,
940 942 }
941 943 except Exception:
942 944 log.exception(
943 945 "Exception occurred while trying to delete field from repo")
944 946 raise JSONRPCError(
945 947 'failed to delete field for repository `%s`' % (repoid,))
946 948
947 949
948 950 @jsonrpc_method()
949 951 def update_repo(
950 952 request, apiuser, repoid, repo_name=Optional(None),
951 953 owner=Optional(OAttr('apiuser')), description=Optional(''),
952 954 private=Optional(False),
953 955 clone_uri=Optional(None), push_uri=Optional(None),
954 956 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
955 957 enable_statistics=Optional(False),
956 958 enable_locking=Optional(False),
957 959 enable_downloads=Optional(False), fields=Optional('')):
958 960 """
959 961 Updates a repository with the given information.
960 962
961 963 This command can only be run using an |authtoken| with at least
962 964 admin permissions to the |repo|.
963 965
964 966 * If the repository name contains "/", repository will be updated
965 967 accordingly with a repository group or nested repository groups
966 968
967 969 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
968 970 called "repo-test" and place it inside group "foo/bar".
969 971 You have to have permissions to access and write to the last repository
970 972 group ("bar" in this example)
971 973
972 974 :param apiuser: This is filled automatically from the |authtoken|.
973 975 :type apiuser: AuthUser
974 976 :param repoid: repository name or repository ID.
975 977 :type repoid: str or int
976 978 :param repo_name: Update the |repo| name, including the
977 979 repository group it's in.
978 980 :type repo_name: str
979 981 :param owner: Set the |repo| owner.
980 982 :type owner: str
981 983 :param fork_of: Set the |repo| as fork of another |repo|.
982 984 :type fork_of: str
983 985 :param description: Update the |repo| description.
984 986 :type description: str
985 987 :param private: Set the |repo| as private. (True | False)
986 988 :type private: bool
987 989 :param clone_uri: Update the |repo| clone URI.
988 990 :type clone_uri: str
989 991 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
990 992 :type landing_rev: str
991 993 :param enable_statistics: Enable statistics on the |repo|, (True | False).
992 994 :type enable_statistics: bool
993 995 :param enable_locking: Enable |repo| locking.
994 996 :type enable_locking: bool
995 997 :param enable_downloads: Enable downloads from the |repo|, (True | False).
996 998 :type enable_downloads: bool
997 999 :param fields: Add extra fields to the |repo|. Use the following
998 1000 example format: ``field_key=field_val,field_key2=fieldval2``.
999 1001 Escape ', ' with \,
1000 1002 :type fields: str
1001 1003 """
1002 1004
1003 1005 repo = get_repo_or_error(repoid)
1004 1006
1005 1007 include_secrets = False
1006 1008 if not has_superadmin_permission(apiuser):
1007 1009 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1008 1010 else:
1009 1011 include_secrets = True
1010 1012
1011 1013 updates = dict(
1012 1014 repo_name=repo_name
1013 1015 if not isinstance(repo_name, Optional) else repo.repo_name,
1014 1016
1015 1017 fork_id=fork_of
1016 1018 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1017 1019
1018 1020 user=owner
1019 1021 if not isinstance(owner, Optional) else repo.user.username,
1020 1022
1021 1023 repo_description=description
1022 1024 if not isinstance(description, Optional) else repo.description,
1023 1025
1024 1026 repo_private=private
1025 1027 if not isinstance(private, Optional) else repo.private,
1026 1028
1027 1029 clone_uri=clone_uri
1028 1030 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1029 1031
1030 1032 push_uri=push_uri
1031 1033 if not isinstance(push_uri, Optional) else repo.push_uri,
1032 1034
1033 1035 repo_landing_rev=landing_rev
1034 1036 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1035 1037
1036 1038 repo_enable_statistics=enable_statistics
1037 1039 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1038 1040
1039 1041 repo_enable_locking=enable_locking
1040 1042 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1041 1043
1042 1044 repo_enable_downloads=enable_downloads
1043 1045 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1044 1046
1045 1047 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1046 1048 request.translate, repo=repo)
1047 1049
1048 1050 old_values = repo.get_api_data()
1049 1051 repo_type = repo.repo_type
1050 1052 schema = repo_schema.RepoSchema().bind(
1051 1053 repo_type_options=rhodecode.BACKENDS.keys(),
1052 1054 repo_ref_options=ref_choices,
1053 1055 repo_type=repo_type,
1054 1056 # user caller
1055 1057 user=apiuser,
1056 1058 old_values=old_values)
1057 1059 try:
1058 1060 schema_data = schema.deserialize(dict(
1059 1061 # we save old value, users cannot change type
1060 1062 repo_type=repo_type,
1061 1063
1062 1064 repo_name=updates['repo_name'],
1063 1065 repo_owner=updates['user'],
1064 1066 repo_description=updates['repo_description'],
1065 1067 repo_clone_uri=updates['clone_uri'],
1066 1068 repo_push_uri=updates['push_uri'],
1067 1069 repo_fork_of=updates['fork_id'],
1068 1070 repo_private=updates['repo_private'],
1069 1071 repo_landing_commit_ref=updates['repo_landing_rev'],
1070 1072 repo_enable_statistics=updates['repo_enable_statistics'],
1071 1073 repo_enable_downloads=updates['repo_enable_downloads'],
1072 1074 repo_enable_locking=updates['repo_enable_locking']))
1073 1075 except validation_schema.Invalid as err:
1074 1076 raise JSONRPCValidationError(colander_exc=err)
1075 1077
1076 1078 # save validated data back into the updates dict
1077 1079 validated_updates = dict(
1078 1080 repo_name=schema_data['repo_group']['repo_name_without_group'],
1079 1081 repo_group=schema_data['repo_group']['repo_group_id'],
1080 1082
1081 1083 user=schema_data['repo_owner'],
1082 1084 repo_description=schema_data['repo_description'],
1083 1085 repo_private=schema_data['repo_private'],
1084 1086 clone_uri=schema_data['repo_clone_uri'],
1085 1087 push_uri=schema_data['repo_push_uri'],
1086 1088 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1087 1089 repo_enable_statistics=schema_data['repo_enable_statistics'],
1088 1090 repo_enable_locking=schema_data['repo_enable_locking'],
1089 1091 repo_enable_downloads=schema_data['repo_enable_downloads'],
1090 1092 )
1091 1093
1092 1094 if schema_data['repo_fork_of']:
1093 1095 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1094 1096 validated_updates['fork_id'] = fork_repo.repo_id
1095 1097
1096 1098 # extra fields
1097 1099 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1098 1100 if fields:
1099 1101 validated_updates.update(fields)
1100 1102
1101 1103 try:
1102 1104 RepoModel().update(repo, **validated_updates)
1103 1105 audit_logger.store_api(
1104 1106 'repo.edit', action_data={'old_data': old_values},
1105 1107 user=apiuser, repo=repo)
1106 1108 Session().commit()
1107 1109 return {
1108 1110 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1109 1111 'repository': repo.get_api_data(include_secrets=include_secrets)
1110 1112 }
1111 1113 except Exception:
1112 1114 log.exception(
1113 1115 u"Exception while trying to update the repository %s",
1114 1116 repoid)
1115 1117 raise JSONRPCError('failed to update repo `%s`' % repoid)
1116 1118
1117 1119
1118 1120 @jsonrpc_method()
1119 1121 def fork_repo(request, apiuser, repoid, fork_name,
1120 1122 owner=Optional(OAttr('apiuser')),
1121 1123 description=Optional(''),
1122 1124 private=Optional(False),
1123 1125 clone_uri=Optional(None),
1124 1126 landing_rev=Optional('rev:tip'),
1125 1127 copy_permissions=Optional(False)):
1126 1128 """
1127 1129 Creates a fork of the specified |repo|.
1128 1130
1129 1131 * If the fork_name contains "/", fork will be created inside
1130 1132 a repository group or nested repository groups
1131 1133
1132 1134 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1133 1135 inside group "foo/bar". You have to have permissions to access and
1134 1136 write to the last repository group ("bar" in this example)
1135 1137
1136 1138 This command can only be run using an |authtoken| with minimum
1137 1139 read permissions of the forked repo, create fork permissions for an user.
1138 1140
1139 1141 :param apiuser: This is filled automatically from the |authtoken|.
1140 1142 :type apiuser: AuthUser
1141 1143 :param repoid: Set repository name or repository ID.
1142 1144 :type repoid: str or int
1143 1145 :param fork_name: Set the fork name, including it's repository group membership.
1144 1146 :type fork_name: str
1145 1147 :param owner: Set the fork owner.
1146 1148 :type owner: str
1147 1149 :param description: Set the fork description.
1148 1150 :type description: str
1149 1151 :param copy_permissions: Copy permissions from parent |repo|. The
1150 1152 default is False.
1151 1153 :type copy_permissions: bool
1152 1154 :param private: Make the fork private. The default is False.
1153 1155 :type private: bool
1154 1156 :param landing_rev: Set the landing revision. The default is tip.
1155 1157
1156 1158 Example output:
1157 1159
1158 1160 .. code-block:: bash
1159 1161
1160 1162 id : <id_for_response>
1161 1163 api_key : "<api_key>"
1162 1164 args: {
1163 1165 "repoid" : "<reponame or repo_id>",
1164 1166 "fork_name": "<forkname>",
1165 1167 "owner": "<username or user_id = Optional(=apiuser)>",
1166 1168 "description": "<description>",
1167 1169 "copy_permissions": "<bool>",
1168 1170 "private": "<bool>",
1169 1171 "landing_rev": "<landing_rev>"
1170 1172 }
1171 1173
1172 1174 Example error output:
1173 1175
1174 1176 .. code-block:: bash
1175 1177
1176 1178 id : <id_given_in_input>
1177 1179 result: {
1178 1180 "msg": "Created fork of `<reponame>` as `<forkname>`",
1179 1181 "success": true,
1180 1182 "task": "<celery task id or None if done sync>"
1181 1183 }
1182 1184 error: null
1183 1185
1184 1186 """
1185 1187
1186 1188 repo = get_repo_or_error(repoid)
1187 1189 repo_name = repo.repo_name
1188 1190
1189 1191 if not has_superadmin_permission(apiuser):
1190 1192 # check if we have at least read permission for
1191 1193 # this repo that we fork !
1192 1194 _perms = (
1193 1195 'repository.admin', 'repository.write', 'repository.read')
1194 1196 validate_repo_permissions(apiuser, repoid, repo, _perms)
1195 1197
1196 1198 # check if the regular user has at least fork permissions as well
1197 1199 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1198 1200 raise JSONRPCForbidden()
1199 1201
1200 1202 # check if user can set owner parameter
1201 1203 owner = validate_set_owner_permissions(apiuser, owner)
1202 1204
1203 1205 description = Optional.extract(description)
1204 1206 copy_permissions = Optional.extract(copy_permissions)
1205 1207 clone_uri = Optional.extract(clone_uri)
1206 1208 landing_commit_ref = Optional.extract(landing_rev)
1207 1209 private = Optional.extract(private)
1208 1210
1209 1211 schema = repo_schema.RepoSchema().bind(
1210 1212 repo_type_options=rhodecode.BACKENDS.keys(),
1211 1213 repo_type=repo.repo_type,
1212 1214 # user caller
1213 1215 user=apiuser)
1214 1216
1215 1217 try:
1216 1218 schema_data = schema.deserialize(dict(
1217 1219 repo_name=fork_name,
1218 1220 repo_type=repo.repo_type,
1219 1221 repo_owner=owner.username,
1220 1222 repo_description=description,
1221 1223 repo_landing_commit_ref=landing_commit_ref,
1222 1224 repo_clone_uri=clone_uri,
1223 1225 repo_private=private,
1224 1226 repo_copy_permissions=copy_permissions))
1225 1227 except validation_schema.Invalid as err:
1226 1228 raise JSONRPCValidationError(colander_exc=err)
1227 1229
1228 1230 try:
1229 1231 data = {
1230 1232 'fork_parent_id': repo.repo_id,
1231 1233
1232 1234 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1233 1235 'repo_name_full': schema_data['repo_name'],
1234 1236 'repo_group': schema_data['repo_group']['repo_group_id'],
1235 1237 'repo_type': schema_data['repo_type'],
1236 1238 'description': schema_data['repo_description'],
1237 1239 'private': schema_data['repo_private'],
1238 1240 'copy_permissions': schema_data['repo_copy_permissions'],
1239 1241 'landing_rev': schema_data['repo_landing_commit_ref'],
1240 1242 }
1241 1243
1242 1244 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1243 1245 # no commit, it's done in RepoModel, or async via celery
1244 1246 task_id = get_task_id(task)
1245 1247
1246 1248 return {
1247 1249 'msg': 'Created fork of `%s` as `%s`' % (
1248 1250 repo.repo_name, schema_data['repo_name']),
1249 1251 'success': True, # cannot return the repo data here since fork
1250 1252 # can be done async
1251 1253 'task': task_id
1252 1254 }
1253 1255 except Exception:
1254 1256 log.exception(
1255 1257 u"Exception while trying to create fork %s",
1256 1258 schema_data['repo_name'])
1257 1259 raise JSONRPCError(
1258 1260 'failed to fork repository `%s` as `%s`' % (
1259 1261 repo_name, schema_data['repo_name']))
1260 1262
1261 1263
1262 1264 @jsonrpc_method()
1263 1265 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1264 1266 """
1265 1267 Deletes a repository.
1266 1268
1267 1269 * When the `forks` parameter is set it's possible to detach or delete
1268 1270 forks of deleted repository.
1269 1271
1270 1272 This command can only be run using an |authtoken| with admin
1271 1273 permissions on the |repo|.
1272 1274
1273 1275 :param apiuser: This is filled automatically from the |authtoken|.
1274 1276 :type apiuser: AuthUser
1275 1277 :param repoid: Set the repository name or repository ID.
1276 1278 :type repoid: str or int
1277 1279 :param forks: Set to `detach` or `delete` forks from the |repo|.
1278 1280 :type forks: Optional(str)
1279 1281
1280 1282 Example error output:
1281 1283
1282 1284 .. code-block:: bash
1283 1285
1284 1286 id : <id_given_in_input>
1285 1287 result: {
1286 1288 "msg": "Deleted repository `<reponame>`",
1287 1289 "success": true
1288 1290 }
1289 1291 error: null
1290 1292 """
1291 1293
1292 1294 repo = get_repo_or_error(repoid)
1293 1295 repo_name = repo.repo_name
1294 1296 if not has_superadmin_permission(apiuser):
1295 1297 _perms = ('repository.admin',)
1296 1298 validate_repo_permissions(apiuser, repoid, repo, _perms)
1297 1299
1298 1300 try:
1299 1301 handle_forks = Optional.extract(forks)
1300 1302 _forks_msg = ''
1301 1303 _forks = [f for f in repo.forks]
1302 1304 if handle_forks == 'detach':
1303 1305 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1304 1306 elif handle_forks == 'delete':
1305 1307 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1306 1308 elif _forks:
1307 1309 raise JSONRPCError(
1308 1310 'Cannot delete `%s` it still contains attached forks' %
1309 1311 (repo.repo_name,)
1310 1312 )
1311 1313 old_data = repo.get_api_data()
1312 1314 RepoModel().delete(repo, forks=forks)
1313 1315
1314 1316 repo = audit_logger.RepoWrap(repo_id=None,
1315 1317 repo_name=repo.repo_name)
1316 1318
1317 1319 audit_logger.store_api(
1318 1320 'repo.delete', action_data={'old_data': old_data},
1319 1321 user=apiuser, repo=repo)
1320 1322
1321 1323 ScmModel().mark_for_invalidation(repo_name, delete=True)
1322 1324 Session().commit()
1323 1325 return {
1324 1326 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1325 1327 'success': True
1326 1328 }
1327 1329 except Exception:
1328 1330 log.exception("Exception occurred while trying to delete repo")
1329 1331 raise JSONRPCError(
1330 1332 'failed to delete repository `%s`' % (repo_name,)
1331 1333 )
1332 1334
1333 1335
1334 1336 #TODO: marcink, change name ?
1335 1337 @jsonrpc_method()
1336 1338 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1337 1339 """
1338 1340 Invalidates the cache for the specified repository.
1339 1341
1340 1342 This command can only be run using an |authtoken| with admin rights to
1341 1343 the specified repository.
1342 1344
1343 1345 This command takes the following options:
1344 1346
1345 1347 :param apiuser: This is filled automatically from |authtoken|.
1346 1348 :type apiuser: AuthUser
1347 1349 :param repoid: Sets the repository name or repository ID.
1348 1350 :type repoid: str or int
1349 1351 :param delete_keys: This deletes the invalidated keys instead of
1350 1352 just flagging them.
1351 1353 :type delete_keys: Optional(``True`` | ``False``)
1352 1354
1353 1355 Example output:
1354 1356
1355 1357 .. code-block:: bash
1356 1358
1357 1359 id : <id_given_in_input>
1358 1360 result : {
1359 1361 'msg': Cache for repository `<repository name>` was invalidated,
1360 1362 'repository': <repository name>
1361 1363 }
1362 1364 error : null
1363 1365
1364 1366 Example error output:
1365 1367
1366 1368 .. code-block:: bash
1367 1369
1368 1370 id : <id_given_in_input>
1369 1371 result : null
1370 1372 error : {
1371 1373 'Error occurred during cache invalidation action'
1372 1374 }
1373 1375
1374 1376 """
1375 1377
1376 1378 repo = get_repo_or_error(repoid)
1377 1379 if not has_superadmin_permission(apiuser):
1378 1380 _perms = ('repository.admin', 'repository.write',)
1379 1381 validate_repo_permissions(apiuser, repoid, repo, _perms)
1380 1382
1381 1383 delete = Optional.extract(delete_keys)
1382 1384 try:
1383 1385 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1384 1386 return {
1385 1387 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1386 1388 'repository': repo.repo_name
1387 1389 }
1388 1390 except Exception:
1389 1391 log.exception(
1390 1392 "Exception occurred while trying to invalidate repo cache")
1391 1393 raise JSONRPCError(
1392 1394 'Error occurred during cache invalidation action'
1393 1395 )
1394 1396
1395 1397
1396 1398 #TODO: marcink, change name ?
1397 1399 @jsonrpc_method()
1398 1400 def lock(request, apiuser, repoid, locked=Optional(None),
1399 1401 userid=Optional(OAttr('apiuser'))):
1400 1402 """
1401 1403 Sets the lock state of the specified |repo| by the given user.
1402 1404 From more information, see :ref:`repo-locking`.
1403 1405
1404 1406 * If the ``userid`` option is not set, the repository is locked to the
1405 1407 user who called the method.
1406 1408 * If the ``locked`` parameter is not set, the current lock state of the
1407 1409 repository is displayed.
1408 1410
1409 1411 This command can only be run using an |authtoken| with admin rights to
1410 1412 the specified repository.
1411 1413
1412 1414 This command takes the following options:
1413 1415
1414 1416 :param apiuser: This is filled automatically from the |authtoken|.
1415 1417 :type apiuser: AuthUser
1416 1418 :param repoid: Sets the repository name or repository ID.
1417 1419 :type repoid: str or int
1418 1420 :param locked: Sets the lock state.
1419 1421 :type locked: Optional(``True`` | ``False``)
1420 1422 :param userid: Set the repository lock to this user.
1421 1423 :type userid: Optional(str or int)
1422 1424
1423 1425 Example error output:
1424 1426
1425 1427 .. code-block:: bash
1426 1428
1427 1429 id : <id_given_in_input>
1428 1430 result : {
1429 1431 'repo': '<reponame>',
1430 1432 'locked': <bool: lock state>,
1431 1433 'locked_since': <int: lock timestamp>,
1432 1434 'locked_by': <username of person who made the lock>,
1433 1435 'lock_reason': <str: reason for locking>,
1434 1436 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1435 1437 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1436 1438 or
1437 1439 'msg': 'Repo `<repository name>` not locked.'
1438 1440 or
1439 1441 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1440 1442 }
1441 1443 error : null
1442 1444
1443 1445 Example error output:
1444 1446
1445 1447 .. code-block:: bash
1446 1448
1447 1449 id : <id_given_in_input>
1448 1450 result : null
1449 1451 error : {
1450 1452 'Error occurred locking repository `<reponame>`'
1451 1453 }
1452 1454 """
1453 1455
1454 1456 repo = get_repo_or_error(repoid)
1455 1457 if not has_superadmin_permission(apiuser):
1456 1458 # check if we have at least write permission for this repo !
1457 1459 _perms = ('repository.admin', 'repository.write',)
1458 1460 validate_repo_permissions(apiuser, repoid, repo, _perms)
1459 1461
1460 1462 # make sure normal user does not pass someone else userid,
1461 1463 # he is not allowed to do that
1462 1464 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1463 1465 raise JSONRPCError('userid is not the same as your user')
1464 1466
1465 1467 if isinstance(userid, Optional):
1466 1468 userid = apiuser.user_id
1467 1469
1468 1470 user = get_user_or_error(userid)
1469 1471
1470 1472 if isinstance(locked, Optional):
1471 1473 lockobj = repo.locked
1472 1474
1473 1475 if lockobj[0] is None:
1474 1476 _d = {
1475 1477 'repo': repo.repo_name,
1476 1478 'locked': False,
1477 1479 'locked_since': None,
1478 1480 'locked_by': None,
1479 1481 'lock_reason': None,
1480 1482 'lock_state_changed': False,
1481 1483 'msg': 'Repo `%s` not locked.' % repo.repo_name
1482 1484 }
1483 1485 return _d
1484 1486 else:
1485 1487 _user_id, _time, _reason = lockobj
1486 1488 lock_user = get_user_or_error(userid)
1487 1489 _d = {
1488 1490 'repo': repo.repo_name,
1489 1491 'locked': True,
1490 1492 'locked_since': _time,
1491 1493 'locked_by': lock_user.username,
1492 1494 'lock_reason': _reason,
1493 1495 'lock_state_changed': False,
1494 1496 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1495 1497 % (repo.repo_name, lock_user.username,
1496 1498 json.dumps(time_to_datetime(_time))))
1497 1499 }
1498 1500 return _d
1499 1501
1500 1502 # force locked state through a flag
1501 1503 else:
1502 1504 locked = str2bool(locked)
1503 1505 lock_reason = Repository.LOCK_API
1504 1506 try:
1505 1507 if locked:
1506 1508 lock_time = time.time()
1507 1509 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1508 1510 else:
1509 1511 lock_time = None
1510 1512 Repository.unlock(repo)
1511 1513 _d = {
1512 1514 'repo': repo.repo_name,
1513 1515 'locked': locked,
1514 1516 'locked_since': lock_time,
1515 1517 'locked_by': user.username,
1516 1518 'lock_reason': lock_reason,
1517 1519 'lock_state_changed': True,
1518 1520 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1519 1521 % (user.username, repo.repo_name, locked))
1520 1522 }
1521 1523 return _d
1522 1524 except Exception:
1523 1525 log.exception(
1524 1526 "Exception occurred while trying to lock repository")
1525 1527 raise JSONRPCError(
1526 1528 'Error occurred locking repository `%s`' % repo.repo_name
1527 1529 )
1528 1530
1529 1531
1530 1532 @jsonrpc_method()
1531 1533 def comment_commit(
1532 1534 request, apiuser, repoid, commit_id, message, status=Optional(None),
1533 1535 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1534 1536 resolves_comment_id=Optional(None),
1535 1537 userid=Optional(OAttr('apiuser'))):
1536 1538 """
1537 1539 Set a commit comment, and optionally change the status of the commit.
1538 1540
1539 1541 :param apiuser: This is filled automatically from the |authtoken|.
1540 1542 :type apiuser: AuthUser
1541 1543 :param repoid: Set the repository name or repository ID.
1542 1544 :type repoid: str or int
1543 1545 :param commit_id: Specify the commit_id for which to set a comment.
1544 1546 :type commit_id: str
1545 1547 :param message: The comment text.
1546 1548 :type message: str
1547 1549 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1548 1550 'approved', 'rejected', 'under_review'
1549 1551 :type status: str
1550 1552 :param comment_type: Comment type, one of: 'note', 'todo'
1551 1553 :type comment_type: Optional(str), default: 'note'
1552 1554 :param userid: Set the user name of the comment creator.
1553 1555 :type userid: Optional(str or int)
1554 1556
1555 1557 Example error output:
1556 1558
1557 1559 .. code-block:: bash
1558 1560
1559 1561 {
1560 1562 "id" : <id_given_in_input>,
1561 1563 "result" : {
1562 1564 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1563 1565 "status_change": null or <status>,
1564 1566 "success": true
1565 1567 },
1566 1568 "error" : null
1567 1569 }
1568 1570
1569 1571 """
1570 1572 repo = get_repo_or_error(repoid)
1571 1573 if not has_superadmin_permission(apiuser):
1572 1574 _perms = ('repository.read', 'repository.write', 'repository.admin')
1573 1575 validate_repo_permissions(apiuser, repoid, repo, _perms)
1574 1576
1575 1577 try:
1576 1578 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1577 1579 except Exception as e:
1578 1580 log.exception('Failed to fetch commit')
1579 1581 raise JSONRPCError(safe_str(e))
1580 1582
1581 1583 if isinstance(userid, Optional):
1582 1584 userid = apiuser.user_id
1583 1585
1584 1586 user = get_user_or_error(userid)
1585 1587 status = Optional.extract(status)
1586 1588 comment_type = Optional.extract(comment_type)
1587 1589 resolves_comment_id = Optional.extract(resolves_comment_id)
1588 1590
1589 1591 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1590 1592 if status and status not in allowed_statuses:
1591 1593 raise JSONRPCError('Bad status, must be on '
1592 1594 'of %s got %s' % (allowed_statuses, status,))
1593 1595
1594 1596 if resolves_comment_id:
1595 1597 comment = ChangesetComment.get(resolves_comment_id)
1596 1598 if not comment:
1597 1599 raise JSONRPCError(
1598 1600 'Invalid resolves_comment_id `%s` for this commit.'
1599 1601 % resolves_comment_id)
1600 1602 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1601 1603 raise JSONRPCError(
1602 1604 'Comment `%s` is wrong type for setting status to resolved.'
1603 1605 % resolves_comment_id)
1604 1606
1605 1607 try:
1606 1608 rc_config = SettingsModel().get_all_settings()
1607 1609 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1608 1610 status_change_label = ChangesetStatus.get_status_lbl(status)
1609 1611 comment = CommentsModel().create(
1610 1612 message, repo, user, commit_id=commit_id,
1611 1613 status_change=status_change_label,
1612 1614 status_change_type=status,
1613 1615 renderer=renderer,
1614 1616 comment_type=comment_type,
1615 1617 resolves_comment_id=resolves_comment_id,
1616 1618 auth_user=apiuser
1617 1619 )
1618 1620 if status:
1619 1621 # also do a status change
1620 1622 try:
1621 1623 ChangesetStatusModel().set_status(
1622 1624 repo, status, user, comment, revision=commit_id,
1623 1625 dont_allow_on_closed_pull_request=True
1624 1626 )
1625 1627 except StatusChangeOnClosedPullRequestError:
1626 1628 log.exception(
1627 1629 "Exception occurred while trying to change repo commit status")
1628 1630 msg = ('Changing status on a changeset associated with '
1629 1631 'a closed pull request is not allowed')
1630 1632 raise JSONRPCError(msg)
1631 1633
1632 1634 Session().commit()
1633 1635 return {
1634 1636 'msg': (
1635 1637 'Commented on commit `%s` for repository `%s`' % (
1636 1638 comment.revision, repo.repo_name)),
1637 1639 'status_change': status,
1638 1640 'success': True,
1639 1641 }
1640 1642 except JSONRPCError:
1641 1643 # catch any inside errors, and re-raise them to prevent from
1642 1644 # below global catch to silence them
1643 1645 raise
1644 1646 except Exception:
1645 1647 log.exception("Exception occurred while trying to comment on commit")
1646 1648 raise JSONRPCError(
1647 1649 'failed to set comment on repository `%s`' % (repo.repo_name,)
1648 1650 )
1649 1651
1650 1652
1651 1653 @jsonrpc_method()
1652 1654 def get_repo_comments(request, apiuser, repoid,
1653 1655 commit_id=Optional(None), comment_type=Optional(None),
1654 1656 userid=Optional(None)):
1655 1657 """
1656 1658 Get all comments for a repository
1657 1659
1658 1660 :param apiuser: This is filled automatically from the |authtoken|.
1659 1661 :type apiuser: AuthUser
1660 1662 :param repoid: Set the repository name or repository ID.
1661 1663 :type repoid: str or int
1662 1664 :param commit_id: Optionally filter the comments by the commit_id
1663 1665 :type commit_id: Optional(str), default: None
1664 1666 :param comment_type: Optionally filter the comments by the comment_type
1665 1667 one of: 'note', 'todo'
1666 1668 :type comment_type: Optional(str), default: None
1667 1669 :param userid: Optionally filter the comments by the author of comment
1668 1670 :type userid: Optional(str or int), Default: None
1669 1671
1670 1672 Example error output:
1671 1673
1672 1674 .. code-block:: bash
1673 1675
1674 1676 {
1675 1677 "id" : <id_given_in_input>,
1676 1678 "result" : [
1677 1679 {
1678 1680 "comment_author": <USER_DETAILS>,
1679 1681 "comment_created_on": "2017-02-01T14:38:16.309",
1680 1682 "comment_f_path": "file.txt",
1681 1683 "comment_id": 282,
1682 1684 "comment_lineno": "n1",
1683 1685 "comment_resolved_by": null,
1684 1686 "comment_status": [],
1685 1687 "comment_text": "This file needs a header",
1686 1688 "comment_type": "todo"
1687 1689 }
1688 1690 ],
1689 1691 "error" : null
1690 1692 }
1691 1693
1692 1694 """
1693 1695 repo = get_repo_or_error(repoid)
1694 1696 if not has_superadmin_permission(apiuser):
1695 1697 _perms = ('repository.read', 'repository.write', 'repository.admin')
1696 1698 validate_repo_permissions(apiuser, repoid, repo, _perms)
1697 1699
1698 1700 commit_id = Optional.extract(commit_id)
1699 1701
1700 1702 userid = Optional.extract(userid)
1701 1703 if userid:
1702 1704 user = get_user_or_error(userid)
1703 1705 else:
1704 1706 user = None
1705 1707
1706 1708 comment_type = Optional.extract(comment_type)
1707 1709 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1708 1710 raise JSONRPCError(
1709 1711 'comment_type must be one of `{}` got {}'.format(
1710 1712 ChangesetComment.COMMENT_TYPES, comment_type)
1711 1713 )
1712 1714
1713 1715 comments = CommentsModel().get_repository_comments(
1714 1716 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1715 1717 return comments
1716 1718
1717 1719
1718 1720 @jsonrpc_method()
1719 1721 def grant_user_permission(request, apiuser, repoid, userid, perm):
1720 1722 """
1721 1723 Grant permissions for the specified user on the given repository,
1722 1724 or update existing permissions if found.
1723 1725
1724 1726 This command can only be run using an |authtoken| with admin
1725 1727 permissions on the |repo|.
1726 1728
1727 1729 :param apiuser: This is filled automatically from the |authtoken|.
1728 1730 :type apiuser: AuthUser
1729 1731 :param repoid: Set the repository name or repository ID.
1730 1732 :type repoid: str or int
1731 1733 :param userid: Set the user name.
1732 1734 :type userid: str
1733 1735 :param perm: Set the user permissions, using the following format
1734 1736 ``(repository.(none|read|write|admin))``
1735 1737 :type perm: str
1736 1738
1737 1739 Example output:
1738 1740
1739 1741 .. code-block:: bash
1740 1742
1741 1743 id : <id_given_in_input>
1742 1744 result: {
1743 1745 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1744 1746 "success": true
1745 1747 }
1746 1748 error: null
1747 1749 """
1748 1750
1749 1751 repo = get_repo_or_error(repoid)
1750 1752 user = get_user_or_error(userid)
1751 1753 perm = get_perm_or_error(perm)
1752 1754 if not has_superadmin_permission(apiuser):
1753 1755 _perms = ('repository.admin',)
1754 1756 validate_repo_permissions(apiuser, repoid, repo, _perms)
1755 1757
1756 1758 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1757 1759 try:
1758 1760 changes = RepoModel().update_permissions(
1759 1761 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1760 1762
1761 1763 action_data = {
1762 1764 'added': changes['added'],
1763 1765 'updated': changes['updated'],
1764 1766 'deleted': changes['deleted'],
1765 1767 }
1766 1768 audit_logger.store_api(
1767 1769 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1768 1770
1769 1771 Session().commit()
1770 1772 return {
1771 1773 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1772 1774 perm.permission_name, user.username, repo.repo_name
1773 1775 ),
1774 1776 'success': True
1775 1777 }
1776 1778 except Exception:
1777 1779 log.exception("Exception occurred while trying edit permissions for repo")
1778 1780 raise JSONRPCError(
1779 1781 'failed to edit permission for user: `%s` in repo: `%s`' % (
1780 1782 userid, repoid
1781 1783 )
1782 1784 )
1783 1785
1784 1786
1785 1787 @jsonrpc_method()
1786 1788 def revoke_user_permission(request, apiuser, repoid, userid):
1787 1789 """
1788 1790 Revoke permission for a user on the specified repository.
1789 1791
1790 1792 This command can only be run using an |authtoken| with admin
1791 1793 permissions on the |repo|.
1792 1794
1793 1795 :param apiuser: This is filled automatically from the |authtoken|.
1794 1796 :type apiuser: AuthUser
1795 1797 :param repoid: Set the repository name or repository ID.
1796 1798 :type repoid: str or int
1797 1799 :param userid: Set the user name of revoked user.
1798 1800 :type userid: str or int
1799 1801
1800 1802 Example error output:
1801 1803
1802 1804 .. code-block:: bash
1803 1805
1804 1806 id : <id_given_in_input>
1805 1807 result: {
1806 1808 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1807 1809 "success": true
1808 1810 }
1809 1811 error: null
1810 1812 """
1811 1813
1812 1814 repo = get_repo_or_error(repoid)
1813 1815 user = get_user_or_error(userid)
1814 1816 if not has_superadmin_permission(apiuser):
1815 1817 _perms = ('repository.admin',)
1816 1818 validate_repo_permissions(apiuser, repoid, repo, _perms)
1817 1819
1818 1820 perm_deletions = [[user.user_id, None, "user"]]
1819 1821 try:
1820 1822 changes = RepoModel().update_permissions(
1821 1823 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1822 1824
1823 1825 action_data = {
1824 1826 'added': changes['added'],
1825 1827 'updated': changes['updated'],
1826 1828 'deleted': changes['deleted'],
1827 1829 }
1828 1830 audit_logger.store_api(
1829 1831 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1830 1832
1831 1833 Session().commit()
1832 1834 return {
1833 1835 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1834 1836 user.username, repo.repo_name
1835 1837 ),
1836 1838 'success': True
1837 1839 }
1838 1840 except Exception:
1839 1841 log.exception("Exception occurred while trying revoke permissions to repo")
1840 1842 raise JSONRPCError(
1841 1843 'failed to edit permission for user: `%s` in repo: `%s`' % (
1842 1844 userid, repoid
1843 1845 )
1844 1846 )
1845 1847
1846 1848
1847 1849 @jsonrpc_method()
1848 1850 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1849 1851 """
1850 1852 Grant permission for a user group on the specified repository,
1851 1853 or update existing permissions.
1852 1854
1853 1855 This command can only be run using an |authtoken| with admin
1854 1856 permissions on the |repo|.
1855 1857
1856 1858 :param apiuser: This is filled automatically from the |authtoken|.
1857 1859 :type apiuser: AuthUser
1858 1860 :param repoid: Set the repository name or repository ID.
1859 1861 :type repoid: str or int
1860 1862 :param usergroupid: Specify the ID of the user group.
1861 1863 :type usergroupid: str or int
1862 1864 :param perm: Set the user group permissions using the following
1863 1865 format: (repository.(none|read|write|admin))
1864 1866 :type perm: str
1865 1867
1866 1868 Example output:
1867 1869
1868 1870 .. code-block:: bash
1869 1871
1870 1872 id : <id_given_in_input>
1871 1873 result : {
1872 1874 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1873 1875 "success": true
1874 1876
1875 1877 }
1876 1878 error : null
1877 1879
1878 1880 Example error output:
1879 1881
1880 1882 .. code-block:: bash
1881 1883
1882 1884 id : <id_given_in_input>
1883 1885 result : null
1884 1886 error : {
1885 1887 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1886 1888 }
1887 1889
1888 1890 """
1889 1891
1890 1892 repo = get_repo_or_error(repoid)
1891 1893 perm = get_perm_or_error(perm)
1892 1894 if not has_superadmin_permission(apiuser):
1893 1895 _perms = ('repository.admin',)
1894 1896 validate_repo_permissions(apiuser, repoid, repo, _perms)
1895 1897
1896 1898 user_group = get_user_group_or_error(usergroupid)
1897 1899 if not has_superadmin_permission(apiuser):
1898 1900 # check if we have at least read permission for this user group !
1899 1901 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1900 1902 if not HasUserGroupPermissionAnyApi(*_perms)(
1901 1903 user=apiuser, user_group_name=user_group.users_group_name):
1902 1904 raise JSONRPCError(
1903 1905 'user group `%s` does not exist' % (usergroupid,))
1904 1906
1905 1907 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1906 1908 try:
1907 1909 changes = RepoModel().update_permissions(
1908 1910 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1909 1911 action_data = {
1910 1912 'added': changes['added'],
1911 1913 'updated': changes['updated'],
1912 1914 'deleted': changes['deleted'],
1913 1915 }
1914 1916 audit_logger.store_api(
1915 1917 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1916 1918
1917 1919 Session().commit()
1918 1920 return {
1919 1921 'msg': 'Granted perm: `%s` for user group: `%s` in '
1920 1922 'repo: `%s`' % (
1921 1923 perm.permission_name, user_group.users_group_name,
1922 1924 repo.repo_name
1923 1925 ),
1924 1926 'success': True
1925 1927 }
1926 1928 except Exception:
1927 1929 log.exception(
1928 1930 "Exception occurred while trying change permission on repo")
1929 1931 raise JSONRPCError(
1930 1932 'failed to edit permission for user group: `%s` in '
1931 1933 'repo: `%s`' % (
1932 1934 usergroupid, repo.repo_name
1933 1935 )
1934 1936 )
1935 1937
1936 1938
1937 1939 @jsonrpc_method()
1938 1940 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1939 1941 """
1940 1942 Revoke the permissions of a user group on a given repository.
1941 1943
1942 1944 This command can only be run using an |authtoken| with admin
1943 1945 permissions on the |repo|.
1944 1946
1945 1947 :param apiuser: This is filled automatically from the |authtoken|.
1946 1948 :type apiuser: AuthUser
1947 1949 :param repoid: Set the repository name or repository ID.
1948 1950 :type repoid: str or int
1949 1951 :param usergroupid: Specify the user group ID.
1950 1952 :type usergroupid: str or int
1951 1953
1952 1954 Example output:
1953 1955
1954 1956 .. code-block:: bash
1955 1957
1956 1958 id : <id_given_in_input>
1957 1959 result: {
1958 1960 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1959 1961 "success": true
1960 1962 }
1961 1963 error: null
1962 1964 """
1963 1965
1964 1966 repo = get_repo_or_error(repoid)
1965 1967 if not has_superadmin_permission(apiuser):
1966 1968 _perms = ('repository.admin',)
1967 1969 validate_repo_permissions(apiuser, repoid, repo, _perms)
1968 1970
1969 1971 user_group = get_user_group_or_error(usergroupid)
1970 1972 if not has_superadmin_permission(apiuser):
1971 1973 # check if we have at least read permission for this user group !
1972 1974 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1973 1975 if not HasUserGroupPermissionAnyApi(*_perms)(
1974 1976 user=apiuser, user_group_name=user_group.users_group_name):
1975 1977 raise JSONRPCError(
1976 1978 'user group `%s` does not exist' % (usergroupid,))
1977 1979
1978 1980 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
1979 1981 try:
1980 1982 changes = RepoModel().update_permissions(
1981 1983 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
1982 1984 action_data = {
1983 1985 'added': changes['added'],
1984 1986 'updated': changes['updated'],
1985 1987 'deleted': changes['deleted'],
1986 1988 }
1987 1989 audit_logger.store_api(
1988 1990 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1989 1991
1990 1992 Session().commit()
1991 1993 return {
1992 1994 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1993 1995 user_group.users_group_name, repo.repo_name
1994 1996 ),
1995 1997 'success': True
1996 1998 }
1997 1999 except Exception:
1998 2000 log.exception("Exception occurred while trying revoke "
1999 2001 "user group permission on repo")
2000 2002 raise JSONRPCError(
2001 2003 'failed to edit permission for user group: `%s` in '
2002 2004 'repo: `%s`' % (
2003 2005 user_group.users_group_name, repo.repo_name
2004 2006 )
2005 2007 )
2006 2008
2007 2009
2008 2010 @jsonrpc_method()
2009 2011 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2010 2012 """
2011 2013 Triggers a pull on the given repository from a remote location. You
2012 2014 can use this to keep remote repositories up-to-date.
2013 2015
2014 2016 This command can only be run using an |authtoken| with admin
2015 2017 rights to the specified repository. For more information,
2016 2018 see :ref:`config-token-ref`.
2017 2019
2018 2020 This command takes the following options:
2019 2021
2020 2022 :param apiuser: This is filled automatically from the |authtoken|.
2021 2023 :type apiuser: AuthUser
2022 2024 :param repoid: The repository name or repository ID.
2023 2025 :type repoid: str or int
2024 2026 :param remote_uri: Optional remote URI to pass in for pull
2025 2027 :type remote_uri: str
2026 2028
2027 2029 Example output:
2028 2030
2029 2031 .. code-block:: bash
2030 2032
2031 2033 id : <id_given_in_input>
2032 2034 result : {
2033 2035 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2034 2036 "repository": "<repository name>"
2035 2037 }
2036 2038 error : null
2037 2039
2038 2040 Example error output:
2039 2041
2040 2042 .. code-block:: bash
2041 2043
2042 2044 id : <id_given_in_input>
2043 2045 result : null
2044 2046 error : {
2045 2047 "Unable to push changes from `<remote_url>`"
2046 2048 }
2047 2049
2048 2050 """
2049 2051
2050 2052 repo = get_repo_or_error(repoid)
2051 2053 remote_uri = Optional.extract(remote_uri)
2052 2054 remote_uri_display = remote_uri or repo.clone_uri_hidden
2053 2055 if not has_superadmin_permission(apiuser):
2054 2056 _perms = ('repository.admin',)
2055 2057 validate_repo_permissions(apiuser, repoid, repo, _perms)
2056 2058
2057 2059 try:
2058 2060 ScmModel().pull_changes(
2059 2061 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2060 2062 return {
2061 2063 'msg': 'Pulled from url `%s` on repo `%s`' % (
2062 2064 remote_uri_display, repo.repo_name),
2063 2065 'repository': repo.repo_name
2064 2066 }
2065 2067 except Exception:
2066 2068 log.exception("Exception occurred while trying to "
2067 2069 "pull changes from remote location")
2068 2070 raise JSONRPCError(
2069 2071 'Unable to pull changes from `%s`' % remote_uri_display
2070 2072 )
2071 2073
2072 2074
2073 2075 @jsonrpc_method()
2074 2076 def strip(request, apiuser, repoid, revision, branch):
2075 2077 """
2076 2078 Strips the given revision from the specified repository.
2077 2079
2078 2080 * This will remove the revision and all of its decendants.
2079 2081
2080 2082 This command can only be run using an |authtoken| with admin rights to
2081 2083 the specified repository.
2082 2084
2083 2085 This command takes the following options:
2084 2086
2085 2087 :param apiuser: This is filled automatically from the |authtoken|.
2086 2088 :type apiuser: AuthUser
2087 2089 :param repoid: The repository name or repository ID.
2088 2090 :type repoid: str or int
2089 2091 :param revision: The revision you wish to strip.
2090 2092 :type revision: str
2091 2093 :param branch: The branch from which to strip the revision.
2092 2094 :type branch: str
2093 2095
2094 2096 Example output:
2095 2097
2096 2098 .. code-block:: bash
2097 2099
2098 2100 id : <id_given_in_input>
2099 2101 result : {
2100 2102 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2101 2103 "repository": "<repository name>"
2102 2104 }
2103 2105 error : null
2104 2106
2105 2107 Example error output:
2106 2108
2107 2109 .. code-block:: bash
2108 2110
2109 2111 id : <id_given_in_input>
2110 2112 result : null
2111 2113 error : {
2112 2114 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2113 2115 }
2114 2116
2115 2117 """
2116 2118
2117 2119 repo = get_repo_or_error(repoid)
2118 2120 if not has_superadmin_permission(apiuser):
2119 2121 _perms = ('repository.admin',)
2120 2122 validate_repo_permissions(apiuser, repoid, repo, _perms)
2121 2123
2122 2124 try:
2123 2125 ScmModel().strip(repo, revision, branch)
2124 2126 audit_logger.store_api(
2125 2127 'repo.commit.strip', action_data={'commit_id': revision},
2126 2128 repo=repo,
2127 2129 user=apiuser, commit=True)
2128 2130
2129 2131 return {
2130 2132 'msg': 'Stripped commit %s from repo `%s`' % (
2131 2133 revision, repo.repo_name),
2132 2134 'repository': repo.repo_name
2133 2135 }
2134 2136 except Exception:
2135 2137 log.exception("Exception while trying to strip")
2136 2138 raise JSONRPCError(
2137 2139 'Unable to strip commit %s from repo `%s`' % (
2138 2140 revision, repo.repo_name)
2139 2141 )
2140 2142
2141 2143
2142 2144 @jsonrpc_method()
2143 2145 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2144 2146 """
2145 2147 Returns all settings for a repository. If key is given it only returns the
2146 2148 setting identified by the key or null.
2147 2149
2148 2150 :param apiuser: This is filled automatically from the |authtoken|.
2149 2151 :type apiuser: AuthUser
2150 2152 :param repoid: The repository name or repository id.
2151 2153 :type repoid: str or int
2152 2154 :param key: Key of the setting to return.
2153 2155 :type: key: Optional(str)
2154 2156
2155 2157 Example output:
2156 2158
2157 2159 .. code-block:: bash
2158 2160
2159 2161 {
2160 2162 "error": null,
2161 2163 "id": 237,
2162 2164 "result": {
2163 2165 "extensions_largefiles": true,
2164 2166 "extensions_evolve": true,
2165 2167 "hooks_changegroup_push_logger": true,
2166 2168 "hooks_changegroup_repo_size": false,
2167 2169 "hooks_outgoing_pull_logger": true,
2168 2170 "phases_publish": "True",
2169 2171 "rhodecode_hg_use_rebase_for_merging": true,
2170 2172 "rhodecode_pr_merge_enabled": true,
2171 2173 "rhodecode_use_outdated_comments": true
2172 2174 }
2173 2175 }
2174 2176 """
2175 2177
2176 2178 # Restrict access to this api method to admins only.
2177 2179 if not has_superadmin_permission(apiuser):
2178 2180 raise JSONRPCForbidden()
2179 2181
2180 2182 try:
2181 2183 repo = get_repo_or_error(repoid)
2182 2184 settings_model = VcsSettingsModel(repo=repo)
2183 2185 settings = settings_model.get_global_settings()
2184 2186 settings.update(settings_model.get_repo_settings())
2185 2187
2186 2188 # If only a single setting is requested fetch it from all settings.
2187 2189 key = Optional.extract(key)
2188 2190 if key is not None:
2189 2191 settings = settings.get(key, None)
2190 2192 except Exception:
2191 2193 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2192 2194 log.exception(msg)
2193 2195 raise JSONRPCError(msg)
2194 2196
2195 2197 return settings
2196 2198
2197 2199
2198 2200 @jsonrpc_method()
2199 2201 def set_repo_settings(request, apiuser, repoid, settings):
2200 2202 """
2201 2203 Update repository settings. Returns true on success.
2202 2204
2203 2205 :param apiuser: This is filled automatically from the |authtoken|.
2204 2206 :type apiuser: AuthUser
2205 2207 :param repoid: The repository name or repository id.
2206 2208 :type repoid: str or int
2207 2209 :param settings: The new settings for the repository.
2208 2210 :type: settings: dict
2209 2211
2210 2212 Example output:
2211 2213
2212 2214 .. code-block:: bash
2213 2215
2214 2216 {
2215 2217 "error": null,
2216 2218 "id": 237,
2217 2219 "result": true
2218 2220 }
2219 2221 """
2220 2222 # Restrict access to this api method to admins only.
2221 2223 if not has_superadmin_permission(apiuser):
2222 2224 raise JSONRPCForbidden()
2223 2225
2224 2226 if type(settings) is not dict:
2225 2227 raise JSONRPCError('Settings have to be a JSON Object.')
2226 2228
2227 2229 try:
2228 2230 settings_model = VcsSettingsModel(repo=repoid)
2229 2231
2230 2232 # Merge global, repo and incoming settings.
2231 2233 new_settings = settings_model.get_global_settings()
2232 2234 new_settings.update(settings_model.get_repo_settings())
2233 2235 new_settings.update(settings)
2234 2236
2235 2237 # Update the settings.
2236 2238 inherit_global_settings = new_settings.get(
2237 2239 'inherit_global_settings', False)
2238 2240 settings_model.create_or_update_repo_settings(
2239 2241 new_settings, inherit_global_settings=inherit_global_settings)
2240 2242 Session().commit()
2241 2243 except Exception:
2242 2244 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2243 2245 log.exception(msg)
2244 2246 raise JSONRPCError(msg)
2245 2247
2246 2248 # Indicate success.
2247 2249 return True
2248 2250
2249 2251
2250 2252 @jsonrpc_method()
2251 2253 def maintenance(request, apiuser, repoid):
2252 2254 """
2253 2255 Triggers a maintenance on the given repository.
2254 2256
2255 2257 This command can only be run using an |authtoken| with admin
2256 2258 rights to the specified repository. For more information,
2257 2259 see :ref:`config-token-ref`.
2258 2260
2259 2261 This command takes the following options:
2260 2262
2261 2263 :param apiuser: This is filled automatically from the |authtoken|.
2262 2264 :type apiuser: AuthUser
2263 2265 :param repoid: The repository name or repository ID.
2264 2266 :type repoid: str or int
2265 2267
2266 2268 Example output:
2267 2269
2268 2270 .. code-block:: bash
2269 2271
2270 2272 id : <id_given_in_input>
2271 2273 result : {
2272 2274 "msg": "executed maintenance command",
2273 2275 "executed_actions": [
2274 2276 <action_message>, <action_message2>...
2275 2277 ],
2276 2278 "repository": "<repository name>"
2277 2279 }
2278 2280 error : null
2279 2281
2280 2282 Example error output:
2281 2283
2282 2284 .. code-block:: bash
2283 2285
2284 2286 id : <id_given_in_input>
2285 2287 result : null
2286 2288 error : {
2287 2289 "Unable to execute maintenance on `<reponame>`"
2288 2290 }
2289 2291
2290 2292 """
2291 2293
2292 2294 repo = get_repo_or_error(repoid)
2293 2295 if not has_superadmin_permission(apiuser):
2294 2296 _perms = ('repository.admin',)
2295 2297 validate_repo_permissions(apiuser, repoid, repo, _perms)
2296 2298
2297 2299 try:
2298 2300 maintenance = repo_maintenance.RepoMaintenance()
2299 2301 executed_actions = maintenance.execute(repo)
2300 2302
2301 2303 return {
2302 2304 'msg': 'executed maintenance command',
2303 2305 'executed_actions': executed_actions,
2304 2306 'repository': repo.repo_name
2305 2307 }
2306 2308 except Exception:
2307 2309 log.exception("Exception occurred while trying to run maintenance")
2308 2310 raise JSONRPCError(
2309 2311 'Unable to execute maintenance on `%s`' % repo.repo_name)
General Comments 0
You need to be logged in to leave comments. Login now