##// END OF EJS Templates
added delay before doing an auto merge
added delay before doing an auto merge

File last commit:

r5651:bad147da default
r5660:cba707aa default
Show More
repo.py
1220 lines | 44.6 KiB | text/x-python | PythonLexer
core: updated copyright to 2024
r5608 # Copyright (C) 2010-2024 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 os
import re
import shutil
import time
audit-logs: expose tailoed audit logs in repository view
r2156 import logging
project: added all source files and assets
r1 import traceback
repository-groups: introduce last change for repository groups.
r1940 import datetime
project: added all source files and assets
r1
repo-summary: re-implemented summary view as pyramid....
r1785 from pyramid.threadlocal import get_current_request
models: major update for python3,...
r5070 from sqlalchemy.orm import aliased
project: added all source files and assets
r1 from zope.cachedescriptors.property import Lazy as LazyProperty
dan
events: add event system for RepoEvents
r375 from rhodecode import events
project: added all source files and assets
r1 from rhodecode.lib.auth import HasUserGroupPermissionAny
from rhodecode.lib.caching_query import FromCache
feat(artifacts): new artifact storage engines allowing an s3 based uploads
r5516 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError, AttachedArtifactsError
comments: added rcextensions hoooks for comment editing, and renamed methods to remove odd log_ prefix which...
r4445 from rhodecode.lib import hooks_base
fix(encoding for file): fixed support of non utf-8 files in all backends
r5647 from rhodecode.lib.str_utils import safe_bytes
audit-logs: expose tailoed audit logs in repository view
r2156 from rhodecode.lib.user_log_filter import user_log_filter
project: added all source files and assets
r1 from rhodecode.lib.utils import make_db_config
from rhodecode.lib.utils2 import (
models: major update for python3,...
r5070 safe_str, remove_prefix, obfuscate_url_pw,
descriptions: fixed rendering problem with certain meta-tags....
r4223 get_current_rhodecode_user, safe_int, action_logger_generic)
project: added all source files and assets
r1 from rhodecode.lib.vcs.backends import get_backend
fix(submodules): fixed an error if reaching out submodule path....
r5261 from rhodecode.lib.vcs.nodes import NodeKind
project: added all source files and assets
r1 from rhodecode.model import BaseModel
audit-logs: expose tailoed audit logs in repository view
r2156 from rhodecode.model.db import (
exported get_repos_as_dict for new grids
r4147 _hash_key, func, case, joinedload, or_, in_filter_generator,
Session, Repository, UserRepoToPerm, UserGroupRepoToPerm,
audit-logs: expose tailoed audit logs in repository view
r2156 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
permissions: flush permissions on owner changes for repo and repo groups.
r4662 from rhodecode.model.permission import PermissionModel
project: added all source files and assets
r1 from rhodecode.model.settings import VcsSettingsModel
log = logging.getLogger(__name__)
class RepoModel(BaseModel):
cls = Repository
def _get_user_group(self, users_group):
return self._get_instance(UserGroup, users_group,
callback=UserGroup.get_by_group_name)
def _get_repo_group(self, repo_group):
return self._get_instance(RepoGroup, repo_group,
callback=RepoGroup.get_by_group_name)
def _create_default_perms(self, repository, private):
# create default permission
default = 'repository.read'
def_user = User.get_default_user()
for p in def_user.user_perms:
if p.permission.permission_name.startswith('repository.'):
default = p.permission.permission_name
break
default_perm = 'repository.none' if private else default
repo_to_perm = UserRepoToPerm()
repo_to_perm.permission = Permission.get_by_key(default_perm)
repo_to_perm.repository = repository
models: major update for python3,...
r5070 repo_to_perm.user = def_user
project: added all source files and assets
r1
return repo_to_perm
caches: removed unused cache option from repo by id fetch.
r2889 def get(self, repo_id):
project: added all source files and assets
r1 repo = self.sa.query(Repository) \
.filter(Repository.repo_id == repo_id)
return repo.scalar()
def get_repo(self, repository):
return self._get_repo(repository)
def get_by_repo_name(self, repo_name, cache=False):
repo = self.sa.query(Repository) \
.filter(Repository.repo_name == repo_name)
if cache:
caches: ensure we don't use non-ascii characters in cache keys....
r1749 name_key = _hash_key(repo_name)
repo = repo.options(
caches: cleanup code...
r5009 FromCache("sql_cache_short", f"get_repo_{name_key}"))
project: added all source files and assets
r1 return repo.scalar()
def _extract_id_from_repo_name(self, repo_name):
if repo_name.startswith('/'):
repo_name = repo_name.lstrip('/')
models: major update for python3,...
r5070 by_id_match = re.match(r'^_(\d+)', repo_name)
project: added all source files and assets
r1 if by_id_match:
return by_id_match.groups()[0]
def get_repo_by_id(self, repo_name):
"""
Extracts repo_name by id from special urls.
Example url is _11/repo_name
:param repo_name:
:return: repo object if matched else None
"""
repos: recover properly from bad extraction of repo_id from URL and DB calls.
r4725 _repo_id = None
project: added all source files and assets
r1 try:
_repo_id = self._extract_id_from_repo_name(repo_name)
if _repo_id:
return self.get(_repo_id)
except Exception:
log.exception('Failed to extract repo_name from URL')
repos: recover properly from bad extraction of repo_id from URL and DB calls.
r4725 if _repo_id:
Session().rollback()
project: added all source files and assets
r1
return None
api: get_repos allows now to filter by root locations, and optionally traverse the returned data....
r1267 def get_repos_for_root(self, root, traverse=False):
if traverse:
models: major update for python3,...
r5070 like_expression = u'{}%'.format(safe_str(root))
api: get_repos allows now to filter by root locations, and optionally traverse the returned data....
r1267 repos = Repository.query().filter(
Repository.repo_name.like(like_expression)).all()
else:
if root and not isinstance(root, RepoGroup):
raise ValueError(
'Root must be an instance '
'of RepoGroup, got:{} instead'.format(type(root)))
repos = Repository.query().filter(Repository.group == root).all()
return repos
events: expose permalink urls for different set of object....
r1788 def get_url(self, repo, request=None, permalink=False):
repo-summary: re-implemented summary view as pyramid....
r1785 if not request:
request = get_current_request()
events: expose permalink urls for different set of object....
r1788
events: re-organizate events handling....
r1789 if not request:
return
events: expose permalink urls for different set of object....
r1788 if permalink:
return request.route_url(
repo-model: fixed permalink generation for repositories.
r2413 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
events: expose permalink urls for different set of object....
r1788 else:
return request.route_url(
'repo_summary', repo_name=safe_str(repo.repo_name))
def get_commit_url(self, repo, commit_id, request=None, permalink=False):
if not request:
request = get_current_request()
events: re-organizate events handling....
r1789 if not request:
return
events: expose permalink urls for different set of object....
r1788 if permalink:
return request.route_url(
'repo_commit', repo_name=safe_str(repo.repo_id),
commit_id=commit_id)
else:
return request.route_url(
'repo_commit', repo_name=safe_str(repo.repo_name),
commit_id=commit_id)
dan
events: add serialization .to_dict() to events based on marshmallow
r379
audit-logs: expose tailoed audit logs in repository view
r2156 def get_repo_log(self, repo, filter_term):
repo_log = UserLog.query()\
.filter(or_(UserLog.repository_id == repo.repo_id,
UserLog.repository_name == repo.repo_name))\
.options(joinedload(UserLog.user))\
.options(joinedload(UserLog.repository))\
.order_by(UserLog.action_date.desc())
repo_log = user_log_filter(repo_log, filter_term)
return repo_log
project: added all source files and assets
r1 @classmethod
feat: changed last change of repo group to be commit of a latest repository.
r3689 def update_commit_cache(cls, repositories=None):
project: added all source files and assets
r1 if not repositories:
repositories = Repository.getAll()
for repo in repositories:
repo.update_commit_cache()
def get_repos_as_dict(self, repo_list=None, admin=False,
repositories: bring back watch action in summary view
r3670 super_user_actions=False, short_name=None):
descriptions: fixed rendering problem with certain meta-tags....
r4223
core: use new style pyramid partial renderer where possible.
r1897 _render = get_current_request().get_partial_renderer(
partial-renderer: use package resource format for templates....
r2313 'rhodecode:templates/data_table/_dt_elements.mako')
core: use new style pyramid partial renderer where possible.
r1897 c = _render.get_call_context()
descriptions: fixed rendering problem with certain meta-tags....
r4223 h = _render.get_helpers()
project: added all source files and assets
r1
def quick_menu(repo_name):
return _render('quick_menu', repo_name)
models: major update for python3,...
r5070 def repo_lnk(name, rtype, rstate, private, archived, fork_repo_name):
repositories: bring back watch action in summary view
r3670 if short_name is not None:
short_name_var = short_name
else:
short_name_var = not admin
models: major update for python3,...
r5070 return _render('repo_name', name, rtype, rstate, private, archived, fork_repo_name,
repositories: bring back watch action in summary view
r3670 short_name=short_name_var, admin=False)
project: added all source files and assets
r1
def last_change(last_change):
repository-groups: introduce last change for repository groups.
r1940 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
admin: fixed problems with generating last change in admin panels....
r4000 ts = time.time()
utc_offset = (datetime.datetime.fromtimestamp(ts)
- datetime.datetime.utcfromtimestamp(ts)).total_seconds()
last_change = last_change + datetime.timedelta(seconds=utc_offset)
project: added all source files and assets
r1 return _render("last_change", last_change)
def rss_lnk(repo_name):
return _render("rss", repo_name)
def atom_lnk(repo_name):
return _render("atom", repo_name)
def last_rev(repo_name, cs_cache):
return _render('revision', repo_name, cs_cache.get('revision'),
cs_cache.get('raw_id'), cs_cache.get('author'),
dashboard: show date of commit inside the tooltip.
r2489 cs_cache.get('message'), cs_cache.get('date'))
project: added all source files and assets
r1
def desc(desc):
meta-tags: cleanup support for metatags....
r2091 return _render('repo_desc', desc, c.visual.stylify_metatags)
project: added all source files and assets
r1
def state(repo_state):
return _render("repo_state", repo_state)
def repo_actions(repo_name):
return _render('repo_actions', repo_name, super_user_actions)
def user_profile(username):
return _render('user_profile', username)
repos_data = []
for repo in repo_list:
exported get_repos_as_dict for new grids
r4147 # NOTE(marcink): because we use only raw column we need to load it like that
changeset_cache = Repository._load_changeset_cache(
repo.repo_id, repo._changeset_cache)
project: added all source files and assets
r1 row = {
"menu": quick_menu(repo.repo_name),
repositories: added option to archive repositories instead of deleting them....
r3090 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
models: major update for python3,...
r5070 repo.private, repo.archived, repo.fork_repo_name),
grids: columns and sorting fixes
r4150
descriptions: fixed rendering problem with certain meta-tags....
r4223 "desc": desc(h.escape(repo.description)),
project: added all source files and assets
r1
grids: columns and sorting fixes
r4150 "last_change": last_change(repo.updated_on),
project: added all source files and assets
r1
exported get_repos_as_dict for new grids
r4147 "last_changeset": last_rev(repo.repo_name, changeset_cache),
"last_changeset_raw": changeset_cache.get('revision'),
project: added all source files and assets
r1
models: major update for python3,...
r5070 "owner": user_profile(repo.owner_username),
project: added all source files and assets
r1
"state": state(repo.repo_state),
"rss": rss_lnk(repo.repo_name),
"atom": atom_lnk(repo.repo_name),
}
if admin:
row.update({
"action": repo_actions(repo.repo_name),
})
repos_data.append(row)
return repos_data
dashboard: main page grids async load....
r4148 def get_repos_data_table(
self, draw, start, limit,
search_q, order_by, order_dir,
auth_user, repo_group_id):
from rhodecode.model.scm import RepoList
_perms = ['repository.read', 'repository.write', 'repository.admin']
repos = Repository.query() \
.filter(Repository.group_id == repo_group_id) \
.all()
auth_repo_list = RepoList(
repos, perm_set=_perms,
extra_kwargs=dict(user=auth_user))
allowed_ids = [-1]
for repo in auth_repo_list:
allowed_ids.append(repo.repo_id)
repos_data_total_count = Repository.query() \
.filter(Repository.group_id == repo_group_id) \
.filter(or_(
# generate multiple IN to fix limitation problems
*in_filter_generator(Repository.repo_id, allowed_ids))
) \
.count()
models: major update for python3,...
r5070 RepoFork = aliased(Repository)
OwnerUser = aliased(User)
dashboard: main page grids async load....
r4148 base_q = Session.query(
Repository.repo_id,
Repository.repo_name,
Repository.description,
Repository.repo_type,
Repository.repo_state,
Repository.private,
Repository.archived,
Repository.updated_on,
Repository._changeset_cache,
models: major update for python3,...
r5070 RepoFork.repo_name.label('fork_repo_name'),
OwnerUser.username.label('owner_username'),
dashboard: main page grids async load....
r4148 ) \
.filter(Repository.group_id == repo_group_id) \
.filter(or_(
# generate multiple IN to fix limitation problems
*in_filter_generator(Repository.repo_id, allowed_ids))
) \
models: major update for python3,...
r5070 .outerjoin(RepoFork, Repository.fork_id == RepoFork.repo_id) \
.join(OwnerUser, Repository.user_id == OwnerUser.user_id)
dashboard: main page grids async load....
r4148
repos_data_total_filtered_count = base_q.count()
sort_defined = False
if order_by == 'repo_name':
sort_col = func.lower(Repository.repo_name)
sort_defined = True
elif order_by == 'user_username':
sort_col = User.username
else:
sort_col = getattr(Repository, order_by, None)
if sort_defined or sort_col:
if order_dir == 'asc':
sort_col = sort_col.asc()
else:
sort_col = sort_col.desc()
base_q = base_q.order_by(sort_col)
base_q = base_q.offset(start).limit(limit)
repos_list = base_q.all()
repos_data = RepoModel().get_repos_as_dict(
repo_list=repos_list, admin=False)
data = ({
'draw': draw,
'data': repos_data,
'recordsTotal': repos_data_total_count,
'recordsFiltered': repos_data_total_filtered_count,
})
return data
project: added all source files and assets
r1 def _get_defaults(self, repo_name):
"""
Gets information about repository, and returns a dict for
usage in forms
:param repo_name:
"""
repo_info = Repository.get_by_repo_name(repo_name)
if repo_info is None:
return None
defaults = repo_info.get_dict()
defaults['repo_name'] = repo_info.just_name
repo-permissions: moved permissions into pyramid....
r1734 groups = repo_info.groups_with_parents
project: added all source files and assets
r1 parent_group = groups[-1] if groups else None
# we use -1 as this is how in HTML, we mark an empty group
defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
keys_to_process = (
{'k': 'repo_type', 'strip': False},
{'k': 'repo_enable_downloads', 'strip': True},
{'k': 'repo_description', 'strip': True},
{'k': 'repo_enable_locking', 'strip': True},
{'k': 'repo_landing_rev', 'strip': True},
{'k': 'clone_uri', 'strip': False},
repositories: allow properly updating repository push url.
r2562 {'k': 'push_uri', 'strip': False},
project: added all source files and assets
r1 {'k': 'repo_private', 'strip': True},
{'k': 'repo_enable_statistics', 'strip': True}
)
for item in keys_to_process:
attr = item['k']
if item['strip']:
attr = remove_prefix(item['k'], 'repo_')
val = defaults[attr]
if item['k'] == 'repo_landing_rev':
val = ':'.join(defaults[attr])
defaults[item['k']] = val
if item['k'] == 'clone_uri':
defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
repositories: allow properly updating repository push url.
r2562 if item['k'] == 'push_uri':
defaults['push_uri_hidden'] = repo_info.push_uri_hidden
project: added all source files and assets
r1
# fill owner
if repo_info.user:
defaults.update({'user': repo_info.user.username})
else:
refactor: renamed get_first_admin to get_first_super_admin which...
r278 replacement_user = User.get_first_super_admin().username
project: added all source files and assets
r1 defaults.update({'user': replacement_user})
return defaults
def update(self, repo, **kwargs):
try:
cur_repo = self._get_repo(repo)
source_repo_name = cur_repo.repo_name
permissions: flush permissions on owner changes for repo and repo groups.
r4662
affected_user_ids = []
project: added all source files and assets
r1 if 'user' in kwargs:
permissions: flush permissions on owner changes for repo and repo groups.
r4662 old_owner_id = cur_repo.user.user_id
new_owner = User.get_by_username(kwargs['user'])
cur_repo.user = new_owner
if old_owner_id != new_owner.user_id:
affected_user_ids = [new_owner.user_id, old_owner_id]
project: added all source files and assets
r1
if 'repo_group' in kwargs:
cur_repo.group = RepoGroup.get(kwargs['repo_group'])
log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
update_keys = [
(1, 'repo_description'),
(1, 'repo_landing_rev'),
(1, 'repo_private'),
repo-schemas: refactor repository schemas and use it in API update/create functions....
r1153 (1, 'repo_enable_downloads'),
(1, 'repo_enable_locking'),
project: added all source files and assets
r1 (1, 'repo_enable_statistics'),
(0, 'clone_uri'),
repositories: allow properly updating repository push url.
r2562 (0, 'push_uri'),
project: added all source files and assets
r1 (0, 'fork_id')
]
for strip, k in update_keys:
if k in kwargs:
val = kwargs[k]
if strip:
k = remove_prefix(k, 'repo_')
setattr(cur_repo, k, val)
fix(permissions): added a common way to update private flag via repo model...
r5551 new_name = source_repo_name
if 'repo_name' in kwargs:
new_name = cur_repo.get_new_name(kwargs['repo_name'])
cur_repo.repo_name = new_name
project: added all source files and assets
r1
fix(permissions): added a common way to update private flag via repo model...
r5551 if 'repo_private' in kwargs:
# if private flag is set to True, reset default permission to NONE
set_private_to = kwargs.get('repo_private')
if set_private_to:
EMPTY_PERM = 'repository.none'
RepoModel().grant_user_permission(
repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
)
if set_private_to != cur_repo.private:
# NOTE(dan): we change repo private mode we need to notify all USERS
# this is just by having this value set to a different value then it was before
affected_user_ids = User.get_all_user_ids()
vcs-settings: when setting landing ref from UI switch default GIT branch too....
r4753 if kwargs.get('repo_landing_rev'):
landing_rev_val = kwargs['repo_landing_rev']
RepoModel().set_landing_rev(cur_repo, landing_rev_val)
project: added all source files and assets
r1
# handle extra fields
repository-permissions: enable shortcut to set private mode in permission page.
r3809 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
project: added all source files and assets
r1 k = RepositoryField.un_prefix_key(field)
ex_field = RepositoryField.get_by_key_name(
key=k, repo=cur_repo)
if ex_field:
ex_field.field_value = kwargs[field]
self.sa.add(ex_field)
admin: fixed problems with generating last change in admin panels....
r4000
project: added all source files and assets
r1 self.sa.add(cur_repo)
if source_repo_name != new_name:
# rename repository
self._rename_filesystem_repo(
old=source_repo_name, new=new_name)
permissions: flush permissions on owner changes for repo and repo groups.
r4662 if affected_user_ids:
PermissionModel().trigger_permission_flush(affected_user_ids)
project: added all source files and assets
r1 return cur_repo
except Exception:
log.error(traceback.format_exc())
raise
def _create_repo(self, repo_name, repo_type, description, owner,
private=False, clone_uri=None, repo_group=None,
repos: cleannup and fix landing-ref code....
r4852 landing_rev=None, fork_of=None,
project: added all source files and assets
r1 copy_fork_permissions=False, enable_statistics=False,
enable_locking=False, enable_downloads=False,
copy_group_permissions=False,
state=Repository.STATE_PENDING):
"""
Create repository inside database with PENDING state, this should be
only executed by create() repo. With exception of importing existing
repos
"""
from rhodecode.model.scm import ScmModel
owner = self._get_user(owner)
fork_of = self._get_repo(fork_of)
repo_group = self._get_repo_group(safe_int(repo_group))
repos: cleannup and fix landing-ref code....
r4852 default_landing_ref, _lbl = ScmModel.backend_landing_ref(repo_type)
landing_rev = landing_rev or default_landing_ref
project: added all source files and assets
r1
try:
models: major update for python3,...
r5070 repo_name = safe_str(repo_name)
description = safe_str(description)
project: added all source files and assets
r1 # repo name is just a name of repository
# while repo_name_full is a full qualified name that is combined
# with name and path of group
repo_name_full = repo_name
repo_name = repo_name.split(Repository.NAME_SEP)[-1]
new_repo = Repository()
new_repo.repo_state = state
new_repo.enable_statistics = False
new_repo.repo_name = repo_name_full
new_repo.repo_type = repo_type
new_repo.user = owner
new_repo.group = repo_group
new_repo.description = description or repo_name
new_repo.private = private
repositories: added option to archive repositories instead of deleting them....
r3090 new_repo.archived = False
project: added all source files and assets
r1 new_repo.clone_uri = clone_uri
new_repo.landing_rev = landing_rev
new_repo.enable_statistics = enable_statistics
new_repo.enable_locking = enable_locking
new_repo.enable_downloads = enable_downloads
if repo_group:
new_repo.enable_locking = repo_group.enable_locking
if fork_of:
parent_repo = fork_of
new_repo.fork = parent_repo
dan
events: add event system for RepoEvents
r375 events.trigger(events.RepoPreCreateEvent(new_repo))
project: added all source files and assets
r1 self.sa.add(new_repo)
EMPTY_PERM = 'repository.none'
if fork_of and copy_fork_permissions:
repo = fork_of
user_perms = UserRepoToPerm.query() \
.filter(UserRepoToPerm.repository == repo).all()
group_perms = UserGroupRepoToPerm.query() \
.filter(UserGroupRepoToPerm.repository == repo).all()
for perm in user_perms:
UserRepoToPerm.create(
perm.user, new_repo, perm.permission)
for perm in group_perms:
UserGroupRepoToPerm.create(
perm.users_group, new_repo, perm.permission)
# in case we copy permissions and also set this repo to private
permissions: show user group count in permissions summary, and unified some text labels.
r3385 # override the default user permission to make it a private repo
project: added all source files and assets
r1 if private:
RepoModel(self.sa).grant_user_permission(
repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
elif repo_group and copy_group_permissions:
user_perms = UserRepoGroupToPerm.query() \
.filter(UserRepoGroupToPerm.group == repo_group).all()
group_perms = UserGroupRepoGroupToPerm.query() \
.filter(UserGroupRepoGroupToPerm.group == repo_group).all()
for perm in user_perms:
perm_name = perm.permission.permission_name.replace(
'group.', 'repository.')
perm_obj = Permission.get_by_key(perm_name)
UserRepoToPerm.create(perm.user, new_repo, perm_obj)
for perm in group_perms:
perm_name = perm.permission.permission_name.replace(
'group.', 'repository.')
perm_obj = Permission.get_by_key(perm_name)
permissions: show user group count in permissions summary, and unified some text labels.
r3385 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
project: added all source files and assets
r1
if private:
RepoModel(self.sa).grant_user_permission(
repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
else:
perm_obj = self._create_default_perms(new_repo, private)
self.sa.add(perm_obj)
# now automatically start following this repository as owner
permissions: show user group count in permissions summary, and unified some text labels.
r3385 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, owner.user_id)
dan
events: add event system for RepoEvents
r375
project: added all source files and assets
r1 # we need to flush here, in order to check if database won't
# throw any exceptions, create filesystem dirs at the very end
self.sa.flush()
fix(events): fixed celery based events where it was missing request object
r5340 events.trigger(events.RepoCreateEvent(new_repo, actor=owner))
dan
events: add event system for RepoEvents
r375 return new_repo
project: added all source files and assets
r1
except Exception:
log.error(traceback.format_exc())
raise
def create(self, form_data, cur_user):
"""
Create repository using celery tasks
:param form_data:
:param cur_user:
"""
from rhodecode.lib.celerylib import tasks, run_task
return run_task(tasks.create_repo, form_data, cur_user)
def update_permissions(self, repo, perm_additions=None, perm_updates=None,
perm_deletions=None, check_perms=True,
cur_user=None):
if not perm_additions:
perm_additions = []
if not perm_updates:
perm_updates = []
if not perm_deletions:
perm_deletions = []
req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
repo-permissions: moved permissions into pyramid....
r1734 changes = {
'added': [],
'updated': [],
dan
permissions: flush all user permissions in case of default user permission changes....
r4187 'deleted': [],
'default_user_changed': None
repo-permissions: moved permissions into pyramid....
r1734 }
dan
permissions: flush all user permissions in case of default user permission changes....
r4187
repo = self._get_repo(repo)
project: added all source files and assets
r1 # update permissions
for member_id, perm, member_type in perm_updates:
member_id = int(member_id)
if member_type == 'user':
repo-permissions: moved permissions into pyramid....
r1734 member_name = User.get(member_id).username
dan
permissions: flush all user permissions in case of default user permission changes....
r4187 if member_name == User.DEFAULT_USER:
# NOTE(dan): detect if we changed permissions for default user
perm_obj = self.sa.query(UserRepoToPerm) \
.filter(UserRepoToPerm.user_id == member_id) \
.filter(UserRepoToPerm.repository == repo) \
.scalar()
if perm_obj and perm_obj.permission.permission_name != perm:
changes['default_user_changed'] = True
project: added all source files and assets
r1 # this updates also current one if found
self.grant_user_permission(
repo=repo, user=member_id, perm=perm)
tests: added tests for permission update views to catch obvious form errors.
r2827 elif member_type == 'user_group':
project: added all source files and assets
r1 # check if we have permissions to alter this usergroup
member_name = UserGroup.get(member_id).users_group_name
if not check_perms or HasUserGroupPermissionAny(
*req_perms)(member_name, user=cur_user):
self.grant_user_group_permission(
repo=repo, group_name=member_id, perm=perm)
tests: added tests for permission update views to catch obvious form errors.
r2827 else:
raise ValueError("member_type must be 'user' or 'user_group' "
"got {} instead".format(member_type))
repo-permissions: moved permissions into pyramid....
r1734 changes['updated'].append({'type': member_type, 'id': member_id,
'name': member_name, 'new_perm': perm})
project: added all source files and assets
r1 # set new permissions
for member_id, perm, member_type in perm_additions:
member_id = int(member_id)
if member_type == 'user':
repo-permissions: moved permissions into pyramid....
r1734 member_name = User.get(member_id).username
project: added all source files and assets
r1 self.grant_user_permission(
repo=repo, user=member_id, perm=perm)
tests: added tests for permission update views to catch obvious form errors.
r2827 elif member_type == 'user_group':
project: added all source files and assets
r1 # check if we have permissions to alter this usergroup
member_name = UserGroup.get(member_id).users_group_name
if not check_perms or HasUserGroupPermissionAny(
*req_perms)(member_name, user=cur_user):
self.grant_user_group_permission(
repo=repo, group_name=member_id, perm=perm)
tests: added tests for permission update views to catch obvious form errors.
r2827 else:
raise ValueError("member_type must be 'user' or 'user_group' "
"got {} instead".format(member_type))
repo-permissions: moved permissions into pyramid....
r1734 changes['added'].append({'type': member_type, 'id': member_id,
'name': member_name, 'new_perm': perm})
project: added all source files and assets
r1 # delete permissions
for member_id, perm, member_type in perm_deletions:
member_id = int(member_id)
if member_type == 'user':
repo-permissions: moved permissions into pyramid....
r1734 member_name = User.get(member_id).username
project: added all source files and assets
r1 self.revoke_user_permission(repo=repo, user=member_id)
tests: added tests for permission update views to catch obvious form errors.
r2827 elif member_type == 'user_group':
project: added all source files and assets
r1 # check if we have permissions to alter this usergroup
member_name = UserGroup.get(member_id).users_group_name
if not check_perms or HasUserGroupPermissionAny(
*req_perms)(member_name, user=cur_user):
self.revoke_user_group_permission(
repo=repo, group_name=member_id)
tests: added tests for permission update views to catch obvious form errors.
r2827 else:
raise ValueError("member_type must be 'user' or 'user_group' "
"got {} instead".format(member_type))
project: added all source files and assets
r1
repo-permissions: moved permissions into pyramid....
r1734 changes['deleted'].append({'type': member_type, 'id': member_id,
'name': member_name, 'new_perm': perm})
return changes
project: added all source files and assets
r1 def create_fork(self, form_data, cur_user):
"""
Simple wrapper into executing celery task for fork creation
:param form_data:
:param cur_user:
"""
from rhodecode.lib.celerylib import tasks, run_task
return run_task(tasks.create_repo_fork, form_data, cur_user)
repositories: added option to archive repositories instead of deleting them....
r3090 def archive(self, repo):
"""
Archive given repository. Set archive flag.
:param repo:
"""
repo = self._get_repo(repo)
if repo:
try:
repo.archived = True
self.sa.add(repo)
self.sa.commit()
except Exception:
log.error(traceback.format_exc())
raise
feat(remap and rescan): added more relient remap and removal option, and also split the logic to either add or cleanup
r5619 def delete(self, repo, forks=None, pull_requests=None, artifacts=None, fs_remove=True, cur_user=None, call_events=True):
project: added all source files and assets
r1 """
Delete given repository, forks parameter defines what do do with
attached forks. Throws AttachedForksError if deleted repo has attached
forks
:param repo:
:param forks: str 'delete' or 'detach'
repositories: added option to archive repositories instead of deleting them....
r3090 :param pull_requests: str 'delete' or None
feat(artifacts): new artifact storage engines allowing an s3 based uploads
r5516 :param artifacts: str 'delete' or None
project: added all source files and assets
r1 :param fs_remove: remove(archive) repo from filesystem
"""
if not cur_user:
cur_user = getattr(get_current_rhodecode_user(), 'username', None)
repo = self._get_repo(repo)
feat(remap and rescan): added more relient remap and removal option, and also split the logic to either add or cleanup
r5619 if not repo:
return False
project: added all source files and assets
r1
feat(remap and rescan): added more relient remap and removal option, and also split the logic to either add or cleanup
r5619 if forks == 'detach':
for r in repo.forks:
r.fork = None
self.sa.add(r)
elif forks == 'delete':
for r in repo.forks:
self.delete(r, forks='delete')
elif [f for f in repo.forks]:
raise AttachedForksError()
# check for pull requests
pr_sources = repo.pull_requests_source
pr_targets = repo.pull_requests_target
if pull_requests != 'delete' and (pr_sources or pr_targets):
raise AttachedPullRequestsError()
repository: add check preventing of removal of repo with attached pull requests....
r3089
feat(remap and rescan): added more relient remap and removal option, and also split the logic to either add or cleanup
r5619 artifacts_objs = repo.artifacts
if artifacts == 'delete':
for a in artifacts_objs:
self.sa.delete(a)
elif [a for a in artifacts_objs]:
raise AttachedArtifactsError()
feat(artifacts): new artifact storage engines allowing an s3 based uploads
r5516
feat(remap and rescan): added more relient remap and removal option, and also split the logic to either add or cleanup
r5619 old_repo_dict = repo.get_dict()
if call_events:
dan
events: add event system for RepoEvents
r375 events.trigger(events.RepoPreDeleteEvent(repo))
feat(remap and rescan): added more relient remap and removal option, and also split the logic to either add or cleanup
r5619
try:
self.sa.delete(repo)
if fs_remove:
self._delete_filesystem_repo(repo)
else:
log.debug('skipping removal from filesystem')
old_repo_dict.update({
'deleted_by': cur_user,
'deleted_on': time.time(),
})
if call_events:
comments: added rcextensions hoooks for comment editing, and renamed methods to remove odd log_ prefix which...
r4445 hooks_base.delete_repository(**old_repo_dict)
dan
integrations: add integration support...
r411 events.trigger(events.RepoDeleteEvent(repo))
feat(remap and rescan): added more relient remap and removal option, and also split the logic to either add or cleanup
r5619 except Exception:
log.error(traceback.format_exc())
raise
return True
project: added all source files and assets
r1
def grant_user_permission(self, repo, user, perm):
"""
Grant permission for user on given repository, or update existing one
if found
:param repo: Instance of Repository, repository_id, or repository name
:param user: Instance of User, user_id or username
:param perm: Instance of Permission, or permission_name
"""
user = self._get_user(user)
repo = self._get_repo(repo)
permission = self._get_perm(perm)
# check if we have that permission already
obj = self.sa.query(UserRepoToPerm) \
.filter(UserRepoToPerm.user == user) \
.filter(UserRepoToPerm.repository == repo) \
.scalar()
if obj is None:
# create new !
obj = UserRepoToPerm()
obj.repository = repo
obj.user = user
obj.permission = permission
self.sa.add(obj)
log.debug('Granted perm %s to %s on %s', perm, user, repo)
action_logger_generic(
'granted permission: {} to user: {} on repo: {}'.format(
perm, user, repo), namespace='security.repo')
return obj
def revoke_user_permission(self, repo, user):
"""
Revoke permission for user on given repository
:param repo: Instance of Repository, repository_id, or repository name
:param user: Instance of User, user_id or username
"""
user = self._get_user(user)
repo = self._get_repo(repo)
obj = self.sa.query(UserRepoToPerm) \
.filter(UserRepoToPerm.repository == repo) \
.filter(UserRepoToPerm.user == user) \
.scalar()
if obj:
self.sa.delete(obj)
log.debug('Revoked perm on %s on %s', repo, user)
action_logger_generic(
'revoked permission from user: {} on repo: {}'.format(
user, repo), namespace='security.repo')
def grant_user_group_permission(self, repo, group_name, perm):
"""
Grant permission for user group on given repository, or update
existing one if found
:param repo: Instance of Repository, repository_id, or repository name
:param group_name: Instance of UserGroup, users_group_id,
or user group name
:param perm: Instance of Permission, or permission_name
"""
repo = self._get_repo(repo)
group_name = self._get_user_group(group_name)
permission = self._get_perm(perm)
# check if we have that permission already
obj = self.sa.query(UserGroupRepoToPerm) \
.filter(UserGroupRepoToPerm.users_group == group_name) \
.filter(UserGroupRepoToPerm.repository == repo) \
.scalar()
if obj is None:
# create new
obj = UserGroupRepoToPerm()
obj.repository = repo
obj.users_group = group_name
obj.permission = permission
self.sa.add(obj)
log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
action_logger_generic(
'granted permission: {} to usergroup: {} on repo: {}'.format(
perm, group_name, repo), namespace='security.repo')
return obj
def revoke_user_group_permission(self, repo, group_name):
"""
Revoke permission for user group on given repository
:param repo: Instance of Repository, repository_id, or repository name
:param group_name: Instance of UserGroup, users_group_id,
or user group name
"""
repo = self._get_repo(repo)
group_name = self._get_user_group(group_name)
obj = self.sa.query(UserGroupRepoToPerm) \
.filter(UserGroupRepoToPerm.repository == repo) \
.filter(UserGroupRepoToPerm.users_group == group_name) \
.scalar()
if obj:
self.sa.delete(obj)
log.debug('Revoked perm to %s on %s', repo, group_name)
action_logger_generic(
'revoked permission from usergroup: {} on repo: {}'.format(
group_name, repo), namespace='security.repo')
def delete_stats(self, repo_name):
"""
removes stats for given repo
:param repo_name:
"""
repo = self._get_repo(repo_name)
try:
obj = self.sa.query(Statistics) \
.filter(Statistics.repository == repo).scalar()
if obj:
self.sa.delete(obj)
except Exception:
log.error(traceback.format_exc())
raise
def add_repo_field(self, repo_name, field_key, field_label, field_value='',
field_type='str', field_desc=''):
repo = self._get_repo(repo_name)
new_field = RepositoryField()
new_field.repository = repo
new_field.field_key = field_key
new_field.field_type = field_type # python type
new_field.field_value = field_value
new_field.field_desc = field_desc
new_field.field_label = field_label
self.sa.add(new_field)
return new_field
def delete_repo_field(self, repo_name, field_key):
repo = self._get_repo(repo_name)
field = RepositoryField.get_by_key_name(field_key, repo)
if field:
self.sa.delete(field)
vcs-settings: when setting landing ref from UI switch default GIT branch too....
r4753 def set_landing_rev(self, repo, landing_rev_name):
if landing_rev_name.startswith('branch:'):
landing_rev_name = landing_rev_name.split('branch:')[-1]
scm_instance = repo.scm_instance()
caches: make sure we init caches on repo names withou '/' to not create a new cache subpath
r4766 if scm_instance:
return scm_instance._remote.set_head_ref(landing_rev_name)
vcs-settings: when setting landing ref from UI switch default GIT branch too....
r4753
project: added all source files and assets
r1 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
clone_uri=None, repo_store_location=None,
gists: fixed cache problems and updated ui
r3869 use_global_config=False, install_hooks=True):
project: added all source files and assets
r1 """
makes repository on filesystem. It's group aware means it'll create
a repository within a group, and alter the paths accordingly of
group location
:param repo_name:
:param alias:
:param parent:
:param clone_uri:
:param repo_store_location:
"""
from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
from rhodecode.model.scm import ScmModel
if Repository.NAME_SEP in repo_name:
raise ValueError(
'repo_name must not contain groups got `%s`' % repo_name)
if isinstance(repo_group, RepoGroup):
new_parent_path = os.sep.join(repo_group.full_path_splitted)
else:
new_parent_path = repo_group or ''
if repo_store_location:
_paths = [repo_store_location]
else:
_paths = [self.repos_path, new_parent_path, repo_name]
# we need to make it str for mercurial
repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
# check if this path is not a repository
if is_valid_repo(repo_path, self.repos_path):
models: major update for python3,...
r5070 raise Exception(f'This path {repo_path} is a valid repository')
project: added all source files and assets
r1
# check if this path is a group
if is_valid_repo_group(repo_path, self.repos_path):
models: major update for python3,...
r5070 raise Exception(f'This path {repo_path} is a valid group')
project: added all source files and assets
r1
log.info('creating repo %s in %s from url: `%s`',
models: major update for python3,...
r5070 repo_name, safe_str(repo_path),
project: added all source files and assets
r1 obfuscate_url_pw(clone_uri))
backend = get_backend(repo_type)
config_repo = None if use_global_config else repo_name
if config_repo and new_parent_path:
config_repo = Repository.NAME_SEP.join(
(new_parent_path, config_repo))
config = make_db_config(clear_session=False, repo=config_repo)
config.set('extensions', 'largefiles', '')
# patch and reset hooks section of UI config to not run any
# hooks on creating remote repo
config.clear_section('hooks')
# TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
if repo_type == 'git':
repo = backend(
caches: enable no-cache repos for certain scenarios where we explicitly don't need to cache things like gists etc.
r3868 repo_path, config=config, create=True, src_url=clone_uri, bare=True,
with_wire={"cache": False})
project: added all source files and assets
r1 else:
repo = backend(
caches: enable no-cache repos for certain scenarios where we explicitly don't need to cache things like gists etc.
r3868 repo_path, config=config, create=True, src_url=clone_uri,
with_wire={"cache": False})
project: added all source files and assets
r1
gists: fixed cache problems and updated ui
r3869 if install_hooks:
repo.install_hooks()
project: added all source files and assets
r1
log.debug('Created repo %s with %s backend',
models: major update for python3,...
r5070 safe_str(repo_name), safe_str(repo_type))
project: added all source files and assets
r1 return repo
def _rename_filesystem_repo(self, old, new):
"""
renames repository on filesystem
:param old: old name
:param new: new name
"""
log.info('renaming repo from %s to %s', old, new)
old_path = os.path.join(self.repos_path, old)
new_path = os.path.join(self.repos_path, new)
if os.path.isdir(new_path):
raise Exception(
'Was trying to rename to already existing dir %s' % new_path
)
shutil.move(old_path, new_path)
def _delete_filesystem_repo(self, repo):
"""
models: major update for python3,...
r5070 removes repo from filesystem, the removal is actually made by
added rm__ prefix into dir, and rename internal .hg/.git dirs so this
project: added all source files and assets
r1 repository is no longer valid for rhodecode, can be undeleted later on
by reverting the renames on this repository
:param repo: repo object
"""
rm_path = os.path.join(self.repos_path, repo.repo_name)
repo_group = repo.group
models: major update for python3,...
r5070 log.info("delete_filesystem_repo: removing repository %s", rm_path)
project: added all source files and assets
r1 # disable hg/git internal that it doesn't get detected as repo
alias = repo.repo_type
config = make_db_config(clear_session=False)
config.set('extensions', 'largefiles', '')
bare = getattr(repo.scm_instance(config=config), 'bare', False)
# skip this for bare git repos
if not bare:
# disable VCS repo
vcs_path = os.path.join(rm_path, '.%s' % alias)
if os.path.exists(vcs_path):
shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
repository-groups: introduce last change for repository groups.
r1940 _now = datetime.datetime.now()
project: added all source files and assets
r1 _ms = str(_now.microsecond).rjust(6, '0')
modernize: python3 updates
r5096 _d = 'rm__{}__{}'.format(_now.strftime('%Y%m%d_%H%M%S_' + _ms),
project: added all source files and assets
r1 repo.just_name)
if repo_group:
# if repository is in group, prefix the removal path with the group
args = repo_group.full_path_splitted + [_d]
_d = os.path.join(*args)
if os.path.isdir(rm_path):
shutil.move(rm_path, os.path.join(self.repos_path, _d))
summary: Move logic to find a readme out...
r768
diff-cache: remove diff cache dir on repository removal.
r2689 # finally cleanup diff-cache if it exists
cached_diffs_dir = repo.cached_diffs_dir
if os.path.isdir(cached_diffs_dir):
shutil.rmtree(cached_diffs_dir)
summary: Move logic to find a readme out...
r768
class ReadmeFinder:
"""
Utility which knows how to find a readme for a specific commit.
The main idea is that this is a configurable algorithm. When creating an
instance you can define parameters, currently only the `default_renderer`.
Based on this configuration the method :meth:`search` behaves slightly
different.
"""
fix(encoding for file): fixed support of non utf-8 files in all backends
r5647 readme_re = re.compile(br'^readme(\.[^.]+)?$', re.IGNORECASE)
path_re = re.compile(br'^docs?', re.IGNORECASE)
repo: Implement ReadmeFinder...
r772
default_priorities = {
models: major update for python3,...
r5070 None: 0,
fix(encoding for file): fixed support of non utf-8 files in all backends
r5647 b'.rst': 1,
b'.md': 1,
b'.rest': 2,
b'.mkdn': 2,
b'.text': 2,
b'.txt': 3,
b'.mdown': 3,
b'.markdown': 4,
repo: Implement ReadmeFinder...
r772 }
path_priority = {
fix(encoding for file): fixed support of non utf-8 files in all backends
r5647 b'doc': 0,
b'docs': 1,
repo: Implement ReadmeFinder...
r772 }
FALLBACK_PRIORITY = 99
RENDERER_TO_EXTENSION = {
fix(encoding for file): fixed support of non utf-8 files in all backends
r5647 'rst': [b'.rst', b'.rest'],
'markdown': [b'.md', b'mkdn', b'.mdown', b'.markdown'],
repo: Implement ReadmeFinder...
r772 }
def __init__(self, default_renderer=None):
summary: Move logic to find a readme out...
r768 self._default_renderer = default_renderer
fix(encoding for file): fixed support of non utf-8 files in all backends
r5647 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(default_renderer, [])
summary: Move logic to find a readme out...
r768
fix(file-caching): fixed cases when old cache was used before changes to operate on bytestrings
r5651 def search(self, commit, path=b'/', nodes=None):
repo: Implement ReadmeFinder...
r772 """
Find a readme in the given `commit`.
"""
fix(submodules): fixed an error if reaching out submodule path....
r5261 # firstly, check the PATH type if it is actually a DIR
fix(encoding for file): fixed support of non utf-8 files in all backends
r5647 bytes_path = safe_bytes(path)
if commit.get_node(bytes_path).kind != NodeKind.DIR:
fix(submodules): fixed an error if reaching out submodule path....
r5261 return None
fix(file-caching): fixed cases when old cache was used before changes to operate on bytestrings
r5651 if not nodes:
nodes = commit.get_nodes(bytes_path)
repo: Implement ReadmeFinder...
r772 matches = self._match_readmes(nodes)
matches = self._sort_according_to_priority(matches)
if matches:
summary: Correctly return the Node instance...
r775 return matches[0].node
repo: Implement ReadmeFinder...
r772
paths = self._match_paths(nodes)
paths = self._sort_paths_according_to_priority(paths)
fix(encoding for file): fixed support of non utf-8 files in all backends
r5647 for bytes_path in paths:
match = self.search(commit, path=bytes_path)
repo: Implement ReadmeFinder...
r772 if match:
return match
return None
def _match_readmes(self, nodes):
for node in nodes:
if not node.is_file():
continue
fix(encoding for file): fixed support of non utf-8 files in all backends
r5647 path = node.bytes_path.rsplit(b'/', 1)[-1]
repo: Implement ReadmeFinder...
r772 match = self.readme_re.match(path)
if match:
extension = match.group(1)
yield ReadmeMatch(node, match, self._priority(extension))
def _match_paths(self, nodes):
for node in nodes:
if not node.is_dir():
continue
fix(encoding for file): fixed support of non utf-8 files in all backends
r5647 match = self.path_re.match(node.bytes_path)
repo: Implement ReadmeFinder...
r772 if match:
fix(encoding for file): fixed support of non utf-8 files in all backends
r5647 yield node.bytes_path
repo: Implement ReadmeFinder...
r772
def _priority(self, extension):
fix(encoding for file): fixed support of non utf-8 files in all backends
r5647 renderer_priority = 0 if extension in self._renderer_extensions else 1
extension_priority = self.default_priorities.get(extension, self.FALLBACK_PRIORITY)
return renderer_priority, extension_priority
repo: Implement ReadmeFinder...
r772
def _sort_according_to_priority(self, matches):
def priority_and_path(match):
fix(encoding for file): fixed support of non utf-8 files in all backends
r5647 return match.priority, match.path
repo: Implement ReadmeFinder...
r772
return sorted(matches, key=priority_and_path)
def _sort_paths_according_to_priority(self, paths):
def priority_and_path(path):
fix(encoding for file): fixed support of non utf-8 files in all backends
r5647 return self.path_priority.get(path, self.FALLBACK_PRIORITY), path
repo: Implement ReadmeFinder...
r772
return sorted(paths, key=priority_and_path)
class ReadmeMatch:
def __init__(self, node, match, priority):
summary: Correctly return the Node instance...
r775 self.node = node
repo: Implement ReadmeFinder...
r772 self._match = match
self.priority = priority
@property
def path(self):
summary: Correctly return the Node instance...
r775 return self.node.path
repo: Implement ReadmeFinder...
r772
def __repr__(self):
modernize: python3 updates
r5096 return f'<ReadmeMatch {self.path} priority={self.priority}'