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