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