##// END OF EJS Templates
build(deps): bumped pastedeploy==3.1.0
build(deps): bumped pastedeploy==3.1.0

File last commit:

r5178:30100c99 default
r5204:fd68e5e0 default
Show More
repo_api.py
2533 lines | 86.8 KiB | text/x-python | PythonLexer
copyrights: updated for 2023
r5088 # Copyright (C) 2011-2023 RhodeCode GmbH
project: added all source files and assets
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
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 import rhodecode
from rhodecode.api import (
jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
project: added all source files and assets
r1 from rhodecode.api.utils import (
has_superadmin_permission, Optional, OAttr, get_repo_or_error,
repo-schemas: refactor repository schemas and use it in API update/create functions....
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)
channelstream: cleanup, and re-organize code for posting comments/pr updated messages....
r4505 from rhodecode.lib import audit_logger, rc_cache, channelstream
api: added maintenance command into API.
r1742 from rhodecode.lib import repo_maintenance
api: added get_comment method, and return versions for comments to allow simple edits via API.
r4440 from rhodecode.lib.auth import (
HasPermissionAnyApi, HasUserGroupPermissionAnyApi,
HasRepoPermissionAnyApi)
celery: celery 4.X support. Fixes #4169...
r2359 from rhodecode.lib.celerylib.utils import get_task_id
api: added get_comment method, and return versions for comments to allow simple edits via API.
r4440 from rhodecode.lib.utils2 import (
api: code fixes / cleanups for python3
r5047 str2bool, time_to_datetime, safe_str, safe_int)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 from rhodecode.lib.ext_json import json
api: added get_comment method, and return versions for comments to allow simple edits via API.
r4440 from rhodecode.lib.exceptions import (
StatusChangeOnClosedPullRequestError, CommentVersionMismatch)
api: add cache for fts index builder call that returns file tree. This is often re-used and should return unchanged state fast
r3462 from rhodecode.lib.vcs import RepositoryError
api: report better error when fetching a node that doesn't exist.
r3490 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
project: added all source files and assets
r1 from rhodecode.model.changeset_status import ChangesetStatusModel
comments: renamed ChangesetCommentsModel to CommentsModel to reflect what it actually does....
r1323 from rhodecode.model.comment import CommentsModel
project: added all source files and assets
r1 from rhodecode.model.db import (
api: added possibility to specify comment_type for comment API.
r1337 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
ChangesetComment)
permissions: properly flush user cache permissions in more cases of permission changes....
r3824 from rhodecode.model.permission import PermissionModel
comments: added new events for comment editing to handle them in integrations.
r4444 from rhodecode.model.pull_request import PullRequestModel
project: added all source files and assets
r1 from rhodecode.model.repo import RepoModel
from rhodecode.model.scm import ScmModel, RepoList
Martin Bornhold
api: Add api methods to get/set repository settings, implements #4021
r387 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 from rhodecode.model import validation_schema
gists: use colander schema to validate input data....
r523 from rhodecode.model.validation_schema.schemas import repo_schema
project: added all source files and assets
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)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153
project: added all source files and assets
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',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
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
api: code fixes / cleanups for python3
r5047
project: added all source files and assets
r1 return data
@jsonrpc_method()
api: get_repos allows now to filter by root locations, and optionally traverse the returned data....
r1267 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
project: added all source files and assets
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
api: get_repos allows now to filter by root locations, and optionally traverse the returned data....
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)
project: added all source files and assets
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}
api: get_repos allows now to filter by root locations, and optionally traverse the returned data....
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(
api: modernize code for python3
r5092 f'Root repository group `{root}` does not exist')
api: get_repos allows now to filter by root locations, and optionally traverse the returned data....
r1267
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)
project: added all source files and assets
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):
repo-api: allow repo admins to get/set settings. Previously it was only super-admins that could do that, and it's wrong.
r4474 _perms = ('repository.admin', 'repository.write', 'repository.read',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
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)))
api: fixed SVN raw diff export. The API method was incosistent, and used different logic....
r4531 vcs_repo = repo.scm_instance()
project: added all source files and assets
r1 pre_load = ['author', 'branch', 'date', 'message', 'parents',
'status', '_commit', '_file_paths']
try:
api: fixed SVN raw diff export. The API method was incosistent, and used different logic....
r4531 commit = repo.get_commit(commit_id=revision, pre_load=pre_load)
project: added all source files and assets
r1 except TypeError as e:
exceptions: use python3 compatible exception handling
r3104 raise JSONRPCError(safe_str(e))
api: fixed SVN raw diff export. The API method was incosistent, and used different logic....
r4531 _cs_json = commit.__json__()
_cs_json['diff'] = build_commit_data(vcs_repo, commit, changes_details)
project: added all source files and assets
r1 if changes_details == 'full':
api: fixed SVN raw diff export. The API method was incosistent, and used different logic....
r4531 _cs_json['refs'] = commit._get_refs()
project: added all source files and assets
r1 return _cs_json
@jsonrpc_method()
def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
details=Optional('basic')):
"""
api: gracefully handle errors on repos that are damaged or missing from filesystem.
r61 Returns a set of commits limited by the number starting
project: added all source files and assets
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
api: gracefully handle errors on repos that are damaged or missing from filesystem.
r61 :param limit: Limit the number of commits to this amount
project: added all source files and assets
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):
repo-api: allow repo admins to get/set settings. Previously it was only super-admins that could do that, and it's wrong.
r4474 _perms = ('repository.admin', 'repository.write', 'repository.read',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
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
api: gracefully handle errors on repos that are damaged or missing from filesystem.
r61 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
project: added all source files and assets
r1 start_rev = vcs_repo.commit_ids[0]
try:
api: gracefully handle errors on repos that are damaged or missing from filesystem.
r61 commits = vcs_repo.get_commits(
commits: allow tag commit translation to be skipped for faster commit fetching in big chunks.
r3468 start_id=start_rev, pre_load=pre_load, translate_tags=False)
project: added all source files and assets
r1 except TypeError as e:
exceptions: use python3 compatible exception handling
r3104 raise JSONRPCError(safe_str(e))
api: gracefully handle errors on repos that are damaged or missing from filesystem.
r61 except Exception:
log.exception('Fetching of commits failed')
raise JSONRPCError('Error occurred during commit fetching')
project: added all source files and assets
r1
ret = []
for cnt, commit in enumerate(commits):
if cnt >= limit != -1:
break
_cs_json = commit.__json__()
api: fixed SVN raw diff export. The API method was incosistent, and used different logic....
r4531 _cs_json['diff'] = build_commit_data(vcs_repo, commit, changes_details)
project: added all source files and assets
r1 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,
dan
api: add a max_file_bytes parameter to get_nodes so that large...
r502 ret_type=Optional('all'), details=Optional('basic'),
max_file_bytes=Optional(None)):
project: added all source files and assets
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
api: expose new functions for FTS...
r3460 md5, binary, and or content.
The valid options are ``basic`` and ``full``.
project: added all source files and assets
r1 :type details: Optional(str)
dan
api: add a max_file_bytes parameter to get_nodes so that large...
r502 :param max_file_bytes: Only return file content under this file size bytes
:type details: Optional(int)
project: added all source files and assets
r1
Example output:
.. code-block:: bash
id : <id_given_in_input>
result: [
api: expose new functions for FTS...
r3460 {
"binary": false,
docs: updated documentation structure
r3693 "content": "File line",
api: expose new functions for FTS...
r3460 "extension": "md",
"lines": 2,
"md5": "059fa5d29b19c0657e384749480f6422",
"mimetype": "text/x-minidsrc",
"name": "file.md",
"size": 580,
"type": "file"
},
project: added all source files and assets
r1 ...
]
error: null
"""
repo = get_repo_or_error(repoid)
if not has_superadmin_permission(apiuser):
api: expose new functions for FTS...
r3460 _perms = ('repository.admin', 'repository.write', 'repository.read',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
r1
ret_type = Optional.extract(ret_type)
details = Optional.extract(details)
api: fixes and changes to always return content type in API...
r5001 max_file_bytes = Optional.extract(max_file_bytes)
project: added all source files and assets
r1 _extended_types = ['basic', 'full']
if details not in _extended_types:
api: code fixes / cleanups for python3
r5047 ret_types = ','.join(_extended_types)
raise JSONRPCError(f'ret_type must be one of {ret_types}')
project: added all source files and assets
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,
dan
api: add a max_file_bytes parameter to get_nodes so that large...
r502 extended_info=extended_info, content=content,
max_file_bytes=max_file_bytes)
api: fixes and changes to always return content type in API...
r5001
project: added all source files and assets
r1 _map = {
'all': _d + _f,
'files': _f,
'dirs': _d,
}
api: code fixes / cleanups for python3
r5047
project: added all source files and assets
r1 return _map[ret_type]
except KeyError:
api: code fixes / cleanups for python3
r5047 keys = ','.join(sorted(_map.keys()))
raise JSONRPCError(f'ret_type must be one of {keys}')
project: added all source files and assets
r1 except Exception:
log.exception("Exception occurred while trying to get repo nodes")
api: code fixes / cleanups for python3
r5047 raise JSONRPCError(f'failed to get repo: `{repo.repo_name}` nodes')
project: added all source files and assets
r1
@jsonrpc_method()
api: expose new functions for FTS...
r3460 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
api: code fixes / cleanups for python3
r5047 max_file_bytes=Optional(0), details=Optional('basic'),
api: allow uncached content fetching....
r3479 cache=Optional(True)):
api: expose new functions for FTS...
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
api: allow uncached content fetching....
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)
docs: updated documentation structure
r3693
api: expose new functions for FTS...
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)
api: allow uncached content fetching....
r3479 cache = Optional.extract(cache, binary=True)
api: expose new functions for FTS...
r3460 details = Optional.extract(details)
api: code fixes / cleanups for python3
r5047 max_file_bytes = Optional.extract(max_file_bytes)
api: expose new functions for FTS...
r3460 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
if details not in _extended_types:
api: code fixes / cleanups for python3
r5047 ret_types = ','.join(_extended_types)
raise JSONRPCError(f'ret_type must be one of %s, got {ret_types}', details)
api: expose new functions for FTS...
r3460 extended_info = False
content = False
if details == 'minimal':
extended_info = False
elif details == 'basic':
extended_info = True
elif details == 'full':
extended_info = content = True
api: code fixes / cleanups for python3
r5047 file_path = safe_str(file_path)
api: expose new functions for FTS...
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 None
node = ScmModel().get_node(
repo, commit_id, file_path, extended_info=extended_info,
api: allow uncached content fetching....
r3479 content=content, max_file_bytes=max_file_bytes, cache=cache)
api: code fixes / cleanups for python3
r5047
api: report better error when fetching a node that doesn't exist.
r3490 except NodeDoesNotExistError:
api: code fixes / cleanups for python3
r5047 raise JSONRPCError(
f'There is no file in repo: `{repo.repo_name}` at path `{file_path}` for commit: `{commit_id}`')
api: expose new functions for FTS...
r3460 except Exception:
api: code fixes / cleanups for python3
r5047 log.exception("Exception occurred while trying to get repo %s file",
api: maked logging of get_repo_file API function tell a bit more info about errors.
r3480 repo.repo_name)
api: code fixes / cleanups for python3
r5047 raise JSONRPCError(f'failed to get repo: `{repo.repo_name}` file at path {file_path}')
api: expose new functions for FTS...
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)
api: add cache for fts index builder call that returns file tree. This is often re-used and should return unchanged state fast
r3462 repo_id = repo.repo_id
api: code fixes / cleanups for python3
r5047 cache_seconds = rhodecode.ConfigGet().get_int('rc_cache.cache_repo.expiration_time')
api: add cache for fts index builder call that returns file tree. This is often re-used and should return unchanged state fast
r3462 cache_on = cache_seconds > 0
caches: make sure the global cache namespace prefixes are used....
r5106 cache_namespace_uid = f'repo.{rc_cache.FILE_TREE_CACHE_VER}.{repo_id}'
api: code fixes / cleanups for python3
r5047 rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
api: add cache for fts index builder call that returns file tree. This is often re-used and should return unchanged state fast
r3462
caches: make sure the global cache namespace prefixes are used....
r5106 def compute_fts_tree(repo_id, commit_id, root_path):
api: add cache for fts index builder call that returns file tree. This is often re-used and should return unchanged state fast
r3462 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
api: expose new functions for FTS...
r3460 try:
# check if repo is not empty by any chance, skip quicker if it is.
_scm = repo.scm_instance()
api: code fixes / cleanups for python3
r5047 if not _scm or _scm.is_empty():
api: expose new functions for FTS...
r3460 return []
api: add cache for fts index builder call that returns file tree. This is often re-used and should return unchanged state fast
r3462 except RepositoryError:
log.exception("Exception occurred while trying to get repo nodes")
api: code fixes / cleanups for python3
r5047 raise JSONRPCError(f'failed to get repo: `{repo.repo_name}` nodes')
api: expose new functions for FTS...
r3460
api: add cache for fts index builder call that returns file tree. This is often re-used and should return unchanged state fast
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))
caches: make sure the global cache namespace prefixes are used....
r5106 tree_files = compute_fts_tree(repo_id, commit_id, root_path)
api: code fixes / cleanups for python3
r5047
api: expose new functions for FTS...
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()
project: added all source files and assets
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>
api: implemente missing get_repo_refs api backend. Fixes #4677
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",
}
}
project: added all source files and assets
r1 error: null
"""
repo = get_repo_or_error(repoid)
if not has_superadmin_permission(apiuser):
_perms = ('repository.admin', 'repository.write', 'repository.read',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
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()
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 def create_repo(
request, apiuser, repo_name, repo_type,
owner=Optional(OAttr('apiuser')),
description=Optional(''),
private=Optional(False),
clone_uri=Optional(None),
repositories: allow properly updating repository push url.
r2562 push_uri=Optional(None),
landing-rev: fixes #4102, use branches instead of landing tip refs by default....
r3881 landing_rev=Optional(None),
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 enable_statistics=Optional(False),
enable_locking=Optional(False),
enable_downloads=Optional(False),
copy_permissions=Optional(False)):
project: added all source files and assets
r1 """
Creates a repository.
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 * If the repository name contains "/", repository will be created inside
a repository group or nested repository groups
project: added all source files and assets
r1
repo-schemas: refactor repository schemas and use it in API update/create functions....
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)
project: added all source files and assets
r1
This command can only be run using an |authtoken| with at least
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 permissions to create repositories, or write permissions to
parent repository groups.
project: added all source files and assets
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)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 :param private: set repository as private
project: added all source files and assets
r1 :type private: bool
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 :param clone_uri: set clone_uri
project: added all source files and assets
r1 :type clone_uri: str
repositories: allow properly updating repository push url.
r2562 :param push_uri: set push_uri
:type push_uri: str
landing-rev: fixes #4102, use branches instead of landing tip refs by default....
r3881 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
project: added all source files and assets
r1 :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 : {
docs: fixed small wanrings/errors during build.
r1120 'failed to create repository `<repo_name>`'
project: added all source files and assets
r1 }
"""
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 owner = validate_set_owner_permissions(apiuser, owner)
project: added all source files and assets
r1
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 description = Optional.extract(description)
copy_permissions = Optional.extract(copy_permissions)
clone_uri = Optional.extract(clone_uri)
repositories: allow properly updating repository push url.
r2562 push_uri = Optional.extract(push_uri)
project: added all source files and assets
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')
landing-rev: fixes #4102, use branches instead of landing tip refs by default....
r3881 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
ref_choices = list(set(ref_choices + [landing_ref]))
landing_commit_ref = Optional.extract(landing_rev) or landing_ref
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 schema = repo_schema.RepoSchema().bind(
repo_type_options=rhodecode.BACKENDS.keys(),
landing-rev: fixes #4102, use branches instead of landing tip refs by default....
r3881 repo_ref_options=ref_choices,
api: security, fix problem when absolute paths are specified with API call, that would allow...
r2664 repo_type=repo_type,
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 # user caller
user=apiuser)
project: added all source files and assets
r1
try:
repo-schemas: refactor repository schemas and use it in API update/create functions....
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,
repositories: allow properly updating repository push url.
r2562 repo_push_uri=push_uri,
repo-schemas: refactor repository schemas and use it in API update/create functions....
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:
project: added all source files and assets
r1 data = {
'owner': owner,
repo-schemas: refactor repository schemas and use it in API update/create functions....
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'],
repositories: allow properly updating repository push url.
r2562 'push_uri': schema_data['repo_push_uri'],
repo-schemas: refactor repository schemas and use it in API update/create functions....
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'],
project: added all source files and assets
r1 }
api: fixed problems with repository fork/create using celery backend....
r2683 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
celery: celery 4.X support. Fixes #4169...
r2359 task_id = get_task_id(task)
project: added all source files and assets
r1 # no commit, it's done in RepoModel, or async via celery
return {
api: modernize code for python3
r5092 'msg': "Created new repository `{}`".format(schema_data['repo_name']),
project: added all source files and assets
r1 'success': True, # cannot return the repo data here since fork
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 # can be done async
project: added all source files and assets
r1 'task': task_id
}
except Exception:
log.exception(
api: modernize code for python3
r5092 "Exception while trying to create the repository %s",
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 schema_data['repo_name'])
project: added all source files and assets
r1 raise JSONRPCError(
api: modernize code for python3
r5092 'failed to create repository `{}`'.format(schema_data['repo_name']))
project: added all source files and assets
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',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
r1
label = Optional.extract(label) or key
description = Optional.extract(description)
field = RepositoryField.get_by_key_name(key, repo)
if field:
code: import fix/pep8
r5178 raise JSONRPCError(f'Field with key `{key}` exists for repo `{repoid}`')
project: added all source files and assets
r1
try:
RepoModel().add_repo_field(repo, key, field_label=label,
field_desc=description)
Session().commit()
return {
modernize: updates for python3
r5095 'msg': f"Added new repository field `{key}`",
project: added all source files and assets
r1 'success': True,
}
except Exception:
log.exception("Exception occurred while trying to add field to repo")
raise JSONRPCError(
modernize: updates for python3
r5095 f'failed to create new field for repository `{repoid}`')
project: added all source files and assets
r1
@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',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
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 {
modernize: updates for python3
r5095 'msg': f"Deleted repository field `{key}`",
project: added all source files and assets
r1 'success': True,
}
except Exception:
log.exception(
"Exception occurred while trying to delete field from repo")
raise JSONRPCError(
modernize: updates for python3
r5095 f'failed to delete field for repository `{repoid}`')
project: added all source files and assets
r1
@jsonrpc_method()
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 def update_repo(
request, apiuser, repoid, repo_name=Optional(None),
owner=Optional(OAttr('apiuser')), description=Optional(''),
api: update pull method with possible specification of the url
r2563 private=Optional(False),
clone_uri=Optional(None), push_uri=Optional(None),
landing-rev: fixes #4102, use branches instead of landing tip refs by default....
r3881 landing_rev=Optional(None), fork_of=Optional(None),
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 enable_statistics=Optional(False),
enable_locking=Optional(False),
enable_downloads=Optional(False), fields=Optional('')):
api: modernize code for python3
r5092 r"""
project: added all source files and assets
r1 Updates a repository with the given information.
This command can only be run using an |authtoken| with at least
repo-schemas: refactor repository schemas and use it in API update/create functions....
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)
project: added all source files and assets
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
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 :param repo_name: Update the |repo| name, including the
repository group it's in.
:type repo_name: str
project: added all source files and assets
r1 :param owner: Set the |repo| owner.
:type owner: str
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 :param fork_of: Set the |repo| as fork of another |repo|.
project: added all source files and assets
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
landing-rev: fixes #4102, use branches instead of landing tip refs by default....
r3881 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
project: added all source files and assets
r1 :type landing_rev: str
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 :param enable_statistics: Enable statistics on the |repo|, (True | False).
project: added all source files and assets
r1 :type enable_statistics: bool
:param enable_locking: Enable |repo| locking.
:type enable_locking: bool
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 :param enable_downloads: Enable downloads from the |repo|, (True | False).
project: added all source files and assets
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
"""
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153
project: added all source files and assets
r1 repo = get_repo_or_error(repoid)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153
project: added all source files and assets
r1 include_secrets = False
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 if not has_superadmin_permission(apiuser):
repo-api: allow repo admins to get/set settings. Previously it was only super-admins that could do that, and it's wrong.
r4474 _perms = ('repository.admin',)
validate_repo_permissions(apiuser, repoid, repo, _perms)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 else:
project: added all source files and assets
r1 include_secrets = True
repo-schemas: refactor repository schemas and use it in API update/create functions....
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,
api: update pull method with possible specification of the url
r2563 push_uri=push_uri
if not isinstance(push_uri, Optional) else repo.push_uri,
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 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)
landing-rev: fixes #4102, use branches instead of landing tip refs by default....
r3881 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
pylons: fixed code and test suite after removal of pylons.
r2358 ref_choices, _labels = ScmModel().get_repo_landing_revs(
request.translate, repo=repo)
landing-rev: fixes #4102, use branches instead of landing tip refs by default....
r3881 ref_choices = list(set(ref_choices + [landing_ref]))
project: added all source files and assets
r1
audit-logs: implemented full audit logs across application....
r1829 old_values = repo.get_api_data()
api: security, fix problem when absolute paths are specified with API call, that would allow...
r2664 repo_type = repo.repo_type
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 schema = repo_schema.RepoSchema().bind(
repo_type_options=rhodecode.BACKENDS.keys(),
repo_ref_options=ref_choices,
api: security, fix problem when absolute paths are specified with API call, that would allow...
r2664 repo_type=repo_type,
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 # user caller
user=apiuser,
audit-logs: implemented full audit logs across application....
r1829 old_values=old_values)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 try:
schema_data = schema.deserialize(dict(
# we save old value, users cannot change type
api: security, fix problem when absolute paths are specified with API call, that would allow...
r2664 repo_type=repo_type,
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153
repo_name=updates['repo_name'],
repo_owner=updates['user'],
repo_description=updates['repo_description'],
repo_clone_uri=updates['clone_uri'],
repositories: allow properly updating repository push url.
r2562 repo_push_uri=updates['push_uri'],
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 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)
project: added all source files and assets
r1
repo-schemas: refactor repository schemas and use it in API update/create functions....
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'],
repositories: allow properly updating repository push url.
r2562 push_uri=schema_data['repo_push_uri'],
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 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)
project: added all source files and assets
r1
try:
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 RepoModel().update(repo, **validated_updates)
audit-logs: implemented full audit logs across application....
r1829 audit_logger.store_api(
'repo.edit', action_data={'old_data': old_values},
user=apiuser, repo=repo)
project: added all source files and assets
r1 Session().commit()
return {
modernize: updates for python3
r5095 'msg': f'updated repo ID:{repo.repo_id} {repo.repo_name}',
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 'repository': repo.get_api_data(include_secrets=include_secrets)
project: added all source files and assets
r1 }
except Exception:
log.exception(
api: modernize code for python3
r5092 "Exception while trying to update the repository %s",
project: added all source files and assets
r1 repoid)
raise JSONRPCError('failed to update repo `%s`' % repoid)
@jsonrpc_method()
def fork_repo(request, apiuser, repoid, fork_name,
owner=Optional(OAttr('apiuser')),
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 description=Optional(''),
private=Optional(False),
clone_uri=Optional(None),
landing-rev: fixes #4102, use branches instead of landing tip refs by default....
r3881 landing_rev=Optional(None),
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 copy_permissions=Optional(False)):
project: added all source files and assets
r1 """
Creates a fork of the specified |repo|.
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 * If the fork_name contains "/", fork will be created inside
a repository group or nested repository groups
project: added all source files and assets
r1
repo-schemas: refactor repository schemas and use it in API update/create functions....
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.
project: added all source files and assets
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
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 :param fork_name: Set the fork name, including it's repository group membership.
project: added all source files and assets
r1 :type fork_name: str
:param owner: Set the fork owner.
:type owner: str
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 :param description: Set the fork description.
project: added all source files and assets
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
landing-rev: fixes #4102, use branches instead of landing tip refs by default....
r3881 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
project: added all source files and assets
r1
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 !
repo-api: allow repo admins to get/set settings. Previously it was only super-admins that could do that, and it's wrong.
r4474 _perms = ('repository.admin', 'repository.write', 'repository.read')
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
r1
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 # check if the regular user has at least fork permissions as well
permissions: use constant for fork enable/disable
r4663 if not HasPermissionAnyApi(PermissionModel.FORKING_ENABLED)(user=apiuser):
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 raise JSONRPCForbidden()
# check if user can set owner parameter
owner = validate_set_owner_permissions(apiuser, owner)
project: added all source files and assets
r1
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 description = Optional.extract(description)
copy_permissions = Optional.extract(copy_permissions)
clone_uri = Optional.extract(clone_uri)
landing-rev: fixes #4102, use branches instead of landing tip refs by default....
r3881
landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
ref_choices = list(set(ref_choices + [landing_ref]))
landing_commit_ref = Optional.extract(landing_rev) or landing_ref
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 private = Optional.extract(private)
project: added all source files and assets
r1
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 schema = repo_schema.RepoSchema().bind(
repo_type_options=rhodecode.BACKENDS.keys(),
landing-rev: fixes #4102, use branches instead of landing tip refs by default....
r3881 repo_ref_options=ref_choices,
api: security, fix problem when absolute paths are specified with API call, that would allow...
r2664 repo_type=repo.repo_type,
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 # user caller
user=apiuser)
project: added all source files and assets
r1
try:
repo-schemas: refactor repository schemas and use it in API update/create functions....
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 = {
project: added all source files and assets
r1 'fork_parent_id': repo.repo_id,
repo-schemas: refactor repository schemas and use it in API update/create functions....
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'],
project: added all source files and assets
r1 }
api: fixed problems with repository fork/create using celery backend....
r2683 task = RepoModel().create_fork(data, cur_user=owner.user_id)
project: added all source files and assets
r1 # no commit, it's done in RepoModel, or async via celery
celery: celery 4.X support. Fixes #4169...
r2359 task_id = get_task_id(task)
project: added all source files and assets
r1 return {
api: modernize code for python3
r5092 'msg': 'Created fork of `{}` as `{}`'.format(
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 repo.repo_name, schema_data['repo_name']),
project: added all source files and assets
r1 'success': True, # cannot return the repo data here since fork
# can be done async
'task': task_id
}
except Exception:
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 log.exception(
api: modernize code for python3
r5092 "Exception while trying to create fork %s",
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 schema_data['repo_name'])
project: added all source files and assets
r1 raise JSONRPCError(
api: modernize code for python3
r5092 'failed to fork repository `{}` as `{}`'.format(
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 repo_name, schema_data['repo_name']))
project: added all source files and assets
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)
audit-logger: unify calls to repo.delete and also store source of call, api/web.
r1752 repo_name = repo.repo_name
project: added all source files and assets
r1 if not has_superadmin_permission(apiuser):
_perms = ('repository.admin',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
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,)
)
audit-logs: implemented full audit logs across application....
r1829 old_data = repo.get_api_data()
audit-logger: unify calls to repo.delete and also store source of call, api/web.
r1752 RepoModel().delete(repo, forks=forks)
project: added all source files and assets
r1
audit-logger: unify calls to repo.delete and also store source of call, api/web.
r1752 repo = audit_logger.RepoWrap(repo_id=None,
repo_name=repo.repo_name)
audit-logs: use specific web/api calls....
r1806 audit_logger.store_api(
audit-logs: implemented full audit logs across application....
r1829 'repo.delete', action_data={'old_data': old_data},
audit-logs: use specific web/api calls....
r1806 user=apiuser, repo=repo)
audit-logger: unify calls to repo.delete and also store source of call, api/web.
r1752
ScmModel().mark_for_invalidation(repo_name, delete=True)
project: added all source files and assets
r1 Session().commit()
return {
modernize: updates for python3
r5095 'msg': f'Deleted repository `{repo_name}`{_forks_msg}',
project: added all source files and assets
r1 'success': True
}
except Exception:
log.exception("Exception occurred while trying to delete repo")
raise JSONRPCError(
modernize: updates for python3
r5095 f'failed to delete repository `{repo_name}`'
project: added all source files and assets
r1 )
#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',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
r1
delete = Optional.extract(delete_keys)
try:
ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
return {
modernize: updates for python3
r5095 'msg': f'Cache for repository `{repoid}` was invalidated',
project: added all source files and assets
r1 '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 : {
docs: fixed small wanrings/errors during build.
r1120 'Error occurred locking repository `<reponame>`'
project: added all source files and assets
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',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
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(
api: added possibility to specify comment_type for comment API.
r1337 request, apiuser, repoid, commit_id, message, status=Optional(None),
comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
api: allow extra recipients for pr/commit comments api methods...
r4049 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
api: add send_email flag for comments api to allow commenting without email notification....
r4196 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
project: added all source files and assets
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
api: added possibility to specify comment_type for comment API.
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'
api: allow extra recipients for pr/commit comments api methods...
r4049 :param resolves_comment_id: id of comment which this one will resolve
:type resolves_comment_id: Optional(int)
:param extra_recipients: list of user ids or usernames to add
notifications for this comment. Acts like a CC for notification
:type extra_recipients: Optional(list)
project: added all source files and assets
r1 :param userid: Set the user name of the comment creator.
:type userid: Optional(str or int)
api: add send_email flag for comments api to allow commenting without email notification....
r4196 :param send_email: Define if this comment should also send email notification
:type send_email: Optional(bool)
project: added all source files and assets
r1
Example error output:
api: added possibility to specify comment_type for comment API.
r1337 .. code-block:: bash
project: added all source files and assets
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
}
"""
channelstream: cleanup, and re-organize code for posting comments/pr updated messages....
r4505 _ = request.translate
project: added all source files and assets
r1 repo = get_repo_or_error(repoid)
if not has_superadmin_permission(apiuser):
api: make comment_commits api have consistent permissions with web interface.
r352 _perms = ('repository.read', 'repository.write', 'repository.admin')
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
channelstream: cleanup, and re-organize code for posting comments/pr updated messages....
r4505 db_repo_name = repo.repo_name
project: added all source files and assets
r1
api: validate commit_id when using commit_comment API
r1416 try:
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 commit = repo.scm_instance().get_commit(commit_id=commit_id)
commit_id = commit.raw_id
api: validate commit_id when using commit_comment API
r1416 except Exception as e:
log.exception('Failed to fetch commit')
exceptions: use python3 compatible exception handling
r3104 raise JSONRPCError(safe_str(e))
api: validate commit_id when using commit_comment API
r1416
project: added all source files and assets
r1 if isinstance(userid, Optional):
userid = apiuser.user_id
user = get_user_or_error(userid)
status = Optional.extract(status)
api: added possibility to specify comment_type for comment API.
r1337 comment_type = Optional.extract(comment_type)
api: added comment_resolved_id into aprameters to resolve TODO notes via API.
r1338 resolves_comment_id = Optional.extract(resolves_comment_id)
api: allow extra recipients for pr/commit comments api methods...
r4049 extra_recipients = Optional.extract(extra_recipients)
api: add send_email flag for comments api to allow commenting without email notification....
r4196 send_email = Optional.extract(send_email, binary=True)
project: added all source files and assets
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,))
api: added comment_resolved_id into aprameters to resolve TODO notes via API.
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)
project: added all source files and assets
r1 try:
rc_config = SettingsModel().get_all_settings()
renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
emails: added new tags to status sent...
r548 status_change_label = ChangesetStatus.get_status_lbl(status)
audit-logs: implemented full audit logs across application....
r1829 comment = CommentsModel().create(
comments: changed signature of create method of ChangesetComment....
r1322 message, repo, user, commit_id=commit_id,
emails: added new tags to status sent...
r548 status_change=status_change_label,
status_change_type=status,
api: added possibility to specify comment_type for comment API.
r1337 renderer=renderer,
api: added comment_resolved_id into aprameters to resolve TODO notes via API.
r1338 comment_type=comment_type,
audit-logs: store properly IP and user for certain comments types....
r2728 resolves_comment_id=resolves_comment_id,
api: allow extra recipients for pr/commit comments api methods...
r4049 auth_user=apiuser,
api: add send_email flag for comments api to allow commenting without email notification....
r4196 extra_recipients=extra_recipients,
send_email=send_email
api: added possibility to specify comment_type for comment API.
r1337 )
observers: code cleanups and fixed tests.
r4519 is_inline = comment.is_inline
channelstream: cleanup, and re-organize code for posting comments/pr updated messages....
r4505
project: added all source files and assets
r1 if status:
# also do a status change
try:
ChangesetStatusModel().set_status(
audit-logs: implemented full audit logs across application....
r1829 repo, status, user, comment, revision=commit_id,
project: added all source files and assets
r1 dont_allow_on_closed_pull_request=True
)
except StatusChangeOnClosedPullRequestError:
log.exception(
"Exception occurred while trying to change repo commit status")
comments: extend API data with references to commit_id or pull_request_id for audit-logs.
r4304 msg = ('Changing status on a commit associated with '
project: added all source files and assets
r1 'a closed pull request is not allowed')
raise JSONRPCError(msg)
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 CommentsModel().trigger_commit_comment_hook(
repo, apiuser, 'create',
data={'comment': comment, 'commit': commit})
project: added all source files and assets
r1 Session().commit()
channelstream: cleanup, and re-organize code for posting comments/pr updated messages....
r4505
comment_broadcast_channel = channelstream.comment_channel(
db_repo_name, commit_obj=commit)
comment_data = {'comment': comment, 'comment_id': comment.comment_id}
comment_type = 'inline' if is_inline else 'general'
channelstream.comment_channelstream_push(
request, comment_broadcast_channel, apiuser,
_('posted a new {} comment').format(comment_type),
comment_data=comment_data)
project: added all source files and assets
r1 return {
'msg': (
api: modernize code for python3
r5092 'Commented on commit `{}` for repository `{}`'.format(
audit-logs: implemented full audit logs across application....
r1829 comment.revision, repo.repo_name)),
project: added all source files and assets
r1 '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(
modernize: updates for python3
r5095 f'failed to set comment on repository `{repo.repo_name}`'
project: added all source files and assets
r1 )
@jsonrpc_method()
api: added function to fetch comments for a repository.
r3435 def get_repo_comments(request, apiuser, repoid,
commit_id=Optional(None), comment_type=Optional(None),
userid=Optional(None)):
"""
Get all comments for a repository
: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: Optionally filter the comments by the commit_id
:type commit_id: Optional(str), default: None
:param comment_type: Optionally filter the comments by the comment_type
one of: 'note', 'todo'
:type comment_type: Optional(str), default: None
:param userid: Optionally filter the comments by the author of comment
:type userid: Optional(str or int), Default: None
Example error output:
.. code-block:: bash
{
"id" : <id_given_in_input>,
"result" : [
{
"comment_author": <USER_DETAILS>,
"comment_created_on": "2017-02-01T14:38:16.309",
"comment_f_path": "file.txt",
"comment_id": 282,
"comment_lineno": "n1",
"comment_resolved_by": null,
"comment_status": [],
"comment_text": "This file needs a header",
api: added get_comment method, and return versions for comments to allow simple edits via API.
r4440 "comment_type": "todo",
"comment_last_version: 0
api: added function to fetch comments for a repository.
r3435 }
],
"error" : null
}
"""
repo = get_repo_or_error(repoid)
if not has_superadmin_permission(apiuser):
_perms = ('repository.read', 'repository.write', 'repository.admin')
validate_repo_permissions(apiuser, repoid, repo, _perms)
commit_id = Optional.extract(commit_id)
userid = Optional.extract(userid)
if userid:
user = get_user_or_error(userid)
else:
user = None
comment_type = Optional.extract(comment_type)
if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
raise JSONRPCError(
'comment_type must be one of `{}` got {}'.format(
ChangesetComment.COMMENT_TYPES, comment_type)
)
comments = CommentsModel().get_repository_comments(
repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
return comments
@jsonrpc_method()
api: added get_comment method, and return versions for comments to allow simple edits via API.
r4440 def get_comment(request, apiuser, comment_id):
"""
Get single comment from repository or pull_request
:param apiuser: This is filled automatically from the |authtoken|.
:type apiuser: AuthUser
:param comment_id: comment id found in the URL of comment
:type comment_id: str or int
Example error output:
.. code-block:: bash
{
"id" : <id_given_in_input>,
"result" : {
"comment_author": <USER_DETAILS>,
"comment_created_on": "2017-02-01T14:38:16.309",
"comment_f_path": "file.txt",
"comment_id": 282,
"comment_lineno": "n1",
"comment_resolved_by": null,
"comment_status": [],
"comment_text": "This file needs a header",
"comment_type": "todo",
"comment_last_version: 0
},
"error" : null
}
"""
comment = ChangesetComment.get(comment_id)
if not comment:
modernize: updates for python3
r5095 raise JSONRPCError(f'comment `{comment_id}` does not exist')
api: added get_comment method, and return versions for comments to allow simple edits via API.
r4440
perms = ('repository.read', 'repository.write', 'repository.admin')
has_comment_perm = HasRepoPermissionAnyApi(*perms)\
(user=apiuser, repo_name=comment.repo.repo_name)
if not has_comment_perm:
modernize: updates for python3
r5095 raise JSONRPCError(f'comment `{comment_id}` does not exist')
api: added get_comment method, and return versions for comments to allow simple edits via API.
r4440
return comment
@jsonrpc_method()
def edit_comment(request, apiuser, message, comment_id, version,
userid=Optional(OAttr('apiuser'))):
"""
Edit comment on the pull request or commit,
specified by the `comment_id` and version. Initially version should be 0
:param apiuser: This is filled automatically from the |authtoken|.
:type apiuser: AuthUser
:param comment_id: Specify the comment_id for editing
:type comment_id: int
:param version: version of the comment that will be created, starts from 0
:type version: int
:param message: The text content of the comment.
:type message: str
:param userid: Comment on the pull request as this user
:type userid: Optional(str or int)
Example output:
.. code-block:: bash
id : <id_given_in_input>
result : {
"comment": "<comment data>",
"version": "<Integer>",
},
error : null
"""
auth_user = apiuser
comment = ChangesetComment.get(comment_id)
if not comment:
modernize: updates for python3
r5095 raise JSONRPCError(f'comment `{comment_id}` does not exist')
api: added get_comment method, and return versions for comments to allow simple edits via API.
r4440
is_super_admin = has_superadmin_permission(apiuser)
is_repo_admin = HasRepoPermissionAnyApi('repository.admin')\
(user=apiuser, repo_name=comment.repo.repo_name)
if not isinstance(userid, Optional):
if is_super_admin or is_repo_admin:
apiuser = get_user_or_error(userid)
auth_user = apiuser.AuthUser()
else:
raise JSONRPCError('userid is not the same as your user')
comment_author = comment.author.user_id == auth_user.user_id
api: code fixes / cleanups for python3
r5047
if comment.immutable:
raise JSONRPCError("Immutable comment cannot be edited")
if not (is_super_admin or is_repo_admin or comment_author):
api: added get_comment method, and return versions for comments to allow simple edits via API.
r4440 raise JSONRPCError("you don't have access to edit this comment")
try:
comment_history = CommentsModel().edit(
comment_id=comment_id,
text=message,
auth_user=auth_user,
version=version,
)
Session().commit()
except CommentVersionMismatch:
raise JSONRPCError(
api: modernize code for python3
r5092 f'comment ({comment_id}) version ({version}) mismatch'
api: added get_comment method, and return versions for comments to allow simple edits via API.
r4440 )
if not comment_history and not message:
raise JSONRPCError(
api: modernize code for python3
r5092 f"comment ({comment_id}) can't be changed with empty string"
api: added get_comment method, and return versions for comments to allow simple edits via API.
r4440 )
comments: added new events for comment editing to handle them in integrations.
r4444
if comment.pull_request:
pull_request = comment.pull_request
PullRequestModel().trigger_pull_request_hook(
pull_request, apiuser, 'comment_edit',
data={'comment': comment})
else:
db_repo = comment.repo
commit_id = comment.revision
commit = db_repo.get_commit(commit_id)
CommentsModel().trigger_commit_comment_hook(
db_repo, apiuser, 'edit',
data={'comment': comment, 'commit': commit})
api: added get_comment method, and return versions for comments to allow simple edits via API.
r4440 data = {
'comment': comment,
'version': comment_history.version if comment_history else None,
}
return data
# TODO(marcink): write this with all required logic for deleting a comments in PR or commits
# @jsonrpc_method()
# def delete_comment(request, apiuser, comment_id):
# auth_user = apiuser
#
# comment = ChangesetComment.get(comment_id)
# if not comment:
# raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
#
# is_super_admin = has_superadmin_permission(apiuser)
# is_repo_admin = HasRepoPermissionAnyApi('repository.admin')\
# (user=apiuser, repo_name=comment.repo.repo_name)
#
# comment_author = comment.author.user_id == auth_user.user_id
# if not (comment.immutable is False and (is_super_admin or is_repo_admin) or comment_author):
# raise JSONRPCError("you don't have access to edit this comment")
@jsonrpc_method()
project: added all source files and assets
r1 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',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
r1
audit-logs: add audit logs for API permission calls....
r3342 perm_additions = [[user.user_id, perm.permission_name, "user"]]
project: added all source files and assets
r1 try:
audit-logs: add audit logs for API permission calls....
r3342 changes = RepoModel().update_permissions(
repo=repo, perm_additions=perm_additions, cur_user=apiuser)
project: added all source files and assets
r1
audit-logs: add audit logs for API permission calls....
r3342 action_data = {
'added': changes['added'],
'updated': changes['updated'],
'deleted': changes['deleted'],
}
audit_logger.store_api(
'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
permissions: properly flush user cache permissions in more cases of permission changes....
r3824 Session().commit()
PermissionModel().flush_user_permission_caches(changes)
project: added all source files and assets
r1
return {
api: modernize code for python3
r5092 'msg': 'Granted perm: `{}` for user: `{}` in repo: `{}`'.format(
project: added all source files and assets
r1 perm.permission_name, user.username, repo.repo_name
),
'success': True
}
except Exception:
audit-logs: add audit logs for API permission calls....
r3342 log.exception("Exception occurred while trying edit permissions for repo")
project: added all source files and assets
r1 raise JSONRPCError(
api: modernize code for python3
r5092 'failed to edit permission for user: `{}` in repo: `{}`'.format(
project: added all source files and assets
r1 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',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
r1
audit-logs: add audit logs for API permission calls....
r3342 perm_deletions = [[user.user_id, None, "user"]]
project: added all source files and assets
r1 try:
audit-logs: add audit logs for API permission calls....
r3342 changes = RepoModel().update_permissions(
repo=repo, perm_deletions=perm_deletions, cur_user=user)
action_data = {
'added': changes['added'],
'updated': changes['updated'],
'deleted': changes['deleted'],
}
audit_logger.store_api(
'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
permissions: properly flush user cache permissions in more cases of permission changes....
r3824 Session().commit()
PermissionModel().flush_user_permission_caches(changes)
audit-logs: add audit logs for API permission calls....
r3342
project: added all source files and assets
r1 return {
api: modernize code for python3
r5092 'msg': 'Revoked perm for user: `{}` in repo: `{}`'.format(
project: added all source files and assets
r1 user.username, repo.repo_name
),
'success': True
}
except Exception:
audit-logs: add audit logs for API permission calls....
r3342 log.exception("Exception occurred while trying revoke permissions to repo")
project: added all source files and assets
r1 raise JSONRPCError(
api: modernize code for python3
r5092 'failed to edit permission for user: `{}` in repo: `{}`'.format(
project: added all source files and assets
r1 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',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
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(
modernize: updates for python3
r5095 f'user group `{usergroupid}` does not exist')
project: added all source files and assets
r1
audit-logs: add audit logs for API permission calls....
r3342 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
project: added all source files and assets
r1 try:
audit-logs: add audit logs for API permission calls....
r3342 changes = RepoModel().update_permissions(
repo=repo, perm_additions=perm_additions, cur_user=apiuser)
action_data = {
'added': changes['added'],
'updated': changes['updated'],
'deleted': changes['deleted'],
}
audit_logger.store_api(
'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
permissions: properly flush user cache permissions in more cases of permission changes....
r3824 Session().commit()
PermissionModel().flush_user_permission_caches(changes)
project: added all source files and assets
r1
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',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
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(
modernize: updates for python3
r5095 f'user group `{usergroupid}` does not exist')
project: added all source files and assets
r1
audit-logs: add audit logs for API permission calls....
r3342 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
project: added all source files and assets
r1 try:
audit-logs: add audit logs for API permission calls....
r3342 changes = RepoModel().update_permissions(
repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
action_data = {
'added': changes['added'],
'updated': changes['updated'],
'deleted': changes['deleted'],
}
audit_logger.store_api(
'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
permissions: properly flush user cache permissions in more cases of permission changes....
r3824 Session().commit()
PermissionModel().flush_user_permission_caches(changes)
project: added all source files and assets
r1
return {
api: modernize code for python3
r5092 'msg': 'Revoked perm for user group: `{}` in repo: `{}`'.format(
project: added all source files and assets
r1 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()
api: update pull method with possible specification of the url
r2563 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
project: added all source files and assets
r1 """
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
api: update pull method with possible specification of the url
r2563 :param remote_uri: Optional remote URI to pass in for pull
:type remote_uri: str
project: added all source files and assets
r1
Example output:
.. code-block:: bash
id : <id_given_in_input>
result : {
api: update pull method with possible specification of the url
r2563 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
project: added all source files and assets
r1 "repository": "<repository name>"
}
error : null
Example error output:
.. code-block:: bash
id : <id_given_in_input>
result : null
error : {
api: update pull method with possible specification of the url
r2563 "Unable to push changes from `<remote_url>`"
project: added all source files and assets
r1 }
"""
repo = get_repo_or_error(repoid)
api: update pull method with possible specification of the url
r2563 remote_uri = Optional.extract(remote_uri)
remote_uri_display = remote_uri or repo.clone_uri_hidden
project: added all source files and assets
r1 if not has_superadmin_permission(apiuser):
_perms = ('repository.admin',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
r1
try:
api: update pull method with possible specification of the url
r2563 ScmModel().pull_changes(
repo.repo_name, apiuser.username, remote_uri=remote_uri)
project: added all source files and assets
r1 return {
api: modernize code for python3
r5092 'msg': 'Pulled from url `{}` on repo `{}`'.format(
api: update pull method with possible specification of the url
r2563 remote_uri_display, repo.repo_name),
project: added all source files and assets
r1 'repository': repo.repo_name
}
except Exception:
log.exception("Exception occurred while trying to "
"pull changes from remote location")
raise JSONRPCError(
api: update pull method with possible specification of the url
r2563 'Unable to pull changes from `%s`' % remote_uri_display
project: added all source files and assets
r1 )
@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',)
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
project: added all source files and assets
r1
try:
ScmModel().strip(repo, revision, branch)
audit-logs: implemented full audit logs across application....
r1829 audit_logger.store_api(
'repo.commit.strip', action_data={'commit_id': revision},
repo=repo,
user=apiuser, commit=True)
project: added all source files and assets
r1 return {
api: modernize code for python3
r5092 'msg': 'Stripped commit {} from repo `{}`'.format(
project: added all source files and assets
r1 revision, repo.repo_name),
'repository': repo.repo_name
}
except Exception:
log.exception("Exception while trying to strip")
raise JSONRPCError(
api: modernize code for python3
r5092 'Unable to strip commit {} from repo `{}`'.format(
project: added all source files and assets
r1 revision, repo.repo_name)
)
Martin Bornhold
api: Add api methods to get/set repository settings, implements #4021
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,
mercurial-evolve: enable evolve setting on repositories.
r1738 "extensions_evolve": true,
Martin Bornhold
api: Add api methods to get/set repository settings, implements #4021
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
}
}
"""
repo-api: allow repo admins to get/set settings. Previously it was only super-admins that could do that, and it's wrong.
r4474 # Restrict access to this api method to super-admins, and repo admins only.
repo = get_repo_or_error(repoid)
Martin Bornhold
api: Add api methods to get/set repository settings, implements #4021
r387 if not has_superadmin_permission(apiuser):
repo-api: allow repo admins to get/set settings. Previously it was only super-admins that could do that, and it's wrong.
r4474 _perms = ('repository.admin',)
validate_repo_permissions(apiuser, repoid, repo, _perms)
Martin Bornhold
api: Add api methods to get/set repository settings, implements #4021
r387
try:
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:
api: modernize code for python3
r5092 msg = f'Failed to fetch settings for repository `{repoid}`'
Martin Bornhold
api: Add api methods to get/set repository settings, implements #4021
r387 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
}
"""
repo-api: allow repo admins to get/set settings. Previously it was only super-admins that could do that, and it's wrong.
r4474 # Restrict access to this api method to super-admins, and repo admins only.
repo = get_repo_or_error(repoid)
Martin Bornhold
api: Add api methods to get/set repository settings, implements #4021
r387 if not has_superadmin_permission(apiuser):
repo-api: allow repo admins to get/set settings. Previously it was only super-admins that could do that, and it's wrong.
r4474 _perms = ('repository.admin',)
validate_repo_permissions(apiuser, repoid, repo, _perms)
Martin Bornhold
api: Add api methods to get/set repository settings, implements #4021
r387
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:
api: modernize code for python3
r5092 msg = f'Failed to update settings for repository `{repoid}`'
Martin Bornhold
api: Add api methods to get/set repository settings, implements #4021
r387 log.exception(msg)
raise JSONRPCError(msg)
# Indicate success.
return True
api: added maintenance command into API.
r1742
@jsonrpc_method()
def maintenance(request, apiuser, repoid):
"""
Triggers a maintenance on the given repository.
This command can only be run using an |authtoken| with admin
rights to the specified repository. For more information,
see :ref:`config-token-ref`.
This command takes the following options:
:param apiuser: This is filled automatically from the |authtoken|.
:type apiuser: AuthUser
:param repoid: The repository name or repository ID.
:type repoid: str or int
Example output:
.. code-block:: bash
id : <id_given_in_input>
result : {
"msg": "executed maintenance command",
"executed_actions": [
<action_message>, <action_message2>...
],
"repository": "<repository name>"
}
error : null
Example error output:
.. code-block:: bash
id : <id_given_in_input>
result : null
error : {
"Unable to execute maintenance on `<reponame>`"
}
"""
repo = get_repo_or_error(repoid)
if not has_superadmin_permission(apiuser):
_perms = ('repository.admin',)
validate_repo_permissions(apiuser, repoid, repo, _perms)
try:
maintenance = repo_maintenance.RepoMaintenance()
executed_actions = maintenance.execute(repo)
return {
'msg': 'executed maintenance command',
'executed_actions': executed_actions,
'repository': repo.repo_name
}
except Exception:
log.exception("Exception occurred while trying to run maintenance")
raise JSONRPCError(
'Unable to execute maintenance on `%s`' % repo.repo_name)