# HG changeset patch # User Liad Shani # Date 1318874939 -7200 # Node ID 51bd5404529c07472982a209b7c3b0dfbf2fb527 # Parent 59ae82850e7624448f46dedd2060a1590a0e2cd2# Parent a9888895b60d1af2a16fecfe670a3e39327ed290 Merge with upstream diff -r 59ae82850e76 -r 51bd5404529c CONTRIBUTORS --- a/CONTRIBUTORS Sun Oct 09 23:49:00 2011 +0200 +++ b/CONTRIBUTORS Mon Oct 17 20:08:59 2011 +0200 @@ -4,6 +4,7 @@ Jason Harris Thayne Harbaugh cejones + Thomas Waldmann Lorenzo M. Catucci Dmitri Kuznetsov Jared Bunting @@ -11,4 +12,4 @@ Augosto Hermann Ankit Solanki Liad Shani - + Les Peabody diff -r 59ae82850e76 -r 51bd5404529c docs/changelog.rst --- a/docs/changelog.rst Sun Oct 09 23:49:00 2011 +0200 +++ b/docs/changelog.rst Mon Oct 17 20:08:59 2011 +0200 @@ -13,9 +13,34 @@ news ---- + fixes ----- + +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**) ====================== diff -r 59ae82850e76 -r 51bd5404529c docs/setup.rst --- a/docs/setup.rst Sun Oct 09 23:49:00 2011 +0200 +++ b/docs/setup.rst Mon Oct 17 20:08:59 2011 +0200 @@ -443,8 +443,8 @@ 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 @@ 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 @@ # 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 @@ 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 -r 59ae82850e76 -r 51bd5404529c init.d/rhodecode-daemon3 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/init.d/rhodecode-daemon3 Mon Oct 17 20:08:59 2011 +0200 @@ -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 -r 59ae82850e76 -r 51bd5404529c rhodecode/__init__.py --- a/rhodecode/__init__.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/__init__.py Mon Oct 17 20:08:59 2011 +0200 @@ -35,7 +35,7 @@ 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 -r 59ae82850e76 -r 51bd5404529c rhodecode/config/routing.py --- a/rhodecode/config/routing.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/config/routing.py Mon Oct 17 20:08:59 2011 +0200 @@ -19,10 +19,10 @@ 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 @@ :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 @@ :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 @@ # 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 -r 59ae82850e76 -r 51bd5404529c rhodecode/controllers/admin/repos.py --- a/rhodecode/controllers/admin/repos.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/controllers/admin/repos.py Mon Oct 17 20:08:59 2011 +0200 @@ -64,20 +64,10 @@ 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() @@ -90,7 +80,7 @@ self.__load_defaults() c.repo_info = db_repo = Repository.get_by_repo_name(repo_name) - repo = scm_repo = db_repo.scm_instance + repo = db_repo.scm_instance if c.repo_info is None: h.flash(_('%s repository is not mapped to db perhaps' @@ -234,11 +224,11 @@ 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) diff -r 59ae82850e76 -r 51bd5404529c rhodecode/controllers/admin/repos_groups.py --- a/rhodecode/controllers/admin/repos_groups.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/controllers/admin/repos_groups.py Mon Oct 17 20:08:59 2011 +0200 @@ -9,9 +9,10 @@ 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 @@ 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 @@ data = repo_group.get_dict() + data['group_name'] = repo_group.name + return data @HasPermissionAnyDecorator('hg.admin') @@ -169,13 +160,28 @@ 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 -r 59ae82850e76 -r 51bd5404529c rhodecode/controllers/admin/settings.py --- a/rhodecode/controllers/admin/settings.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/controllers/admin/settings.py Mon Oct 17 20:08:59 2011 +0200 @@ -366,17 +366,7 @@ 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 -r 59ae82850e76 -r 51bd5404529c rhodecode/controllers/files.py --- a/rhodecode/controllers/files.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/controllers/files.py Mon Oct 17 20:08:59 2011 +0200 @@ -316,13 +316,6 @@ filename = file_obj.filename content = file_obj.file - #TODO: REMOVE THIS !! - ################################ - import ipdb;ipdb.set_trace() - print 'setting ipdb debuggin for rhodecode.controllers.files.FilesController.add' - ################################ - - node_path = os.path.join(location, filename) author = self.rhodecode_user.full_contact diff -r 59ae82850e76 -r 51bd5404529c rhodecode/lib/__init__.py --- a/rhodecode/lib/__init__.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/lib/__init__.py Mon Oct 17 20:08:59 2011 +0200 @@ -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,28 @@ from rhodecode.lib.utils import EmptyChangeset cs = EmptyChangeset(requested_revision=rev) return cs + + +def get_current_revision(quiet=False): + """ + Returns tuple of (number, id) from repository containing this package + or None if repository could not be found. + + :param quiet: prints error for fetching revision if True + """ + + 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: + if not quiet: + print ("Cannot retrieve rhodecode's revision. Original error " + "was: %s" % err) + return None + diff -r 59ae82850e76 -r 51bd5404529c rhodecode/lib/celerylib/__init__.py --- a/rhodecode/lib/celerylib/__init__.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/lib/celerylib/__init__.py Mon Oct 17 20:08:59 2011 +0200 @@ -94,11 +94,11 @@ 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 -r 59ae82850e76 -r 51bd5404529c rhodecode/lib/celerylib/tasks.py --- a/rhodecode/lib/celerylib/tasks.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/lib/celerylib/tasks.py Mon Oct 17 20:08:59 2011 +0200 @@ -97,10 +97,11 @@ 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 -r 59ae82850e76 -r 51bd5404529c rhodecode/lib/compat.py --- a/rhodecode/lib/compat.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/lib/compat.py Mon Oct 17 20:08:59 2011 +0200 @@ -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 @@ # 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 -r 59ae82850e76 -r 51bd5404529c rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/lib/helpers.py Mon Oct 17 20:08:59 2011 +0200 @@ -598,12 +598,11 @@ 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 -r 59ae82850e76 -r 51bd5404529c rhodecode/lib/indexers/__init__.py --- a/rhodecode/lib/indexers/__init__.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/lib/indexers/__init__.py Mon Oct 17 20:08:59 2011 +0200 @@ -101,7 +101,7 @@ 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 -r 59ae82850e76 -r 51bd5404529c rhodecode/lib/pidlock.py --- a/rhodecode/lib/pidlock.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/lib/pidlock.py Mon Oct 17 20:08:59 2011 +0200 @@ -6,20 +6,7 @@ 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 @@ """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 -r 59ae82850e76 -r 51bd5404529c rhodecode/lib/utils.py --- a/rhodecode/lib/utils.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/lib/utils.py Mon Oct 17 20:08:59 2011 +0200 @@ -360,16 +360,19 @@ 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 @@ 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 @@ 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 @@ 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 -r 59ae82850e76 -r 51bd5404529c rhodecode/model/db.py --- a/rhodecode/model/db.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/model/db.py Mon Oct 17 20:08:59 2011 +0200 @@ -31,9 +31,10 @@ 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.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 @@ @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 @@ __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 @@ @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 @@ -283,7 +305,7 @@ @classmethod def get_by_username(cls, username, case_insensitive=False): if case_insensitive: - return Session.query(cls).filter(cls.username.like(username)).scalar() + return Session.query(cls).filter(cls.username.ilike(username)).scalar() else: return Session.query(cls).filter(cls.username == username).scalar() @@ -486,12 +508,16 @@ self.repo_id, self.repo_name) @classmethod + 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() @@ -506,13 +532,14 @@ :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): @@ -541,7 +568,8 @@ 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 @@ -551,9 +579,18 @@ # 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): """ @@ -718,9 +755,26 @@ 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 @@ -750,9 +804,16 @@ 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): @@ -771,6 +832,17 @@ 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 -r 59ae82850e76 -r 51bd5404529c rhodecode/model/forms.py --- a/rhodecode/model/forms.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/model/forms.py Mon Oct 17 20:08:59 2011 +0200 @@ -122,7 +122,7 @@ 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) @@ -250,7 +250,7 @@ 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 @@ 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(): diff -r 59ae82850e76 -r 51bd5404529c rhodecode/model/repo.py --- a/rhodecode/model/repo.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/model/repo.py Mon Oct 17 20:08:59 2011 +0200 @@ -98,7 +98,7 @@ 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)\ @@ -122,7 +122,7 @@ 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() @@ -144,26 +144,29 @@ .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.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() @@ -235,7 +238,7 @@ 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() @@ -302,7 +305,7 @@ :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()) @@ -313,12 +316,20 @@ 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 -r 59ae82850e76 -r 51bd5404529c rhodecode/model/repos_group.py --- a/rhodecode/model/repos_group.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/model/repos_group.py Mon Oct 17 20:08:59 2011 +0200 @@ -50,7 +50,7 @@ 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 @@ :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 @@ 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 @@ 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 -r 59ae82850e76 -r 51bd5404529c rhodecode/model/scm.py --- a/rhodecode/model/scm.py Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/model/scm.py Mon Oct 17 20:08:59 2011 +0200 @@ -22,6 +22,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os import time import traceback import logging @@ -146,10 +147,15 @@ 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]) diff -r 59ae82850e76 -r 51bd5404529c rhodecode/public/css/style.css --- a/rhodecode/public/css/style.css Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/public/css/style.css Mon Oct 17 20:08:59 2011 +0200 @@ -246,12 +246,13 @@ } #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 @@ #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 @@ 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 @@ 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 @@ } +.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 @@ 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: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 @@ 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 @@ 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 -r 59ae82850e76 -r 51bd5404529c rhodecode/templates/admin/repos_groups/repos_groups.html --- a/rhodecode/templates/admin/repos_groups/repos_groups.html Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/templates/admin/repos_groups/repos_groups.html Mon Oct 17 20:08:59 2011 +0200 @@ -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 -r 59ae82850e76 -r 51bd5404529c rhodecode/templates/admin/repos_groups/repos_groups_edit.html --- a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/templates/admin/repos_groups/repos_groups_edit.html Mon Oct 17 20:08:59 2011 +0200 @@ -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 -r 59ae82850e76 -r 51bd5404529c rhodecode/templates/admin/repos_groups/repos_groups_show.html --- a/rhodecode/templates/admin/repos_groups/repos_groups_show.html Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/templates/admin/repos_groups/repos_groups_show.html Mon Oct 17 20:08:59 2011 +0200 @@ -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 -r 59ae82850e76 -r 51bd5404529c rhodecode/templates/files/files_browser.html --- a/rhodecode/templates/files/files_browser.html Sun Oct 09 23:49:00 2011 +0200 +++ b/rhodecode/templates/files/files_browser.html Mon Oct 17 20:08:59 2011 +0200 @@ -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 @@