repo_api.py
2314 lines
| 79.0 KiB
| text/x-python
|
PythonLexer
r1 | # -*- coding: utf-8 -*- | ||
r3363 | # Copyright (C) 2011-2019 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) | |||
r3462 | from rhodecode.lib import audit_logger, rc_cache | ||
r1742 | from rhodecode.lib import repo_maintenance | ||
r1153 | from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi | ||
r2359 | from rhodecode.lib.celerylib.utils import get_task_id | ||
r3462 | from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str, safe_int | ||
r1153 | from rhodecode.lib.ext_json import json | ||
r1416 | from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError | ||
r3462 | from rhodecode.lib.vcs import RepositoryError | ||
r3490 | from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError | ||
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 | |||
|
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, | |||
"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['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: | |||
r3104 | raise JSONRPCError(safe_str(e)) | ||
r1 | _cs_json = cs.__json__() | ||
_cs_json['diff'] = build_commit_data(cs, changes_details) | |||
if changes_details == 'full': | |||
r2337 | _cs_json['refs'] = cs._get_refs() | ||
r1 | 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( | ||
r3468 | start_id=start_rev, pre_load=pre_load, translate_tags=False) | ||
r1 | except TypeError as e: | ||
r3104 | raise JSONRPCError(safe_str(e)) | ||
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 | |||
r3460 | md5, binary, and or content. | ||
The valid options are ``basic`` and ``full``. | |||
r1 | :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: [ | |||
r3460 | { | ||
"binary": false, | |||
"content": "File line\nLine2\n", | |||
"extension": "md", | |||
"lines": 2, | |||
"md5": "059fa5d29b19c0657e384749480f6422", | |||
"mimetype": "text/x-minidsrc", | |||
"name": "file.md", | |||
"size": 580, | |||
"type": "file" | |||
}, | |||
r1 | ... | ||
] | |||
error: null | |||
""" | |||
repo = get_repo_or_error(repoid) | |||
if not has_superadmin_permission(apiuser): | |||
r3460 | _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: | |||
r3460 | raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types))) | ||
r1 | 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() | |||
r3460 | def get_repo_file(request, apiuser, repoid, commit_id, file_path, | ||
r3479 | max_file_bytes=Optional(None), details=Optional('basic'), | ||
cache=Optional(True)): | |||
r3460 | """ | ||
Returns a single file from repository at given revision. | |||
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 commit_id: The revision for which listing should be done. | |||
:type commit_id: str | |||
:param file_path: The path from which to start displaying. | |||
:type file_path: str | |||
:param details: Returns different set of information about nodes. | |||
The valid options are ``minimal`` ``basic`` and ``full``. | |||
:type details: Optional(str) | |||
:param max_file_bytes: Only return file content under this file size bytes | |||
r3479 | :type max_file_bytes: Optional(int) | ||
:param cache: Use internal caches for fetching files. If disabled fetching | |||
files is slower but more memory efficient | |||
:type cache: Optional(bool) | |||
r3460 | Example output: | ||
.. code-block:: bash | |||
id : <id_given_in_input> | |||
result: { | |||
"binary": false, | |||
"extension": "py", | |||
"lines": 35, | |||
"content": "....", | |||
"md5": "76318336366b0f17ee249e11b0c99c41", | |||
"mimetype": "text/x-python", | |||
"name": "python.py", | |||
"size": 817, | |||
"type": "file", | |||
} | |||
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) | |||
r3479 | cache = Optional.extract(cache, binary=True) | ||
r3460 | details = Optional.extract(details) | ||
_extended_types = ['minimal', 'minimal+search', 'basic', 'full'] | |||
if details not in _extended_types: | |||
raise JSONRPCError( | |||
'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details) | |||
extended_info = False | |||
content = False | |||
if details == 'minimal': | |||
extended_info = False | |||
elif details == 'basic': | |||
extended_info = True | |||
elif details == 'full': | |||
extended_info = content = True | |||
try: | |||
# check if repo is not empty by any chance, skip quicker if it is. | |||
_scm = repo.scm_instance() | |||
if _scm.is_empty(): | |||
return None | |||
node = ScmModel().get_node( | |||
repo, commit_id, file_path, extended_info=extended_info, | |||
r3479 | content=content, max_file_bytes=max_file_bytes, cache=cache) | ||
r3490 | except NodeDoesNotExistError: | ||
raise JSONRPCError('There is no file in repo: `{}` at path `{}` for commit: `{}`'.format( | |||
repo.repo_name, file_path, commit_id)) | |||
r3460 | except Exception: | ||
r3480 | log.exception("Exception occurred while trying to get repo %s file", | ||
repo.repo_name) | |||
raise JSONRPCError('failed to get repo: `{}` file at path {}'.format( | |||
repo.repo_name, file_path)) | |||
r3460 | |||
return node | |||
@jsonrpc_method() | |||
def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path): | |||
""" | |||
Returns a list of tree nodes for path at given revision. This api is built | |||
strictly for usage in full text search building, and shouldn't be consumed | |||
This command can only be run using an |authtoken| with admin rights, | |||
or users with at least read rights to |repos|. | |||
""" | |||
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) | |||
r3462 | repo_id = repo.repo_id | ||
cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time')) | |||
cache_on = cache_seconds > 0 | |||
cache_namespace_uid = 'cache_repo.{}'.format(repo_id) | |||
region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid) | |||
@region.conditional_cache_on_arguments(namespace=cache_namespace_uid, | |||
condition=cache_on) | |||
def compute_fts_tree(repo_id, commit_id, root_path, cache_ver): | |||
return ScmModel().get_fts_data(repo_id, commit_id, root_path) | |||
r3460 | try: | ||
# check if repo is not empty by any chance, skip quicker if it is. | |||
_scm = repo.scm_instance() | |||
if _scm.is_empty(): | |||
return [] | |||
r3462 | except RepositoryError: | ||
log.exception("Exception occurred while trying to get repo nodes") | |||
raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name) | |||
r3460 | |||
r3462 | try: | ||
# we need to resolve commit_id to a FULL sha for cache to work correctly. | |||
# sending 'master' is a pointer that needs to be translated to current commit. | |||
commit_id = _scm.get_commit(commit_id=commit_id).raw_id | |||
log.debug( | |||
'Computing FTS REPO TREE for repo_id %s commit_id `%s` ' | |||
'with caching: %s[TTL: %ss]' % ( | |||
repo_id, commit_id, cache_on, cache_seconds or 0)) | |||
tree_files = compute_fts_tree(repo_id, commit_id, root_path, 'v1') | |||
r3460 | return tree_files | ||
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() | |||
r1 | 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), | |||
r2562 | push_uri=Optional(None), | ||
r1153 | 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 | ||
r2562 | :param push_uri: set push_uri | ||
:type push_uri: str | |||
r1 | :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) | |||
r2562 | push_uri = Optional.extract(push_uri) | ||
r1153 | 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(), | |||
r2664 | repo_type=repo_type, | ||
r1153 | # 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, | |||
r2562 | repo_push_uri=push_uri, | ||
r1153 | 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'], | |||
r2562 | 'push_uri': schema_data['repo_push_uri'], | ||
r1153 | '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 | } | ||
r2683 | task = RepoModel().create(form_data=data, cur_user=owner.user_id) | ||
r2359 | task_id = get_task_id(task) | ||
r1 | # 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']) |