repo.py
516 lines
| 18.0 KiB
| text/x-python
|
PythonLexer
r786 | # -*- coding: utf-8 -*- | |||
""" | ||||
r811 | rhodecode.model.repo | |||
~~~~~~~~~~~~~~~~~~~~ | ||||
r786 | ||||
Repository model for rhodecode | ||||
r1203 | ||||
r786 | :created_on: Jun 5, 2010 | |||
:author: marcink | ||||
r1824 | :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com> | |||
r786 | :license: GPLv3, see COPYING for more details. | |||
""" | ||||
r1206 | # This program is free software: you can redistribute it and/or modify | |||
# it under the terms of the GNU General Public License as published by | ||||
# the Free Software Foundation, either version 3 of the License, or | ||||
# (at your option) any later version. | ||||
r1203 | # | |||
r629 | # 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. | ||||
r1203 | # | |||
r629 | # You should have received a copy of the GNU General Public License | |||
r1206 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
r2404 | from __future__ import with_statement | |||
r786 | import os | |||
import shutil | ||||
import logging | ||||
import traceback | ||||
r2404 | import pkg_resources | |||
from os.path import dirname as dn, join as jn | ||||
r629 | from datetime import datetime | |||
r786 | ||||
r2007 | from rhodecode.lib.vcs.backends import get_backend | |||
r2142 | from rhodecode.lib.compat import json | |||
r2109 | from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode | |||
r1669 | from rhodecode.lib.caching_query import FromCache | |||
r1972 | from rhodecode.lib.hooks import log_create_repository | |||
r1401 | ||||
r786 | from rhodecode.model import BaseModel | |||
r1633 | from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \ | |||
Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup | ||||
r2142 | from rhodecode.lib import helpers as h | |||
r786 | ||||
r1982 | ||||
r629 | log = logging.getLogger(__name__) | |||
r1271 | ||||
r752 | class RepoModel(BaseModel): | |||
r629 | ||||
r1982 | def __get_users_group(self, users_group): | |||
return self._get_instance(UsersGroup, users_group, | ||||
callback=UsersGroup.get_by_group_name) | ||||
def __get_repos_group(self, repos_group): | ||||
return self._get_instance(RepoGroup, repos_group, | ||||
callback=RepoGroup.get_by_group_name) | ||||
r1036 | @LazyProperty | |||
def repos_path(self): | ||||
r1716 | """ | |||
Get's the repositories root path from database | ||||
r1036 | """ | |||
r1017 | ||||
r1036 | q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one() | |||
return q.ui_value | ||||
r1013 | ||||
r629 | def get(self, repo_id, cache=False): | |||
repo = self.sa.query(Repository)\ | ||||
r735 | .filter(Repository.repo_id == repo_id) | |||
r629 | ||||
if cache: | ||||
repo = repo.options(FromCache("sql_cache_short", | ||||
r735 | "get_repo_%s" % repo_id)) | |||
r629 | return repo.scalar() | |||
r2010 | def get_repo(self, repository): | |||
r2432 | return self._get_repo(repository) | |||
r2010 | ||||
r735 | def get_by_repo_name(self, repo_name, cache=False): | |||
repo = self.sa.query(Repository)\ | ||||
.filter(Repository.repo_name == repo_name) | ||||
if cache: | ||||
repo = repo.options(FromCache("sql_cache_short", | ||||
"get_repo_%s" % repo_name)) | ||||
r752 | return repo.scalar() | |||
r735 | ||||
r629 | def get_users_js(self): | |||
users = self.sa.query(User).filter(User.active == True).all() | ||||
r2142 | return json.dumps([ | |||
{ | ||||
'id': u.user_id, | ||||
'fname': u.name, | ||||
'lname': u.lastname, | ||||
'nname': u.username, | ||||
'gravatar_lnk': h.gravatar_url(u.email, 14) | ||||
} for u in users] | ||||
) | ||||
r629 | ||||
r1013 | def get_users_groups_js(self): | |||
users_groups = self.sa.query(UsersGroup)\ | ||||
.filter(UsersGroup.users_group_active == True).all() | ||||
r2142 | return json.dumps([ | |||
{ | ||||
'id': gr.users_group_id, | ||||
'grname': gr.users_group_name, | ||||
'grmembers': len(gr.members), | ||||
} for gr in users_groups] | ||||
) | ||||
r1013 | ||||
r1594 | def _get_defaults(self, repo_name): | |||
""" | ||||
r1818 | Get's information about repository, and returns a dict for | |||
r1594 | usage in forms | |||
r1818 | ||||
r1594 | :param repo_name: | |||
""" | ||||
repo_info = Repository.get_by_repo_name(repo_name) | ||||
if repo_info is None: | ||||
return None | ||||
defaults = repo_info.get_dict() | ||||
group, repo_name = repo_info.groups_and_repo | ||||
defaults['repo_name'] = repo_name | ||||
defaults['repo_group'] = getattr(group[-1] if group else None, | ||||
'group_id', None) | ||||
# fill owner | ||||
if repo_info.user: | ||||
defaults.update({'user': repo_info.user.username}) | ||||
else: | ||||
replacement_user = User.query().filter(User.admin == | ||||
True).first().username | ||||
defaults.update({'user': replacement_user}) | ||||
# fill repository users | ||||
for p in repo_info.repo_to_perm: | ||||
defaults.update({'u_perm_%s' % p.user.username: | ||||
p.permission.permission_name}) | ||||
# fill repository groups | ||||
for p in repo_info.users_group_to_perm: | ||||
defaults.update({'g_perm_%s' % p.users_group.users_group_name: | ||||
p.permission.permission_name}) | ||||
return defaults | ||||
r629 | def update(self, repo_name, form_data): | |||
try: | ||||
r822 | cur_repo = self.get_by_repo_name(repo_name, cache=False) | |||
r629 | ||||
r1539 | # update permissions | |||
r1014 | for member, perm, member_type in form_data['perms_updates']: | |||
if member_type == 'user': | ||||
r1982 | # this updates existing one | |||
RepoModel().grant_user_permission( | ||||
repo=cur_repo, user=member, perm=perm | ||||
) | ||||
r1014 | else: | |||
r1982 | RepoModel().grant_users_group_permission( | |||
repo=cur_repo, group_name=member, perm=perm | ||||
) | ||||
r1539 | # set new permissions | |||
r1014 | for member, perm, member_type in form_data['perms_new']: | |||
if member_type == 'user': | ||||
r1982 | RepoModel().grant_user_permission( | |||
repo=cur_repo, user=member, perm=perm | ||||
) | ||||
r1014 | else: | |||
r1982 | RepoModel().grant_users_group_permission( | |||
repo=cur_repo, group_name=member, perm=perm | ||||
) | ||||
r629 | ||||
r1539 | # update current repo | |||
r629 | for k, v in form_data.items(): | |||
if k == 'user': | ||||
r1530 | cur_repo.user = User.get_by_username(v) | |||
r1323 | elif k == 'repo_name': | |||
r1539 | pass | |||
r1379 | elif k == 'repo_group': | |||
r1633 | cur_repo.group = RepoGroup.get(v) | |||
r1323 | ||||
r629 | else: | |||
setattr(cur_repo, k, v) | ||||
r1539 | new_name = cur_repo.get_new_name(form_data['repo_name']) | |||
cur_repo.repo_name = new_name | ||||
r629 | self.sa.add(cur_repo) | |||
r1539 | if repo_name != new_name: | |||
r1323 | # rename repository | |||
r1539 | self.__rename_repo(old=repo_name, new=new_name) | |||
r629 | ||||
r1539 | return cur_repo | |||
r629 | except: | |||
log.error(traceback.format_exc()) | ||||
raise | ||||
def create(self, form_data, cur_user, just_db=False, fork=False): | ||||
r1722 | from rhodecode.model.scm import ScmModel | |||
r1366 | ||||
r629 | try: | |||
if fork: | ||||
r1722 | fork_parent_id = form_data['fork_parent_id'] | |||
r629 | ||||
r1722 | # 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 = form_data['repo_name'] | ||||
repo_name_full = form_data['repo_name_full'] | ||||
r1361 | ||||
r629 | new_repo = Repository() | |||
r1075 | new_repo.enable_statistics = False | |||
r1722 | ||||
r629 | for k, v in form_data.items(): | |||
if k == 'repo_name': | ||||
r1722 | v = repo_name_full | |||
r1350 | if k == 'repo_group': | |||
k = 'group_id' | ||||
r1517 | if k == 'description': | |||
v = v or repo_name | ||||
r629 | setattr(new_repo, k, v) | |||
if fork: | ||||
r1722 | parent_repo = Repository.get(fork_parent_id) | |||
r629 | new_repo.fork = parent_repo | |||
new_repo.user_id = cur_user.user_id | ||||
self.sa.add(new_repo) | ||||
r1729 | def _create_default_perms(): | |||
# create default permission | ||||
repo_to_perm = UserRepoToPerm() | ||||
default = 'repository.read' | ||||
for p in User.get_by_username('default').user_perms: | ||||
if p.permission.permission_name.startswith('repository.'): | ||||
default = p.permission.permission_name | ||||
break | ||||
default_perm = 'repository.none' if form_data['private'] else default | ||||
repo_to_perm.permission_id = self.sa.query(Permission)\ | ||||
.filter(Permission.permission_name == default_perm)\ | ||||
.one().permission_id | ||||
repo_to_perm.repository = new_repo | ||||
repo_to_perm.user_id = User.get_by_username('default').user_id | ||||
self.sa.add(repo_to_perm) | ||||
r629 | ||||
r1729 | if fork: | |||
if form_data.get('copy_permissions'): | ||||
repo = Repository.get(fork_parent_id) | ||||
user_perms = UserRepoToPerm.query()\ | ||||
.filter(UserRepoToPerm.repository == repo).all() | ||||
group_perms = UsersGroupRepoToPerm.query()\ | ||||
.filter(UsersGroupRepoToPerm.repository == repo).all() | ||||
r629 | ||||
r1729 | for perm in user_perms: | |||
UserRepoToPerm.create(perm.user, new_repo, | ||||
perm.permission) | ||||
r629 | ||||
r1729 | for perm in group_perms: | |||
UsersGroupRepoToPerm.create(perm.users_group, new_repo, | ||||
perm.permission) | ||||
else: | ||||
_create_default_perms() | ||||
else: | ||||
_create_default_perms() | ||||
r1028 | ||||
if not just_db: | ||||
r1112 | self.__create_repo(repo_name, form_data['repo_type'], | |||
r1350 | form_data['repo_group'], | |||
r1112 | form_data['clone_uri']) | |||
r2185 | log_create_repository(new_repo.get_dict(), | |||
created_by=cur_user.username) | ||||
r1028 | ||||
r1722 | # now automatically start following this repository as owner | |||
r786 | ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, | |||
r1722 | cur_user.user_id) | |||
r1539 | return new_repo | |||
r629 | except: | |||
log.error(traceback.format_exc()) | ||||
raise | ||||
def create_fork(self, form_data, cur_user): | ||||
r1722 | """ | |||
Simple wrapper into executing celery task for fork creation | ||||
r1818 | ||||
r1722 | :param form_data: | |||
:param cur_user: | ||||
""" | ||||
r629 | from rhodecode.lib.celerylib import tasks, run_task | |||
run_task(tasks.create_repo_fork, form_data, cur_user) | ||||
def delete(self, repo): | ||||
r2432 | repo = self._get_repo(repo) | |||
r629 | try: | |||
self.sa.delete(repo) | ||||
r668 | self.__delete_repo(repo) | |||
r629 | except: | |||
log.error(traceback.format_exc()) | ||||
raise | ||||
r1982 | 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 | ||||
""" | ||||
r2432 | user = self._get_user(user) | |||
repo = self._get_repo(repo) | ||||
permission = self._get_perm(perm) | ||||
r1982 | ||||
# 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) | ||||
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 | ||||
""" | ||||
r2142 | ||||
r2432 | user = self._get_user(user) | |||
repo = self._get_repo(repo) | ||||
r1982 | ||||
obj = self.sa.query(UserRepoToPerm)\ | ||||
.filter(UserRepoToPerm.repository == repo)\ | ||||
.filter(UserRepoToPerm.user == user)\ | ||||
.one() | ||||
self.sa.delete(obj) | ||||
r629 | ||||
r1982 | def grant_users_group_permission(self, repo, group_name, perm): | |||
""" | ||||
Grant permission for users 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 users group name | ||||
:param perm: Instance of Permission, or permission_name | ||||
""" | ||||
r2432 | repo = self._get_repo(repo) | |||
r1982 | group_name = self.__get_users_group(group_name) | |||
r2432 | permission = self._get_perm(perm) | |||
r1982 | ||||
# check if we have that permission already | ||||
obj = self.sa.query(UsersGroupRepoToPerm)\ | ||||
.filter(UsersGroupRepoToPerm.users_group == group_name)\ | ||||
.filter(UsersGroupRepoToPerm.repository == repo)\ | ||||
.scalar() | ||||
if obj is None: | ||||
# create new | ||||
obj = UsersGroupRepoToPerm() | ||||
obj.repository = repo | ||||
obj.users_group = group_name | ||||
obj.permission = permission | ||||
self.sa.add(obj) | ||||
def revoke_users_group_permission(self, repo, group_name): | ||||
""" | ||||
Revoke permission for users group on given repository | ||||
:param repo: Instance of Repository, repository_id, or repository name | ||||
:param group_name: Instance of UserGroup, users_group_id, | ||||
or users group name | ||||
""" | ||||
r2432 | repo = self._get_repo(repo) | |||
r1982 | group_name = self.__get_users_group(group_name) | |||
obj = self.sa.query(UsersGroupRepoToPerm)\ | ||||
.filter(UsersGroupRepoToPerm.repository == repo)\ | ||||
.filter(UsersGroupRepoToPerm.users_group == group_name)\ | ||||
.one() | ||||
self.sa.delete(obj) | ||||
r1015 | ||||
r708 | def delete_stats(self, repo_name): | |||
r1722 | """ | |||
removes stats for given repo | ||||
r1818 | ||||
r1722 | :param repo_name: | |||
""" | ||||
r708 | try: | |||
r1713 | obj = self.sa.query(Statistics)\ | |||
r1982 | .filter(Statistics.repository == | |||
self.get_by_repo_name(repo_name))\ | ||||
.one() | ||||
r1713 | self.sa.delete(obj) | |||
r708 | except: | |||
log.error(traceback.format_exc()) | ||||
raise | ||||
r1350 | def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False): | |||
r787 | """ | |||
r1361 | 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 | ||||
r1203 | ||||
r787 | :param repo_name: | |||
:param alias: | ||||
r1350 | :param parent_id: | |||
:param clone_uri: | ||||
r787 | """ | |||
r1594 | from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group | |||
r1517 | ||||
r1350 | if new_parent_id: | |||
r1716 | paths = RepoGroup.get(new_parent_id)\ | |||
.full_path.split(RepoGroup.url_sep()) | ||||
r1350 | new_parent_path = os.sep.join(paths) | |||
else: | ||||
new_parent_path = '' | ||||
r1973 | # we need to make it str for mercurial | |||
r1982 | repo_path = os.path.join(*map(lambda x: safe_str(x), | |||
r1401 | [self.repos_path, new_parent_path, repo_name])) | |||
r1350 | ||||
r1550 | # check if this path is not a repository | |||
if is_valid_repo(repo_path, self.repos_path): | ||||
raise Exception('This path %s is a valid repository' % repo_path) | ||||
r1401 | ||||
r1550 | # check if this path is a group | |||
if is_valid_repos_group(repo_path, self.repos_path): | ||||
raise Exception('This path %s is a valid group' % repo_path) | ||||
r1594 | ||||
r1973 | log.info('creating repo %s in %s @ %s' % ( | |||
repo_name, safe_unicode(repo_path), clone_uri | ||||
) | ||||
) | ||||
r1550 | backend = get_backend(alias) | |||
r2321 | if alias == 'hg': | |||
backend(repo_path, create=True, src_url=clone_uri) | ||||
elif alias == 'git': | ||||
r2404 | r = backend(repo_path, create=True, src_url=clone_uri, bare=True) | |||
# add rhodecode hook into this repo | ||||
loc = jn(r.path, 'hooks') | ||||
if not r.bare: | ||||
loc = jn(r.path, '.git', 'hooks') | ||||
if not os.path.isdir(loc): | ||||
os.makedirs(loc) | ||||
tmpl = pkg_resources.resource_string( | ||||
r2411 | 'rhodecode', jn('config', 'post_receive_tmpl.py') | |||
r2404 | ) | |||
r2407 | _hook_file = jn(loc, 'post-receive') | |||
r2404 | with open(_hook_file, 'wb') as f: | |||
f.write(tmpl) | ||||
r2411 | os.chmod(_hook_file, 0755) | |||
r2404 | ||||
r2321 | else: | |||
raise Exception('Undefined alias %s' % alias) | ||||
r629 | ||||
def __rename_repo(self, old, new): | ||||
r787 | """ | |||
renames repository on filesystem | ||||
r1203 | ||||
r787 | :param old: old name | |||
:param new: new name | ||||
""" | ||||
r1976 | log.info('renaming repo from %s to %s' % (old, new)) | |||
r629 | ||||
r1036 | old_path = os.path.join(self.repos_path, old) | |||
new_path = os.path.join(self.repos_path, new) | ||||
r629 | if os.path.isdir(new_path): | |||
r1982 | raise Exception( | |||
'Was trying to rename to already existing dir %s' % new_path | ||||
) | ||||
r629 | shutil.move(old_path, new_path) | |||
r668 | def __delete_repo(self, repo): | |||
r787 | """ | |||
removes repo from filesystem, the removal is acctually made by | ||||
added rm__ prefix into dir, and rename internat .hg/.git dirs so this | ||||
repository is no longer valid for rhodecode, can be undeleted later on | ||||
by reverting the renames on this repository | ||||
r1203 | ||||
r787 | :param repo: repo object | |||
""" | ||||
r1036 | rm_path = os.path.join(self.repos_path, repo.repo_name) | |||
r1976 | log.info("Removing %s" % (rm_path)) | |||
r2327 | # disable hg/git internal that it doesn't get detected as repo | |||
r668 | alias = repo.repo_type | |||
r2327 | ||||
bare = getattr(repo.scm_instance, 'bare', False) | ||||
if not bare: | ||||
# skip this for bare git repos | ||||
shutil.move(os.path.join(rm_path, '.%s' % alias), | ||||
os.path.join(rm_path, 'rm__.%s' % alias)) | ||||
r1749 | # disable repo | |||
r1982 | _d = 'rm__%s__%s' % (datetime.now().strftime('%Y%m%d_%H%M%S_%f'), | |||
repo.repo_name) | ||||
shutil.move(rm_path, os.path.join(self.repos_path, _d)) | ||||