repo_api.py
1988 lines
| 67.2 KiB
| text/x-python
|
PythonLexer
r1 | # -*- coding: utf-8 -*- | |||
r1271 | # Copyright (C) 2011-2017 RhodeCode GmbH | |||
r1 | # | |||
# 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 <http://www.gnu.org/licenses/>. | ||||
# | ||||
# 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 | ||||
r1153 | import rhodecode | |||
from rhodecode.api import ( | ||||
jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError) | ||||
r1 | from rhodecode.api.utils import ( | |||
has_superadmin_permission, Optional, OAttr, get_repo_or_error, | ||||
r1153 | 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.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi | ||||
r1 | from rhodecode.lib.utils2 import str2bool, time_to_datetime | |||
r1153 | from rhodecode.lib.ext_json import json | |||
r1416 | from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError | |||
r1 | from rhodecode.model.changeset_status import ChangesetStatusModel | |||
r1323 | from rhodecode.model.comment import CommentsModel | |||
r1 | from rhodecode.model.db import ( | |||
r1337 | Session, ChangesetStatus, RepositoryField, Repository, RepoGroup, | |||
ChangesetComment) | ||||
r1 | from rhodecode.model.repo import RepoModel | |||
from rhodecode.model.scm import ScmModel, RepoList | ||||
Martin Bornhold
|
r387 | from rhodecode.model.settings import SettingsModel, VcsSettingsModel | ||
r1153 | from rhodecode.model import validation_schema | |||
r523 | from rhodecode.model.validation_schema.schemas import repo_schema | |||
r1 | ||||
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": <repo_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": <user_id>, | ||||
"username": "name" | ||||
} | ||||
], | ||||
"fork_of": "parent-repo", | ||||
"landing_rev": [ | ||||
"rev", | ||||
"tip" | ||||
], | ||||
"last_changeset": { | ||||
"author": "User <user@example.com>", | ||||
"branch": "default", | ||||
"date": "timestamp", | ||||
"message": "last commit message", | ||||
"parents": [ | ||||
{ | ||||
"raw_id": "commit-id" | ||||
} | ||||
], | ||||
"raw_id": "commit-id", | ||||
"revision": <revision number>, | ||||
"short_id": "short id" | ||||
}, | ||||
"lock_reason": null, | ||||
"locked_by": null, | ||||
"locked_date": null, | ||||
"members": [ | ||||
{ | ||||
"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" | ||||
} | ||||
], | ||||
"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) | ||||
r1153 | ||||
r1 | 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',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
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['members'] = permissions # TODO: this should be deprecated soon | ||||
data['permissions'] = permissions | ||||
data['followers'] = following_users | ||||
return data | ||||
@jsonrpc_method() | ||||
r1267 | def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)): | |||
r1 | """ | |||
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 | ||||
r1267 | :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) | ||||
r1 | ||||
Example output: | ||||
.. code-block:: bash | ||||
id : <id_given_in_input> | ||||
result: [ | ||||
{ | ||||
"repo_id" : "<repo_id>", | ||||
"repo_name" : "<reponame>" | ||||
"repo_type" : "<repo_type>", | ||||
"clone_uri" : "<clone_uri>", | ||||
"private": : "<bool>", | ||||
"created_on" : "<datetimecreated>", | ||||
"description" : "<description>", | ||||
"landing_rev": "<landing_rev>", | ||||
"owner": "<repo_owner>", | ||||
"fork_of": "<name_of_fork_parent>", | ||||
"enable_downloads": "<bool>", | ||||
"enable_locking": "<bool>", | ||||
"enable_statistics": "<bool>", | ||||
}, | ||||
... | ||||
] | ||||
error: null | ||||
""" | ||||
include_secrets = has_superadmin_permission(apiuser) | ||||
_perms = ('repository.read', 'repository.write', 'repository.admin',) | ||||
extras = {'user': apiuser} | ||||
r1267 | 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) | ||||
r1 | 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',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
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'] = { | ||||
'branches': [cs.branch], | ||||
'bookmarks': getattr(cs, 'bookmarks', []), | ||||
'tags': cs.tags | ||||
} | ||||
return _cs_json | ||||
@jsonrpc_method() | ||||
def get_repo_changesets(request, apiuser, repoid, start_rev, limit, | ||||
details=Optional('basic')): | ||||
""" | ||||
r61 | Returns a set of commits limited by the number starting | |||
r1 | 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 | ||||
r61 | :param limit: Limit the number of commits to this amount | |||
r1 | :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',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
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 | ||||
r61 | if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'): | |||
r1 | start_rev = vcs_repo.commit_ids[0] | |||
try: | ||||
r61 | commits = vcs_repo.get_commits( | |||
r1 | start_id=start_rev, pre_load=pre_load) | |||
except TypeError as e: | ||||
raise JSONRPCError(e.message) | ||||
r61 | except Exception: | |||
log.exception('Fetching of commits failed') | ||||
raise JSONRPCError('Error occurred during commit fetching') | ||||
r1 | ||||
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, | ||||
r502 | ret_type=Optional('all'), details=Optional('basic'), | |||
max_file_bytes=Optional(None)): | ||||
r1 | """ | |||
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) | ||||
r502 | :param max_file_bytes: Only return file content under this file size bytes | |||
:type details: Optional(int) | ||||
r1 | ||||
Example output: | ||||
.. code-block:: bash | ||||
id : <id_given_in_input> | ||||
result: [ | ||||
{ | ||||
"name" : "<name>" | ||||
"type" : "<type>", | ||||
"binary": "<true|false>" (only in extended mode) | ||||
"md5" : "<md5 of file content>" (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',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
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, | ||||
r502 | extended_info=extended_info, content=content, | |||
max_file_bytes=max_file_bytes) | ||||
r1 | _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 : <id_given_in_input> | ||||
r1243 | "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", | ||||
} | ||||
} | ||||
r1 | error: null | |||
""" | ||||
repo = get_repo_or_error(repoid) | ||||
if not has_superadmin_permission(apiuser): | ||||
_perms = ('repository.admin', 'repository.write', 'repository.read',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
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() | ||||
r1153 | 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)): | ||||
r1 | """ | |||
Creates a repository. | ||||
r1153 | * If the repository name contains "/", repository will be created inside | |||
a repository group or nested repository groups | ||||
r1 | ||||
r1153 | 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) | ||||
r1 | ||||
This command can only be run using an |authtoken| with at least | ||||
r1153 | permissions to create repositories, or write permissions to | |||
parent repository groups. | ||||
r1 | ||||
: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) | ||||
r1153 | :param private: set repository as private | |||
r1 | :type private: bool | |||
r1153 | :param clone_uri: set clone_uri | |||
r1 | :type clone_uri: str | |||
:param landing_rev: <rev_type>:<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 : <id_given_in_input> | ||||
result: { | ||||
"msg": "Created new repository `<reponame>`", | ||||
"success": true, | ||||
"task": "<celery task id or None if done sync>" | ||||
} | ||||
error: null | ||||
Example error output: | ||||
.. code-block:: bash | ||||
id : <id_given_in_input> | ||||
result : null | ||||
error : { | ||||
r1120 | 'failed to create repository `<repo_name>`' | |||
r1 | } | |||
""" | ||||
r1153 | owner = validate_set_owner_permissions(apiuser, owner) | |||
r1 | ||||
r1153 | description = Optional.extract(description) | |||
copy_permissions = Optional.extract(copy_permissions) | ||||
clone_uri = Optional.extract(clone_uri) | ||||
landing_commit_ref = Optional.extract(landing_rev) | ||||
r1 | ||||
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') | ||||
r1153 | schema = repo_schema.RepoSchema().bind( | |||
repo_type_options=rhodecode.BACKENDS.keys(), | ||||
# user caller | ||||
user=apiuser) | ||||
r1 | ||||
try: | ||||
r1153 | 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: | ||||
r1 | data = { | |||
'owner': owner, | ||||
r1153 | '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'], | ||||
r1 | } | |||
task = RepoModel().create(form_data=data, cur_user=owner) | ||||
from celery.result import BaseAsyncResult | ||||
task_id = None | ||||
if isinstance(task, BaseAsyncResult): | ||||
task_id = task.task_id | ||||
# no commit, it's done in RepoModel, or async via celery | ||||
return { | ||||
r1153 | 'msg': "Created new repository `%s`" % (schema_data['repo_name'],), | |||
r1 | 'success': True, # cannot return the repo data here since fork | |||
r1153 | # can be done async | |||
r1 | 'task': task_id | |||
} | ||||
except Exception: | ||||
log.exception( | ||||
u"Exception while trying to create the repository %s", | ||||
r1153 | schema_data['repo_name']) | |||
r1 | raise JSONRPCError( | |||
r1153 | 'failed to create repository `%s`' % (schema_data['repo_name'],)) | |||
r1 | ||||
@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',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
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',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
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() | ||||
r1153 | 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('')): | ||||
r1 | """ | |||
Updates a repository with the given information. | ||||
This command can only be run using an |authtoken| with at least | ||||
r1153 | 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) | ||||
r1 | ||||
:param apiuser: This is filled automatically from the |authtoken|. | ||||
:type apiuser: AuthUser | ||||
:param repoid: repository name or repository ID. | ||||
:type repoid: str or int | ||||
r1153 | :param repo_name: Update the |repo| name, including the | |||
repository group it's in. | ||||
:type repo_name: str | ||||
r1 | :param owner: Set the |repo| owner. | |||
:type owner: str | ||||
r1153 | :param fork_of: Set the |repo| as fork of another |repo|. | |||
r1 | :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 | ||||
r1153 | :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``. | |||
r1 | :type landing_rev: str | |||
r1153 | :param enable_statistics: Enable statistics on the |repo|, (True | False). | |||
r1 | :type enable_statistics: bool | |||
:param enable_locking: Enable |repo| locking. | ||||
:type enable_locking: bool | ||||
r1153 | :param enable_downloads: Enable downloads from the |repo|, (True | False). | |||
r1 | :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 | ||||
""" | ||||
r1153 | ||||
r1 | repo = get_repo_or_error(repoid) | |||
r1153 | ||||
r1 | include_secrets = False | |||
r1153 | if not has_superadmin_permission(apiuser): | |||
validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',)) | ||||
else: | ||||
r1 | include_secrets = True | |||
r1153 | ||||
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(repo=repo) | ||||
r1 | ||||
r1153 | schema = repo_schema.RepoSchema().bind( | |||
repo_type_options=rhodecode.BACKENDS.keys(), | ||||
repo_ref_options=ref_choices, | ||||
# user caller | ||||
user=apiuser, | ||||
old_values=repo.get_api_data()) | ||||
try: | ||||
schema_data = schema.deserialize(dict( | ||||
# we save old value, users cannot change type | ||||
repo_type=repo.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) | ||||
r1 | ||||
r1153 | # 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) | ||||
r1 | ||||
try: | ||||
r1153 | RepoModel().update(repo, **validated_updates) | |||
r1 | Session().commit() | |||
return { | ||||
r1153 | 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name), | |||
'repository': repo.get_api_data(include_secrets=include_secrets) | ||||
r1 | } | |||
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')), | ||||
r1153 | description=Optional(''), | |||
private=Optional(False), | ||||
clone_uri=Optional(None), | ||||
landing_rev=Optional('rev:tip'), | ||||
copy_permissions=Optional(False)): | ||||
r1 | """ | |||
Creates a fork of the specified |repo|. | ||||
r1153 | * If the fork_name contains "/", fork will be created inside | |||
a repository group or nested repository groups | ||||
r1 | ||||
r1153 | 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. | ||||
r1 | ||||
: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 | ||||
r1153 | :param fork_name: Set the fork name, including it's repository group membership. | |||
r1 | :type fork_name: str | |||
:param owner: Set the fork owner. | ||||
:type owner: str | ||||
r1153 | :param description: Set the fork description. | |||
r1 | :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 : <id_for_response> | ||||
api_key : "<api_key>" | ||||
args: { | ||||
"repoid" : "<reponame or repo_id>", | ||||
"fork_name": "<forkname>", | ||||
"owner": "<username or user_id = Optional(=apiuser)>", | ||||
"description": "<description>", | ||||
"copy_permissions": "<bool>", | ||||
"private": "<bool>", | ||||
"landing_rev": "<landing_rev>" | ||||
} | ||||
Example error output: | ||||
.. code-block:: bash | ||||
id : <id_given_in_input> | ||||
result: { | ||||
"msg": "Created fork of `<reponame>` as `<forkname>`", | ||||
"success": true, | ||||
"task": "<celery task id or None if done sync>" | ||||
} | ||||
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') | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
r1153 | # 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) | ||||
r1 | ||||
r1153 | 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) | ||||
r1 | ||||
r1153 | schema = repo_schema.RepoSchema().bind( | |||
repo_type_options=rhodecode.BACKENDS.keys(), | ||||
# user caller | ||||
user=apiuser) | ||||
r1 | ||||
try: | ||||
r1153 | 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 = { | ||||
r1 | 'fork_parent_id': repo.repo_id, | |||
r1153 | ||||
'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'], | ||||
r1 | } | |||
r1153 | task = RepoModel().create_fork(data, cur_user=owner) | |||
r1 | # no commit, it's done in RepoModel, or async via celery | |||
from celery.result import BaseAsyncResult | ||||
task_id = None | ||||
if isinstance(task, BaseAsyncResult): | ||||
task_id = task.task_id | ||||
return { | ||||
'msg': 'Created fork of `%s` as `%s`' % ( | ||||
r1153 | repo.repo_name, schema_data['repo_name']), | |||
r1 | 'success': True, # cannot return the repo data here since fork | |||
# can be done async | ||||
'task': task_id | ||||
} | ||||
except Exception: | ||||
r1153 | log.exception( | |||
u"Exception while trying to create fork %s", | ||||
schema_data['repo_name']) | ||||
r1 | raise JSONRPCError( | |||
'failed to fork repository `%s` as `%s`' % ( | ||||
r1153 | repo_name, schema_data['repo_name'])) | |||
r1 | ||||
@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 : <id_given_in_input> | ||||
result: { | ||||
"msg": "Deleted repository `<reponame>`", | ||||
"success": true | ||||
} | ||||
error: null | ||||
""" | ||||
repo = get_repo_or_error(repoid) | ||||
if not has_superadmin_permission(apiuser): | ||||
_perms = ('repository.admin',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
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,) | ||||
) | ||||
RepoModel().delete(repo, forks=forks) | ||||
Session().commit() | ||||
return { | ||||
'msg': 'Deleted repository `%s`%s' % ( | ||||
repo.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.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 : <id_given_in_input> | ||||
result : { | ||||
'msg': Cache for repository `<repository name>` was invalidated, | ||||
'repository': <repository name> | ||||
} | ||||
error : null | ||||
Example error output: | ||||
.. code-block:: bash | ||||
id : <id_given_in_input> | ||||
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',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
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 : <id_given_in_input> | ||||
result : { | ||||
'repo': '<reponame>', | ||||
'locked': <bool: lock state>, | ||||
'locked_since': <int: lock timestamp>, | ||||
'locked_by': <username of person who made the lock>, | ||||
'lock_reason': <str: reason for locking>, | ||||
'lock_state_changed': <bool: True if lock state has been changed in this request>, | ||||
'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.' | ||||
or | ||||
'msg': 'Repo `<repository name>` not locked.' | ||||
or | ||||
'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`' | ||||
} | ||||
error : null | ||||
Example error output: | ||||
.. code-block:: bash | ||||
id : <id_given_in_input> | ||||
result : null | ||||
error : { | ||||
r1120 | 'Error occurred locking repository `<reponame>`' | |||
r1 | } | |||
""" | ||||
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',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
# 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( | ||||
r1337 | request, apiuser, repoid, commit_id, message, status=Optional(None), | |||
comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE), | ||||
r1338 | resolves_comment_id=Optional(None), | |||
r1337 | userid=Optional(OAttr('apiuser'))): | |||
r1 | """ | |||
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 | ||||
r1337 | :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' | ||||
r1 | :param userid: Set the user name of the comment creator. | |||
:type userid: Optional(str or int) | ||||
Example error output: | ||||
r1337 | .. code-block:: bash | |||
r1 | ||||
{ | ||||
"id" : <id_given_in_input>, | ||||
"result" : { | ||||
"msg": "Commented on commit `<commit_id>` for repository `<repoid>`", | ||||
"status_change": null or <status>, | ||||
"success": true | ||||
}, | ||||
"error" : null | ||||
} | ||||
""" | ||||
repo = get_repo_or_error(repoid) | ||||
if not has_superadmin_permission(apiuser): | ||||
r352 | _perms = ('repository.read', 'repository.write', 'repository.admin') | |||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
r1416 | 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) | ||||
r1 | if isinstance(userid, Optional): | |||
userid = apiuser.user_id | ||||
user = get_user_or_error(userid) | ||||
status = Optional.extract(status) | ||||
r1337 | comment_type = Optional.extract(comment_type) | |||
r1338 | resolves_comment_id = Optional.extract(resolves_comment_id) | |||
r1 | ||||
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,)) | ||||
r1338 | 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) | ||||
r1 | try: | |||
rc_config = SettingsModel().get_all_settings() | ||||
renderer = rc_config.get('rhodecode_markup_renderer', 'rst') | ||||
r548 | status_change_label = ChangesetStatus.get_status_lbl(status) | |||
r1323 | comm = CommentsModel().create( | |||
r1322 | message, repo, user, commit_id=commit_id, | |||
r548 | status_change=status_change_label, | |||
status_change_type=status, | ||||
r1337 | renderer=renderer, | |||
r1338 | comment_type=comment_type, | |||
resolves_comment_id=resolves_comment_id | ||||
r1337 | ) | |||
r1 | if status: | |||
# also do a status change | ||||
try: | ||||
ChangesetStatusModel().set_status( | ||||
repo, status, user, comm, 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`' % ( | ||||
comm.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 : <id_given_in_input> | ||||
result: { | ||||
"msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`", | ||||
"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',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
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 : <id_given_in_input> | ||||
result: { | ||||
"msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`", | ||||
"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',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
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 : <id_given_in_input> | ||||
result : { | ||||
"msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`", | ||||
"success": true | ||||
} | ||||
error : null | ||||
Example error output: | ||||
.. code-block:: bash | ||||
id : <id_given_in_input> | ||||
result : null | ||||
error : { | ||||
"failed to edit permission for user group: `<usergroup>` in repo `<repo>`' | ||||
} | ||||
""" | ||||
repo = get_repo_or_error(repoid) | ||||
perm = get_perm_or_error(perm) | ||||
if not has_superadmin_permission(apiuser): | ||||
_perms = ('repository.admin',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
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 : <id_given_in_input> | ||||
result: { | ||||
"msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`", | ||||
"success": true | ||||
} | ||||
error: null | ||||
""" | ||||
repo = get_repo_or_error(repoid) | ||||
if not has_superadmin_permission(apiuser): | ||||
_perms = ('repository.admin',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
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 : <id_given_in_input> | ||||
result : { | ||||
"msg": "Pulled from `<repository name>`" | ||||
"repository": "<repository name>" | ||||
} | ||||
error : null | ||||
Example error output: | ||||
.. code-block:: bash | ||||
id : <id_given_in_input> | ||||
result : null | ||||
error : { | ||||
"Unable to pull changes from `<reponame>`" | ||||
} | ||||
""" | ||||
repo = get_repo_or_error(repoid) | ||||
if not has_superadmin_permission(apiuser): | ||||
_perms = ('repository.admin',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
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 : <id_given_in_input> | ||||
result : { | ||||
"msg": "'Stripped commit <commit_hash> from repo `<repository name>`'" | ||||
"repository": "<repository name>" | ||||
} | ||||
error : null | ||||
Example error output: | ||||
.. code-block:: bash | ||||
id : <id_given_in_input> | ||||
result : null | ||||
error : { | ||||
"Unable to strip commit <commit_hash> from repo `<repository name>`" | ||||
} | ||||
""" | ||||
repo = get_repo_or_error(repoid) | ||||
if not has_superadmin_permission(apiuser): | ||||
_perms = ('repository.admin',) | ||||
r1153 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |||
r1 | ||||
try: | ||||
ScmModel().strip(repo, revision, branch) | ||||
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) | ||||
) | ||||
Martin Bornhold
|
r387 | |||
@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, | ||||
r1738 | "extensions_evolve": true, | |||
Martin Bornhold
|
r387 | "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 | ||||