diff --git a/CONTRIBUTORS b/CONTRIBUTORS --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -12,4 +12,4 @@ List of contributors to RhodeCode projec Augosto Hermann Ankit Solanki Liad Shani - \ No newline at end of file + Les Peabody diff --git a/docs/changelog.rst b/docs/changelog.rst --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -3,6 +3,30 @@ Changelog ========= + +1.2.2 (**2011-10-17**) +====================== + +news +---- + +- #226 repo groups are available by path instead of numerical id + +fixes +----- + +- #259 Groups with the same name but with different parent group +- #260 Put repo in group, then move group to another group -> repo becomes unavailable +- #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems) +- #265 ldap save fails sometimes on converting attributes to booleans, + added getter and setter into model that will prevent from this on db model level +- fixed problems with timestamps issues #251 and #213 +- fixes #266 Rhodecode allows to create repo with the same name and in + the same parent as group +- fixes #245 Rescan of the repositories on Windows +- fixes #248 cannot edit repos inside a group on windows +- fixes #219 forking problems on windows + 1.2.1 (**2011-10-08**) ====================== @@ -17,11 +41,9 @@ fixes - gui fixes - fixed logger - 1.2.0 (**2011-10-07**) ====================== - news ---- diff --git a/docs/setup.rst b/docs/setup.rst --- a/docs/setup.rst +++ b/docs/setup.rst @@ -443,8 +443,8 @@ in the production.ini file:: In order to not have the statics served by the application. This improves speed. -Apache virtual host example ---------------------------- +Apache virtual host reverse proxy example +----------------------------------------- Here is a sample configuration file for apache using proxy:: @@ -503,6 +503,31 @@ then change into your choos Apache's WSGI config -------------------- +Alternatively, RhodeCode can be set up with Apache under mod_wsgi. For +that, you'll need to: + +- Install mod_wsgi. If using a Debian-based distro, you can install + the package libapache2-mod-wsgi:: + + aptitude install libapache2-mod-wsgi + +- Enable mod_wsgi:: + + a2enmod wsgi + +- Create a wsgi dispatch script, like the one below. Make sure you + check the paths correctly point to where you installed RhodeCode + and its Python Virtual Environment. +- Enable the WSGIScriptAlias directive for the wsgi dispatch script, + as in the following example. Once again, check the paths are + correctly specified. + +Here is a sample excerpt from an Apache Virtual Host configuration file:: + + WSGIDaemonProcess pylons user=www-data group=www-data processes=1 \ + threads=4 \ + python-path=/home/web/rhodecode/pyenv/lib/python2.6/site-packages + WSGIScriptAlias / /home/web/rhodecode/dispatch.wsgi Example wsgi dispatch script:: @@ -512,6 +537,9 @@ Example wsgi dispatch script:: # sometimes it's needed to set the curent dir os.chdir('/home/web/rhodecode/') + + import site + site.addsitedir("/home/web/rhodecode/pyenv/lib/python2.6/site-packages") from paste.deploy import loadapp from paste.script.util.logging_config import fileConfig @@ -519,6 +547,10 @@ Example wsgi dispatch script:: fileConfig('/home/web/rhodecode/production.ini') application = loadapp('config:/home/web/rhodecode/production.ini') +Note: when using mod_wsgi you'll need to install the same version of +Mercurial that's inside RhodeCode's virtualenv also on the system's Python +environment. + Other configuration files ------------------------- diff --git a/init.d/rhodecode-daemon3 b/init.d/rhodecode-daemon3 new file mode 100755 --- /dev/null +++ b/init.d/rhodecode-daemon3 @@ -0,0 +1,132 @@ +#!/bin/sh +######################################## +#### THIS IS A REDHAT INIT.D SCRIPT #### +######################################## + +################################################## +# +# RhodeCode server startup script +# Recommended default-startup: 2 3 4 5 +# Recommended default-stop: 0 1 6 +# +################################################## + + +APP_NAME="rhodecode" +# the location of your app +# since this is a web app, it should go in /var/www +APP_PATH="/var/www/$APP_NAME" + +CONF_NAME="production.ini" + +# write to wherever the PID should be stored, just ensure +# that the user you run paster as has the appropriate permissions +# same goes for the log file +PID_PATH="/var/run/rhodecode/pid" +LOG_PATH="/var/log/rhodecode/rhodecode.log" + +# replace this with the path to the virtual environment you +# made for RhodeCode +PYTHON_PATH="/opt/python_virtualenvironments/rhodecode-venv" + +RUN_AS="rhodecode" + +DAEMON="$PYTHON_PATH/bin/paster" + +DAEMON_OPTS="serve --daemon \ + --user=$RUN_AS \ + --group=$RUN_AS \ + --pid-file=$PID_PATH \ + --log-file=$LOG_PATH $APP_PATH/$CONF_NAME" + +DESC="rhodecode-server" +LOCK_FILE="/var/lock/subsys/$APP_NAME" + +# source CentOS init functions +. /etc/init.d/functions + +RETVAL=0 + +remove_pid () { + rm -f ${PID_PATH} + rmdir `dirname ${PID_PATH}` +} + +ensure_pid_dir () { + PID_DIR=`dirname ${PID_PATH}` + if [ ! -d ${PID_DIR} ] ; then + mkdir -p ${PID_DIR} + chown -R ${RUN_AS}:${RUN_AS} ${PID_DIR} + chmod 755 ${PID_DIR} + fi +} + +start_rhodecode () { + ensure_pid_dir + PYTHON_EGG_CACHE="/tmp" daemon --pidfile $PID_PATH \ + --user $RUN_AS "$DAEMON $DAEMON_OPTS" + RETVAL=$? + [ $RETVAL -eq 0 ] && touch $LOCK_FILE + return $RETVAL +} + +stop_rhodecode () { + if [ -e $LOCK_FILE ]; then + killproc -p $PID_PATH + RETVAL=$? + rm -f $LOCK_FILE + rm -f $PID_PATH + else + RETVAL=1 + fi + return $RETVAL +} + +status_rhodecode() { + if [ -e $LOCK_FILE ]; then + # exit with non-zero to indicate failure + RETVAL=1 + else + RETVAL=0 + fi + return $RETVAL +} + +restart_rhodecode () { + stop_rhodecode + start_rhodecode + RETVAL=$? +} + +case "$1" in + start) + echo -n $"Starting $DESC: " + start_rhodecode + echo + ;; + stop) + echo -n $"Stopping $DESC: " + stop_rhodecode + echo + ;; + status) + status_rhodecode + RETVAL=$? + if [ ! $RETVAL -eq 0 ]; then + echo "RhodeCode server is running..." + else + echo "RhodeCode server is stopped." + fi + ;; + restart) + echo -n $"Restarting $DESC: " + restart_rhodecode + echo + ;; + *) + echo $"Usage: $0 {start|stop|restart|status}" + RETVAL=1 + ;; +esac + +exit $RETVAL \ No newline at end of file diff --git a/rhodecode/__init__.py b/rhodecode/__init__.py --- a/rhodecode/__init__.py +++ b/rhodecode/__init__.py @@ -25,7 +25,7 @@ # along with this program. If not, see . import platform -VERSION = (1, 2, 1) +VERSION = (1, 2, 2) __version__ = '.'.join((str(each) for each in VERSION[:4])) __dbversion__ = 3 #defines current db version for migrations __platform__ = platform.system() @@ -35,7 +35,7 @@ PLATFORM_WIN = ('Windows') PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS') try: - from rhodecode.lib.utils import get_current_revision + from rhodecode.lib import get_current_revision _rev = get_current_revision() except ImportError: #this is needed when doing some setup.py operations diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -19,10 +19,10 @@ def make_map(config): always_scan=config['debug']) rmap.minimization = False rmap.explicit = False - + from rhodecode.lib.utils import is_valid_repo from rhodecode.lib.utils import is_valid_repos_group - + def check_repo(environ, match_dict): """ check for valid repository for proper 404 handling @@ -30,7 +30,7 @@ def make_map(config): :param environ: :param match_dict: """ - + repo_name = match_dict.get('repo_name') return is_valid_repo(repo_name, config['base_path']) @@ -42,7 +42,7 @@ def make_map(config): :param match_dict: """ repos_group_name = match_dict.get('group_name') - + return is_valid_repos_group(repos_group_name, config['base_path']) @@ -333,13 +333,13 @@ def make_map(config): # REPOSITORY ROUTES #========================================================================== rmap.connect('summary_home', '/{repo_name:.*}', - controller='summary', + controller='summary', conditions=dict(function=check_repo)) - -# rmap.connect('repo_group_home', '/{group_name:.*}', -# controller='admin/repos_groups',action="show_by_name", -# conditions=dict(function=check_group)) - + + rmap.connect('repos_group_home', '/{group_name:.*}', + controller='admin/repos_groups', action="show_by_name", + conditions=dict(function=check_group)) + rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}', controller='changeset', revision='tip', conditions=dict(function=check_repo)) diff --git a/rhodecode/controllers/admin/permissions.py b/rhodecode/controllers/admin/permissions.py --- a/rhodecode/controllers/admin/permissions.py +++ b/rhodecode/controllers/admin/permissions.py @@ -144,7 +144,7 @@ class PermissionsController(BaseControll c.create_choices = self.create_choices if id == 'default': - default_user = User.by_username('default') + default_user = User.get_by_username('default') defaults = {'_method': 'put', 'anonymous': default_user.active} diff --git a/rhodecode/controllers/admin/repos.py b/rhodecode/controllers/admin/repos.py --- a/rhodecode/controllers/admin/repos.py +++ b/rhodecode/controllers/admin/repos.py @@ -64,20 +64,10 @@ class ReposController(BaseController): super(ReposController, self).__before__() def __load_defaults(self): + c.repo_groups = Group.groups_choices() + c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups) + repo_model = RepoModel() - - c.repo_groups = [('', '')] - parents_link = lambda k: h.literal('»'.join( - map(lambda k: k.group_name, - k.parents + [k]) - ) - ) - - c.repo_groups.extend([(x.group_id, parents_link(x)) for \ - x in self.sa.query(Group).all()]) - c.repo_groups = sorted(c.repo_groups, - key=lambda t: t[1].split('»')[0]) - c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups) c.users_array = repo_model.get_users_js() c.users_groups_array = repo_model.get_users_groups_js() @@ -89,8 +79,8 @@ class ReposController(BaseController): """ self.__load_defaults() - c.repo_info = db_repo = Repository.by_repo_name(repo_name) - repo = scm_repo = db_repo.scm_instance + c.repo_info = db_repo = Repository.get_by_repo_name(repo_name) + repo = db_repo.scm_instance if c.repo_info is None: h.flash(_('%s repository is not mapped to db perhaps' @@ -101,7 +91,7 @@ class ReposController(BaseController): return redirect(url('repos')) - c.default_user_id = User.by_username('default').user_id + c.default_user_id = User.get_by_username('default').user_id c.in_public_journal = self.sa.query(UserFollowing)\ .filter(UserFollowing.user_id == c.default_user_id)\ .filter(UserFollowing.follows_repository == c.repo_info).scalar() @@ -234,11 +224,11 @@ class ReposController(BaseController): repo_groups=c.repo_groups_choices)() try: form_result = _form.to_python(dict(request.POST)) - repo_model.update(repo_name, form_result) + repo = repo_model.update(repo_name, form_result) invalidate_cache('get_repo_cached_%s' % repo_name) h.flash(_('Repository %s updated successfully' % repo_name), category='success') - changed_name = form_result['repo_name_full'] + changed_name = repo.repo_name action_logger(self.rhodecode_user, 'admin_updated_repo', changed_name, '', self.sa) @@ -381,8 +371,8 @@ class ReposController(BaseController): token = get_token() if cur_token == token: try: - repo_id = Repository.by_repo_name(repo_name).repo_id - user_id = User.by_username('default').user_id + repo_id = Repository.get_by_repo_name(repo_name).repo_id + user_id = User.get_by_username('default').user_id self.scm_model.toggle_following_repo(repo_id, user_id) h.flash(_('Updated repository visibility in public journal'), category='success') diff --git a/rhodecode/controllers/admin/repos_groups.py b/rhodecode/controllers/admin/repos_groups.py --- a/rhodecode/controllers/admin/repos_groups.py +++ b/rhodecode/controllers/admin/repos_groups.py @@ -9,9 +9,10 @@ from pylons import request, response, se from pylons.controllers.util import abort, redirect from pylons.i18n.translation import _ +from sqlalchemy.exc import IntegrityError + from rhodecode.lib import helpers as h -from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \ - HasPermissionAnyDecorator +from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator from rhodecode.lib.base import BaseController, render from rhodecode.model.db import Group from rhodecode.model.repos_group import ReposGroupModel @@ -31,19 +32,7 @@ class ReposGroupsController(BaseControll super(ReposGroupsController, self).__before__() def __load_defaults(self): - - c.repo_groups = [('', '')] - parents_link = lambda k: h.literal('»'.join( - map(lambda k: k.group_name, - k.parents + [k]) - ) - ) - - c.repo_groups.extend([(x.group_id, parents_link(x)) for \ - x in self.sa.query(Group).all()]) - - c.repo_groups = sorted(c.repo_groups, - key=lambda t: t[1].split('»')[0]) + c.repo_groups = Group.groups_choices() c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups) def __load_data(self, group_id): @@ -58,6 +47,8 @@ class ReposGroupsController(BaseControll data = repo_group.get_dict() + data['group_name'] = repo_group.name + return data @HasPermissionAnyDecorator('hg.admin') @@ -169,13 +160,28 @@ class ReposGroupsController(BaseControll repos_group_model.delete(id) h.flash(_('removed repos group %s' % gr.group_name), category='success') #TODO: in future action_logger(, '', '', '', self.sa) + except IntegrityError, e: + if e.message.find('groups_group_parent_id_fkey'): + log.error(traceback.format_exc()) + h.flash(_('Cannot delete this group it still contains ' + 'subgroups'), + category='warning') + else: + log.error(traceback.format_exc()) + h.flash(_('error occurred during deletion of repos ' + 'group %s' % gr.group_name), category='error') + except Exception: log.error(traceback.format_exc()) - h.flash(_('error occurred during deletion of repos group %s' % gr.group_name), - category='error') + h.flash(_('error occurred during deletion of repos ' + 'group %s' % gr.group_name), category='error') return redirect(url('repos_groups')) + def show_by_name(self, group_name): + id_ = Group.get_by_group_name(group_name).group_id + return self.show(id_) + def show(self, id, format='html'): """GET /repos_groups/id: Show a specific item""" # url('repos_group', id=ID) diff --git a/rhodecode/controllers/admin/settings.py b/rhodecode/controllers/admin/settings.py --- a/rhodecode/controllers/admin/settings.py +++ b/rhodecode/controllers/admin/settings.py @@ -366,17 +366,7 @@ class SettingsController(BaseController) def create_repository(self): """GET /_admin/create_repository: Form to create a new item""" - c.repo_groups = [('', '')] - parents_link = lambda k: h.literal('»'.join( - map(lambda k: k.group_name, - k.parents + [k]) - ) - ) - - c.repo_groups.extend([(x.group_id, parents_link(x)) for \ - x in self.sa.query(Group).all()]) - c.repo_groups = sorted(c.repo_groups, - key=lambda t: t[1].split('»')[0]) + c.repo_groups = Group.groups_choices() c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups) new_repo = request.GET.get('repo', '') diff --git a/rhodecode/controllers/login.py b/rhodecode/controllers/login.py --- a/rhodecode/controllers/login.py +++ b/rhodecode/controllers/login.py @@ -64,8 +64,7 @@ class LoginController(BaseController): c.form_result = login_form.to_python(dict(request.POST)) #form checks for username/password, now we're authenticated username = c.form_result['username'] - user = User.by_username(username, - case_insensitive=True) + user = User.get_by_username(username, case_insensitive=True) auth_user = AuthUser(user.user_id) auth_user.set_authenticated() session['rhodecode_user'] = auth_user @@ -95,8 +94,7 @@ class LoginController(BaseController): def register(self): user_model = UserModel() c.auto_active = False - for perm in user_model.get_by_username('default', - cache=False).user_perms: + for perm in User.get_by_username('default').user_perms: if perm.permission.permission_name == 'hg.register.auto_activate': c.auto_active = True break diff --git a/rhodecode/lib/__init__.py b/rhodecode/lib/__init__.py --- a/rhodecode/lib/__init__.py +++ b/rhodecode/lib/__init__.py @@ -23,6 +23,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os + def __get_lem(): from pygments import lexers from string import lower @@ -379,3 +381,24 @@ def get_changeset_safe(repo, rev): from rhodecode.lib.utils import EmptyChangeset cs = EmptyChangeset(requested_revision=rev) return cs + + +def get_current_revision(): + """ + Returns tuple of (number, id) from repository containing this package + or None if repository could not be found. + """ + + try: + from vcs import get_repo + from vcs.utils.helpers import get_scm + from vcs.exceptions import RepositoryError, VCSError + repopath = os.path.join(os.path.dirname(__file__), '..', '..') + scm = get_scm(repopath)[0] + repo = get_repo(path=repopath, alias=scm) + tip = repo.get_changeset() + return (tip.revision, tip.short_id) + except (ImportError, RepositoryError, VCSError), err: + print ("Cannot retrieve rhodecode's revision. Original error " + "was: %s" % err) + return None diff --git a/rhodecode/lib/auth.py b/rhodecode/lib/auth.py --- a/rhodecode/lib/auth.py +++ b/rhodecode/lib/auth.py @@ -6,8 +6,8 @@ authentication and permission libraries :created_on: Apr 4, 2010 - :copyright: (c) 2010 by marcink. - :license: LICENSE_NAME, see LICENSE_FILE for more details. + :copyright: (C) 2009-2011 Marcin Kuzminski + :license: GPLv3, see COPYING for more details. """ # 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 @@ -48,7 +48,7 @@ from rhodecode.lib.auth_ldap import Auth from rhodecode.model import meta from rhodecode.model.user import UserModel -from rhodecode.model.db import Permission, RhodeCodeSettings +from rhodecode.model.db import Permission, RhodeCodeSettings, User log = logging.getLogger(__name__) @@ -151,7 +151,7 @@ def authenticate(username, password): """ user_model = UserModel() - user = user_model.get_by_username(username, cache=False) + user = User.get_by_username(username) log.debug('Authenticating user using RhodeCode account') if user is not None and not user.ldap_dn: @@ -170,8 +170,7 @@ def authenticate(username, password): else: log.debug('Regular authentication failed') - user_obj = user_model.get_by_username(username, cache=False, - case_insensitive=True) + user_obj = User.get_by_username(username, case_insensitive=True) if user_obj is not None and not user_obj.ldap_dn: log.debug('this user already exists as non ldap') @@ -252,7 +251,7 @@ class AuthUser(object): def propagate_data(self): user_model = UserModel() - self.anonymous_user = user_model.get_by_username('default', cache=True) + self.anonymous_user = User.get_by_username('default') if self._api_key and self._api_key != self.anonymous_user.api_key: #try go get user by api key log.debug('Auth User lookup by API KEY %s', self._api_key) diff --git a/rhodecode/lib/backup_manager.py b/rhodecode/lib/backup_manager.py --- a/rhodecode/lib/backup_manager.py +++ b/rhodecode/lib/backup_manager.py @@ -7,8 +7,8 @@ repositories and send it to backup server using RSA key via ssh. :created_on: Feb 28, 2010 - :copyright: (c) 2010 by marcink. - :license: LICENSE_NAME, see LICENSE_FILE for more details. + :copyright: (C) 2009-2011 Marcin Kuzminski + :license: GPLv3, see COPYING for more details. """ # 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 diff --git a/rhodecode/lib/base.py b/rhodecode/lib/base.py --- a/rhodecode/lib/base.py +++ b/rhodecode/lib/base.py @@ -67,7 +67,7 @@ class BaseRepoController(BaseController) super(BaseRepoController, self).__before__() if c.repo_name: - c.rhodecode_db_repo = Repository.by_repo_name(c.repo_name) + c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name) c.rhodecode_repo = c.rhodecode_db_repo.scm_instance if c.rhodecode_repo is None: diff --git a/rhodecode/lib/celerylib/__init__.py b/rhodecode/lib/celerylib/__init__.py --- a/rhodecode/lib/celerylib/__init__.py +++ b/rhodecode/lib/celerylib/__init__.py @@ -94,11 +94,11 @@ def __get_lockkey(func, *fargs, **fkwarg def locked_task(func): def __wrapper(func, *fargs, **fkwargs): lockkey = __get_lockkey(func, *fargs, **fkwargs) - lockkey_path = dn(dn(dn(os.path.abspath(__file__)))) + lockkey_path = config['here'] log.info('running task with lockkey %s', lockkey) try: - l = DaemonLock(jn(lockkey_path, lockkey)) + l = DaemonLock(file_=jn(lockkey_path, lockkey)) ret = func(*fargs, **fkwargs) l.release() return ret diff --git a/rhodecode/lib/celerylib/tasks.py b/rhodecode/lib/celerylib/tasks.py --- a/rhodecode/lib/celerylib/tasks.py +++ b/rhodecode/lib/celerylib/tasks.py @@ -97,10 +97,11 @@ def get_commits_stats(repo_name, ts_min_ lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y, ts_max_y) - lockkey_path = dn(dn(dn(dn(os.path.abspath(__file__))))) + lockkey_path = config['here'] + log.info('running task with lockkey %s', lockkey) try: - lock = l = DaemonLock(jn(lockkey_path, lockkey)) + lock = l = DaemonLock(file_=jn(lockkey_path, lockkey)) #for js data compatibilty cleans the key for person from ' akc = lambda k: person(k).replace('"', "") diff --git a/rhodecode/lib/compat.py b/rhodecode/lib/compat.py --- a/rhodecode/lib/compat.py +++ b/rhodecode/lib/compat.py @@ -24,6 +24,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os +from rhodecode import __platform__, PLATFORM_WIN + #============================================================================== # json #============================================================================== @@ -358,3 +361,19 @@ class OrderedDict(_odict, dict): # OrderedSet #============================================================================== from sqlalchemy.util import OrderedSet + + +#============================================================================== +# kill FUNCTIONS +#============================================================================== +if __platform__ in PLATFORM_WIN: + import ctypes + + def kill(pid, sig): + """kill function for Win32""" + kernel32 = ctypes.windll.kernel32 + handle = kernel32.OpenProcess(1, 0, pid) + return (0 != kernel32.TerminateProcess(handle, 0)) + +else: + kill = os.kill diff --git a/rhodecode/lib/exceptions.py b/rhodecode/lib/exceptions.py --- a/rhodecode/lib/exceptions.py +++ b/rhodecode/lib/exceptions.py @@ -6,8 +6,8 @@ Set of custom exceptions used in RhodeCode :created_on: Nov 17, 2010 - :copyright: (c) 2010 by marcink. - :license: LICENSE_NAME, see LICENSE_FILE for more details. + :copyright: (C) 2009-2011 Marcin Kuzminski + :license: GPLv3, see COPYING for more details. """ # 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 diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py +++ b/rhodecode/lib/helpers.py @@ -598,12 +598,11 @@ def repo_link(groups_and_repos): return repo_name else: def make_link(group): - return link_to(group.group_name, url('repos_group', - id=group.group_id)) + return link_to(group.name, url('repos_group_home', + group_name=group.group_name)) return literal(' » '.join(map(make_link, groups)) + \ " » " + repo_name) - def fancy_file_stats(stats): """ Displays a fancy two colored bar for number of added/deleted diff --git a/rhodecode/lib/indexers/__init__.py b/rhodecode/lib/indexers/__init__.py --- a/rhodecode/lib/indexers/__init__.py +++ b/rhodecode/lib/indexers/__init__.py @@ -101,7 +101,7 @@ class MakeIndex(BasePasterCommand): from rhodecode.lib.pidlock import LockHeld, DaemonLock from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon try: - l = DaemonLock(file=jn(dn(dn(index_location)), 'make_index.lock')) + l = DaemonLock(file_=jn(dn(dn(index_location)), 'make_index.lock')) WhooshIndexingDaemon(index_location=index_location, repo_location=repo_location, repo_list=repo_list)\ diff --git a/rhodecode/lib/middleware/simplegit.py b/rhodecode/lib/middleware/simplegit.py --- a/rhodecode/lib/middleware/simplegit.py +++ b/rhodecode/lib/middleware/simplegit.py @@ -262,7 +262,7 @@ class SimpleGit(object): return repo_name def __get_user(self, username): - return User.by_username(username) + return User.get_by_username(username) def __get_action(self, environ): """Maps git request commands into a pull or push command. diff --git a/rhodecode/lib/middleware/simplehg.py b/rhodecode/lib/middleware/simplehg.py --- a/rhodecode/lib/middleware/simplehg.py +++ b/rhodecode/lib/middleware/simplehg.py @@ -76,7 +76,7 @@ class SimpleHg(object): # skip passing error to error controller environ['pylons.status_code_redirect'] = True - + #====================================================================== # EXTRACT REPOSITORY NAME FROM ENV #====================================================================== @@ -90,12 +90,13 @@ class SimpleHg(object): # GET ACTION PULL or PUSH #====================================================================== action = self.__get_action(environ) - + #====================================================================== # CHECK ANONYMOUS PERMISSION #====================================================================== if action in ['pull', 'push']: anonymous_user = self.__get_user('default') + username = anonymous_user.username anonymous_perm = self.__check_permission(action, anonymous_user, @@ -152,13 +153,13 @@ class SimpleHg(object): #====================================================================== # MERCURIAL REQUEST HANDLING #====================================================================== - + repo_path = safe_str(os.path.join(self.basepath, repo_name)) log.debug('Repository path is %s' % repo_path) - + baseui = make_ui('db') self.__inject_extras(repo_path, baseui, extras) - + # quick check if that dir exists... if is_valid_repo(repo_name, self.basepath) is False: @@ -228,7 +229,7 @@ class SimpleHg(object): return repo_name def __get_user(self, username): - return User.by_username(username) + return User.get_by_username(username) def __get_action(self, environ): """ @@ -257,7 +258,7 @@ class SimpleHg(object): push requests""" invalidate_cache('get_repo_cached_%s' % repo_name) - def __inject_extras(self,repo_path, baseui, extras={}): + def __inject_extras(self, repo_path, baseui, extras={}): """ Injects some extra params into baseui instance diff --git a/rhodecode/lib/pidlock.py b/rhodecode/lib/pidlock.py --- a/rhodecode/lib/pidlock.py +++ b/rhodecode/lib/pidlock.py @@ -6,20 +6,7 @@ import errno from warnings import warn from multiprocessing.util import Finalize -from rhodecode import __platform__, PLATFORM_WIN - -if __platform__ in PLATFORM_WIN: - import ctypes - - def kill(pid, sig): - """kill function for Win32""" - kernel32 = ctypes.windll.kernel32 - handle = kernel32.OpenProcess(1, 0, pid) - return (0 != kernel32.TerminateProcess(handle, 0)) - -else: - kill = os.kill - +from rhodecode.lib.compat import kill class LockHeld(Exception): pass @@ -29,17 +16,17 @@ class DaemonLock(object): """daemon locking USAGE: try: - l = DaemonLock(desc='test lock') + l = DaemonLock(file_='/path/tolockfile',desc='test lock') main() l.release() except LockHeld: sys.exit(1) """ - def __init__(self, file=None, callbackfn=None, + def __init__(self, file_=None, callbackfn=None, desc='daemon lock', debug=False): - self.pidfile = file if file else os.path.join( + self.pidfile = file_ if file_ else os.path.join( os.path.dirname(__file__), 'running.lock') self.callbackfn = callbackfn diff --git a/rhodecode/lib/smtp_mailer.py b/rhodecode/lib/smtp_mailer.py --- a/rhodecode/lib/smtp_mailer.py +++ b/rhodecode/lib/smtp_mailer.py @@ -6,9 +6,21 @@ Simple smtp mailer used in RhodeCode :created_on: Sep 13, 2010 - :copyright: (c) 2011 by marcink. - :license: LICENSE_NAME, see LICENSE_FILE for more details. + :copyright: (C) 2009-2011 Marcin Kuzminski + :license: GPLv3, see COPYING for more details. """ +# 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. +# +# 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 General Public License +# along with this program. If not, see . import logging import smtplib diff --git a/rhodecode/lib/utils.py b/rhodecode/lib/utils.py --- a/rhodecode/lib/utils.py +++ b/rhodecode/lib/utils.py @@ -112,7 +112,7 @@ def action_logger(user, action, repo, ip if hasattr(user, 'user_id'): user_obj = user elif isinstance(user, basestring): - user_obj = User.by_username(user) + user_obj = User.get_by_username(user) else: raise Exception('You have to provide user object or username') @@ -360,16 +360,19 @@ def map_groups(groups): parent = None group = None - for lvl, group_name in enumerate(groups[:-1]): + + # last element is repo in nested groups structure + groups = groups[:-1] + + for lvl, group_name in enumerate(groups): + group_name = '/'.join(groups[:lvl] + [group_name]) group = sa.query(Group).filter(Group.group_name == group_name).scalar() if group is None: group = Group(group_name, parent) sa.add(group) sa.commit() - parent = group - return group @@ -386,8 +389,13 @@ def repo2db_mapper(initial_repo_list, re rm = RepoModel() user = sa.query(User).filter(User.admin == True).first() added = [] + # fixup groups paths to new format on the fly + # TODO: remove this in future + for g in Group.query().all(): + g.group_name = g.get_new_name(g.name) + sa.add(g) for name, repo in initial_repo_list.items(): - group = map_groups(name.split(os.sep)) + group = map_groups(name.split(Repository.url_sep())) if not rm.get_by_repo_name(name, cache=False): log.info('repository %s not found creating default', name) added.append(name) @@ -442,26 +450,6 @@ def add_cache(settings): beaker.cache.cache_regions[region] = region_settings -def get_current_revision(): - """Returns tuple of (number, id) from repository containing this package - or None if repository could not be found. - """ - - try: - from vcs import get_repo - from vcs.utils.helpers import get_scm - from vcs.exceptions import RepositoryError, VCSError - repopath = os.path.join(os.path.dirname(__file__), '..', '..') - scm = get_scm(repopath)[0] - repo = get_repo(path=repopath, alias=scm) - tip = repo.get_changeset() - return (tip.revision, tip.short_id) - except (ImportError, RepositoryError, VCSError), err: - logging.debug("Cannot retrieve rhodecode's revision. Original error " - "was: %s" % err) - return None - - #============================================================================== # TEST FUNCTIONS AND CREATORS #============================================================================== @@ -483,7 +471,7 @@ def create_test_index(repo_location, con os.makedirs(index_location) try: - l = DaemonLock(file=jn(dn(index_location), 'make_index.lock')) + l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock')) WhooshIndexingDaemon(index_location=index_location, repo_location=repo_location)\ .run(full_index=full_index) diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -31,9 +31,10 @@ from datetime import date from sqlalchemy import * from sqlalchemy.exc import DatabaseError -from sqlalchemy.orm import relationship, backref, joinedload, class_mapper +from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.orm import relationship, backref, joinedload, class_mapper, \ + validates from sqlalchemy.orm.interfaces import MapperExtension - from beaker.cache import cache_region, region_invalidate from vcs import get_backend @@ -42,13 +43,14 @@ from vcs.exceptions import VCSError from vcs.utils.lazy import LazyProperty from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \ - generate_api_key + generate_api_key, safe_unicode from rhodecode.lib.exceptions import UsersGroupsAssignedException from rhodecode.lib.compat import json from rhodecode.model.meta import Base, Session from rhodecode.model.caching_query import FromCache + log = logging.getLogger(__name__) #============================================================================== @@ -126,7 +128,8 @@ class BaseModel(object): @classmethod def get(cls, id_): - return Session.query(cls).get(id_) + if id_: + return Session.query(cls).get(id_) @classmethod def delete(cls, id_): @@ -140,12 +143,34 @@ class RhodeCodeSettings(Base, BaseModel) __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True}) app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) def __init__(self, k='', v=''): self.app_settings_name = k self.app_settings_value = v + + @validates('_app_settings_value') + def validate_settings_value(self, key, val): + assert type(val) == unicode + return val + + @hybrid_property + def app_settings_value(self): + v = self._app_settings_value + if v == 'ldap_active': + v = str2bool(v) + return v + + @app_settings_value.setter + def app_settings_value(self,val): + """ + Setter that will always make sure we use unicode in app_settings_value + + :param val: + """ + self._app_settings_value = safe_unicode(val) + def __repr__(self): return "<%s('%s:%s')>" % (self.__class__.__name__, self.app_settings_name, self.app_settings_value) @@ -176,14 +201,11 @@ class RhodeCodeSettings(Base, BaseModel) @classmethod def get_ldap_settings(cls, cache=False): ret = Session.query(cls)\ - .filter(cls.app_settings_name.startswith('ldap_'))\ - .all() + .filter(cls.app_settings_name.startswith('ldap_')).all() fd = {} for row in ret: fd.update({row.app_settings_name:row.app_settings_value}) - fd.update({'ldap_active':str2bool(fd.get('ldap_active'))}) - return fd @@ -281,17 +303,16 @@ class User(Base, BaseModel): return self.__class__.__name__ @classmethod - def by_username(cls, username, case_insensitive=False): + def get_by_username(cls, username, case_insensitive=False): if case_insensitive: - return Session.query(cls).filter(cls.username.like(username)).one() + return Session.query(cls).filter(cls.username.ilike(username)).scalar() else: - return Session.query(cls).filter(cls.username == username).one() + return Session.query(cls).filter(cls.username == username).scalar() @classmethod def get_by_api_key(cls, api_key): return Session.query(cls).filter(cls.api_key == api_key).one() - def update_lastlogin(self): """Update user lastlogin""" @@ -487,12 +508,16 @@ class Repository(Base, BaseModel): self.repo_id, self.repo_name) @classmethod - def by_repo_name(cls, repo_name): + def url_sep(cls): + return '/' + + @classmethod + def get_by_repo_name(cls, repo_name): q = Session.query(cls).filter(cls.repo_name == repo_name) q = q.options(joinedload(Repository.fork))\ - .options(joinedload(Repository.user))\ - .options(joinedload(Repository.group))\ + .options(joinedload(Repository.user))\ + .options(joinedload(Repository.group))\ return q.one() @@ -507,13 +532,14 @@ class Repository(Base, BaseModel): :param cls: """ - q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/') + q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == + cls.url_sep()) q.options(FromCache("sql_cache_short", "repository_repo_path")) return q.one().ui_value @property def just_name(self): - return self.repo_name.split(os.sep)[-1] + return self.repo_name.split(Repository.url_sep())[-1] @property def groups_with_parents(self): @@ -542,7 +568,8 @@ class Repository(Base, BaseModel): Returns base full path for that repository means where it actually exists on a filesystem """ - q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/') + q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == + Repository.url_sep()) q.options(FromCache("sql_cache_short", "repository_repo_path")) return q.one().ui_value @@ -552,9 +579,18 @@ class Repository(Base, BaseModel): # we need to split the name by / since this is how we store the # names in the database, but that eventually needs to be converted # into a valid system path - p += self.repo_name.split('/') + p += self.repo_name.split(Repository.url_sep()) return os.path.join(*p) + def get_new_name(self, repo_name): + """ + returns new full repository name based on assigned group and new new + + :param group_name: + """ + path_prefix = self.group.full_path_splitted if self.group else [] + return Repository.url_sep().join(path_prefix + [repo_name]) + @property def _ui(self): """ @@ -719,9 +755,26 @@ class Group(Base, BaseModel): self.group_name) @classmethod + def groups_choices(cls): + from webhelpers.html import literal as _literal + repo_groups = [('', '')] + sep = ' » ' + _name = lambda k: _literal(sep.join(k)) + + repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) + for x in cls.query().all()]) + + repo_groups = sorted(repo_groups,key=lambda t: t[1].split(sep)[0]) + return repo_groups + + @classmethod def url_sep(cls): return '/' + @classmethod + def get_by_group_name(cls, group_name): + return cls.query().filter(cls.group_name == group_name).scalar() + @property def parents(self): parents_recursion_limit = 5 @@ -751,9 +804,16 @@ class Group(Base, BaseModel): return Session.query(Group).filter(Group.parent_group == self) @property + def name(self): + return self.group_name.split(Group.url_sep())[-1] + + @property def full_path(self): - return Group.url_sep().join([g.group_name for g in self.parents] + - [self.group_name]) + return self.group_name + + @property + def full_path_splitted(self): + return self.group_name.split(Group.url_sep()) @property def repositories(self): @@ -772,6 +832,17 @@ class Group(Base, BaseModel): return cnt + children_count(self) + + def get_new_name(self, group_name): + """ + returns new full group name based on parent and new name + + :param group_name: + """ + path_prefix = self.parent_group.full_path_splitted if self.parent_group else [] + return Group.url_sep().join(path_prefix + [group_name]) + + class Permission(Base, BaseModel): __tablename__ = 'permissions' __table_args__ = {'extend_existing':True} diff --git a/rhodecode/model/forms.py b/rhodecode/model/forms.py --- a/rhodecode/model/forms.py +++ b/rhodecode/model/forms.py @@ -32,6 +32,7 @@ from formencode.validators import Unicod from pylons.i18n.translation import _ from webhelpers.pylonslib.secure_form import authentication_token +from rhodecode.config.routing import ADMIN_PREFIX from rhodecode.lib.utils import repo_name_slug from rhodecode.lib.auth import authenticate, get_crypt_password from rhodecode.lib.exceptions import LdapImportError @@ -70,8 +71,7 @@ def ValidUsername(edit, old_data): old_un = UserModel().get(old_data.get('user_id')).username if old_un != value or not edit: - if UserModel().get_by_username(value, cache=False, - case_insensitive=True): + if User.get_by_username(value, case_insensitive=True): raise formencode.Invalid(_('This username already ' 'exists') , value, state) @@ -122,7 +122,7 @@ def ValidReposGroup(edit, old_data): def validate_python(self, value, state): #TODO WRITE VALIDATIONS group_name = value.get('group_name') - group_parent_id = int(value.get('group_parent_id') or - 1) + group_parent_id = int(value.get('group_parent_id') or -1) # slugify repo group just in case :) slug = repo_name_slug(group_name) @@ -206,7 +206,7 @@ class ValidAuth(formencode.validators.Fa def validate_python(self, value, state): password = value['password'] username = value['username'] - user = UserModel().get_by_username(username) + user = User.get_by_username(username) if authenticate(username, password): return value @@ -241,7 +241,7 @@ def ValidRepoName(edit, old_data): repo_name = value.get('repo_name') slug = repo_name_slug(repo_name) - if slug in ['_admin', '']: + if slug in [ADMIN_PREFIX, '']: e_dict = {'repo_name': _('This repository name is disallowed')} raise formencode.Invalid('', value, state, error_dict=e_dict) @@ -250,7 +250,7 @@ def ValidRepoName(edit, old_data): gr = Group.get(value.get('repo_group')) group_path = gr.full_path # value needs to be aware of group name in order to check - # db key This is an actuall just the name to store in the + # db key This is an actual just the name to store in the # database repo_name_full = group_path + Group.url_sep() + repo_name else: @@ -259,25 +259,32 @@ def ValidRepoName(edit, old_data): value['repo_name_full'] = repo_name_full - if old_data.get('repo_name') != repo_name_full or not edit: + rename = old_data.get('repo_name') != repo_name_full + create = not edit + if rename or create: if group_path != '': if RepoModel().get_by_repo_name(repo_name_full,): e_dict = {'repo_name':_('This repository already ' - 'exists in group "%s"') % + 'exists in a group "%s"') % gr.group_name} raise formencode.Invalid('', value, state, error_dict=e_dict) + elif Group.get_by_group_name(repo_name_full): + e_dict = {'repo_name':_('There is a group with this' + ' name already "%s"') % + repo_name_full} + raise formencode.Invalid('', value, state, + error_dict=e_dict) - else: - if RepoModel().get_by_repo_name(repo_name_full): + elif RepoModel().get_by_repo_name(repo_name_full): e_dict = {'repo_name':_('This repository ' 'already exists')} raise formencode.Invalid('', value, state, error_dict=e_dict) + return value - return _ValidRepoName def ValidForkName(): @@ -287,7 +294,7 @@ def ValidForkName(): repo_name = value.get('fork_name') slug = repo_name_slug(repo_name) - if slug in ['_admin', '']: + if slug in [ADMIN_PREFIX, '']: e_dict = {'repo_name': _('This repository name is disallowed')} raise formencode.Invalid('', value, state, error_dict=e_dict) diff --git a/rhodecode/model/repo.py b/rhodecode/model/repo.py --- a/rhodecode/model/repo.py +++ b/rhodecode/model/repo.py @@ -98,11 +98,11 @@ class RepoModel(BaseModel): try: cur_repo = self.get_by_repo_name(repo_name, cache=False) - #update permissions + # update permissions for member, perm, member_type in form_data['perms_updates']: if member_type == 'user': r2p = self.sa.query(RepoToPerm)\ - .filter(RepoToPerm.user == User.by_username(member))\ + .filter(RepoToPerm.user == User.get_by_username(member))\ .filter(RepoToPerm.repository == cur_repo)\ .one() @@ -122,12 +122,12 @@ class RepoModel(BaseModel): perm).scalar() self.sa.add(g2p) - #set new permissions + # set new permissions for member, perm, member_type in form_data['perms_new']: if member_type == 'user': r2p = RepoToPerm() r2p.repository = cur_repo - r2p.user = User.by_username(member) + r2p.user = User.get_by_username(member) r2p.permission = self.sa.query(Permission)\ .filter(Permission. @@ -144,26 +144,29 @@ class RepoModel(BaseModel): .scalar() self.sa.add(g2p) - #update current repo + # update current repo for k, v in form_data.items(): if k == 'user': - cur_repo.user = User.by_username(v) + cur_repo.user = User.get_by_username(v) elif k == 'repo_name': - cur_repo.repo_name = form_data['repo_name_full'] + pass elif k == 'repo_group': cur_repo.group_id = v else: setattr(cur_repo, k, v) + new_name = cur_repo.get_new_name(form_data['repo_name']) + cur_repo.repo_name = new_name + self.sa.add(cur_repo) - if repo_name != form_data['repo_name_full']: + if repo_name != new_name: # rename repository - self.__rename_repo(old=repo_name, - new=form_data['repo_name_full']) + self.__rename_repo(old=repo_name, new=new_name) self.sa.commit() + return cur_repo except: log.error(traceback.format_exc()) self.sa.rollback() @@ -208,8 +211,7 @@ class RepoModel(BaseModel): #create default permission repo_to_perm = RepoToPerm() default = 'repository.read' - for p in UserModel(self.sa).get_by_username('default', - cache=False).user_perms: + for p in User.get_by_username('default').user_perms: if p.permission.permission_name.startswith('repository.'): default = p.permission.permission_name break @@ -221,8 +223,7 @@ class RepoModel(BaseModel): .one().permission_id repo_to_perm.repository = new_repo - repo_to_perm.user_id = UserModel(self.sa)\ - .get_by_username('default', cache=False).user_id + repo_to_perm.user_id = User.get_by_username('default').user_id self.sa.add(repo_to_perm) @@ -237,7 +238,7 @@ class RepoModel(BaseModel): from rhodecode.model.scm import ScmModel ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, cur_user.user_id) - + return new_repo except: log.error(traceback.format_exc()) self.sa.rollback() @@ -304,7 +305,7 @@ class RepoModel(BaseModel): :param parent_id: :param clone_uri: """ - from rhodecode.lib.utils import is_valid_repo + from rhodecode.lib.utils import is_valid_repo,is_valid_repos_group if new_parent_id: paths = Group.get(new_parent_id).full_path.split(Group.url_sep()) @@ -315,12 +316,20 @@ class RepoModel(BaseModel): repo_path = os.path.join(*map(lambda x:safe_str(x), [self.repos_path, new_parent_path, repo_name])) - if is_valid_repo(repo_path, self.repos_path) is False: - log.info('creating repo %s in %s @ %s', repo_name, repo_path, - clone_uri) - backend = get_backend(alias) + + # 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) - backend(repo_path, create=True, src_url=clone_uri) + # 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) + + log.info('creating repo %s in %s @ %s', repo_name, repo_path, + clone_uri) + backend = get_backend(alias) + + backend(repo_path, create=True, src_url=clone_uri) def __rename_repo(self, old, new): diff --git a/rhodecode/model/repos_group.py b/rhodecode/model/repos_group.py --- a/rhodecode/model/repos_group.py +++ b/rhodecode/model/repos_group.py @@ -50,7 +50,7 @@ class ReposGroupModel(BaseModel): q = RhodeCodeUi.get_by_key('/').one() return q.ui_value - def __create_group(self, group_name, parent_id): + def __create_group(self, group_name): """ makes repositories group on filesystem @@ -58,44 +58,30 @@ class ReposGroupModel(BaseModel): :param parent_id: """ - if parent_id: - paths = Group.get(parent_id).full_path.split(Group.url_sep()) - parent_path = os.sep.join(paths) - else: - parent_path = '' - - create_path = os.path.join(self.repos_path, parent_path, group_name) + create_path = os.path.join(self.repos_path, group_name) log.debug('creating new group in %s', create_path) if os.path.isdir(create_path): raise Exception('That directory already exists !') - os.makedirs(create_path) - - def __rename_group(self, old, old_parent_id, new, new_parent_id): + def __rename_group(self, old, new): """ Renames a group on filesystem :param group_name: """ + + if old == new: + log.debug('skipping group rename') + return + log.debug('renaming repos group from %s to %s', old, new) - if new_parent_id: - paths = Group.get(new_parent_id).full_path.split(Group.url_sep()) - new_parent_path = os.sep.join(paths) - else: - new_parent_path = '' - if old_parent_id: - paths = Group.get(old_parent_id).full_path.split(Group.url_sep()) - old_parent_path = os.sep.join(paths) - else: - old_parent_path = '' - - old_path = os.path.join(self.repos_path, old_parent_path, old) - new_path = os.path.join(self.repos_path, new_parent_path, new) + old_path = os.path.join(self.repos_path, old) + new_path = os.path.join(self.repos_path, new) log.debug('renaming repos paths from %s to %s', old_path, new_path) @@ -114,22 +100,23 @@ class ReposGroupModel(BaseModel): paths = os.sep.join(paths) rm_path = os.path.join(self.repos_path, paths) - os.rmdir(rm_path) + if os.path.isdir(rm_path): + # delete only if that path really exists + os.rmdir(rm_path) def create(self, form_data): try: new_repos_group = Group() - new_repos_group.group_name = form_data['group_name'] - new_repos_group.group_description = \ - form_data['group_description'] - new_repos_group.group_parent_id = form_data['group_parent_id'] + new_repos_group.group_description = form_data['group_description'] + new_repos_group.parent_group = Group.get(form_data['group_parent_id']) + new_repos_group.group_name = new_repos_group.get_new_name(form_data['group_name']) self.sa.add(new_repos_group) - self.__create_group(form_data['group_name'], - form_data['group_parent_id']) + self.__create_group(new_repos_group.group_name) self.sa.commit() + return new_repos_group except: log.error(traceback.format_exc()) self.sa.rollback() @@ -139,23 +126,27 @@ class ReposGroupModel(BaseModel): try: repos_group = Group.get(repos_group_id) - old_name = repos_group.group_name - old_parent_id = repos_group.group_parent_id + old_path = repos_group.full_path - repos_group.group_name = form_data['group_name'] - repos_group.group_description = \ - form_data['group_description'] - repos_group.group_parent_id = form_data['group_parent_id'] + #change properties + repos_group.group_description = form_data['group_description'] + repos_group.parent_group = Group.get(form_data['group_parent_id']) + repos_group.group_name = repos_group.get_new_name(form_data['group_name']) + + new_path = repos_group.full_path self.sa.add(repos_group) - if old_name != form_data['group_name'] or (old_parent_id != - form_data['group_parent_id']): - self.__rename_group(old=old_name, old_parent_id=old_parent_id, - new=form_data['group_name'], - new_parent_id=form_data['group_parent_id']) + self.__rename_group(old_path, new_path) + + # we need to get all repositories from this new group and + # rename them accordingly to new group path + for r in repos_group.repositories: + r.repo_name = r.get_new_name(r.just_name) + self.sa.add(r) self.sa.commit() + return repos_group except: log.error(traceback.format_exc()) self.sa.rollback() diff --git a/rhodecode/model/scm.py b/rhodecode/model/scm.py --- a/rhodecode/model/scm.py +++ b/rhodecode/model/scm.py @@ -41,9 +41,8 @@ from rhodecode.lib.auth import HasRepoPe from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \ action_logger, EmptyChangeset from rhodecode.model import BaseModel -from rhodecode.model.user import UserModel from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \ - UserFollowing, UserLog + UserFollowing, UserLog, User log = logging.getLogger(__name__) @@ -148,10 +147,15 @@ class ScmModel(BaseModel): repos_list = {} for name, path in get_filesystem_repos(repos_path, recursive=True): + + # name need to be decomposed and put back together using the / + # since this is internal storage separator for rhodecode + name = Repository.url_sep().join(name.split(os.sep)) + try: if name in repos_list: raise RepositoryError('Duplicate repository name %s ' - 'found in %s' % (name, path)) + 'found in %s' % (name, path)) else: klass = get_backend(path[0]) @@ -283,7 +287,7 @@ class ScmModel(BaseModel): return f is not None def is_following_user(self, username, user_id, cache=False): - u = UserModel(self.sa).get_by_username(username) + u = User.get_by_username(username) f = self.sa.query(UserFollowing)\ .filter(UserFollowing.follows_user == u)\ @@ -293,24 +297,24 @@ class ScmModel(BaseModel): def get_followers(self, repo_id): if not isinstance(repo_id, int): - repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id') + repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id') return self.sa.query(UserFollowing)\ .filter(UserFollowing.follows_repo_id == repo_id).count() def get_forks(self, repo_id): if not isinstance(repo_id, int): - repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id') + repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id') return self.sa.query(Repository)\ .filter(Repository.fork_id == repo_id).count() def pull_changes(self, repo_name, username): - dbrepo = Repository.by_repo_name(repo_name) + dbrepo = Repository.get_by_repo_name(repo_name) clone_uri = dbrepo.clone_uri if not clone_uri: raise Exception("This repository doesn't have a clone uri") - + repo = dbrepo.scm_instance try: extras = {'ip': '', @@ -363,12 +367,12 @@ class ScmModel(BaseModel): from vcs.backends.git import GitInMemoryChangeset as IMC # decoding here will force that we have proper encoded values # in any other case this will throw exceptions and deny commit - - if isinstance(content,(basestring,)): + + if isinstance(content, (basestring,)): content = safe_str(content) - elif isinstance(content,file): + elif isinstance(content, file): content = content.read() - + message = safe_str(message) path = safe_str(f_path) author = safe_str(author) diff --git a/rhodecode/public/css/style.css b/rhodecode/public/css/style.css --- a/rhodecode/public/css/style.css +++ b/rhodecode/public/css/style.css @@ -246,12 +246,13 @@ color:#FFF; } #header #header-inner { -height:40px; +min-height:40px; clear:both; position:relative; background:#003367 url("../images/header_inner.png") repeat-x; margin:0; padding:0; +display:block; box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6); -webkit-border-radius: 4px 4px 4px 4px; -khtml-border-radius: 4px 4px 4px 4px; @@ -272,7 +273,10 @@ padding:0; #header #header-inner #home a:hover { background-position:0 -40px; } - +#header #header-inner #logo { + float: left; + position: absolute; +} #header #header-inner #logo h1 { color:#FFF; font-size:18px; @@ -348,7 +352,7 @@ top:0; left:0; border-left:none; border-right:1px solid #2e5c89; -padding:8px 8px 4px; +padding:8px 6px 4px; } #header #header-inner #quick li span.icon_short { @@ -356,7 +360,10 @@ top:0; left:0; border-left:none; border-right:1px solid #2e5c89; -padding:9px 4px 4px; +padding:8px 6px 4px; +} +#header #header-inner #quick li span.icon img, #header #header-inner #quick li span.icon_short img { + margin: 0px -2px 0px 0px; } #header #header-inner #quick li a:hover { @@ -564,6 +571,14 @@ padding:12px 9px 7px 24px; } +.groups_breadcrumbs a { + color: #fff; +} +.groups_breadcrumbs a:hover { + color: #bfe3ff; + text-decoration: none; +} + .quick_repo_menu{ background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important; cursor: pointer; @@ -1895,19 +1910,6 @@ div.browserblock .add_node{ padding-left: 5px; } -div.browserblock .search_activate #filter_activate,div.browserblock .add_node a{ - vertical-align: sub; - border: 1px solid; - padding:2px; - -webkit-border-radius: 4px 4px 4px 4px; - -khtml-border-radius: 4px 4px 4px 4px; - -moz-border-radius: 4px 4px 4px 4px; - border-radius: 4px 4px 4px 4px; - background: url("../images/button.png") repeat-x scroll 0 0 #E5E3E3; - border-color: #DDDDDD #DDDDDD #C6C6C6 #C6C6C6; - color: #515151; -} - div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover{ text-decoration: none !important; } @@ -2371,8 +2373,10 @@ border-left:1px solid #316293; border:1px solid #316293; } - -input.ui-button-small { +.ui-button-small a:hover { + +} +input.ui-button-small,.ui-button-small { background:#e5e3e3 url("../images/button.png") repeat-x !important; border-top:1px solid #DDD !important; border-left:1px solid #c6c6c6 !important; @@ -2387,17 +2391,19 @@ margin:0 !important; border-radius: 4px 4px 4px 4px !important; box-shadow: 0 1px 0 #ececec !important; cursor: pointer !important; -} - -input.ui-button-small:hover { +padding:0px 2px 1px 2px; +} + +input.ui-button-small:hover,.ui-button-small:hover { background:#b4b4b4 url("../images/button_selected.png") repeat-x !important; border-top:1px solid #ccc !important; border-left:1px solid #bebebe !important; border-right:1px solid #b1b1b1 !important; -border-bottom:1px solid #afafaf !important; -} - -input.ui-button-small-blue { +border-bottom:1px solid #afafaf !important; +text-decoration: none; +} + +input.ui-button-small-blue,.ui-button-small-blue { background:#4e85bb url("../images/button_highlight.png") repeat-x; border-top:1px solid #5c91a4; border-left:1px solid #2a6f89; @@ -2410,6 +2416,7 @@ color:#fff; border-radius: 4px 4px 4px 4px; box-shadow: 0 1px 0 #ececec; cursor: pointer; +padding:0px 2px 1px 2px; } input.ui-button-small-blue:hover { diff --git a/rhodecode/templates/admin/repos_groups/repos_groups.html b/rhodecode/templates/admin/repos_groups/repos_groups.html --- a/rhodecode/templates/admin/repos_groups/repos_groups.html +++ b/rhodecode/templates/admin/repos_groups/repos_groups.html @@ -5,12 +5,14 @@ <%def name="breadcrumbs()"> + ${_('Groups')} %if c.group.parent_group: - » ${h.link_to(c.group.parent_group.group_name, - h.url('repos_group',id=c.group.parent_group.group_id))} + » ${h.link_to(c.group.parent_group.name, + h.url('repos_group_home',group_name=c.group.parent_group.group_name))} %endif - » "${c.group.group_name}" ${_('with')} + » "${c.group.name}" ${_('with')} + <%def name="page_nav()"> diff --git a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html b/rhodecode/templates/admin/repos_groups/repos_groups_edit.html --- a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html +++ b/rhodecode/templates/admin/repos_groups/repos_groups_edit.html @@ -2,14 +2,14 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Edit repos group')} ${c.repos_group.group_name} - ${c.rhodecode_name} + ${_('Edit repos group')} ${c.repos_group.name} - ${c.rhodecode_name} <%def name="breadcrumbs_links()"> ${h.link_to(_('Admin'),h.url('admin_home'))} » ${h.link_to(_('Repos groups'),h.url('repos_groups'))} » - ${_('edit repos group')} "${c.repos_group.group_name}" + ${_('edit repos group')} "${c.repos_group.name}" <%def name="page_nav()"> diff --git a/rhodecode/templates/admin/repos_groups/repos_groups_show.html b/rhodecode/templates/admin/repos_groups/repos_groups_show.html --- a/rhodecode/templates/admin/repos_groups/repos_groups_show.html +++ b/rhodecode/templates/admin/repos_groups/repos_groups_show.html @@ -44,14 +44,14 @@
${_('Repositories group')} - ${h.link_to(h.literal(' » '.join([g.group_name for g in gr.parents+[gr]])),url('edit_repos_group',id=gr.group_id))} + ${h.link_to(h.literal(' » '.join([g.name for g in gr.parents+[gr]])),url('edit_repos_group',id=gr.group_id))}
${gr.group_description} ${gr.repositories.count()} ${h.form(url('repos_group', id=gr.group_id),method='delete')} - ${h.submit('remove_%s' % gr.group_name,'delete',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this group')+"');")} + ${h.submit('remove_%s' % gr.name,'delete',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this group')+"');")} ${h.end_form()} diff --git a/rhodecode/templates/files/files_browser.html b/rhodecode/templates/files/files_browser.html --- a/rhodecode/templates/files/files_browser.html +++ b/rhodecode/templates/files/files_browser.html @@ -11,9 +11,9 @@ ${h.form(h.url.current())}
${_('view')}@rev - « + « ${h.text('at_rev',value=c.changeset.revision,size=5)} - » + » ## ${h.submit('view',_('view'),class_="ui-button-small")}
${h.end_form()} @@ -24,11 +24,11 @@