# -*- coding: utf-8 -*- # Copyright (C) 2011-2018 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License, version 3 # (only), as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # # This program is dual-licensed. If you wish to learn more about the # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ import logging import time import rhodecode from rhodecode.api import ( jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError) from rhodecode.api.utils import ( has_superadmin_permission, Optional, OAttr, get_repo_or_error, get_user_group_or_error, get_user_or_error, validate_repo_permissions, get_perm_or_error, parse_args, get_origin, build_commit_data, validate_set_owner_permissions) from rhodecode.lib import audit_logger from rhodecode.lib import repo_maintenance from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi from rhodecode.lib.celerylib.utils import get_task_id from rhodecode.lib.utils2 import str2bool, time_to_datetime from rhodecode.lib.ext_json import json from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError from rhodecode.model.changeset_status import ChangesetStatusModel from rhodecode.model.comment import CommentsModel from rhodecode.model.db import ( Session, ChangesetStatus, RepositoryField, Repository, RepoGroup, ChangesetComment) from rhodecode.model.repo import RepoModel from rhodecode.model.scm import ScmModel, RepoList from rhodecode.model.settings import SettingsModel, VcsSettingsModel from rhodecode.model import validation_schema from rhodecode.model.validation_schema.schemas import repo_schema log = logging.getLogger(__name__) @jsonrpc_method() def get_repo(request, apiuser, repoid, cache=Optional(True)): """ Gets an existing repository by its name or repository_id. The members section so the output returns users groups or users associated with that repository. This command can only be run using an |authtoken| with admin rights, or users with at least read rights to the |repo|. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: The repository name or repository id. :type repoid: str or int :param cache: use the cached value for last changeset :type: cache: Optional(bool) Example output: .. code-block:: bash { "error": null, "id": , "result": { "clone_uri": null, "created_on": "timestamp", "description": "repo description", "enable_downloads": false, "enable_locking": false, "enable_statistics": false, "followers": [ { "active": true, "admin": false, "api_key": "****************************************", "api_keys": [ "****************************************" ], "email": "user@example.com", "emails": [ "user@example.com" ], "extern_name": "rhodecode", "extern_type": "rhodecode", "firstname": "username", "ip_addresses": [], "language": null, "last_login": "2015-09-16T17:16:35.854", "lastname": "surname", "user_id": , "username": "name" } ], "fork_of": "parent-repo", "landing_rev": [ "rev", "tip" ], "last_changeset": { "author": "User ", "branch": "default", "date": "timestamp", "message": "last commit message", "parents": [ { "raw_id": "commit-id" } ], "raw_id": "commit-id", "revision": , "short_id": "short id" }, "lock_reason": null, "locked_by": null, "locked_date": null, "owner": "owner-name", "permissions": [ { "name": "super-admin-name", "origin": "super-admin", "permission": "repository.admin", "type": "user" }, { "name": "owner-name", "origin": "owner", "permission": "repository.admin", "type": "user" }, { "name": "user-group-name", "origin": "permission", "permission": "repository.write", "type": "user_group" } ], "private": true, "repo_id": 676, "repo_name": "user-group/repo-name", "repo_type": "hg" } } """ repo = get_repo_or_error(repoid) cache = Optional.extract(cache) include_secrets = False if has_superadmin_permission(apiuser): include_secrets = True else: # check if we have at least read permission for this repo ! _perms = ( 'repository.admin', 'repository.write', 'repository.read',) validate_repo_permissions(apiuser, repoid, repo, _perms) permissions = [] for _user in repo.permissions(): user_data = { 'name': _user.username, 'permission': _user.permission, 'origin': get_origin(_user), 'type': "user", } permissions.append(user_data) for _user_group in repo.permission_user_groups(): user_group_data = { 'name': _user_group.users_group_name, 'permission': _user_group.permission, 'origin': get_origin(_user_group), 'type': "user_group", } permissions.append(user_group_data) following_users = [ user.user.get_api_data(include_secrets=include_secrets) for user in repo.followers] if not cache: repo.update_commit_cache() data = repo.get_api_data(include_secrets=include_secrets) data['permissions'] = permissions data['followers'] = following_users return data @jsonrpc_method() def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)): """ Lists all existing repositories. This command can only be run using an |authtoken| with admin rights, or users with at least read rights to |repos|. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param root: specify root repository group to fetch repositories. filters the returned repositories to be members of given root group. :type root: Optional(None) :param traverse: traverse given root into subrepositories. With this flag set to False, it will only return top-level repositories from `root`. if root is empty it will return just top-level repositories. :type traverse: Optional(True) Example output: .. code-block:: bash id : result: [ { "repo_id" : "", "repo_name" : "" "repo_type" : "", "clone_uri" : "", "private": : "", "created_on" : "", "description" : "", "landing_rev": "", "owner": "", "fork_of": "", "enable_downloads": "", "enable_locking": "", "enable_statistics": "", }, ... ] error: null """ include_secrets = has_superadmin_permission(apiuser) _perms = ('repository.read', 'repository.write', 'repository.admin',) extras = {'user': apiuser} root = Optional.extract(root) traverse = Optional.extract(traverse, binary=True) if root: # verify parent existance, if it's empty return an error parent = RepoGroup.get_by_group_name(root) if not parent: raise JSONRPCError( 'Root repository group `{}` does not exist'.format(root)) if traverse: repos = RepoModel().get_repos_for_root(root=root, traverse=traverse) else: repos = RepoModel().get_repos_for_root(root=parent) else: if traverse: repos = RepoModel().get_all() else: # return just top-level repos = RepoModel().get_repos_for_root(root=None) repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras) return [repo.get_api_data(include_secrets=include_secrets) for repo in repo_list] @jsonrpc_method() def get_repo_changeset(request, apiuser, repoid, revision, details=Optional('basic')): """ Returns information about a changeset. Additionally parameters define the amount of details returned by this function. This command can only be run using an |authtoken| with admin rights, or users with at least read rights to the |repo|. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: The repository name or repository id :type repoid: str or int :param revision: revision for which listing should be done :type revision: str :param details: details can be 'basic|extended|full' full gives diff info details like the diff itself, and number of changed files etc. :type details: Optional(str) """ repo = get_repo_or_error(repoid) if not has_superadmin_permission(apiuser): _perms = ( 'repository.admin', 'repository.write', 'repository.read',) validate_repo_permissions(apiuser, repoid, repo, _perms) changes_details = Optional.extract(details) _changes_details_types = ['basic', 'extended', 'full'] if changes_details not in _changes_details_types: raise JSONRPCError( 'ret_type must be one of %s' % ( ','.join(_changes_details_types))) pre_load = ['author', 'branch', 'date', 'message', 'parents', 'status', '_commit', '_file_paths'] try: cs = repo.get_commit(commit_id=revision, pre_load=pre_load) except TypeError as e: raise JSONRPCError(e.message) _cs_json = cs.__json__() _cs_json['diff'] = build_commit_data(cs, changes_details) if changes_details == 'full': _cs_json['refs'] = cs._get_refs() return _cs_json @jsonrpc_method() def get_repo_changesets(request, apiuser, repoid, start_rev, limit, details=Optional('basic')): """ Returns a set of commits limited by the number starting from the `start_rev` option. Additional parameters define the amount of details returned by this function. This command can only be run using an |authtoken| with admin rights, or users with at least read rights to |repos|. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: The repository name or repository ID. :type repoid: str or int :param start_rev: The starting revision from where to get changesets. :type start_rev: str :param limit: Limit the number of commits to this amount :type limit: str or int :param details: Set the level of detail returned. Valid option are: ``basic``, ``extended`` and ``full``. :type details: Optional(str) .. note:: Setting the parameter `details` to the value ``full`` is extensive and returns details like the diff itself, and the number of changed files. """ repo = get_repo_or_error(repoid) if not has_superadmin_permission(apiuser): _perms = ( 'repository.admin', 'repository.write', 'repository.read',) validate_repo_permissions(apiuser, repoid, repo, _perms) changes_details = Optional.extract(details) _changes_details_types = ['basic', 'extended', 'full'] if changes_details not in _changes_details_types: raise JSONRPCError( 'ret_type must be one of %s' % ( ','.join(_changes_details_types))) limit = int(limit) pre_load = ['author', 'branch', 'date', 'message', 'parents', 'status', '_commit', '_file_paths'] vcs_repo = repo.scm_instance() # SVN needs a special case to distinguish its index and commit id if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'): start_rev = vcs_repo.commit_ids[0] try: commits = vcs_repo.get_commits( start_id=start_rev, pre_load=pre_load) except TypeError as e: raise JSONRPCError(e.message) except Exception: log.exception('Fetching of commits failed') raise JSONRPCError('Error occurred during commit fetching') ret = [] for cnt, commit in enumerate(commits): if cnt >= limit != -1: break _cs_json = commit.__json__() _cs_json['diff'] = build_commit_data(commit, changes_details) if changes_details == 'full': _cs_json['refs'] = { 'branches': [commit.branch], 'bookmarks': getattr(commit, 'bookmarks', []), 'tags': commit.tags } ret.append(_cs_json) return ret @jsonrpc_method() def get_repo_nodes(request, apiuser, repoid, revision, root_path, ret_type=Optional('all'), details=Optional('basic'), max_file_bytes=Optional(None)): """ Returns a list of nodes and children in a flat list for a given path at given revision. It's possible to specify ret_type to show only `files` or `dirs`. This command can only be run using an |authtoken| with admin rights, or users with at least read rights to |repos|. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: The repository name or repository ID. :type repoid: str or int :param revision: The revision for which listing should be done. :type revision: str :param root_path: The path from which to start displaying. :type root_path: str :param ret_type: Set the return type. Valid options are ``all`` (default), ``files`` and ``dirs``. :type ret_type: Optional(str) :param details: Returns extended information about nodes, such as md5, binary, and or content. The valid options are ``basic`` and ``full``. :type details: Optional(str) :param max_file_bytes: Only return file content under this file size bytes :type details: Optional(int) Example output: .. code-block:: bash id : result: [ { "name" : "" "type" : "", "binary": "" (only in extended mode) "md5" : "" (only in extended mode) }, ... ] error: null """ repo = get_repo_or_error(repoid) if not has_superadmin_permission(apiuser): _perms = ( 'repository.admin', 'repository.write', 'repository.read',) validate_repo_permissions(apiuser, repoid, repo, _perms) ret_type = Optional.extract(ret_type) details = Optional.extract(details) _extended_types = ['basic', 'full'] if details not in _extended_types: raise JSONRPCError( 'ret_type must be one of %s' % (','.join(_extended_types))) extended_info = False content = False if details == 'basic': extended_info = True if details == 'full': extended_info = content = True _map = {} try: # check if repo is not empty by any chance, skip quicker if it is. _scm = repo.scm_instance() if _scm.is_empty(): return [] _d, _f = ScmModel().get_nodes( repo, revision, root_path, flat=False, extended_info=extended_info, content=content, max_file_bytes=max_file_bytes) _map = { 'all': _d + _f, 'files': _f, 'dirs': _d, } return _map[ret_type] except KeyError: raise JSONRPCError( 'ret_type must be one of %s' % (','.join(sorted(_map.keys())))) except Exception: log.exception("Exception occurred while trying to get repo nodes") raise JSONRPCError( 'failed to get repo: `%s` nodes' % repo.repo_name ) @jsonrpc_method() def get_repo_refs(request, apiuser, repoid): """ Returns a dictionary of current references. It returns bookmarks, branches, closed_branches, and tags for given repository It's possible to specify ret_type to show only `files` or `dirs`. This command can only be run using an |authtoken| with admin rights, or users with at least read rights to |repos|. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: The repository name or repository ID. :type repoid: str or int Example output: .. code-block:: bash id : "result": { "bookmarks": { "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188", "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf" }, "branches": { "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188", "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf" }, "branches_closed": {}, "tags": { "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188", "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022", "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27", "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17", } } error: null """ repo = get_repo_or_error(repoid) if not has_superadmin_permission(apiuser): _perms = ('repository.admin', 'repository.write', 'repository.read',) validate_repo_permissions(apiuser, repoid, repo, _perms) try: # check if repo is not empty by any chance, skip quicker if it is. vcs_instance = repo.scm_instance() refs = vcs_instance.refs() return refs except Exception: log.exception("Exception occurred while trying to get repo refs") raise JSONRPCError( 'failed to get repo: `%s` references' % repo.repo_name ) @jsonrpc_method() def create_repo( request, apiuser, repo_name, repo_type, owner=Optional(OAttr('apiuser')), description=Optional(''), private=Optional(False), clone_uri=Optional(None), landing_rev=Optional('rev:tip'), enable_statistics=Optional(False), enable_locking=Optional(False), enable_downloads=Optional(False), copy_permissions=Optional(False)): """ Creates a repository. * If the repository name contains "/", repository will be created inside a repository group or nested repository groups For example "foo/bar/repo1" will create |repo| called "repo1" inside group "foo/bar". You have to have permissions to access and write to the last repository group ("bar" in this example) This command can only be run using an |authtoken| with at least permissions to create repositories, or write permissions to parent repository groups. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repo_name: Set the repository name. :type repo_name: str :param repo_type: Set the repository type; 'hg','git', or 'svn'. :type repo_type: str :param owner: user_id or username :type owner: Optional(str) :param description: Set the repository description. :type description: Optional(str) :param private: set repository as private :type private: bool :param clone_uri: set clone_uri :type clone_uri: str :param landing_rev: : :type landing_rev: str :param enable_locking: :type enable_locking: bool :param enable_downloads: :type enable_downloads: bool :param enable_statistics: :type enable_statistics: bool :param copy_permissions: Copy permission from group in which the repository is being created. :type copy_permissions: bool Example output: .. code-block:: bash id : result: { "msg": "Created new repository ``", "success": true, "task": "" } error: null Example error output: .. code-block:: bash id : result : null error : { 'failed to create repository ``' } """ owner = validate_set_owner_permissions(apiuser, owner) description = Optional.extract(description) copy_permissions = Optional.extract(copy_permissions) clone_uri = Optional.extract(clone_uri) landing_commit_ref = Optional.extract(landing_rev) defs = SettingsModel().get_default_repo_settings(strip_prefix=True) if isinstance(private, Optional): private = defs.get('repo_private') or Optional.extract(private) if isinstance(repo_type, Optional): repo_type = defs.get('repo_type') if isinstance(enable_statistics, Optional): enable_statistics = defs.get('repo_enable_statistics') if isinstance(enable_locking, Optional): enable_locking = defs.get('repo_enable_locking') if isinstance(enable_downloads, Optional): enable_downloads = defs.get('repo_enable_downloads') schema = repo_schema.RepoSchema().bind( repo_type_options=rhodecode.BACKENDS.keys(), repo_type=repo_type, # user caller user=apiuser) try: schema_data = schema.deserialize(dict( repo_name=repo_name, repo_type=repo_type, repo_owner=owner.username, repo_description=description, repo_landing_commit_ref=landing_commit_ref, repo_clone_uri=clone_uri, repo_private=private, repo_copy_permissions=copy_permissions, repo_enable_statistics=enable_statistics, repo_enable_downloads=enable_downloads, repo_enable_locking=enable_locking)) except validation_schema.Invalid as err: raise JSONRPCValidationError(colander_exc=err) try: data = { 'owner': owner, 'repo_name': schema_data['repo_group']['repo_name_without_group'], 'repo_name_full': schema_data['repo_name'], 'repo_group': schema_data['repo_group']['repo_group_id'], 'repo_type': schema_data['repo_type'], 'repo_description': schema_data['repo_description'], 'repo_private': schema_data['repo_private'], 'clone_uri': schema_data['repo_clone_uri'], 'repo_landing_rev': schema_data['repo_landing_commit_ref'], 'enable_statistics': schema_data['repo_enable_statistics'], 'enable_locking': schema_data['repo_enable_locking'], 'enable_downloads': schema_data['repo_enable_downloads'], 'repo_copy_permissions': schema_data['repo_copy_permissions'], } task = RepoModel().create(form_data=data, cur_user=owner) task_id = get_task_id(task) # no commit, it's done in RepoModel, or async via celery return { 'msg': "Created new repository `%s`" % (schema_data['repo_name'],), 'success': True, # cannot return the repo data here since fork # can be done async 'task': task_id } except Exception: log.exception( u"Exception while trying to create the repository %s", schema_data['repo_name']) raise JSONRPCError( 'failed to create repository `%s`' % (schema_data['repo_name'],)) @jsonrpc_method() def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''), description=Optional('')): """ Adds an extra field to a repository. This command can only be run using an |authtoken| with at least write permissions to the |repo|. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: Set the repository name or repository id. :type repoid: str or int :param key: Create a unique field key for this repository. :type key: str :param label: :type label: Optional(str) :param description: :type description: Optional(str) """ repo = get_repo_or_error(repoid) if not has_superadmin_permission(apiuser): _perms = ('repository.admin',) validate_repo_permissions(apiuser, repoid, repo, _perms) label = Optional.extract(label) or key description = Optional.extract(description) field = RepositoryField.get_by_key_name(key, repo) if field: raise JSONRPCError('Field with key ' '`%s` exists for repo `%s`' % (key, repoid)) try: RepoModel().add_repo_field(repo, key, field_label=label, field_desc=description) Session().commit() return { 'msg': "Added new repository field `%s`" % (key,), 'success': True, } except Exception: log.exception("Exception occurred while trying to add field to repo") raise JSONRPCError( 'failed to create new field for repository `%s`' % (repoid,)) @jsonrpc_method() def remove_field_from_repo(request, apiuser, repoid, key): """ Removes an extra field from a repository. This command can only be run using an |authtoken| with at least write permissions to the |repo|. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: Set the repository name or repository ID. :type repoid: str or int :param key: Set the unique field key for this repository. :type key: str """ repo = get_repo_or_error(repoid) if not has_superadmin_permission(apiuser): _perms = ('repository.admin',) validate_repo_permissions(apiuser, repoid, repo, _perms) field = RepositoryField.get_by_key_name(key, repo) if not field: raise JSONRPCError('Field with key `%s` does not ' 'exists for repo `%s`' % (key, repoid)) try: RepoModel().delete_repo_field(repo, field_key=key) Session().commit() return { 'msg': "Deleted repository field `%s`" % (key,), 'success': True, } except Exception: log.exception( "Exception occurred while trying to delete field from repo") raise JSONRPCError( 'failed to delete field for repository `%s`' % (repoid,)) @jsonrpc_method() def update_repo( request, apiuser, repoid, repo_name=Optional(None), owner=Optional(OAttr('apiuser')), description=Optional(''), private=Optional(False), clone_uri=Optional(None), landing_rev=Optional('rev:tip'), fork_of=Optional(None), enable_statistics=Optional(False), enable_locking=Optional(False), enable_downloads=Optional(False), fields=Optional('')): """ Updates a repository with the given information. This command can only be run using an |authtoken| with at least admin permissions to the |repo|. * If the repository name contains "/", repository will be updated accordingly with a repository group or nested repository groups For example repoid=repo-test name="foo/bar/repo-test" will update |repo| called "repo-test" and place it inside group "foo/bar". You have to have permissions to access and write to the last repository group ("bar" in this example) :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: repository name or repository ID. :type repoid: str or int :param repo_name: Update the |repo| name, including the repository group it's in. :type repo_name: str :param owner: Set the |repo| owner. :type owner: str :param fork_of: Set the |repo| as fork of another |repo|. :type fork_of: str :param description: Update the |repo| description. :type description: str :param private: Set the |repo| as private. (True | False) :type private: bool :param clone_uri: Update the |repo| clone URI. :type clone_uri: str :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``. :type landing_rev: str :param enable_statistics: Enable statistics on the |repo|, (True | False). :type enable_statistics: bool :param enable_locking: Enable |repo| locking. :type enable_locking: bool :param enable_downloads: Enable downloads from the |repo|, (True | False). :type enable_downloads: bool :param fields: Add extra fields to the |repo|. Use the following example format: ``field_key=field_val,field_key2=fieldval2``. Escape ', ' with \, :type fields: str """ repo = get_repo_or_error(repoid) include_secrets = False if not has_superadmin_permission(apiuser): validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',)) else: include_secrets = True updates = dict( repo_name=repo_name if not isinstance(repo_name, Optional) else repo.repo_name, fork_id=fork_of if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None, user=owner if not isinstance(owner, Optional) else repo.user.username, repo_description=description if not isinstance(description, Optional) else repo.description, repo_private=private if not isinstance(private, Optional) else repo.private, clone_uri=clone_uri if not isinstance(clone_uri, Optional) else repo.clone_uri, repo_landing_rev=landing_rev if not isinstance(landing_rev, Optional) else repo._landing_revision, repo_enable_statistics=enable_statistics if not isinstance(enable_statistics, Optional) else repo.enable_statistics, repo_enable_locking=enable_locking if not isinstance(enable_locking, Optional) else repo.enable_locking, repo_enable_downloads=enable_downloads if not isinstance(enable_downloads, Optional) else repo.enable_downloads) ref_choices, _labels = ScmModel().get_repo_landing_revs( request.translate, repo=repo) old_values = repo.get_api_data() repo_type = repo.repo_type schema = repo_schema.RepoSchema().bind( repo_type_options=rhodecode.BACKENDS.keys(), repo_ref_options=ref_choices, repo_type=repo_type, # user caller user=apiuser, old_values=old_values) try: schema_data = schema.deserialize(dict( # we save old value, users cannot change type repo_type=repo_type, repo_name=updates['repo_name'], repo_owner=updates['user'], repo_description=updates['repo_description'], repo_clone_uri=updates['clone_uri'], repo_fork_of=updates['fork_id'], repo_private=updates['repo_private'], repo_landing_commit_ref=updates['repo_landing_rev'], repo_enable_statistics=updates['repo_enable_statistics'], repo_enable_downloads=updates['repo_enable_downloads'], repo_enable_locking=updates['repo_enable_locking'])) except validation_schema.Invalid as err: raise JSONRPCValidationError(colander_exc=err) # save validated data back into the updates dict validated_updates = dict( repo_name=schema_data['repo_group']['repo_name_without_group'], repo_group=schema_data['repo_group']['repo_group_id'], user=schema_data['repo_owner'], repo_description=schema_data['repo_description'], repo_private=schema_data['repo_private'], clone_uri=schema_data['repo_clone_uri'], repo_landing_rev=schema_data['repo_landing_commit_ref'], repo_enable_statistics=schema_data['repo_enable_statistics'], repo_enable_locking=schema_data['repo_enable_locking'], repo_enable_downloads=schema_data['repo_enable_downloads'], ) if schema_data['repo_fork_of']: fork_repo = get_repo_or_error(schema_data['repo_fork_of']) validated_updates['fork_id'] = fork_repo.repo_id # extra fields fields = parse_args(Optional.extract(fields), key_prefix='ex_') if fields: validated_updates.update(fields) try: RepoModel().update(repo, **validated_updates) audit_logger.store_api( 'repo.edit', action_data={'old_data': old_values}, user=apiuser, repo=repo) Session().commit() return { 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name), 'repository': repo.get_api_data(include_secrets=include_secrets) } except Exception: log.exception( u"Exception while trying to update the repository %s", repoid) raise JSONRPCError('failed to update repo `%s`' % repoid) @jsonrpc_method() def fork_repo(request, apiuser, repoid, fork_name, owner=Optional(OAttr('apiuser')), description=Optional(''), private=Optional(False), clone_uri=Optional(None), landing_rev=Optional('rev:tip'), copy_permissions=Optional(False)): """ Creates a fork of the specified |repo|. * If the fork_name contains "/", fork will be created inside a repository group or nested repository groups For example "foo/bar/fork-repo" will create fork called "fork-repo" inside group "foo/bar". You have to have permissions to access and write to the last repository group ("bar" in this example) This command can only be run using an |authtoken| with minimum read permissions of the forked repo, create fork permissions for an user. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: Set repository name or repository ID. :type repoid: str or int :param fork_name: Set the fork name, including it's repository group membership. :type fork_name: str :param owner: Set the fork owner. :type owner: str :param description: Set the fork description. :type description: str :param copy_permissions: Copy permissions from parent |repo|. The default is False. :type copy_permissions: bool :param private: Make the fork private. The default is False. :type private: bool :param landing_rev: Set the landing revision. The default is tip. Example output: .. code-block:: bash id : api_key : "" args: { "repoid" : "", "fork_name": "", "owner": "", "description": "", "copy_permissions": "", "private": "", "landing_rev": "" } Example error output: .. code-block:: bash id : result: { "msg": "Created fork of `` as ``", "success": true, "task": "" } error: null """ repo = get_repo_or_error(repoid) repo_name = repo.repo_name if not has_superadmin_permission(apiuser): # check if we have at least read permission for # this repo that we fork ! _perms = ( 'repository.admin', 'repository.write', 'repository.read') validate_repo_permissions(apiuser, repoid, repo, _perms) # check if the regular user has at least fork permissions as well if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser): raise JSONRPCForbidden() # check if user can set owner parameter owner = validate_set_owner_permissions(apiuser, owner) description = Optional.extract(description) copy_permissions = Optional.extract(copy_permissions) clone_uri = Optional.extract(clone_uri) landing_commit_ref = Optional.extract(landing_rev) private = Optional.extract(private) schema = repo_schema.RepoSchema().bind( repo_type_options=rhodecode.BACKENDS.keys(), repo_type=repo.repo_type, # user caller user=apiuser) try: schema_data = schema.deserialize(dict( repo_name=fork_name, repo_type=repo.repo_type, repo_owner=owner.username, repo_description=description, repo_landing_commit_ref=landing_commit_ref, repo_clone_uri=clone_uri, repo_private=private, repo_copy_permissions=copy_permissions)) except validation_schema.Invalid as err: raise JSONRPCValidationError(colander_exc=err) try: data = { 'fork_parent_id': repo.repo_id, 'repo_name': schema_data['repo_group']['repo_name_without_group'], 'repo_name_full': schema_data['repo_name'], 'repo_group': schema_data['repo_group']['repo_group_id'], 'repo_type': schema_data['repo_type'], 'description': schema_data['repo_description'], 'private': schema_data['repo_private'], 'copy_permissions': schema_data['repo_copy_permissions'], 'landing_rev': schema_data['repo_landing_commit_ref'], } task = RepoModel().create_fork(data, cur_user=owner) # no commit, it's done in RepoModel, or async via celery task_id = get_task_id(task) return { 'msg': 'Created fork of `%s` as `%s`' % ( repo.repo_name, schema_data['repo_name']), 'success': True, # cannot return the repo data here since fork # can be done async 'task': task_id } except Exception: log.exception( u"Exception while trying to create fork %s", schema_data['repo_name']) raise JSONRPCError( 'failed to fork repository `%s` as `%s`' % ( repo_name, schema_data['repo_name'])) @jsonrpc_method() def delete_repo(request, apiuser, repoid, forks=Optional('')): """ Deletes a repository. * When the `forks` parameter is set it's possible to detach or delete forks of deleted repository. This command can only be run using an |authtoken| with admin permissions on the |repo|. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: Set the repository name or repository ID. :type repoid: str or int :param forks: Set to `detach` or `delete` forks from the |repo|. :type forks: Optional(str) Example error output: .. code-block:: bash id : result: { "msg": "Deleted repository ``", "success": true } error: null """ repo = get_repo_or_error(repoid) repo_name = repo.repo_name if not has_superadmin_permission(apiuser): _perms = ('repository.admin',) validate_repo_permissions(apiuser, repoid, repo, _perms) try: handle_forks = Optional.extract(forks) _forks_msg = '' _forks = [f for f in repo.forks] if handle_forks == 'detach': _forks_msg = ' ' + 'Detached %s forks' % len(_forks) elif handle_forks == 'delete': _forks_msg = ' ' + 'Deleted %s forks' % len(_forks) elif _forks: raise JSONRPCError( 'Cannot delete `%s` it still contains attached forks' % (repo.repo_name,) ) old_data = repo.get_api_data() RepoModel().delete(repo, forks=forks) repo = audit_logger.RepoWrap(repo_id=None, repo_name=repo.repo_name) audit_logger.store_api( 'repo.delete', action_data={'old_data': old_data}, user=apiuser, repo=repo) ScmModel().mark_for_invalidation(repo_name, delete=True) Session().commit() return { 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg), 'success': True } except Exception: log.exception("Exception occurred while trying to delete repo") raise JSONRPCError( 'failed to delete repository `%s`' % (repo_name,) ) #TODO: marcink, change name ? @jsonrpc_method() def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)): """ Invalidates the cache for the specified repository. This command can only be run using an |authtoken| with admin rights to the specified repository. This command takes the following options: :param apiuser: This is filled automatically from |authtoken|. :type apiuser: AuthUser :param repoid: Sets the repository name or repository ID. :type repoid: str or int :param delete_keys: This deletes the invalidated keys instead of just flagging them. :type delete_keys: Optional(``True`` | ``False``) Example output: .. code-block:: bash id : result : { 'msg': Cache for repository `` was invalidated, 'repository': } error : null Example error output: .. code-block:: bash id : result : null error : { 'Error occurred during cache invalidation action' } """ repo = get_repo_or_error(repoid) if not has_superadmin_permission(apiuser): _perms = ('repository.admin', 'repository.write',) validate_repo_permissions(apiuser, repoid, repo, _perms) delete = Optional.extract(delete_keys) try: ScmModel().mark_for_invalidation(repo.repo_name, delete=delete) return { 'msg': 'Cache for repository `%s` was invalidated' % (repoid,), 'repository': repo.repo_name } except Exception: log.exception( "Exception occurred while trying to invalidate repo cache") raise JSONRPCError( 'Error occurred during cache invalidation action' ) #TODO: marcink, change name ? @jsonrpc_method() def lock(request, apiuser, repoid, locked=Optional(None), userid=Optional(OAttr('apiuser'))): """ Sets the lock state of the specified |repo| by the given user. From more information, see :ref:`repo-locking`. * If the ``userid`` option is not set, the repository is locked to the user who called the method. * If the ``locked`` parameter is not set, the current lock state of the repository is displayed. This command can only be run using an |authtoken| with admin rights to the specified repository. This command takes the following options: :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: Sets the repository name or repository ID. :type repoid: str or int :param locked: Sets the lock state. :type locked: Optional(``True`` | ``False``) :param userid: Set the repository lock to this user. :type userid: Optional(str or int) Example error output: .. code-block:: bash id : result : { 'repo': '', 'locked': , 'locked_since': , 'locked_by': , 'lock_reason': , 'lock_state_changed': , 'msg': 'Repo `` locked by `` on .' or 'msg': 'Repo `` not locked.' or 'msg': 'User `` set lock state for repo `` to ``' } error : null Example error output: .. code-block:: bash id : result : null error : { 'Error occurred locking repository ``' } """ repo = get_repo_or_error(repoid) if not has_superadmin_permission(apiuser): # check if we have at least write permission for this repo ! _perms = ('repository.admin', 'repository.write',) validate_repo_permissions(apiuser, repoid, repo, _perms) # make sure normal user does not pass someone else userid, # he is not allowed to do that if not isinstance(userid, Optional) and userid != apiuser.user_id: raise JSONRPCError('userid is not the same as your user') if isinstance(userid, Optional): userid = apiuser.user_id user = get_user_or_error(userid) if isinstance(locked, Optional): lockobj = repo.locked if lockobj[0] is None: _d = { 'repo': repo.repo_name, 'locked': False, 'locked_since': None, 'locked_by': None, 'lock_reason': None, 'lock_state_changed': False, 'msg': 'Repo `%s` not locked.' % repo.repo_name } return _d else: _user_id, _time, _reason = lockobj lock_user = get_user_or_error(userid) _d = { 'repo': repo.repo_name, 'locked': True, 'locked_since': _time, 'locked_by': lock_user.username, 'lock_reason': _reason, 'lock_state_changed': False, 'msg': ('Repo `%s` locked by `%s` on `%s`.' % (repo.repo_name, lock_user.username, json.dumps(time_to_datetime(_time)))) } return _d # force locked state through a flag else: locked = str2bool(locked) lock_reason = Repository.LOCK_API try: if locked: lock_time = time.time() Repository.lock(repo, user.user_id, lock_time, lock_reason) else: lock_time = None Repository.unlock(repo) _d = { 'repo': repo.repo_name, 'locked': locked, 'locked_since': lock_time, 'locked_by': user.username, 'lock_reason': lock_reason, 'lock_state_changed': True, 'msg': ('User `%s` set lock state for repo `%s` to `%s`' % (user.username, repo.repo_name, locked)) } return _d except Exception: log.exception( "Exception occurred while trying to lock repository") raise JSONRPCError( 'Error occurred locking repository `%s`' % repo.repo_name ) @jsonrpc_method() def comment_commit( request, apiuser, repoid, commit_id, message, status=Optional(None), comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE), resolves_comment_id=Optional(None), userid=Optional(OAttr('apiuser'))): """ Set a commit comment, and optionally change the status of the commit. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: Set the repository name or repository ID. :type repoid: str or int :param commit_id: Specify the commit_id for which to set a comment. :type commit_id: str :param message: The comment text. :type message: str :param status: (**Optional**) status of commit, one of: 'not_reviewed', 'approved', 'rejected', 'under_review' :type status: str :param comment_type: Comment type, one of: 'note', 'todo' :type comment_type: Optional(str), default: 'note' :param userid: Set the user name of the comment creator. :type userid: Optional(str or int) Example error output: .. code-block:: bash { "id" : , "result" : { "msg": "Commented on commit `` for repository ``", "status_change": null or , "success": true }, "error" : null } """ repo = get_repo_or_error(repoid) if not has_superadmin_permission(apiuser): _perms = ('repository.read', 'repository.write', 'repository.admin') validate_repo_permissions(apiuser, repoid, repo, _perms) try: commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id except Exception as e: log.exception('Failed to fetch commit') raise JSONRPCError(e.message) if isinstance(userid, Optional): userid = apiuser.user_id user = get_user_or_error(userid) status = Optional.extract(status) comment_type = Optional.extract(comment_type) resolves_comment_id = Optional.extract(resolves_comment_id) allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES] if status and status not in allowed_statuses: raise JSONRPCError('Bad status, must be on ' 'of %s got %s' % (allowed_statuses, status,)) if resolves_comment_id: comment = ChangesetComment.get(resolves_comment_id) if not comment: raise JSONRPCError( 'Invalid resolves_comment_id `%s` for this commit.' % resolves_comment_id) if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO: raise JSONRPCError( 'Comment `%s` is wrong type for setting status to resolved.' % resolves_comment_id) try: rc_config = SettingsModel().get_all_settings() renderer = rc_config.get('rhodecode_markup_renderer', 'rst') status_change_label = ChangesetStatus.get_status_lbl(status) comment = CommentsModel().create( message, repo, user, commit_id=commit_id, status_change=status_change_label, status_change_type=status, renderer=renderer, comment_type=comment_type, resolves_comment_id=resolves_comment_id ) if status: # also do a status change try: ChangesetStatusModel().set_status( repo, status, user, comment, revision=commit_id, dont_allow_on_closed_pull_request=True ) except StatusChangeOnClosedPullRequestError: log.exception( "Exception occurred while trying to change repo commit status") msg = ('Changing status on a changeset associated with ' 'a closed pull request is not allowed') raise JSONRPCError(msg) Session().commit() return { 'msg': ( 'Commented on commit `%s` for repository `%s`' % ( comment.revision, repo.repo_name)), 'status_change': status, 'success': True, } except JSONRPCError: # catch any inside errors, and re-raise them to prevent from # below global catch to silence them raise except Exception: log.exception("Exception occurred while trying to comment on commit") raise JSONRPCError( 'failed to set comment on repository `%s`' % (repo.repo_name,) ) @jsonrpc_method() def grant_user_permission(request, apiuser, repoid, userid, perm): """ Grant permissions for the specified user on the given repository, or update existing permissions if found. This command can only be run using an |authtoken| with admin permissions on the |repo|. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: Set the repository name or repository ID. :type repoid: str or int :param userid: Set the user name. :type userid: str :param perm: Set the user permissions, using the following format ``(repository.(none|read|write|admin))`` :type perm: str Example output: .. code-block:: bash id : result: { "msg" : "Granted perm: `` for user: `` in repo: ``", "success": true } error: null """ repo = get_repo_or_error(repoid) user = get_user_or_error(userid) perm = get_perm_or_error(perm) if not has_superadmin_permission(apiuser): _perms = ('repository.admin',) validate_repo_permissions(apiuser, repoid, repo, _perms) try: RepoModel().grant_user_permission(repo=repo, user=user, perm=perm) Session().commit() return { 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % ( perm.permission_name, user.username, repo.repo_name ), 'success': True } except Exception: log.exception( "Exception occurred while trying edit permissions for repo") raise JSONRPCError( 'failed to edit permission for user: `%s` in repo: `%s`' % ( userid, repoid ) ) @jsonrpc_method() def revoke_user_permission(request, apiuser, repoid, userid): """ Revoke permission for a user on the specified repository. This command can only be run using an |authtoken| with admin permissions on the |repo|. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: Set the repository name or repository ID. :type repoid: str or int :param userid: Set the user name of revoked user. :type userid: str or int Example error output: .. code-block:: bash id : result: { "msg" : "Revoked perm for user: `` in repo: ``", "success": true } error: null """ repo = get_repo_or_error(repoid) user = get_user_or_error(userid) if not has_superadmin_permission(apiuser): _perms = ('repository.admin',) validate_repo_permissions(apiuser, repoid, repo, _perms) try: RepoModel().revoke_user_permission(repo=repo, user=user) Session().commit() return { 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % ( user.username, repo.repo_name ), 'success': True } except Exception: log.exception( "Exception occurred while trying revoke permissions to repo") raise JSONRPCError( 'failed to edit permission for user: `%s` in repo: `%s`' % ( userid, repoid ) ) @jsonrpc_method() def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm): """ Grant permission for a user group on the specified repository, or update existing permissions. This command can only be run using an |authtoken| with admin permissions on the |repo|. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: Set the repository name or repository ID. :type repoid: str or int :param usergroupid: Specify the ID of the user group. :type usergroupid: str or int :param perm: Set the user group permissions using the following format: (repository.(none|read|write|admin)) :type perm: str Example output: .. code-block:: bash id : result : { "msg" : "Granted perm: `` for group: `` in repo: ``", "success": true } error : null Example error output: .. code-block:: bash id : result : null error : { "failed to edit permission for user group: `` in repo ``' } """ repo = get_repo_or_error(repoid) perm = get_perm_or_error(perm) if not has_superadmin_permission(apiuser): _perms = ('repository.admin',) validate_repo_permissions(apiuser, repoid, repo, _perms) user_group = get_user_group_or_error(usergroupid) if not has_superadmin_permission(apiuser): # check if we have at least read permission for this user group ! _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) if not HasUserGroupPermissionAnyApi(*_perms)( user=apiuser, user_group_name=user_group.users_group_name): raise JSONRPCError( 'user group `%s` does not exist' % (usergroupid,)) try: RepoModel().grant_user_group_permission( repo=repo, group_name=user_group, perm=perm) Session().commit() return { 'msg': 'Granted perm: `%s` for user group: `%s` in ' 'repo: `%s`' % ( perm.permission_name, user_group.users_group_name, repo.repo_name ), 'success': True } except Exception: log.exception( "Exception occurred while trying change permission on repo") raise JSONRPCError( 'failed to edit permission for user group: `%s` in ' 'repo: `%s`' % ( usergroupid, repo.repo_name ) ) @jsonrpc_method() def revoke_user_group_permission(request, apiuser, repoid, usergroupid): """ Revoke the permissions of a user group on a given repository. This command can only be run using an |authtoken| with admin permissions on the |repo|. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: Set the repository name or repository ID. :type repoid: str or int :param usergroupid: Specify the user group ID. :type usergroupid: str or int Example output: .. code-block:: bash id : result: { "msg" : "Revoked perm for group: `` in repo: ``", "success": true } error: null """ repo = get_repo_or_error(repoid) if not has_superadmin_permission(apiuser): _perms = ('repository.admin',) validate_repo_permissions(apiuser, repoid, repo, _perms) user_group = get_user_group_or_error(usergroupid) if not has_superadmin_permission(apiuser): # check if we have at least read permission for this user group ! _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) if not HasUserGroupPermissionAnyApi(*_perms)( user=apiuser, user_group_name=user_group.users_group_name): raise JSONRPCError( 'user group `%s` does not exist' % (usergroupid,)) try: RepoModel().revoke_user_group_permission( repo=repo, group_name=user_group) Session().commit() return { 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % ( user_group.users_group_name, repo.repo_name ), 'success': True } except Exception: log.exception("Exception occurred while trying revoke " "user group permission on repo") raise JSONRPCError( 'failed to edit permission for user group: `%s` in ' 'repo: `%s`' % ( user_group.users_group_name, repo.repo_name ) ) @jsonrpc_method() def pull(request, apiuser, repoid): """ Triggers a pull on the given repository from a remote location. You can use this to keep remote repositories up-to-date. This command can only be run using an |authtoken| with admin rights to the specified repository. For more information, see :ref:`config-token-ref`. This command takes the following options: :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: The repository name or repository ID. :type repoid: str or int Example output: .. code-block:: bash id : result : { "msg": "Pulled from ``" "repository": "" } error : null Example error output: .. code-block:: bash id : result : null error : { "Unable to pull changes from ``" } """ repo = get_repo_or_error(repoid) if not has_superadmin_permission(apiuser): _perms = ('repository.admin',) validate_repo_permissions(apiuser, repoid, repo, _perms) try: ScmModel().pull_changes(repo.repo_name, apiuser.username) return { 'msg': 'Pulled from `%s`' % repo.repo_name, 'repository': repo.repo_name } except Exception: log.exception("Exception occurred while trying to " "pull changes from remote location") raise JSONRPCError( 'Unable to pull changes from `%s`' % repo.repo_name ) @jsonrpc_method() def strip(request, apiuser, repoid, revision, branch): """ Strips the given revision from the specified repository. * This will remove the revision and all of its decendants. This command can only be run using an |authtoken| with admin rights to the specified repository. This command takes the following options: :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: The repository name or repository ID. :type repoid: str or int :param revision: The revision you wish to strip. :type revision: str :param branch: The branch from which to strip the revision. :type branch: str Example output: .. code-block:: bash id : result : { "msg": "'Stripped commit from repo ``'" "repository": "" } error : null Example error output: .. code-block:: bash id : result : null error : { "Unable to strip commit from repo ``" } """ repo = get_repo_or_error(repoid) if not has_superadmin_permission(apiuser): _perms = ('repository.admin',) validate_repo_permissions(apiuser, repoid, repo, _perms) try: ScmModel().strip(repo, revision, branch) audit_logger.store_api( 'repo.commit.strip', action_data={'commit_id': revision}, repo=repo, user=apiuser, commit=True) return { 'msg': 'Stripped commit %s from repo `%s`' % ( revision, repo.repo_name), 'repository': repo.repo_name } except Exception: log.exception("Exception while trying to strip") raise JSONRPCError( 'Unable to strip commit %s from repo `%s`' % ( revision, repo.repo_name) ) @jsonrpc_method() def get_repo_settings(request, apiuser, repoid, key=Optional(None)): """ Returns all settings for a repository. If key is given it only returns the setting identified by the key or null. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: The repository name or repository id. :type repoid: str or int :param key: Key of the setting to return. :type: key: Optional(str) Example output: .. code-block:: bash { "error": null, "id": 237, "result": { "extensions_largefiles": true, "extensions_evolve": true, "hooks_changegroup_push_logger": true, "hooks_changegroup_repo_size": false, "hooks_outgoing_pull_logger": true, "phases_publish": "True", "rhodecode_hg_use_rebase_for_merging": true, "rhodecode_pr_merge_enabled": true, "rhodecode_use_outdated_comments": true } } """ # Restrict access to this api method to admins only. if not has_superadmin_permission(apiuser): raise JSONRPCForbidden() try: repo = get_repo_or_error(repoid) settings_model = VcsSettingsModel(repo=repo) settings = settings_model.get_global_settings() settings.update(settings_model.get_repo_settings()) # If only a single setting is requested fetch it from all settings. key = Optional.extract(key) if key is not None: settings = settings.get(key, None) except Exception: msg = 'Failed to fetch settings for repository `{}`'.format(repoid) log.exception(msg) raise JSONRPCError(msg) return settings @jsonrpc_method() def set_repo_settings(request, apiuser, repoid, settings): """ Update repository settings. Returns true on success. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: The repository name or repository id. :type repoid: str or int :param settings: The new settings for the repository. :type: settings: dict Example output: .. code-block:: bash { "error": null, "id": 237, "result": true } """ # Restrict access to this api method to admins only. if not has_superadmin_permission(apiuser): raise JSONRPCForbidden() if type(settings) is not dict: raise JSONRPCError('Settings have to be a JSON Object.') try: settings_model = VcsSettingsModel(repo=repoid) # Merge global, repo and incoming settings. new_settings = settings_model.get_global_settings() new_settings.update(settings_model.get_repo_settings()) new_settings.update(settings) # Update the settings. inherit_global_settings = new_settings.get( 'inherit_global_settings', False) settings_model.create_or_update_repo_settings( new_settings, inherit_global_settings=inherit_global_settings) Session().commit() except Exception: msg = 'Failed to update settings for repository `{}`'.format(repoid) log.exception(msg) raise JSONRPCError(msg) # Indicate success. return True @jsonrpc_method() def maintenance(request, apiuser, repoid): """ Triggers a maintenance on the given repository. This command can only be run using an |authtoken| with admin rights to the specified repository. For more information, see :ref:`config-token-ref`. This command takes the following options: :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: The repository name or repository ID. :type repoid: str or int Example output: .. code-block:: bash id : result : { "msg": "executed maintenance command", "executed_actions": [ , ... ], "repository": "" } error : null Example error output: .. code-block:: bash id : result : null error : { "Unable to execute maintenance on ``" } """ repo = get_repo_or_error(repoid) if not has_superadmin_permission(apiuser): _perms = ('repository.admin',) validate_repo_permissions(apiuser, repoid, repo, _perms) try: maintenance = repo_maintenance.RepoMaintenance() executed_actions = maintenance.execute(repo) return { 'msg': 'executed maintenance command', 'executed_actions': executed_actions, 'repository': repo.repo_name } except Exception: log.exception("Exception occurred while trying to run maintenance") raise JSONRPCError( 'Unable to execute maintenance on `%s`' % repo.repo_name)