# HG changeset patch # User Marcin Kuzminski # Date 1332957256 -7200 # Node ID dc2584ba5fbc491f15f4b72ddaa63730f3067fbc # Parent 8fd6650bb436379924e0dac7a81d346e03ca50e8# Parent 0d2ce995f6a44b4977c89262261752348956d1bf merged beta into default branch diff -r 8fd6650bb436 -r dc2584ba5fbc .hgignore --- a/.hgignore Sat Mar 03 03:41:19 2012 +0200 +++ b/.hgignore Wed Mar 28 19:54:16 2012 +0200 @@ -6,6 +6,7 @@ *.egg syntax: regexp +^rcextensions ^build ^docs/build/ ^docs/_build/ diff -r 8fd6650bb436 -r dc2584ba5fbc README.rst --- a/README.rst Sat Mar 03 03:41:19 2012 +0200 +++ b/README.rst Wed Mar 28 19:54:16 2012 +0200 @@ -15,10 +15,11 @@ however RhodeCode can be run as standalone hosted application on your own server. It is open source and donation ware and focuses more on providing a customized, self administered interface for Mercurial_ and GIT_ repositories. -RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to -handle multiple different version control systems. +RhodeCode works on *nix systems and Windows it is powered by a vcs_ library +that Lukasz Balcerzak and Marcin Kuzminski created to handle multiple +different version control systems. -RhodeCode uses `Semantic Versioning `_ +RhodeCode uses `PEP386 versioning http://www.python.org/dev/peps/pep-0386/`_ Installation ------------ @@ -99,7 +100,7 @@ - Intelligent cache with invalidation after push or project change, provides high performance and always up to date data. - Rss / atom feeds, gravatar support, download sources as zip/tar/gz -- Async tasks for speed and performance using celery_ (works without them too) +- Optional async tasks for speed and performance using celery_ - Backup scripts can do backup of whole app and send it over scp to desired location - Based on pylons / sqlalchemy / sqlite / whoosh / vcs diff -r 8fd6650bb436 -r dc2584ba5fbc development.ini --- a/development.ini Sat Mar 03 03:41:19 2012 +0200 +++ b/development.ini Wed Mar 28 19:54:16 2012 +0200 @@ -93,6 +93,11 @@ ## all running rhodecode instances. Leave empty if you don't use it instance_id = +## alternative return HTTP header for failed authentication. Default HTTP +## response is 401 HTTPUnauthorized. Currently HG clients have troubles with +## handling that. Set this variable to 403 to return HTTPForbidden +auth_ret_code = + #################################### ### CELERY CONFIG #### #################################### @@ -171,6 +176,7 @@ beaker.session.type = file beaker.session.key = rhodecode +# secure cookie requires AES python libraries #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu #beaker.session.validate_key = 9712sds2212c--zxc123 beaker.session.timeout = 36000 @@ -207,13 +213,13 @@ sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode sqlalchemy.db1.echo = false sqlalchemy.db1.pool_recycle = 3600 -sqlalchemy.convert_unicode = true +sqlalchemy.db1.convert_unicode = true ################################ ### LOGGING CONFIGURATION #### ################################ [loggers] -keys = root, routes, rhodecode, sqlalchemy, beaker, templates +keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer [handlers] keys = console, console_sql @@ -259,6 +265,12 @@ qualname = sqlalchemy.engine propagate = 0 +[logger_whoosh_indexer] +level = DEBUG +handlers = +qualname = whoosh_indexer +propagate = 1 + ############## ## HANDLERS ## ############## diff -r 8fd6650bb436 -r dc2584ba5fbc docs/api/api.rst --- a/docs/api/api.rst Sat Mar 03 03:41:19 2012 +0200 +++ b/docs/api/api.rst Wed Mar 28 19:54:16 2012 +0200 @@ -27,7 +27,7 @@ All clients are required to send JSON-RPC spec JSON data:: { - "id:, + "id:"", "api_key":"", "method":"", "args":{"":""} @@ -50,9 +50,9 @@ RhodeCode API will return always a JSON-RPC response:: { - "id":, - "result": "", - "error": null + "id":, # matching id sent by request + "result": ""|null, # JSON formatted result, null if any errors + "error": "null"| # JSON formatted error (if any) } All responses from API will be `HTTP/1.0 200 OK`, if there's an error while @@ -72,6 +72,7 @@ INPUT:: + id : api_key : "" method : "pull" args : { @@ -94,6 +95,7 @@ INPUT:: + id : api_key : "" method : "get_user" args : { @@ -111,7 +113,15 @@ "email" : "", "active" : "", "admin" :  "", - "ldap_dn" : "" + "ldap_dn" : "", + "last_login": "", + "permissions": { + "global": ["hg.create.repository", + "repository.read", + "hg.register.manual_activate"], + "repositories": {"repo1": "repository.none"}, + "repositories_groups": {"Group1": "group.read"} + }, } error: null @@ -126,6 +136,7 @@ INPUT:: + id : api_key : "" method : "get_users" args : { } @@ -141,7 +152,8 @@ "email" : "", "active" : "", "admin" :  "", - "ldap_dn" : "" + "ldap_dn" : "", + "last_login": "", }, … ] @@ -157,6 +169,7 @@ INPUT:: + id : api_key : "" method : "create_user" args : { @@ -188,6 +201,7 @@ INPUT:: + id : api_key : "" method : "update_user" args : { @@ -220,6 +234,7 @@ INPUT:: + id : api_key : "" method : "get_users_group" args : { @@ -258,6 +273,7 @@ INPUT:: + id : api_key : "" method : "get_users_groups" args : { } @@ -296,6 +312,7 @@ INPUT:: + id : api_key : "" method : "create_users_group" args: { @@ -322,6 +339,7 @@ INPUT:: + id : api_key : "" method : "add_user_users_group" args: { @@ -350,6 +368,7 @@ INPUT:: + id : api_key : "" method : "remove_user_from_users_group" args: { @@ -370,12 +389,14 @@ get_repo -------- -Gets an existing repository by it's name or repository_id. This command can +Gets an existing repository by it's name or repository_id. Members will return +either users_group or user associated to that repository. This command can be executed only using api_key belonging to user with admin rights. INPUT:: + id : api_key : "" method : "get_repo" args: { @@ -391,7 +412,9 @@ "type" : "", "description" : "", "members" : [ - { "id" : "", + { + "type": "user", + "id" : "", "username" : "", "firstname": "", "lastname" : "", @@ -402,7 +425,8 @@ "permission" : "repository.(read|write|admin)" }, … - { + { + "type": "users_group", "id" : "", "name" : "", "active": "", @@ -423,6 +447,7 @@ INPUT:: + id : api_key : "" method : "get_repos" args: { } @@ -452,6 +477,7 @@ INPUT:: + id : api_key : "" method : "get_repo_nodes" args: { @@ -485,6 +511,7 @@ INPUT:: + id : api_key : "" method : "create_repo" args: { @@ -514,6 +541,7 @@ INPUT:: + id : api_key : "" method : "delete_repo" args: { @@ -538,6 +566,7 @@ INPUT:: + id : api_key : "" method : "grant_user_permission" args: { @@ -563,6 +592,7 @@ INPUT:: + id : api_key : "" method : "revoke_user_permission" args: { @@ -588,6 +618,7 @@ INPUT:: + id : api_key : "" method : "grant_users_group_permission" args: { @@ -612,6 +643,7 @@ INPUT:: + id : api_key : "" method : "revoke_users_group_permission" args: { diff -r 8fd6650bb436 -r dc2584ba5fbc docs/changelog.rst --- a/docs/changelog.rst Sat Mar 03 03:41:19 2012 +0200 +++ b/docs/changelog.rst Wed Mar 28 19:54:16 2012 +0200 @@ -5,7 +5,45 @@ ========= +1.3.4 (**2012-03-28**) +---------------------- +news +++++ + +- Whoosh logging is now controlled by the .ini files logging setup +- added clone-url into edit form on /settings page +- added help text into repo add/edit forms +- created rcextensions module with additional mappings (ref #322) and + post push/pull/create repo hooks callbacks +- implemented #377 Users view for his own permissions on account page +- #399 added inheritance of permissions for users group on repos groups +- #401 repository group is automatically pre-selected when adding repos + inside a repository group +- added alternative HTTP 403 response when client failed to authenticate. Helps + solving issues with Mercurial and LDAP +- #402 removed group prefix from repository name when listing repositories + inside a group +- added gravatars into permission view and permissions autocomplete +- #347 when running multiple RhodeCode instances, properly invalidates cache + for all registered servers + +fixes ++++++ + +- fixed #390 cache invalidation problems on repos inside group +- fixed #385 clone by ID url was loosing proxy prefix in URL +- fixed some unicode problems with waitress +- fixed issue with escaping < and > in changeset commits +- fixed error occurring during recursive group creation in API + create_repo function +- fixed #393 py2.5 fixes for routes url generator +- fixed #397 Private repository groups shows up before login +- fixed #396 fixed problems with revoking users in nested groups +- fixed mysql unicode issues + specified InnoDB as default engine with + utf8 charset +- #406 trim long branch/tag names in changelog to not break UI + 1.3.3 (**2012-03-02**) ---------------------- diff -r 8fd6650bb436 -r dc2584ba5fbc docs/index.rst --- a/docs/index.rst Sat Mar 03 03:41:19 2012 +0200 +++ b/docs/index.rst Wed Mar 28 19:54:16 2012 +0200 @@ -23,7 +23,8 @@ usage/git_support usage/statistics usage/backup - + usage/debugging + **Develop** .. toctree:: diff -r 8fd6650bb436 -r dc2584ba5fbc docs/setup.rst --- a/docs/setup.rst Sat Mar 03 03:41:19 2012 +0200 +++ b/docs/setup.rst Wed Mar 28 19:54:16 2012 +0200 @@ -20,9 +20,10 @@ Next, you need to create the databases used by RhodeCode. I recommend that you -use sqlite (default) or postgresql. If you choose a database other than the +use postgresql or sqlite (default). If you choose a database other than the default ensure you properly adjust the db url in your production.ini -configuration file to use this other database. Create the databases by running +configuration file to use this other database. RhodeCode currently supports +postgresql, sqlite and mysql databases. Create the database by running the following command:: paster setup-app production.ini @@ -57,15 +58,18 @@ - In the admin panel you can toggle ldap, anonymous, permissions settings. As well as edit more advanced options on users and repositories -Try copying your own mercurial repository into the "root" directory you are -using, then from within the RhodeCode web application choose Admin > -repositories. Then choose Add New Repository. Add the repository you copied -into the root. Test that you can browse your repository from within RhodeCode -and then try cloning your repository from RhodeCode with:: +Optionally users can create `rcextensions` package that extends RhodeCode +functionality. To do this simply execute:: + + paster make-rcext production.ini - hg clone http://127.0.0.1:5000/ +This will create `rcextensions` package in the same place that your `ini` file +lives. With `rcextensions` it's possible to add additional mapping for whoosh, +stats and add additional code into the push/pull/create repo hooks. For example +for sending signals to build-bots such as jenkins. +Please see the `__init__.py` file inside `rcextensions` package +for more details. -where *repository name* is replaced by the name of your repository. Using RhodeCode with SSH ------------------------ diff -r 8fd6650bb436 -r dc2584ba5fbc docs/upgrade.rst --- a/docs/upgrade.rst Sat Mar 03 03:41:19 2012 +0200 +++ b/docs/upgrade.rst Wed Mar 28 19:54:16 2012 +0200 @@ -47,6 +47,9 @@ and will always recheck the settings of the application, if there are no new options that need to be set. +.. note:: + If you're using Celery, make sure you restart all instances of it after + upgrade. .. _virtualenv: http://pypi.python.org/pypi/virtualenv .. _python: http://www.python.org/ diff -r 8fd6650bb436 -r dc2584ba5fbc docs/usage/debugging.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/usage/debugging.rst Wed Mar 28 19:54:16 2012 +0200 @@ -0,0 +1,30 @@ +.. _debugging: + +=================== +Debugging RhodeCode +=================== + +If you encountered problems with RhodeCode here are some instructions how to +possibly debug them. + +** First make sure you're using the latest version available.** + +enable detailed debug +--------------------- + +RhodeCode uses standard python logging modules to log it's output. +By default only loggers with INFO level are displayed. To enable full output +change `level = DEBUG` for all logging handlers in currently used .ini file. +This change will allow to see much more detailed output in the logfile or +console. This generally helps a lot to track issues. + + +enable interactive debug mode +----------------------------- + +To enable interactive debug mode simply comment out `set debug = false` in +.ini file, this will trigger and interactive debugger each time there an +error in browser, or send a http link if error occured in the backend. This +is a great tool for fast debugging as you get a handy python console right +in the web view. ** NEVER ENABLE THIS ON PRODUCTION ** the interactive console +can be a serious security threat to you system. diff -r 8fd6650bb436 -r dc2584ba5fbc docs/usage/general.rst --- a/docs/usage/general.rst Sat Mar 03 03:41:19 2012 +0200 +++ b/docs/usage/general.rst Wed Mar 28 19:54:16 2012 +0200 @@ -71,6 +71,11 @@ on errors the mails will have a detailed traceback of error. +Mails are also sent for code comments. If someone comments on a changeset +mail is sent to all participants, the person who commited the changeset +(if present in RhodeCode), and to all people mentioned with @mention system. + + Trending source files --------------------- diff -r 8fd6650bb436 -r dc2584ba5fbc init.d/rhodecode-daemon4 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/init.d/rhodecode-daemon4 Wed Mar 28 19:54:16 2012 +0200 @@ -0,0 +1,70 @@ +#!/bin/bash +########################################### +#### THIS IS AN ARCH LINUX RC.D SCRIPT #### +########################################### + +. /etc/rc.conf +. /etc/rc.d/functions + +DAEMON=rhodecode +APP_HOMEDIR="/srv" +APP_PATH="$APP_HOMEDIR/$DAEMON" +CONF_NAME="production.ini" +LOG_FILE="/var/log/$DAEMON.log" +PID_FILE="/run/daemons/$DAEMON" +APPL=/usr/bin/paster +RUN_AS="*****" + +ARGS="serve --daemon \ +--user=$RUN_AS \ +--group=$RUN_AS \ +--pid-file=$PID_FILE \ +--log-file=$LOG_FILE \ +$APP_PATH/$CONF_NAME" + +[ -r /etc/conf.d/$DAEMON ] && . /etc/conf.d/$DAEMON + +if [[ -r $PID_FILE ]]; then + read -r PID < "$PID_FILE" + if [[ $PID && ! -d /proc/$PID ]]; then + unset PID + rm_daemon $DAEMON + fi +fi + +case "$1" in +start) + stat_busy "Starting $DAEMON" + export HOME=$APP_PATH + [ -z "$PID" ] && $APPL $ARGS &>/dev/null + if [ $? = 0 ]; then + add_daemon $DAEMON + stat_done + else + stat_fail + exit 1 + fi + ;; +stop) + stat_busy "Stopping $DAEMON" + [ -n "$PID" ] && kill $PID &>/dev/null + if [ $? = 0 ]; then + rm_daemon $DAEMON + stat_done + else + stat_fail + exit 1 + fi + ;; +restart) + $0 stop + sleep 1 + $0 start + ;; +status) + stat_busy "Checking $name status"; + ck_status $name + ;; +*) + echo "usage: $0 {start|stop|restart|status}" +esac \ No newline at end of file diff -r 8fd6650bb436 -r dc2584ba5fbc production.ini --- a/production.ini Sat Mar 03 03:41:19 2012 +0200 +++ b/production.ini Wed Mar 28 19:54:16 2012 +0200 @@ -93,6 +93,11 @@ ## all running rhodecode instances. Leave empty if you don't use it instance_id = +## alternative return HTTP header for failed authentication. Default HTTP +## response is 401 HTTPUnauthorized. Currently HG clients have troubles with +## handling that. Set this variable to 403 to return HTTPForbidden +auth_ret_code = + #################################### ### CELERY CONFIG #### #################################### @@ -208,13 +213,13 @@ sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode sqlalchemy.db1.echo = false sqlalchemy.db1.pool_recycle = 3600 -sqlalchemy.convert_unicode = true +sqlalchemy.db1.convert_unicode = true ################################ ### LOGGING CONFIGURATION #### ################################ [loggers] -keys = root, routes, rhodecode, sqlalchemy, beaker, templates +keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer [handlers] keys = console, console_sql @@ -260,6 +265,12 @@ qualname = sqlalchemy.engine propagate = 0 +[logger_whoosh_indexer] +level = DEBUG +handlers = +qualname = whoosh_indexer +propagate = 1 + ############## ## HANDLERS ## ############## diff -r 8fd6650bb436 -r dc2584ba5fbc requires.txt --- a/requires.txt Sat Mar 03 03:41:19 2012 +0200 +++ b/requires.txt Wed Mar 28 19:54:16 2012 +0200 @@ -1,17 +1,17 @@ Pylons==1.0.0 Beaker==1.6.3 -WebHelpers>=1.2 +WebHelpers==1.3 formencode==1.2.4 -SQLAlchemy==0.7.4 -Mako==0.5.0 +SQLAlchemy==0.7.6 +Mako==0.6.2 pygments>=1.4 whoosh>=2.3.0,<2.4 celery>=2.2.5,<2.3 babel python-dateutil>=1.5.0,<2.0.0 -dulwich>=0.8.0,<0.9.0 +https://github.com/jelmer/dulwich/tarball/master webob==1.0.8 markdown==2.1.1 docutils==0.8.1 py-bcrypt -mercurial>=2.1,<2.2 \ No newline at end of file +mercurial>=2.1,<2.2 diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/__init__.py --- a/rhodecode/__init__.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/__init__.py Wed Mar 28 19:54:16 2012 +0200 @@ -4,7 +4,7 @@ ~~~~~~~~~~~~~~~~~~ RhodeCode, a web based repository management based on pylons - versioning implementation: http://semver.org/ + versioning implementation: http://www.python.org/dev/peps/pep-0386/ :created_on: Apr 9, 2010 :author: marcink @@ -26,8 +26,18 @@ import sys import platform -VERSION = (1, 3, 3) -__version__ = '.'.join((str(each) for each in VERSION[:4])) +VERSION = (1, 3, 4) + +try: + from rhodecode.lib import get_current_revision + _rev = get_current_revision() + if _rev: + VERSION += ('dev%s' % _rev[0],) +except ImportError: + pass + +__version__ = ('.'.join((str(each) for each in VERSION[:3])) + + '.'.join(VERSION[3:])) __dbversion__ = 5 # defines current db version for migrations __platform__ = platform.system() __license__ = 'GPLv3' @@ -39,16 +49,16 @@ requirements = [ "Pylons==1.0.0", "Beaker==1.6.3", - "WebHelpers>=1.2", + "WebHelpers==1.3", "formencode==1.2.4", - "SQLAlchemy==0.7.4", - "Mako==0.5.0", + "SQLAlchemy==0.7.6", + "Mako==0.6.2", "pygments>=1.4", "whoosh>=2.3.0,<2.4", "celery>=2.2.5,<2.3", "babel", "python-dateutil>=1.5.0,<2.0.0", - "dulwich>=0.8.0,<0.9.0", + "dulwich>=0.8.4,<0.9.0", "webob==1.0.8", "markdown==2.1.1", "docutils==0.8.1", @@ -65,17 +75,6 @@ requirements.append("mercurial>=2.1,<2.2") -try: - from rhodecode.lib import get_current_revision - _rev = get_current_revision(quiet=True) -except ImportError: - # this is needed when doing some setup.py operations - _rev = False - -if len(VERSION) > 3 and _rev: - __version__ += ' [rev:%s]' % _rev[0] - - def get_version(): """Returns shorter version (digit parts only) as string.""" @@ -90,3 +89,6 @@ # link to config for pylons CONFIG = {} + +# Linked module for extensions +EXTENSIONS = {} diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/config/conf.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/config/conf.py Wed Mar 28 19:54:16 2012 +0200 @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +""" + package.rhodecode.config.conf + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Various config settings for RhodeCode + + :created_on: Mar 7, 2012 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :license: , see LICENSE_FILE for more details. +""" +from rhodecode import EXTENSIONS + +from rhodecode.lib.utils2 import __get_lem + + +# language map is also used by whoosh indexer, which for those specified +# extensions will index it's content +LANGUAGES_EXTENSIONS_MAP = __get_lem() + +#============================================================================== +# WHOOSH INDEX EXTENSIONS +#============================================================================== +# EXTENSIONS WE WANT TO INDEX CONTENT OFF USING WHOOSH +INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys() + +# list of readme files to search in file tree and display in summary +# attached weights defines the search order lower is first +ALL_READMES = [ + ('readme', 0), ('README', 0), ('Readme', 0), + ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1), + ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2), + ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2), + ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2), +] + +# extension together with weights to search lower is first +RST_EXTS = [ + ('', 0), ('.rst', 1), ('.rest', 1), + ('.RST', 2), ('.REST', 2), + ('.txt', 3), ('.TXT', 3) +] + +MARKDOWN_EXTS = [ + ('.md', 1), ('.MD', 1), + ('.mkdn', 2), ('.MKDN', 2), + ('.mdown', 3), ('.MDOWN', 3), + ('.markdown', 4), ('.MARKDOWN', 4) +] + +PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)] + +ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS + +DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" + +DATE_FORMAT = "%Y-%m-%d" diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/config/deployment.ini_tmpl --- a/rhodecode/config/deployment.ini_tmpl Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/config/deployment.ini_tmpl Wed Mar 28 19:54:16 2012 +0200 @@ -93,6 +93,11 @@ ## all running rhodecode instances. Leave empty if you don't use it instance_id = +## alternative return HTTP header for failed authentication. Default HTTP +## response is 401 HTTPUnauthorized. Currently HG clients have troubles with +## handling that. Set this variable to 403 to return HTTPForbidden +auth_ret_code = + #################################### ### CELERY CONFIG #### #################################### @@ -218,13 +223,13 @@ sqlalchemy.db1.echo = false sqlalchemy.db1.pool_recycle = 3600 -sqlalchemy.convert_unicode = true +sqlalchemy.db1.convert_unicode = true ################################ ### LOGGING CONFIGURATION #### ################################ [loggers] -keys = root, routes, rhodecode, sqlalchemy, beaker, templates +keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer [handlers] keys = console, console_sql @@ -270,6 +275,12 @@ qualname = sqlalchemy.engine propagate = 0 +[logger_whoosh_indexer] +level = DEBUG +handlers = +qualname = whoosh_indexer +propagate = 1 + ############## ## HANDLERS ## ############## diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/config/environment.py --- a/rhodecode/config/environment.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/config/environment.py Wed Mar 28 19:54:16 2012 +0200 @@ -2,21 +2,24 @@ import os import logging +import rhodecode from mako.lookup import TemplateLookup from pylons.configuration import PylonsConfig from pylons.error import handle_mako_error -import rhodecode +# don't remove this import it does magic for celery +from rhodecode.lib import celerypylons + import rhodecode.lib.app_globals as app_globals -import rhodecode.lib.helpers from rhodecode.config.routing import make_map -# don't remove this import it does magic for celery -from rhodecode.lib import celerypylons, str2bool -from rhodecode.lib import engine_from_config + +from rhodecode.lib import helpers from rhodecode.lib.auth import set_available_permissions -from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config +from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config,\ + load_rcextensions +from rhodecode.lib.utils2 import engine_from_config, str2bool from rhodecode.model import init_model from rhodecode.model.scm import ScmModel @@ -24,17 +27,20 @@ def load_environment(global_conf, app_conf, initial=False): - """Configure the Pylons environment via the ``pylons.config`` + """ + Configure the Pylons environment via the ``pylons.config`` object """ config = PylonsConfig() # Pylons paths root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - paths = dict(root=root, - controllers=os.path.join(root, 'controllers'), - static_files=os.path.join(root, 'public'), - templates=[os.path.join(root, 'templates')]) + paths = dict( + root=root, + controllers=os.path.join(root, 'controllers'), + static_files=os.path.join(root, 'public'), + templates=[os.path.join(root, 'templates')] + ) # Initialize config with the basic options config.init_app(global_conf, app_conf, package='rhodecode', paths=paths) @@ -44,8 +50,11 @@ config['routes.map'] = make_map(config) config['pylons.app_globals'] = app_globals.Globals(config) - config['pylons.h'] = rhodecode.lib.helpers + config['pylons.h'] = helpers rhodecode.CONFIG = config + + load_rcextensions(root_path=config['here']) + # Setup cache object as early as possible import pylons pylons.cache._push_object(config['pylons.app_globals'].cache) diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/config/rcextensions/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/config/rcextensions/__init__.py Wed Mar 28 19:54:16 2012 +0200 @@ -0,0 +1,84 @@ +# Additional mappings that are not present in the pygments lexers +# used for building stats +# format is {'ext':'Name'} eg. {'py':'Python'} +# NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP +# build by pygments +EXTRA_MAPPINGS = {} + +#============================================================================== +# WHOOSH INDEX EXTENSIONS +#============================================================================== +# if INDEX_EXTENSIONS is [] it'll use pygments lexers extensions by default. +# To set your own just add to this list extensions to index with content +INDEX_EXTENSIONS = [] + +# additional extensions for indexing besides the default from pygments +# those get's added to INDEX_EXTENSIONS +EXTRA_INDEX_EXTENSIONS = [] + + +#============================================================================== +# POST CREATE REPOSITORY HOOK +#============================================================================== +# this function will be executed after each repository is created +def _crhook(*args, **kwargs): + """ + Post create repository HOOK + kwargs available: + :param repo_name: + :param repo_type: + :param description: + :param private: + :param created_on: + :param enable_downloads: + :param repo_id: + :param user_id: + :param enable_statistics: + :param clone_uri: + :param fork_id: + :param group_id: + :param created_by: + """ + return 0 +CREATE_REPO_HOOK = _crhook + + +#============================================================================== +# POST PUSH HOOK +#============================================================================== + +# this function will be executed after each push it's runned after the build-in +# hook that rhodecode uses for logging pushes +def _pushhook(*args, **kwargs): + """ + Post push hook + kwargs available: + + :param username: name of user who pushed + :param ip: ip of who pushed + :param action: pull + :param repository: repository name + :param pushed_revs: generator of pushed revisions + """ + return 0 +PUSH_HOOK = _pushhook + + +#============================================================================== +# POST PULL HOOK +#============================================================================== + +# this function will be executed after each push it's runned after the build-in +# hook that rhodecode uses for logging pushes +def _pullhook(*args, **kwargs): + """ + Post pull hook + kwargs available:: + + :param username: name of user who pulled + :param ip: ip of who pushed + :param action: pull + :param repository: repository name + """ + return 0 +PULL_HOOK = _pullhook diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/config/rcextensions/make_rcextensions.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/config/rcextensions/make_rcextensions.py Wed Mar 28 19:54:16 2012 +0200 @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.config.rcextensions.make_rcextensions + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Whoosh indexing module for RhodeCode + + :created_on: Mar 6, 2012 + :author: marcink + :copyright: (C) 2010-2012 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 os +import sys +import pkg_resources +import traceback +import logging +from os.path import dirname as dn, join as jn + +#to get the rhodecode import +sys.path.append(dn(dn(dn(os.path.realpath(__file__))))) + +from rhodecode.lib.utils import BasePasterCommand, Command, ask_ok + +log = logging.getLogger(__name__) + + +class MakeRcExt(BasePasterCommand): + + max_args = 1 + min_args = 1 + + usage = "CONFIG_FILE" + summary = "Creates additional extensions for rhodecode" + group_name = "RhodeCode" + takes_config_file = -1 + parser = Command.standard_parser(verbose=True) + + def command(self): + logging.config.fileConfig(self.path_to_ini_file) + from pylons import config + + def _make_file(ext_file): + bdir = os.path.split(ext_file)[0] + if not os.path.isdir(bdir): + os.makedirs(bdir) + with open(ext_file, 'wb') as f: + f.write(tmpl) + log.info('Writen new extensions file to %s' % ext_file) + + here = config['here'] + tmpl = pkg_resources.resource_string( + 'rhodecode', jn('config', 'rcextensions', '__init__.py') + ) + ext_file = jn(here, 'rcextensions', '__init__.py') + if os.path.exists(ext_file): + msg = ('Extension file already exists, do you want ' + 'to overwrite it ? [y/n]') + if ask_ok(msg): + _make_file(ext_file) + else: + log.info('nothing done...') + else: + _make_file(ext_file) + + def update_parser(self): + pass diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/controllers/admin/repos.py --- a/rhodecode/controllers/admin/repos.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/controllers/admin/repos.py Wed Mar 28 19:54:16 2012 +0200 @@ -284,7 +284,6 @@ :param repo_name: """ - try: RepoModel().revoke_user_permission(repo=repo_name, user=request.POST['user_id']) diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/controllers/admin/users.py --- a/rhodecode/controllers/admin/users.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/controllers/admin/users.py Wed Mar 28 19:54:16 2012 +0200 @@ -145,11 +145,12 @@ user_model = UserModel() try: user_model.delete(id) + Session.commit() h.flash(_('successfully deleted user'), category='success') - Session.commit() except (UserOwnsReposException, DefaultUserException), e: - h.flash(str(e), category='warning') + h.flash(e, category='warning') except Exception: + log.error(traceback.format_exc()) h.flash(_('An error occurred during deletion of user'), category='error') return redirect(url('users')) diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/controllers/admin/users_groups.py --- a/rhodecode/controllers/admin/users_groups.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/controllers/admin/users_groups.py Wed Mar 28 19:54:16 2012 +0200 @@ -32,8 +32,9 @@ from pylons.controllers.util import abort, redirect from pylons.i18n.translation import _ +from rhodecode.lib import helpers as h from rhodecode.lib.exceptions import UsersGroupsAssignedException -from rhodecode.lib import helpers as h, safe_unicode +from rhodecode.lib.utils2 import safe_unicode from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator from rhodecode.lib.base import BaseController, render diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/controllers/api/__init__.py --- a/rhodecode/controllers/api/__init__.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/controllers/api/__init__.py Wed Mar 28 19:54:16 2012 +0200 @@ -233,10 +233,10 @@ try: return json.dumps(response) except TypeError, e: - log.debug('Error encoding response: %s' % e) + log.error('API FAILED. Error encoding response: %s' % e) return json.dumps( dict( - self._req_id, + id=self._req_id, result=None, error="Error encoding response" ) diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/controllers/api/api.py --- a/rhodecode/controllers/api/api.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/controllers/api/api.py Wed Mar 28 19:54:16 2012 +0200 @@ -30,16 +30,15 @@ from rhodecode.controllers.api import JSONRPCController, JSONRPCError from rhodecode.lib.auth import HasPermissionAllDecorator, \ - HasPermissionAnyDecorator, PasswordGenerator + HasPermissionAnyDecorator, PasswordGenerator, AuthUser from rhodecode.model.meta import Session from rhodecode.model.scm import ScmModel -from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository +from rhodecode.model.db import User, UsersGroup, Repository from rhodecode.model.repo import RepoModel from rhodecode.model.user import UserModel from rhodecode.model.users_group import UsersGroupModel -from rhodecode.model.repos_group import ReposGroupModel - +from rhodecode.lib.utils import map_groups log = logging.getLogger(__name__) @@ -100,7 +99,9 @@ email=user.email, active=user.active, admin=user.admin, - ldap_dn=user.ldap_dn + ldap_dn=user.ldap_dn, + last_login=user.last_login, + permissions=AuthUser(user_id=user.user_id).permissions ) @HasPermissionAllDecorator('hg.admin') @@ -122,7 +123,8 @@ email=user.email, active=user.active, admin=user.admin, - ldap_dn=user.ldap_dn + ldap_dn=user.ldap_dn, + last_login=user.last_login, ) ) return result @@ -282,7 +284,7 @@ @HasPermissionAllDecorator('hg.admin') def add_user_to_users_group(self, apiuser, group_name, username): """" - Add a user to a group + Add a user to a users group :param apiuser: :param group_name: @@ -360,7 +362,7 @@ user = user.user members.append( dict( - type_="user", + type="user", id=user.user_id, username=user.username, firstname=user.name, @@ -377,7 +379,7 @@ users_group = users_group.users_group members.append( dict( - type_="users_group", + type="users_group", id=users_group.users_group_id, name=users_group.users_group_name, active=users_group.users_group_active, @@ -464,15 +466,10 @@ if Repository.get_by_repo_name(repo_name): raise JSONRPCError("repo %s already exist" % repo_name) - groups = repo_name.split('/') + groups = repo_name.split(Repository.url_sep()) real_name = groups[-1] - groups = groups[:-1] - parent_id = None - for g in groups: - group = RepoGroup.get_by_group_name(g) - if not group: - group = ReposGroupModel().create(g, '', parent_id) - parent_id = group.group_id + # create structure of groups + group = map_groups(repo_name) repo = RepoModel().create( dict( @@ -481,7 +478,7 @@ description=description, private=private, repo_type=repo_type, - repo_group=parent_id, + repo_group=group.group_id if group else None, clone_uri=clone_uri ), owner diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/controllers/branches.py --- a/rhodecode/controllers/branches.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/controllers/branches.py Wed Mar 28 19:54:16 2012 +0200 @@ -31,7 +31,7 @@ from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseRepoController, render from rhodecode.lib.compat import OrderedDict -from rhodecode.lib import safe_unicode +from rhodecode.lib.utils2 import safe_unicode log = logging.getLogger(__name__) diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/controllers/changeset.py --- a/rhodecode/controllers/changeset.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/controllers/changeset.py Wed Mar 28 19:54:16 2012 +0200 @@ -51,13 +51,18 @@ log = logging.getLogger(__name__) -def anchor_url(revision, path): +def _update_with_GET(params, GET): + for k in ['diff1', 'diff2', 'diff']: + params[k] += GET.getall(k) + + +def anchor_url(revision, path, GET): fid = h.FID(revision, path) - return h.url.current(anchor=fid, **dict(request.GET)) + return h.url.current(anchor=fid, **dict(GET)) def get_ignore_ws(fid, GET): - ig_ws_global = request.GET.get('ignorews') + ig_ws_global = GET.get('ignorews') ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid)) if ig_ws: try: @@ -67,12 +72,13 @@ return ig_ws_global -def _ignorews_url(fileid=None): - +def _ignorews_url(GET, fileid=None): + fileid = str(fileid) if fileid else None params = defaultdict(list) + _update_with_GET(params, GET) lbl = _('show white space') - ig_ws = get_ignore_ws(fileid, request.GET) - ln_ctx = get_line_ctx(fileid, request.GET) + ig_ws = get_ignore_ws(fileid, GET) + ln_ctx = get_line_ctx(fileid, GET) # global option if fileid is None: if ig_ws is None: @@ -98,7 +104,7 @@ def get_line_ctx(fid, GET): - ln_ctx_global = request.GET.get('context') + ln_ctx_global = GET.get('context') ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid)) if ln_ctx: @@ -112,16 +118,19 @@ return -def _context_url(fileid=None): +def _context_url(GET, fileid=None): """ Generates url for context lines :param fileid: """ - ig_ws = get_ignore_ws(fileid, request.GET) - ln_ctx = (get_line_ctx(fileid, request.GET) or 3) * 2 + + fileid = str(fileid) if fileid else None + ig_ws = get_ignore_ws(fileid, GET) + ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2 params = defaultdict(list) + _update_with_GET(params, GET) # global option if fileid is None: @@ -162,7 +171,7 @@ c.anchor_url = anchor_url c.ignorews_url = _ignorews_url c.context_url = _context_url - + limit_off = request.GET.get('fulldiff') #get ranges of revisions if preset rev_range = revision.split('...')[:2] enable_comments = True @@ -220,7 +229,7 @@ ign_whitespace_lcl = get_ignore_ws(fid, request.GET) lim = self.cut_off_limit if cumulative_diff > self.cut_off_limit: - lim = -1 + lim = -1 if limit_off is None else None size, cs1, cs2, diff, st = wrapped_diff( filenode_old=None, filenode_new=node, @@ -251,7 +260,7 @@ ign_whitespace_lcl = get_ignore_ws(fid, request.GET) lim = self.cut_off_limit if cumulative_diff > self.cut_off_limit: - lim = -1 + lim = -1 if limit_off is None else None size, cs1, cs2, diff, st = wrapped_diff( filenode_old=filenode_old, filenode_new=node, diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/controllers/feed.py --- a/rhodecode/controllers/feed.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/controllers/feed.py Wed Mar 28 19:54:16 2012 +0200 @@ -28,7 +28,7 @@ from pylons import url, response, tmpl_context as c from pylons.i18n.translation import _ -from rhodecode.lib import safe_unicode +from rhodecode.lib.utils2 import safe_unicode from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseRepoController diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/controllers/files.py --- a/rhodecode/controllers/files.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/controllers/files.py Wed Mar 28 19:54:16 2012 +0200 @@ -32,24 +32,26 @@ from pylons.controllers.util import redirect from pylons.decorators import jsonify -from rhodecode.lib.vcs.conf import settings -from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \ - EmptyRepositoryError, ImproperArchiveTypeError, VCSError, \ - NodeAlreadyExistsError -from rhodecode.lib.vcs.nodes import FileNode +from rhodecode.lib import diffs +from rhodecode.lib import helpers as h from rhodecode.lib.compat import OrderedDict -from rhodecode.lib import convert_line_endings, detect_mode, safe_str +from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseRepoController, render from rhodecode.lib.utils import EmptyChangeset -from rhodecode.lib import diffs -import rhodecode.lib.helpers as h +from rhodecode.lib.vcs.conf import settings +from rhodecode.lib.vcs.exceptions import RepositoryError, \ + ChangesetDoesNotExistError, EmptyRepositoryError, \ + ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError +from rhodecode.lib.vcs.nodes import FileNode + from rhodecode.model.repo import RepoModel +from rhodecode.model.scm import ScmModel + from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\ _context_url, get_line_ctx, get_ignore_ws -from rhodecode.lib.diffs import wrapped_diff -from rhodecode.model.scm import ScmModel + log = logging.getLogger(__name__) @@ -447,7 +449,7 @@ ign_whitespace_lcl = get_ignore_ws(fid, request.GET) lim = request.GET.get('fulldiff') or self.cut_off_limit - _, cs1, cs2, diff, st = wrapped_diff(filenode_old=node1, + _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1, filenode_new=node2, cut_off_limit=lim, ignore_whitespace=ign_whitespace_lcl, diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/controllers/home.py --- a/rhodecode/controllers/home.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/controllers/home.py Wed Mar 28 19:54:16 2012 +0200 @@ -44,7 +44,7 @@ def index(self): c.repos_list = self.scm_model.get_repos() c.groups = self.scm_model.get_repos_groups() - + c.group = None return render('/index.html') def repo_switcher(self): diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/controllers/summary.py --- a/rhodecode/controllers/summary.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/controllers/summary.py Wed Mar 28 19:54:16 2012 +0200 @@ -26,6 +26,7 @@ import traceback import calendar import logging +import urllib from time import mktime from datetime import timedelta, date from urlparse import urlparse @@ -39,15 +40,15 @@ from beaker.cache import cache_region, region_invalidate +from rhodecode.config.conf import ALL_READMES, ALL_EXTS, LANGUAGES_EXTENSIONS_MAP from rhodecode.model.db import Statistics, CacheInvalidation -from rhodecode.lib import ALL_READMES, ALL_EXTS +from rhodecode.lib.utils2 import safe_unicode from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseRepoController, render from rhodecode.lib.utils import EmptyChangeset from rhodecode.lib.markup_renderer import MarkupRenderer from rhodecode.lib.celerylib import run_task -from rhodecode.lib.celerylib.tasks import get_commits_stats, \ - LANGUAGES_EXTENSIONS_MAP +from rhodecode.lib.celerylib.tasks import get_commits_stats from rhodecode.lib.helpers import RepoPage from rhodecode.lib.compat import json, OrderedDict @@ -91,34 +92,37 @@ uri_tmpl = config.get('clone_uri', default_clone_uri) uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s') - + decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) uri_dict = { 'user': username, 'pass': password, 'scheme': parsed_url.scheme, 'netloc': parsed_url.netloc, - 'path': parsed_url.path + 'path': decoded_path } + uri = uri_tmpl % uri_dict # generate another clone url by id - uri_dict.update({'path': '/_%s' % c.dbrepo.repo_id}) + uri_dict.update( + {'path': decoded_path.replace(repo_name, '_%s' % c.dbrepo.repo_id)} + ) uri_id = uri_tmpl % uri_dict c.clone_repo_url = uri c.clone_repo_url_id = uri_id c.repo_tags = OrderedDict() - for name, hash in c.rhodecode_repo.tags.items()[:10]: + for name, hash_ in c.rhodecode_repo.tags.items()[:10]: try: - c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash) + c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash_) except ChangesetError: - c.repo_tags[name] = EmptyChangeset(hash) + c.repo_tags[name] = EmptyChangeset(hash_) c.repo_branches = OrderedDict() - for name, hash in c.rhodecode_repo.branches.items()[:10]: + for name, hash_ in c.rhodecode_repo.branches.items()[:10]: try: - c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash) + c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash_) except ChangesetError: - c.repo_branches[name] = EmptyChangeset(hash) + c.repo_branches[name] = EmptyChangeset(hash_) td = date.today() + timedelta(days=1) td_1m = td - timedelta(days=calendar.mdays[td.month]) @@ -175,7 +179,7 @@ if c.enable_downloads: c.download_options = self._get_download_links(c.rhodecode_repo) - c.readme_data, c.readme_file = self.__get_readme_data(c.rhodecode_repo) + c.readme_data, c.readme_file = self.__get_readme_data(c.rhodecode_db_repo) return render('summary/summary.html') def __get_readme_data(self, repo): @@ -206,7 +210,7 @@ return readme_data, readme_file - key = repo.name + '_README' + key = repo.repo_name + '_README' inv = CacheInvalidation.invalidate(key) if inv is not None: region_invalidate(_get_readme_from_cache, None, key) diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/__init__.py --- a/rhodecode/lib/__init__.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/__init__.py Wed Mar 28 19:54:16 2012 +0200 @@ -1,432 +1,4 @@ -# -*- coding: utf-8 -*- -""" - rhodecode.lib.__init__ - ~~~~~~~~~~~~~~~~~~~~~~~ - - Some simple helper functions - - :created_on: Jan 5, 2011 - :author: marcink - :copyright: (C) 2011-2012 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 os -import re -from rhodecode.lib.vcs.utils.lazy import LazyProperty - - -def __get_lem(): - from pygments import lexers - from string import lower - from collections import defaultdict - - d = defaultdict(lambda: []) - - def __clean(s): - s = s.lstrip('*') - s = s.lstrip('.') - - if s.find('[') != -1: - exts = [] - start, stop = s.find('['), s.find(']') - - for suffix in s[start + 1:stop]: - exts.append(s[:s.find('[')] + suffix) - return map(lower, exts) - else: - return map(lower, [s]) - - for lx, t in sorted(lexers.LEXERS.items()): - m = map(__clean, t[-2]) - if m: - m = reduce(lambda x, y: x + y, m) - for ext in m: - desc = lx.replace('Lexer', '') - d[ext].append(desc) - - return dict(d) - -# language map is also used by whoosh indexer, which for those specified -# extensions will index it's content -LANGUAGES_EXTENSIONS_MAP = __get_lem() - -# Additional mappings that are not present in the pygments lexers -# NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP -ADDITIONAL_MAPPINGS = {'xaml': 'XAML'} - -LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS) - -# list of readme files to search in file tree and display in summary -# attached weights defines the search order lower is first -ALL_READMES = [ - ('readme', 0), ('README', 0), ('Readme', 0), - ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1), - ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2), - ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2), - ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2), -] - -# extension together with weights to search lower is first -RST_EXTS = [ - ('', 0), ('.rst', 1), ('.rest', 1), - ('.RST', 2), ('.REST', 2), - ('.txt', 3), ('.TXT', 3) -] - -MARKDOWN_EXTS = [ - ('.md', 1), ('.MD', 1), - ('.mkdn', 2), ('.MKDN', 2), - ('.mdown', 3), ('.MDOWN', 3), - ('.markdown', 4), ('.MARKDOWN', 4) -] - -PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)] - -ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS - - -def str2bool(_str): - """ - returs True/False value from given string, it tries to translate the - string into boolean - - :param _str: string value to translate into boolean - :rtype: boolean - :returns: boolean from given string - """ - if _str is None: - return False - if _str in (True, False): - return _str - _str = str(_str).strip().lower() - return _str in ('t', 'true', 'y', 'yes', 'on', '1') - - -def convert_line_endings(line, mode): - """ - Converts a given line "line end" accordingly to given mode - - Available modes are:: - 0 - Unix - 1 - Mac - 2 - DOS - - :param line: given line to convert - :param mode: mode to convert to - :rtype: str - :return: converted line according to mode - """ - from string import replace - - if mode == 0: - line = replace(line, '\r\n', '\n') - line = replace(line, '\r', '\n') - elif mode == 1: - line = replace(line, '\r\n', '\r') - line = replace(line, '\n', '\r') - elif mode == 2: - line = re.sub("\r(?!\n)|(?>>>> STARTING QUERY >>>>>")) - - - def after_cursor_execute(conn, cursor, statement, - parameters, context, executemany): - total = time.time() - context._query_start_time - log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total)) - - event.listen(engine, "before_cursor_execute", - before_cursor_execute) - event.listen(engine, "after_cursor_execute", - after_cursor_execute) - - return engine - - -def age(curdate): - """ - turns a datetime into an age string. - - :param curdate: datetime object - :rtype: unicode - :returns: unicode words describing age - """ - - from datetime import datetime - from webhelpers.date import time_ago_in_words - - _ = lambda s: s - - if not curdate: - return '' - - agescales = [(_(u"year"), 3600 * 24 * 365), - (_(u"month"), 3600 * 24 * 30), - (_(u"day"), 3600 * 24), - (_(u"hour"), 3600), - (_(u"minute"), 60), - (_(u"second"), 1), ] - - age = datetime.now() - curdate - age_seconds = (age.days * agescales[2][1]) + age.seconds - pos = 1 - for scale in agescales: - if scale[1] <= age_seconds: - if pos == 6: - pos = 5 - return '%s %s' % (time_ago_in_words(curdate, - agescales[pos][0]), _('ago')) - pos += 1 - - return _(u'just now') - - -def uri_filter(uri): - """ - Removes user:password from given url string - - :param uri: - :rtype: unicode - :returns: filtered list of strings - """ - if not uri: - return '' - - proto = '' - - for pat in ('https://', 'http://'): - if uri.startswith(pat): - uri = uri[len(pat):] - proto = pat - break - - # remove passwords and username - uri = uri[uri.find('@') + 1:] - - # get the port - cred_pos = uri.find(':') - if cred_pos == -1: - host, port = uri, None - else: - host, port = uri[:cred_pos], uri[cred_pos + 1:] - - return filter(None, [proto, host, port]) - - -def credentials_filter(uri): - """ - Returns a url with removed credentials - - :param uri: - """ - - uri = uri_filter(uri) - #check if we have port - if len(uri) > 2 and uri[2]: - uri[2] = ':' + uri[2] - - return ''.join(uri) - - -def get_changeset_safe(repo, rev): - """ - Safe version of get_changeset if this changeset doesn't exists for a - repo it returns a Dummy one instead - - :param repo: - :param rev: - """ - from rhodecode.lib.vcs.backends.base import BaseRepository - from rhodecode.lib.vcs.exceptions import RepositoryError - if not isinstance(repo, BaseRepository): - raise Exception('You must pass an Repository ' - 'object as first argument got %s', type(repo)) - - try: - cs = repo.get_changeset(rev) - except RepositoryError: - from rhodecode.lib.utils import EmptyChangeset - cs = EmptyChangeset(requested_revision=rev) - return cs def get_current_revision(quiet=False): @@ -450,16 +22,3 @@ print ("Cannot retrieve rhodecode's revision. Original error " "was: %s" % err) return None - - -def extract_mentioned_users(s): - """ - Returns unique usernames from given string s that have @mention - - :param s: string to get mentions - """ - usrs = {} - for username in re.findall(r'(?:^@|\s@)(\w+)', s): - usrs[username] = username - - return sorted(usrs.keys()) diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/auth.py --- a/rhodecode/lib/auth.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/auth.py Wed Mar 28 19:54:16 2012 +0200 @@ -43,7 +43,7 @@ if __platform__ in PLATFORM_OTHERS: import bcrypt -from rhodecode.lib import str2bool, safe_unicode +from rhodecode.lib.utils2 import str2bool, safe_unicode from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug from rhodecode.lib.auth_ldap import AuthLdap @@ -521,8 +521,7 @@ self.user = cls.rhodecode_user self.user_perms = self.user.permissions log.debug('checking %s permissions %s for %s %s', - self.__class__.__name__, self.required_perms, cls, - self.user) + self.__class__.__name__, self.required_perms, cls, self.user) if self.check_permissions(): log.debug('Permission granted for %s %s' % (cls, self.user)) @@ -604,6 +603,7 @@ user_perms = set([self.user_perms['repositories'][repo_name]]) except KeyError: return False + if self.required_perms.intersection(user_perms): return True return False @@ -655,29 +655,37 @@ for perm in perms: if perm not in available_perms: - raise Exception("'%s' permission in not defined" % perm) + raise Exception("'%s' permission is not defined" % perm) self.required_perms = set(perms) self.user_perms = None - self.granted_for = '' self.repo_name = None + self.group_name = None def __call__(self, check_Location=''): user = request.user - log.debug('checking %s %s %s', self.__class__.__name__, - self.required_perms, user) + cls_name = self.__class__.__name__ + check_scope = { + 'HasPermissionAll': '', + 'HasPermissionAny': '', + 'HasRepoPermissionAll': 'repo:%s' % self.repo_name, + 'HasRepoPermissionAny': 'repo:%s' % self.repo_name, + 'HasReposGroupPermissionAll': 'group:%s' % self.group_name, + 'HasReposGroupPermissionAny': 'group:%s' % self.group_name, + }.get(cls_name, '?') + log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name, + self.required_perms, user, check_scope, + check_Location or 'unspecified location') if not user: log.debug('Empty request user') return False self.user_perms = user.permissions - self.granted_for = user - if self.check_permissions(): - log.debug('Permission granted %s @ %s', self.granted_for, + log.debug('Permission granted for user: %s @ %s', user, check_Location or 'unspecified location') return True else: - log.debug('Permission denied for %s @ %s', self.granted_for, + log.debug('Permission denied for user: %s @ %s', user, check_Location or 'unspecified location') return False @@ -701,7 +709,6 @@ class HasRepoPermissionAll(PermsFunction): - def __call__(self, repo_name=None, check_Location=''): self.repo_name = repo_name return super(HasRepoPermissionAll, self).__call__(check_Location) @@ -711,19 +718,17 @@ self.repo_name = get_repo_slug(request) try: - self.user_perms = set( + self._user_perms = set( [self.user_perms['repositories'][self.repo_name]] ) except KeyError: return False - self.granted_for = self.repo_name - if self.required_perms.issubset(self.user_perms): + if self.required_perms.issubset(self._user_perms): return True return False class HasRepoPermissionAny(PermsFunction): - def __call__(self, repo_name=None, check_Location=''): self.repo_name = repo_name return super(HasRepoPermissionAny, self).__call__(check_Location) @@ -733,13 +738,12 @@ self.repo_name = get_repo_slug(request) try: - self.user_perms = set( + self._user_perms = set( [self.user_perms['repositories'][self.repo_name]] ) except KeyError: return False - self.granted_for = self.repo_name - if self.required_perms.intersection(self.user_perms): + if self.required_perms.intersection(self._user_perms): return True return False @@ -751,13 +755,12 @@ def check_permissions(self): try: - self.user_perms = set( + self._user_perms = set( [self.user_perms['repositories_groups'][self.group_name]] ) except KeyError: return False - self.granted_for = self.repo_name - if self.required_perms.intersection(self.user_perms): + if self.required_perms.intersection(self._user_perms): return True return False @@ -769,13 +772,12 @@ def check_permissions(self): try: - self.user_perms = set( + self._user_perms = set( [self.user_perms['repositories_groups'][self.group_name]] ) except KeyError: return False - self.granted_for = self.repo_name - if self.required_perms.issubset(self.user_perms): + if self.required_perms.issubset(self._user_perms): return True return False @@ -788,12 +790,16 @@ self.required_perms = set(perms) def __call__(self, user, repo_name): + # repo_name MUST be unicode, since we handle keys in permission + # dict by unicode + repo_name = safe_unicode(repo_name) usr = AuthUser(user.user_id) try: self.user_perms = set([usr.permissions['repositories'][repo_name]]) - except: + except Exception: + log.error('Exception while accessing permissions %s' % + traceback.format_exc()) self.user_perms = set() - self.granted_for = '' self.username = user.username self.repo_name = repo_name return self.check_permissions() @@ -803,7 +809,13 @@ 'permissions %s for user:%s repository:%s', self.user_perms, self.username, self.repo_name) if self.required_perms.intersection(self.user_perms): - log.debug('permission granted') + log.debug('permission granted for user:%s on repo:%s' % ( + self.username, self.repo_name + ) + ) return True - log.debug('permission denied') + log.debug('permission denied for user:%s on repo:%s' % ( + self.username, self.repo_name + ) + ) return False diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/base.py --- a/rhodecode/lib/base.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/base.py Wed Mar 28 19:54:16 2012 +0200 @@ -7,6 +7,8 @@ import traceback from paste.auth.basic import AuthBasicAuthenticator +from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden +from paste.httpheaders import WWW_AUTHENTICATE from pylons import config, tmpl_context as c, request, session, url from pylons.controllers import WSGIController @@ -15,7 +17,7 @@ from rhodecode import __version__, BACKENDS -from rhodecode.lib import str2bool, safe_unicode +from rhodecode.lib.utils2 import str2bool, safe_unicode from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\ HasPermissionAnyMiddleware, CookieStoreWrapper from rhodecode.lib.utils import get_repo_slug, invalidate_cache @@ -28,6 +30,22 @@ log = logging.getLogger(__name__) +class BasicAuth(AuthBasicAuthenticator): + + def __init__(self, realm, authfunc, auth_http_code=None): + self.realm = realm + self.authfunc = authfunc + self._rc_auth_http_code = auth_http_code + + def build_authentication(self): + head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm) + if self._rc_auth_http_code and self._rc_auth_http_code == '403': + # return 403 if alternative http return code is specified in + # RhodeCode config + return HTTPForbidden(headers=head) + return HTTPUnauthorized(headers=head) + + class BaseVCSController(object): def __init__(self, application, config): @@ -36,7 +54,8 @@ # base path of repo locations self.basepath = self.config['base_path'] #authenticate this mercurial request using authfunc - self.authenticate = AuthBasicAuthenticator('', authfunc) + self.authenticate = BasicAuth('', authfunc, + config.get('auth_ret_code')) self.ipaddr = '0.0.0.0' def _handle_request(self, environ, start_response): diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/caching_query.py --- a/rhodecode/lib/caching_query.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/caching_query.py Wed Mar 28 19:54:16 2012 +0200 @@ -24,7 +24,7 @@ from sqlalchemy.orm.interfaces import MapperOption from sqlalchemy.orm.query import Query from sqlalchemy.sql import visitors -from rhodecode.lib import safe_str +from rhodecode.lib.utils2 import safe_str class CachingQuery(Query): diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/celerylib/__init__.py --- a/rhodecode/lib/celerylib/__init__.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/celerylib/__init__.py Wed Mar 28 19:54:16 2012 +0200 @@ -36,7 +36,7 @@ from rhodecode.lib.vcs.utils.lazy import LazyProperty from rhodecode import CELERY_ON -from rhodecode.lib import str2bool, safe_str +from rhodecode.lib.utils2 import str2bool, safe_str from rhodecode.lib.pidlock import DaemonLock, LockHeld from rhodecode.model import init_model from rhodecode.model import meta diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/celerylib/tasks.py --- a/rhodecode/lib/celerylib/tasks.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/celerylib/tasks.py Wed Mar 28 19:54:16 2012 +0200 @@ -40,7 +40,7 @@ from rhodecode.lib.vcs import get_backend from rhodecode import CELERY_ON -from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str +from rhodecode.lib.utils2 import safe_str from rhodecode.lib.celerylib import run_task, locked_task, dbsession, \ str2bool, __get_lockkey, LockHeld, DaemonLock, get_session from rhodecode.lib.helpers import person @@ -147,6 +147,7 @@ last_rev, last_rev + parse_limit) ) for cs in repo[last_rev:last_rev + parse_limit]: + log.debug('parsing %s' % cs) last_cs = cs # remember last parsed changeset k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1], cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0]) @@ -233,10 +234,10 @@ lock.release() return False - #final release + # final release lock.release() - #execute another task if celery is enabled + # execute another task if celery is enabled if len(repo.revisions) > 1 and CELERY_ON: run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y) return True @@ -327,7 +328,7 @@ DBS = get_session() email_config = config - subject = "%s %s" % (email_config.get('email_prefix'), subject) + subject = "%s %s" % (email_config.get('email_prefix', ''), subject) if not recipients: # if recipients are not defined we send to email_config + all admins admins = [u.email for u in User.query() @@ -395,6 +396,7 @@ DBS.commit() def __get_codes_stats(repo_name): + from rhodecode.config.conf import LANGUAGES_EXTENSIONS_MAP repo = Repository.get_by_repo_name(repo_name).scm_instance tip = repo.get_changeset() diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/celerypylons/commands.py --- a/rhodecode/lib/celerypylons/commands.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/celerypylons/commands.py Wed Mar 28 19:54:16 2012 +0200 @@ -1,9 +1,9 @@ import rhodecode -from rhodecode.lib.utils import BasePasterCommand, Command +from rhodecode.lib.utils import BasePasterCommand, Command, load_rcextensions from celery.app import app_or_default from celery.bin import camqadm, celerybeat, celeryd, celeryev -from rhodecode.lib import str2bool +from rhodecode.lib.utils2 import str2bool __all__ = ['CeleryDaemonCommand', 'CeleryBeatCommand', 'CAMQPAdminCommand', 'CeleryEventCommand'] @@ -39,9 +39,11 @@ raise Exception('Please enable celery_on in .ini config ' 'file before running celeryd') rhodecode.CELERY_ON = CELERY_ON + load_rcextensions(config['here']) cmd = self.celery_command(app_or_default()) return cmd.run(**vars(self.options)) + class CeleryDaemonCommand(CeleryCommand): """Start the celery worker @@ -82,6 +84,7 @@ parser = Command.standard_parser(quiet=True) celery_command = camqadm.AMQPAdminCommand + class CeleryEventCommand(CeleryCommand): """Celery event command. diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/compat.py --- a/rhodecode/lib/compat.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/compat.py Wed Mar 28 19:54:16 2012 +0200 @@ -25,16 +25,93 @@ # along with this program. If not, see . import os +import datetime +import functools +import decimal from rhodecode import __platform__, PLATFORM_WIN #============================================================================== # json #============================================================================== + + +def _is_aware(value): + """ + Determines if a given datetime.time is aware. + + The logic is described in Python's docs: + http://docs.python.org/library/datetime.html#datetime.tzinfo + """ + return (value.tzinfo is not None + and value.tzinfo.utcoffset(value) is not None) + + +def _obj_dump(obj): + """ + Custom function for dumping objects to JSON, if obj has __json__ attribute + or method defined it will be used for serialization + + :param obj: + """ + + if isinstance(obj, complex): + return [obj.real, obj.imag] + # See "Date Time String Format" in the ECMA-262 specification. + # some code borrowed from django 1.4 + elif isinstance(obj, datetime.datetime): + r = obj.isoformat() + if obj.microsecond: + r = r[:23] + r[26:] + if r.endswith('+00:00'): + r = r[:-6] + 'Z' + return r + elif isinstance(obj, datetime.date): + return obj.isoformat() + elif isinstance(obj, decimal.Decimal): + return str(obj) + elif isinstance(obj, datetime.time): + if _is_aware(obj): + raise ValueError("JSON can't represent timezone-aware times.") + r = obj.isoformat() + if obj.microsecond: + r = r[:12] + return r + elif isinstance(obj, set): + return list(obj) + elif isinstance(obj, OrderedDict): + return obj.as_dict() + elif hasattr(obj, '__json__'): + if callable(obj.__json__): + return obj.__json__() + else: + return obj.__json__ + else: + raise NotImplementedError + try: import json + + # extended JSON encoder for json + class ExtendedEncoder(json.JSONEncoder): + def default(self, obj): + try: + return _obj_dump(obj) + except NotImplementedError: + pass + return json.JSONEncoder.default(self, obj) + # monkey-patch JSON encoder to use extended version + json.dumps = functools.partial(json.dumps, cls=ExtendedEncoder) except ImportError: import simplejson as json + def extended_encode(obj): + try: + return _obj_dump(obj) + except NotImplementedError: + pass + raise TypeError("%r is not JSON serializable" % (obj,)) + json.dumps = functools.partial(json.dumps, default=extended_encode) + #============================================================================== # izip_longest @@ -44,11 +121,11 @@ except ImportError: import itertools - def izip_longest(*args, **kwds): # noqa + def izip_longest(*args, **kwds): fillvalue = kwds.get("fillvalue") def sentinel(counter=([fillvalue] * (len(args) - 1)).pop): - yield counter() # yields the fillvalue, or raises IndexError + yield counter() # yields the fillvalue, or raises IndexError fillers = itertools.repeat(fillvalue) iters = [itertools.chain(it, sentinel(), fillers) diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/db_manage.py --- a/rhodecode/lib/db_manage.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/db_manage.py Wed Mar 28 19:54:16 2012 +0200 @@ -376,7 +376,7 @@ if not self.tests and not test_repo_path: path = raw_input( - 'Enter a valid path to store repositories. ' + 'Enter a valid absolute path to store repositories. ' 'All repositories in that path will be added automatically:' ) else: @@ -388,8 +388,12 @@ path_ok = False log.error('Given path %s is not a valid directory' % path) + elif not os.path.isabs(path): + path_ok = False + log.error('Given path %s is not an absolute path' % path) + # check write access - if not os.access(path, os.W_OK) and path_ok: + elif not os.access(path, os.W_OK) and path_ok: path_ok = False log.error('No write permission to given path %s' % path) diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/dbmigrate/migrate/exceptions.py --- a/rhodecode/lib/dbmigrate/migrate/exceptions.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/dbmigrate/migrate/exceptions.py Wed Mar 28 19:54:16 2012 +0200 @@ -71,9 +71,6 @@ """Invalid script error.""" -class InvalidVersionError(Error): - """Invalid version error.""" - # migrate.changeset class NotSupportedError(Error): diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/dbmigrate/schema/db_1_2_0.py --- a/rhodecode/lib/dbmigrate/schema/db_1_2_0.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/dbmigrate/schema/db_1_2_0.py Wed Mar 28 19:54:16 2012 +0200 @@ -39,7 +39,7 @@ from rhodecode.lib.vcs.exceptions import VCSError from rhodecode.lib.vcs.utils.lazy import LazyProperty -from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \ +from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ generate_api_key, safe_unicode from rhodecode.lib.exceptions import UsersGroupsAssignedException from rhodecode.lib.compat import json @@ -717,7 +717,7 @@ return repo -class RepoGroup(Base, BaseModel): +class Group(Base, BaseModel): __tablename__ = 'groups' __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'), CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},) @@ -728,8 +728,7 @@ group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - parent_group = relationship('RepoGroup', remote_side=group_id) - + parent_group = relationship('Group', remote_side=group_id) def __init__(self, group_name='', parent_group=None): self.group_name = group_name diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/helpers.py Wed Mar 28 19:54:16 2012 +0200 @@ -39,12 +39,20 @@ from rhodecode.lib.annotate import annotate_highlight from rhodecode.lib.utils import repo_name_slug -from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe +from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \ + get_changeset_safe from rhodecode.lib.markup_renderer import MarkupRenderer log = logging.getLogger(__name__) +def shorter(text, size=20): + postfix = '...' + if len(text) > size: + return text[:size - len(postfix)] + postfix + return text + + def _reset(name, value=None, id=NotGiven, type="reset", **attrs): """ Reset button @@ -67,7 +75,7 @@ :param path: """ - return 'C-%s-%s' % (short_id(raw_id), md5(path).hexdigest()[:12]) + return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12]) def get_token(): @@ -86,6 +94,7 @@ session.save() return session[token_key] + class _GetError(object): """Get error from form_errors, and represent it as span wrapped error message @@ -101,6 +110,7 @@ get_error = _GetError() + class _ToolTip(object): def __call__(self, tooltip_title, trim_at=50): @@ -112,6 +122,7 @@ return escape(tooltip_title) tooltip = _ToolTip() + class _FilesBreadCrumbs(object): def __call__(self, repo_name, rev, paths): @@ -136,8 +147,10 @@ files_breadcrumbs = _FilesBreadCrumbs() + class CodeHtmlFormatter(HtmlFormatter): - """My code Html Formatter for source codes + """ + My code Html Formatter for source codes """ def wrap(self, source, outfile): @@ -319,7 +332,7 @@ # SCM FILTERS available via h. #============================================================================== from rhodecode.lib.vcs.utils import author_name, author_email -from rhodecode.lib import credentials_filter, age as _age +from rhodecode.lib.utils2 import credentials_filter, age as _age from rhodecode.model.db import User age = lambda x: _age(x) @@ -759,10 +772,10 @@ d_v = d if d > 0 else '' def cgen(l_type): - mapping = {'tr': 'top-right-rounded-corner', - 'tl': 'top-left-rounded-corner', - 'br': 'bottom-right-rounded-corner', - 'bl': 'bottom-left-rounded-corner'} + mapping = {'tr': 'top-right-rounded-corner-mid', + 'tl': 'top-left-rounded-corner-mid', + 'br': 'bottom-right-rounded-corner-mid', + 'bl': 'bottom-left-rounded-corner-mid'} map_getter = lambda x: mapping[x] if l_type == 'a' and d_v: @@ -801,6 +814,12 @@ def urlify_changesets(text_, repository): + """ + Extract revision ids from changeset and make link from them + + :param text_: + :param repository: + """ import re URL_PAT = re.compile(r'([0-9a-fA-F]{12,})') @@ -839,8 +858,8 @@ import re import traceback - # urlify changesets - text_ = urlify_changesets(text_, repository) + def escaper(string): + return string.replace('<', '<').replace('>', '>') def linkify_others(t, l): urls = re.compile(r'(\)',) @@ -852,6 +871,11 @@ links.append(e) return ''.join(links) + + + # urlify changesets - extrac revisions and make link out of them + text_ = urlify_changesets(escaper(text_), repository) + try: conf = config['app_conf'] diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/hooks.py --- a/rhodecode/lib/hooks.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/hooks.py Wed Mar 28 19:54:16 2012 +0200 @@ -27,9 +27,10 @@ from mercurial.scmutil import revrange from mercurial.node import nullrev - +from rhodecode import EXTENSIONS from rhodecode.lib import helpers as h from rhodecode.lib.utils import action_logger +from inspect import isfunction def repo_size(ui, repo, hooktype=None, **kwargs): @@ -78,14 +79,19 @@ :param repo: """ - extra_params = dict(repo.ui.configitems('rhodecode_extras')) - username = extra_params['username'] - repository = extra_params['repository'] + extras = dict(repo.ui.configitems('rhodecode_extras')) + username = extras['username'] + repository = extras['repository'] action = 'pull' - action_logger(username, action, repository, extra_params['ip'], - commit=True) + action_logger(username, action, repository, extras['ip'], commit=True) + # extension hook call + callback = getattr(EXTENSIONS, 'PULL_HOOK', None) + if isfunction(callback): + kw = {} + kw.update(extras) + callback(**kw) return 0 @@ -97,10 +103,10 @@ :param repo: """ - extra_params = dict(repo.ui.configitems('rhodecode_extras')) - username = extra_params['username'] - repository = extra_params['repository'] - action = extra_params['action'] + ':%s' + extras = dict(repo.ui.configitems('rhodecode_extras')) + username = extras['username'] + repository = extras['repository'] + action = extras['action'] + ':%s' node = kwargs['node'] def get_revs(repo, rev_opt): @@ -119,16 +125,22 @@ action = action % ','.join(revs) - action_logger(username, action, repository, extra_params['ip'], - commit=True) + action_logger(username, action, repository, extras['ip'], commit=True) + # extension hook call + callback = getattr(EXTENSIONS, 'PUSH_HOOK', None) + if isfunction(callback): + kw = {'pushed_revs': revs} + kw.update(extras) + callback(**kw) return 0 def log_create_repository(repository_dict, created_by, **kwargs): """ Post create repository Hook. This is a dummy function for admins to re-use - if needed + if needed. It's taken from rhodecode-extensions module and executed + if present :param repository: dict dump of repository object :param created_by: username who created repository @@ -151,5 +163,12 @@ """ + callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None) + if isfunction(callback): + kw = {} + kw.update(repository_dict) + kw.update({'created_by': created_by}) + kw.update(kwargs) + return callback(**kw) return 0 diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/indexers/__init__.py --- a/rhodecode/lib/indexers/__init__.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/indexers/__init__.py Wed Mar 28 19:54:16 2012 +0200 @@ -25,6 +25,7 @@ import os import sys import traceback +import logging from os.path import dirname as dn, join as jn #to get the rhodecode import @@ -46,11 +47,9 @@ from rhodecode.model.scm import ScmModel from rhodecode.model.repo import RepoModel from rhodecode.config.environment import load_environment -from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, LazyProperty -from rhodecode.lib.utils import BasePasterCommand, Command, add_cache - -# EXTENSIONS WE WANT TO INDEX CONTENT OFF -INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys() +from rhodecode.lib.utils2 import LazyProperty +from rhodecode.lib.utils import BasePasterCommand, Command, add_cache,\ + load_rcextensions # CUSTOM ANALYZER wordsplit + lowercase filter ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter() @@ -84,18 +83,17 @@ parser = Command.standard_parser(verbose=True) def command(self): - + logging.config.fileConfig(self.path_to_ini_file) from pylons import config add_cache(config) engine = engine_from_config(config, 'sqlalchemy.db1.') init_model(engine) - index_location = config['index_dir'] repo_location = self.options.repo_location \ if self.options.repo_location else RepoModel().repos_path repo_list = map(strip, self.options.repo_list.split(',')) \ if self.options.repo_list else None - + load_rcextensions(config['here']) #====================================================================== # WHOOSH DAEMON #====================================================================== @@ -105,7 +103,7 @@ l = DaemonLock(file_=jn(dn(dn(index_location)), 'make_index.lock')) WhooshIndexingDaemon(index_location=index_location, repo_location=repo_location, - repo_list=repo_list)\ + repo_list=repo_list,)\ .run(full_index=self.options.full_index) l.release() except LockHeld: diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/indexers/daemon.py --- a/rhodecode/lib/indexers/daemon.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/indexers/daemon.py Wed Mar 28 19:54:16 2012 +0200 @@ -38,34 +38,17 @@ project_path = dn(dn(dn(dn(os.path.realpath(__file__))))) sys.path.append(project_path) - +from rhodecode.config.conf import INDEX_EXTENSIONS from rhodecode.model.scm import ScmModel -from rhodecode.lib import safe_unicode -from rhodecode.lib.indexers import INDEX_EXTENSIONS, SCHEMA, IDX_NAME +from rhodecode.lib.utils2 import safe_unicode +from rhodecode.lib.indexers import SCHEMA, IDX_NAME from rhodecode.lib.vcs.exceptions import ChangesetError, RepositoryError, \ NodeDoesNotExistError from whoosh.index import create_in, open_dir - -log = logging.getLogger('whooshIndexer') -# create logger -log.setLevel(logging.DEBUG) -log.propagate = False -# create console handler and set level to debug -ch = logging.StreamHandler() -ch.setLevel(logging.DEBUG) - -# create formatter -formatter = logging.Formatter("%(asctime)s - %(name)s -" - " %(levelname)s - %(message)s") - -# add formatter to ch -ch.setFormatter(formatter) - -# add ch to logger -log.addHandler(ch) +log = logging.getLogger('whoosh_indexer') class WhooshIndexingDaemon(object): @@ -103,7 +86,8 @@ self.initial = True def get_paths(self, repo): - """recursive walk in root dir and return a set of all path in that dir + """ + recursive walk in root dir and return a set of all path in that dir based on repository walk function """ index_paths_ = set() @@ -127,32 +111,39 @@ return mktime(node.last_changeset.date.timetuple()) def add_doc(self, writer, path, repo, repo_name): - """Adding doc to writer this function itself fetches data from - the instance of vcs backend""" - node = self.get_node(repo, path) + """ + Adding doc to writer this function itself fetches data from + the instance of vcs backend + """ - #we just index the content of chosen files, and skip binary files + node = self.get_node(repo, path) + indexed = indexed_w_content = 0 + # we just index the content of chosen files, and skip binary files if node.extension in INDEX_EXTENSIONS and not node.is_binary: - u_content = node.content if not isinstance(u_content, unicode): log.warning(' >> %s Could not get this content as unicode ' - 'replacing with empty content', path) + 'replacing with empty content' % path) u_content = u'' else: log.debug(' >> %s [WITH CONTENT]' % path) + indexed_w_content += 1 else: log.debug(' >> %s' % path) - #just index file name without it's content + # just index file name without it's content u_content = u'' + indexed += 1 - writer.add_document(owner=unicode(repo.contact), - repository=safe_unicode(repo_name), - path=safe_unicode(path), - content=u_content, - modtime=self.get_node_mtime(node), - extension=node.extension) + writer.add_document( + owner=unicode(repo.contact), + repository=safe_unicode(repo_name), + path=safe_unicode(path), + content=u_content, + modtime=self.get_node_mtime(node), + extension=node.extension + ) + return indexed, indexed_w_content def build_index(self): if os.path.exists(self.index_location): @@ -164,19 +155,25 @@ idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME) writer = idx.writer() - + log.debug('BUILDIN INDEX FOR EXTENSIONS %s' % INDEX_EXTENSIONS) for repo_name, repo in self.repo_paths.items(): log.debug('building index @ %s' % repo.path) - + i_cnt = iwc_cnt = 0 for idx_path in self.get_paths(repo): - self.add_doc(writer, idx_path, repo, repo_name) + i, iwc = self.add_doc(writer, idx_path, repo, repo_name) + i_cnt += i + iwc_cnt += iwc + log.debug('added %s files %s with content for repo %s' % ( + i_cnt + iwc_cnt, iwc_cnt, repo.path) + ) log.debug('>> COMMITING CHANGES <<') writer.commit(merge=True) log.debug('>>> FINISHED BUILDING INDEX <<<') def update_index(self): - log.debug('STARTING INCREMENTAL INDEXING UPDATE') + log.debug('STARTING INCREMENTAL INDEXING UPDATE FOR EXTENSIONS %s' % + INDEX_EXTENSIONS) idx = open_dir(self.index_location, indexname=self.indexname) # The set of all paths in the index @@ -215,14 +212,19 @@ # Loop over the files in the filesystem # Assume we have a function that gathers the filenames of the # documents to be indexed + ri_cnt = riwc_cnt = 0 for repo_name, repo in self.repo_paths.items(): for path in self.get_paths(repo): if path in to_index or path not in indexed_paths: # This is either a file that's changed, or a new file # that wasn't indexed before. So index it! - self.add_doc(writer, path, repo, repo_name) + i, iwc = self.add_doc(writer, path, repo, repo_name) log.debug('re indexing %s' % path) - + ri_cnt += i + riwc_cnt += iwc + log.debug('added %s files %s with content for repo %s' % ( + ri_cnt + riwc_cnt, riwc_cnt, repo.path) + ) log.debug('>> COMMITING CHANGES <<') writer.commit(merge=True) log.debug('>>> FINISHED REBUILDING INDEX <<<') diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/markup_renderer.py --- a/rhodecode/lib/markup_renderer.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/markup_renderer.py Wed Mar 28 19:54:16 2012 +0200 @@ -27,7 +27,7 @@ import re import logging -from rhodecode.lib import safe_unicode +from rhodecode.lib.utils2 import safe_unicode log = logging.getLogger(__name__) diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/middleware/https_fixup.py --- a/rhodecode/lib/middleware/https_fixup.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/middleware/https_fixup.py Wed Mar 28 19:54:16 2012 +0200 @@ -23,7 +23,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from rhodecode.lib import str2bool +from rhodecode.lib.utils2 import str2bool class HttpsFixup(object): diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/middleware/simplegit.py --- a/rhodecode/lib/middleware/simplegit.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/middleware/simplegit.py Wed Mar 28 19:54:16 2012 +0200 @@ -65,11 +65,11 @@ } from dulwich.repo import Repo -from dulwich.web import HTTPGitApplication +from dulwich.web import make_wsgi_chain from paste.httpheaders import REMOTE_USER, AUTH_TYPE -from rhodecode.lib import safe_str +from rhodecode.lib.utils2 import safe_str from rhodecode.lib.base import BaseVCSController from rhodecode.lib.auth import get_container_username from rhodecode.lib.utils import is_valid_repo @@ -86,7 +86,9 @@ def is_git(environ): path_info = environ['PATH_INFO'] isgit_path = GIT_PROTO_PAT.match(path_info) - log.debug('is a git path %s pathinfo : %s' % (isgit_path, path_info)) + log.debug('pathinfo: %s detected as GIT %s' % ( + path_info, isgit_path != None) + ) return isgit_path @@ -113,6 +115,10 @@ except: return HTTPInternalServerError()(environ, start_response) + # quick check if that dir exists... + if is_valid_repo(repo_name, self.basepath) is False: + return HTTPNotFound()(environ, start_response) + #====================================================================== # GET ACTION PULL or PUSH #====================================================================== @@ -121,7 +127,6 @@ #====================================================================== # CHECK ANONYMOUS PERMISSION #====================================================================== - if action in ['pull', 'push']: anonymous_user = self.__get_user('default') username = anonymous_user.username @@ -177,13 +182,9 @@ #=================================================================== # GIT REQUEST HANDLING #=================================================================== - repo_path = safe_str(os.path.join(self.basepath, repo_name)) + repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name)) log.debug('Repository path is %s' % repo_path) - # quick check if that dir exists... - if is_valid_repo(repo_name, self.basepath) is False: - return HTTPNotFound()(environ, start_response) - try: #invalidate cache on push if action == 'push': @@ -204,7 +205,7 @@ """ _d = {'/' + repo_name: Repo(repo_path)} backend = dulserver.DictBackend(_d) - gitserve = HTTPGitApplication(backend) + gitserve = make_wsgi_chain(backend) return gitserve diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/middleware/simplehg.py --- a/rhodecode/lib/middleware/simplehg.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/middleware/simplehg.py Wed Mar 28 19:54:16 2012 +0200 @@ -27,13 +27,14 @@ import os import logging import traceback +import urllib from mercurial.error import RepoError from mercurial.hgweb import hgweb_mod from paste.httpheaders import REMOTE_USER, AUTH_TYPE -from rhodecode.lib import safe_str +from rhodecode.lib.utils2 import safe_str from rhodecode.lib.base import BaseVCSController from rhodecode.lib.auth import get_container_username from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections @@ -45,13 +46,21 @@ def is_mercurial(environ): - """Returns True if request's target is mercurial server - header + """ + Returns True if request's target is mercurial server - header ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``. """ http_accept = environ.get('HTTP_ACCEPT') + path_info = environ['PATH_INFO'] if http_accept and http_accept.startswith('application/mercurial'): - return True - return False + ishg_path = True + else: + ishg_path = False + + log.debug('pathinfo: %s detected as HG %s' % ( + path_info, ishg_path) + ) + return ishg_path class SimpleHg(BaseVCSController): @@ -76,16 +85,20 @@ except: return HTTPInternalServerError()(environ, start_response) + # quick check if that dir exists... + if is_valid_repo(repo_name, self.basepath) is False: + return HTTPNotFound()(environ, start_response) + #====================================================================== # 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, repo_name) @@ -132,30 +145,28 @@ start_response) #check permissions for this repository - perm = self._check_permission(action, user, - repo_name) + perm = self._check_permission(action, user, repo_name) if perm is not True: return HTTPForbidden()(environ, start_response) - extras = {'ip': ipaddr, - 'username': username, - 'action': action, - 'repository': repo_name} + # extras are injected into mercurial UI object and later available + # in hg hooks executed by rhodecode + extras = { + 'ip': ipaddr, + 'username': username, + 'action': action, + 'repository': repo_name + } #====================================================================== # MERCURIAL REQUEST HANDLING #====================================================================== - - repo_path = safe_str(os.path.join(self.basepath, repo_name)) + repo_path = os.path.join(safe_str(self.basepath), safe_str(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: - return HTTPNotFound()(environ, start_response) - try: # invalidate cache on push if action == 'push': diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/utils.py --- a/rhodecode/lib/utils.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/utils.py Wed Mar 28 19:54:16 2012 +0200 @@ -51,9 +51,12 @@ from rhodecode.model import meta from rhodecode.model.db import Repository, User, RhodeCodeUi, \ - UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm + UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm,\ + CacheInvalidation from rhodecode.model.meta import Session from rhodecode.model.repos_group import ReposGroupModel +from rhodecode.lib.utils2 import safe_str, safe_unicode +from rhodecode.lib.vcs.utils.fakemod import create_module log = logging.getLogger(__name__) @@ -61,7 +64,8 @@ def recursive_replace(str_, replace=' '): - """Recursive replace of given sign to just one instance + """ + Recursive replace of given sign to just one instance :param str_: given string :param replace: char to find and replace multiple instances @@ -79,7 +83,8 @@ def repo_name_slug(value): - """Return slug of name of repository + """ + Return slug of name of repository This function is called on each creation/modification of repository to prevent bad names in repo """ @@ -154,7 +159,10 @@ user_log.user_ip = ipaddr sa.add(user_log) - log.info('Adding user %s, action %s on %s' % (user_obj, action, repo)) + log.info( + 'Adding user %s, action %s on %s' % (user_obj, action, + safe_unicode(repo)) + ) if commit: sa.commit() except: @@ -198,12 +206,13 @@ def is_valid_repo(repo_name, base_path): """ Returns True if given path is a valid repository False otherwise + :param repo_name: :param base_path: :return True: if given path is a valid repository """ - full_path = os.path.join(base_path, repo_name) + full_path = os.path.join(safe_str(base_path), safe_str(repo_name)) try: get_scm(full_path) @@ -219,7 +228,7 @@ :param repo_name: :param base_path: """ - full_path = os.path.join(base_path, repos_group_name) + full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name)) # check if it's not a repo if is_valid_repo(repos_group_name, base_path): @@ -258,7 +267,8 @@ def make_ui(read_from='file', path=None, checkpaths=True): - """A function that will read python rc files or database + """ + A function that will read python rc files or database and make an mercurial ui object from read options :param path: path to mercurial config file @@ -371,15 +381,16 @@ return 0 -def map_groups(groups): +def map_groups(path): """ - Checks for groups existence, and creates groups structures. - It returns last group in structure + Given a full path to a repository, create all nested groups that this + repo is inside. This function creates parent-child relationships between + groups and creates default perms for all new groups. - :param groups: list of groups structure + :param paths: full path to repository """ sa = meta.Session - + groups = path.split(Repository.url_sep()) parent = None group = None @@ -391,22 +402,18 @@ group = RepoGroup.get_by_group_name(group_name) desc = '%s group' % group_name -# # WTF that doesn't work !? -# if group is None: -# group = rgm.create(group_name, desc, parent, just_db=True) -# sa.commit() - # skip folders that are now removed repos if REMOVED_REPO_PAT.match(group_name): break if group is None: - log.debug('creating group level: %s group_name: %s' % (lvl, group_name)) + log.debug('creating group level: %s group_name: %s' % (lvl, + group_name)) group = RepoGroup(group_name, parent) group.group_description = desc sa.add(group) rgm._create_default_perms(group) - sa.commit() + sa.flush() parent = group return group @@ -429,7 +436,7 @@ added = [] for name, repo in initial_repo_list.items(): - group = map_groups(name.split(Repository.url_sep())) + group = map_groups(name) if not rm.get_by_repo_name(name, cache=False): log.info('repository %s not found creating default' % name) added.append(name) @@ -446,13 +453,19 @@ sa.commit() removed = [] if remove_obsolete: - #remove from database those repositories that are not in the filesystem + # remove from database those repositories that are not in the filesystem for repo in sa.query(Repository).all(): if repo.repo_name not in initial_repo_list.keys(): + log.debug("Removing non existing repository found in db %s" % + repo.repo_name) removed.append(repo.repo_name) sa.delete(repo) sa.commit() + # clear cache keys + log.debug("Clearing cache keys now...") + CacheInvalidation.clear_cache() + sa.commit() return added, removed @@ -484,6 +497,30 @@ beaker.cache.cache_regions[region] = region_settings +def load_rcextensions(root_path): + import rhodecode + from rhodecode.config import conf + + path = os.path.join(root_path, 'rcextensions', '__init__.py') + if os.path.isfile(path): + rcext = create_module('rc', path) + EXT = rhodecode.EXTENSIONS = rcext + log.debug('Found rcextensions now loading %s...' % rcext) + + # Additional mappings that are not present in the pygments lexers + conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {})) + + #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present) + + if getattr(EXT, 'INDEX_EXTENSIONS', []) != []: + log.debug('settings custom INDEX_EXTENSIONS') + conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', []) + + #ADDITIONAL MAPPINGS + log.debug('adding extra into INDEX_EXTENSIONS') + conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', [])) + + #============================================================================== # TEST FUNCTIONS AND CREATORS #============================================================================== @@ -624,6 +661,6 @@ """ from pylons import config as pylonsconfig - path_to_ini_file = os.path.realpath(conf) - conf = paste.deploy.appconfig('config:' + path_to_ini_file) + self.path_to_ini_file = os.path.realpath(conf) + conf = paste.deploy.appconfig('config:' + self.path_to_ini_file) pylonsconfig.init_app(conf.global_conf, conf.local_conf) diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/utils2.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/utils2.py Wed Mar 28 19:54:16 2012 +0200 @@ -0,0 +1,405 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.lib.utils + ~~~~~~~~~~~~~~~~~~~ + + Some simple helper functions + + :created_on: Jan 5, 2011 + :author: marcink + :copyright: (C) 2011-2012 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 re +from rhodecode.lib.vcs.utils.lazy import LazyProperty + + +def __get_lem(): + """ + Get language extension map based on what's inside pygments lexers + """ + from pygments import lexers + from string import lower + from collections import defaultdict + + d = defaultdict(lambda: []) + + def __clean(s): + s = s.lstrip('*') + s = s.lstrip('.') + + if s.find('[') != -1: + exts = [] + start, stop = s.find('['), s.find(']') + + for suffix in s[start + 1:stop]: + exts.append(s[:s.find('[')] + suffix) + return map(lower, exts) + else: + return map(lower, [s]) + + for lx, t in sorted(lexers.LEXERS.items()): + m = map(__clean, t[-2]) + if m: + m = reduce(lambda x, y: x + y, m) + for ext in m: + desc = lx.replace('Lexer', '') + d[ext].append(desc) + + return dict(d) + +def str2bool(_str): + """ + returs True/False value from given string, it tries to translate the + string into boolean + + :param _str: string value to translate into boolean + :rtype: boolean + :returns: boolean from given string + """ + if _str is None: + return False + if _str in (True, False): + return _str + _str = str(_str).strip().lower() + return _str in ('t', 'true', 'y', 'yes', 'on', '1') + + +def convert_line_endings(line, mode): + """ + Converts a given line "line end" accordingly to given mode + + Available modes are:: + 0 - Unix + 1 - Mac + 2 - DOS + + :param line: given line to convert + :param mode: mode to convert to + :rtype: str + :return: converted line according to mode + """ + from string import replace + + if mode == 0: + line = replace(line, '\r\n', '\n') + line = replace(line, '\r', '\n') + elif mode == 1: + line = replace(line, '\r\n', '\r') + line = replace(line, '\n', '\r') + elif mode == 2: + line = re.sub("\r(?!\n)|(?>>>> STARTING QUERY >>>>>")) + + + def after_cursor_execute(conn, cursor, statement, + parameters, context, executemany): + total = time.time() - context._query_start_time + log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total)) + + event.listen(engine, "before_cursor_execute", + before_cursor_execute) + event.listen(engine, "after_cursor_execute", + after_cursor_execute) + + return engine + + +def age(curdate): + """ + turns a datetime into an age string. + + :param curdate: datetime object + :rtype: unicode + :returns: unicode words describing age + """ + + from datetime import datetime + from webhelpers.date import time_ago_in_words + + _ = lambda s: s + + if not curdate: + return '' + + agescales = [(_(u"year"), 3600 * 24 * 365), + (_(u"month"), 3600 * 24 * 30), + (_(u"day"), 3600 * 24), + (_(u"hour"), 3600), + (_(u"minute"), 60), + (_(u"second"), 1), ] + + age = datetime.now() - curdate + age_seconds = (age.days * agescales[2][1]) + age.seconds + pos = 1 + for scale in agescales: + if scale[1] <= age_seconds: + if pos == 6: + pos = 5 + return '%s %s' % (time_ago_in_words(curdate, + agescales[pos][0]), _('ago')) + pos += 1 + + return _(u'just now') + + +def uri_filter(uri): + """ + Removes user:password from given url string + + :param uri: + :rtype: unicode + :returns: filtered list of strings + """ + if not uri: + return '' + + proto = '' + + for pat in ('https://', 'http://'): + if uri.startswith(pat): + uri = uri[len(pat):] + proto = pat + break + + # remove passwords and username + uri = uri[uri.find('@') + 1:] + + # get the port + cred_pos = uri.find(':') + if cred_pos == -1: + host, port = uri, None + else: + host, port = uri[:cred_pos], uri[cred_pos + 1:] + + return filter(None, [proto, host, port]) + + +def credentials_filter(uri): + """ + Returns a url with removed credentials + + :param uri: + """ + + uri = uri_filter(uri) + #check if we have port + if len(uri) > 2 and uri[2]: + uri[2] = ':' + uri[2] + + return ''.join(uri) + + +def get_changeset_safe(repo, rev): + """ + Safe version of get_changeset if this changeset doesn't exists for a + repo it returns a Dummy one instead + + :param repo: + :param rev: + """ + from rhodecode.lib.vcs.backends.base import BaseRepository + from rhodecode.lib.vcs.exceptions import RepositoryError + if not isinstance(repo, BaseRepository): + raise Exception('You must pass an Repository ' + 'object as first argument got %s', type(repo)) + + try: + cs = repo.get_changeset(rev) + except RepositoryError: + from rhodecode.lib.utils import EmptyChangeset + cs = EmptyChangeset(requested_revision=rev) + return cs + + +def extract_mentioned_users(s): + """ + Returns unique usernames from given string s that have @mention + + :param s: string to get mentions + """ + usrs = {} + for username in re.findall(r'(?:^@|\s@)(\w+)', s): + usrs[username] = username + + return sorted(usrs.keys()) diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/lib/vcs/backends/git/changeset.py --- a/rhodecode/lib/vcs/backends/git/changeset.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/lib/vcs/backends/git/changeset.py Wed Mar 28 19:54:16 2012 +0200 @@ -68,19 +68,24 @@ def branch(self): # TODO: Cache as we walk (id <-> branch name mapping) refs = self.repository._repo.get_refs() - heads = [(key[len('refs/heads/'):], val) for key, val in refs.items() - if key.startswith('refs/heads/')] + heads = {} + for key, val in refs.items(): + for ref_key in ['refs/heads/', 'refs/remotes/origin/']: + if key.startswith(ref_key): + n = key[len(ref_key):] + if n not in ['HEAD']: + heads[n] = val - for name, id in heads: + for name, id in heads.iteritems(): walker = self.repository._repo.object_store.get_graph_walker([id]) while True: - id = walker.next() - if not id: + id_ = walker.next() + if not id_: break - if id == self.id: + if id_ == self.id: return safe_unicode(name) raise ChangesetError("This should not happen... Have you manually " - "change id of the changeset?") + "change id of the changeset?") def _fix_path(self, path): """ @@ -92,6 +97,7 @@ return path def _get_id_for_path(self, path): + # FIXME: Please, spare a couple of minutes and make those codes cleaner; if not path in self._paths: path = path.strip('/') @@ -103,24 +109,23 @@ splitted = path.split('/') dirs, name = splitted[:-1], splitted[-1] curdir = '' + + # initially extract things from root dir + for item, stat, id in tree.iteritems(): + if curdir: + name = '/'.join((curdir, item)) + else: + name = item + self._paths[name] = id + self._stat_modes[name] = stat + for dir in dirs: if curdir: curdir = '/'.join((curdir, dir)) else: curdir = dir - #if curdir in self._paths: - ## This path have been already traversed - ## Update tree and continue - #tree = self.repository._repo[self._paths[curdir]] - #continue dir_id = None for item, stat, id in tree.iteritems(): - if curdir: - item_path = '/'.join((curdir, item)) - else: - item_path = item - self._paths[item_path] = id - self._stat_modes[item_path] = stat if dir == item: dir_id = id if dir_id: @@ -130,13 +135,16 @@ raise ChangesetError('%s is not a directory' % curdir) else: raise ChangesetError('%s have not been found' % curdir) - for item, stat, id in tree.iteritems(): - if curdir: - name = '/'.join((curdir, item)) - else: - name = item - self._paths[name] = id - self._stat_modes[name] = stat + + # cache all items from the given traversed tree + for item, stat, id in tree.iteritems(): + if curdir: + name = '/'.join((curdir, item)) + else: + name = item + self._paths[name] = id + self._stat_modes[name] = stat + if not path in self._paths: raise NodeDoesNotExistError("There is no file nor directory " "at the given path %r at revision %r" diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/model/__init__.py --- a/rhodecode/model/__init__.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/model/__init__.py Wed Mar 28 19:54:16 2012 +0200 @@ -85,14 +85,14 @@ if isinstance(instance, cls): return instance - elif isinstance(instance, int) or str(instance).isdigit(): + elif isinstance(instance, (int, long)) or str(instance).isdigit(): return cls.get(instance) else: if instance: if callback is None: raise Exception( - 'given object must be int or Instance of %s got %s, ' - 'no callback provided' % (cls, type(instance)) + 'given object must be int, long or Instance of %s ' + 'got %s, no callback provided' % (cls, type(instance)) ) else: return callback(instance) diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/model/comment.py --- a/rhodecode/model/comment.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/model/comment.py Wed Mar 28 19:54:16 2012 +0200 @@ -29,7 +29,7 @@ from pylons.i18n.translation import _ from sqlalchemy.util.compat import defaultdict -from rhodecode.lib import extract_mentioned_users +from rhodecode.lib.utils2 import extract_mentioned_users from rhodecode.lib import helpers as h from rhodecode.model import BaseModel from rhodecode.model.db import ChangesetComment, User, Repository, Notification @@ -63,6 +63,7 @@ :param f_path: :param line_no: """ + if text: repo = Repository.get(repo_id) cs = repo.scm_instance.get_changeset(revision) @@ -78,7 +79,6 @@ self.sa.add(comment) self.sa.flush() - # make notification line = '' if line_no: diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/model/db.py --- a/rhodecode/model/db.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/model/db.py Wed Mar 28 19:54:16 2012 +0200 @@ -39,7 +39,8 @@ from rhodecode.lib.vcs.exceptions import VCSError from rhodecode.lib.vcs.utils.lazy import LazyProperty -from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode +from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ + safe_unicode from rhodecode.lib.compat import json from rhodecode.lib.caching_query import FromCache @@ -145,12 +146,18 @@ obj = cls.query().get(id_) Session.delete(obj) + def __repr__(self): + if hasattr(self, '__unicode__'): + # python repr needs to return str + return safe_str(self.__unicode__()) + return '' % (self.__class__.__name__) class RhodeCodeSetting(Base, BaseModel): __tablename__ = 'rhodecode_settings' __table_args__ = ( UniqueConstraint('app_settings_name'), - {'extend_existing': True} + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} ) 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) @@ -181,8 +188,8 @@ """ self._app_settings_value = safe_unicode(val) - def __repr__(self): - return "<%s('%s:%s')>" % ( + def __unicode__(self): + return u"<%s('%s:%s')>" % ( self.__class__.__name__, self.app_settings_name, self.app_settings_value ) @@ -224,7 +231,8 @@ __tablename__ = 'rhodecode_ui' __table_args__ = ( UniqueConstraint('ui_key'), - {'extend_existing': True} + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} ) HOOK_UPDATE = 'changegroup.update' @@ -274,7 +282,8 @@ __tablename__ = 'users' __table_args__ = ( UniqueConstraint('username'), UniqueConstraint('email'), - {'extend_existing': True} + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} ) user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) @@ -294,10 +303,15 @@ repositories = relationship('Repository') user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') + repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') group_member = relationship('UsersGroupMember', cascade='all') - notifications = relationship('UserNotification',) + notifications = relationship('UserNotification', cascade='all') + # notifications assigned to this user + user_created_notifications = relationship('Notification', cascade='all') + # comments created by this user + user_comments = relationship('ChangesetComment', cascade='all') @hybrid_property def email(self): @@ -328,8 +342,8 @@ def is_admin(self): return self.admin - def __repr__(self): - return "<%s('id:%s:%s')>" % (self.__class__.__name__, + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.user_id, self.username) @classmethod @@ -376,6 +390,9 @@ def __json__(self): return dict( + user_id=self.user_id, + first_name=self.name, + last_name=self.lastname, email=self.email, full_name=self.full_name, full_name_or_username=self.full_name_or_username, @@ -386,7 +403,10 @@ class UserLog(Base, BaseModel): __tablename__ = 'user_logs' - __table_args__ = {'extend_existing': True} + __table_args__ = ( + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) @@ -405,7 +425,10 @@ class UsersGroup(Base, BaseModel): __tablename__ = 'users_groups' - __table_args__ = {'extend_existing': True} + __table_args__ = ( + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) @@ -413,9 +436,10 @@ members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined") users_group_to_perm = relationship('UsersGroupToPerm', cascade='all') + users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all') - def __repr__(self): - return '' % (self.users_group_name) + def __unicode__(self): + return u'' % (self.users_group_name) @classmethod def get_by_group_name(cls, group_name, cache=False, @@ -443,7 +467,10 @@ class UsersGroupMember(Base, BaseModel): __tablename__ = 'users_groups_members' - __table_args__ = {'extend_existing': True} + __table_args__ = ( + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) @@ -461,7 +488,8 @@ __tablename__ = 'repositories' __table_args__ = ( UniqueConstraint('repo_name'), - {'extend_existing': True}, + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, ) repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) @@ -489,9 +517,9 @@ logs = relationship('UserLog') - def __repr__(self): - return "<%s('%s:%s')>" % (self.__class__.__name__, - self.repo_id, self.repo_name) + def __unicode__(self): + return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id, + self.repo_name) @classmethod def url_sep(cls): @@ -710,7 +738,8 @@ __table_args__ = ( UniqueConstraint('group_name', 'group_parent_id'), CheckConstraint('group_id != group_parent_id'), - {'extend_existing': True}, + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, ) __mapper_args__ = {'order_by': 'group_name'} @@ -728,8 +757,8 @@ self.group_name = group_name self.parent_group = parent_group - def __repr__(self): - return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id, + def __unicode__(self): + return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id, self.group_name) @classmethod @@ -837,13 +866,16 @@ class Permission(Base, BaseModel): __tablename__ = 'permissions' - __table_args__ = {'extend_existing': True} + __table_args__ = ( + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - def __repr__(self): - return "<%s('%s:%s')>" % ( + def __unicode__(self): + return u"<%s('%s:%s')>" % ( self.__class__.__name__, self.permission_id, self.permission_name ) @@ -874,7 +906,8 @@ __tablename__ = 'repo_to_perm' __table_args__ = ( UniqueConstraint('user_id', 'repository_id', 'permission_id'), - {'extend_existing': True} + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} ) repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) @@ -894,15 +927,16 @@ Session.add(n) return n - def __repr__(self): - return ' %s >' % (self.user, self.repository) + def __unicode__(self): + return u' %s >' % (self.user, self.repository) class UserToPerm(Base, BaseModel): __tablename__ = 'user_to_perm' __table_args__ = ( UniqueConstraint('user_id', 'permission_id'), - {'extend_existing': True} + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} ) user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) @@ -916,7 +950,8 @@ __tablename__ = 'users_group_repo_to_perm' __table_args__ = ( UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), - {'extend_existing': True} + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} ) users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) @@ -936,15 +971,16 @@ Session.add(n) return n - def __repr__(self): - return ' %s >' % (self.users_group, self.repository) + def __unicode__(self): + return u' %s >' % (self.users_group, self.repository) class UsersGroupToPerm(Base, BaseModel): __tablename__ = 'users_group_to_perm' __table_args__ = ( UniqueConstraint('users_group_id', 'permission_id',), - {'extend_existing': True} + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} ) users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) @@ -958,7 +994,8 @@ __tablename__ = 'user_repo_group_to_perm' __table_args__ = ( UniqueConstraint('user_id', 'group_id', 'permission_id'), - {'extend_existing': True} + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} ) group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) @@ -975,7 +1012,8 @@ __tablename__ = 'users_group_repo_group_to_perm' __table_args__ = ( UniqueConstraint('users_group_id', 'group_id'), - {'extend_existing': True} + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} ) users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) @@ -990,7 +1028,11 @@ class Statistics(Base, BaseModel): __tablename__ = 'statistics' - __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing': True}) + __table_args__ = ( + UniqueConstraint('repository_id'), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} + ) stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) @@ -1006,7 +1048,8 @@ __table_args__ = ( UniqueConstraint('user_id', 'follows_repository_id'), UniqueConstraint('user_id', 'follows_user_id'), - {'extend_existing': True} + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} ) user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) @@ -1027,7 +1070,11 @@ class CacheInvalidation(Base, BaseModel): __tablename__ = 'cache_invalidation' - __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing': True}) + __table_args__ = ( + UniqueConstraint('cache_key'), + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) @@ -1038,14 +1085,17 @@ self.cache_args = cache_args self.cache_active = False - def __repr__(self): - return "<%s('%s:%s')>" % (self.__class__.__name__, + def __unicode__(self): + return u"<%s('%s:%s')>" % (self.__class__.__name__, self.cache_id, self.cache_key) + @classmethod + def clear_cache(cls): + cls.query().delete() @classmethod def _get_key(cls, key): """ - Wrapper for generating a key + Wrapper for generating a key, together with a prefix :param key: """ @@ -1054,13 +1104,26 @@ iid = rhodecode.CONFIG.get('instance_id') if iid: prefix = iid - return "%s%s" % (prefix, key) + return "%s%s" % (prefix, key), prefix, key.rstrip('_README') @classmethod def get_by_key(cls, key): return cls.query().filter(cls.cache_key == key).scalar() @classmethod + def _get_or_create_key(cls, key, prefix, org_key): + inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar() + if not inv_obj: + try: + inv_obj = CacheInvalidation(key, org_key) + Session.add(inv_obj) + Session.commit() + except Exception: + log.error(traceback.format_exc()) + Session.rollback() + return inv_obj + + @classmethod def invalidate(cls, key): """ Returns Invalidation object if this given key should be invalidated @@ -1069,10 +1132,12 @@ :param key: """ - return cls.query()\ - .filter(CacheInvalidation.cache_key == key)\ - .filter(CacheInvalidation.cache_active == False)\ - .scalar() + + key, _prefix, _org_key = cls._get_key(key) + inv = cls._get_or_create_key(key, _prefix, _org_key) + + if inv and inv.cache_active is False: + return inv @classmethod def set_invalidate(cls, key): @@ -1082,17 +1147,16 @@ :param key: """ - log.debug('marking %s for invalidation' % key) - inv_obj = Session.query(cls)\ - .filter(cls.cache_key == key).scalar() - if inv_obj: - inv_obj.cache_active = False - else: - log.debug('cache key not found in invalidation db -> creating one') - inv_obj = CacheInvalidation(key) + key, _prefix, _org_key = cls._get_key(key) + inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all() + log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs), + _org_key)) + try: + for inv_obj in inv_objs: + if inv_obj: + inv_obj.cache_active = False - try: - Session.add(inv_obj) + Session.add(inv_obj) Session.commit() except Exception: log.error(traceback.format_exc()) @@ -1113,7 +1177,10 @@ class ChangesetComment(Base, BaseModel): __tablename__ = 'changeset_comments' - __table_args__ = ({'extend_existing': True},) + __table_args__ = ( + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) revision = Column('revision', String(40), nullable=False) @@ -1142,7 +1209,10 @@ class Notification(Base, BaseModel): __tablename__ = 'notifications' - __table_args__ = ({'extend_existing': True},) + __table_args__ = ( + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) TYPE_CHANGESET_COMMENT = u'cs_comment' TYPE_MESSAGE = u'message' @@ -1194,7 +1264,8 @@ __tablename__ = 'user_to_notification' __table_args__ = ( UniqueConstraint('user_id', 'notification_id'), - {'extend_existing': True} + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'} ) user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) @@ -1212,7 +1283,10 @@ class DbMigrateVersion(Base, BaseModel): __tablename__ = 'db_migrate_version' - __table_args__ = {'extend_existing': True} + __table_args__ = ( + {'extend_existing': True, 'mysql_engine':'InnoDB', + 'mysql_charset': 'utf8'}, + ) repository_id = Column('repository_id', String(250), primary_key=True) repository_path = Column('repository_path', Text) version = Column('version', Integer) diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/model/notification.py --- a/rhodecode/model/notification.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/model/notification.py Wed Mar 28 19:54:16 2012 +0200 @@ -32,6 +32,7 @@ from pylons.i18n.translation import _ import rhodecode +from rhodecode.config.conf import DATETIME_FORMAT from rhodecode.lib import helpers as h from rhodecode.model import BaseModel from rhodecode.model.db import Notification, User, UserNotification @@ -47,11 +48,11 @@ def __get_notification(self, notification): if isinstance(notification, Notification): return notification - elif isinstance(notification, int): + elif isinstance(notification, (int, long)): return Notification.get(notification) else: if notification: - raise Exception('notification must be int or Instance' + raise Exception('notification must be int, long or Instance' ' of Notification got %s' % type(notification)) def create(self, created_by, subject, body, recipients=None, @@ -111,6 +112,7 @@ kwargs.update(email_kwargs) email_body_html = EmailNotificationModel()\ .get_email_tmpl(type_, **kwargs) + run_task(tasks.send_email, rec.email, email_subject, email_body, email_body_html) @@ -176,14 +178,13 @@ notification.TYPE_REGISTRATION: _('registered in RhodeCode') } - DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" - tmpl = "%(user)s %(action)s %(when)s" if show_age: when = h.age(notification.created_on) else: DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT) when = DTF(notification.created_on) + data = dict( user=notification.created_by_user.username, action=_map[notification.type_], when=when, diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/model/repo.py --- a/rhodecode/model/repo.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/model/repo.py Wed Mar 28 19:54:16 2012 +0200 @@ -29,15 +29,15 @@ from datetime import datetime from rhodecode.lib.vcs.backends import get_backend - -from rhodecode.lib import LazyProperty -from rhodecode.lib import safe_str, safe_unicode +from rhodecode.lib.compat import json +from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode from rhodecode.lib.caching_query import FromCache from rhodecode.lib.hooks import log_create_repository from rhodecode.model import BaseModel from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \ Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup +from rhodecode.lib import helpers as h log = logging.getLogger(__name__) @@ -95,25 +95,28 @@ return repo.scalar() def get_users_js(self): - users = self.sa.query(User).filter(User.active == True).all() - u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},''' - users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name, - u.lastname, u.username) - for u in users]) - return users_array + return json.dumps([ + { + 'id': u.user_id, + 'fname': u.name, + 'lname': u.lastname, + 'nname': u.username, + 'gravatar_lnk': h.gravatar_url(u.email, 14) + } for u in users] + ) def get_users_groups_js(self): users_groups = self.sa.query(UsersGroup)\ .filter(UsersGroup.users_group_active == True).all() - g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},''' - - users_groups_array = '[%s]' % '\n'.join([g_tmpl % \ - (gr.users_group_id, gr.users_group_name, - len(gr.members)) - for gr in users_groups]) - return users_groups_array + return json.dumps([ + { + 'id': gr.users_group_id, + 'grname': gr.users_group_name, + 'grmembers': len(gr.members), + } for gr in users_groups] + ) def _get_defaults(self, repo_name): """ @@ -346,6 +349,7 @@ :param repo: Instance of Repository, repository_id, or repository name :param user: Instance of User, user_id or username """ + user = self.__get_user(user) repo = self.__get_repo(repo) diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/model/repos_group.py --- a/rhodecode/model/repos_group.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/model/repos_group.py Wed Mar 28 19:54:16 2012 +0200 @@ -28,7 +28,7 @@ import traceback import shutil -from rhodecode.lib import LazyProperty +from rhodecode.lib.utils2 import LazyProperty from rhodecode.model import BaseModel from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \ diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/model/scm.py --- a/rhodecode/model/scm.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/model/scm.py Wed Mar 28 19:54:16 2012 +0200 @@ -35,7 +35,7 @@ from rhodecode import BACKENDS from rhodecode.lib import helpers as h -from rhodecode.lib import safe_str +from rhodecode.lib.utils2 import safe_str from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \ action_logger, EmptyChangeset, REMOVED_REPO_PAT @@ -235,13 +235,13 @@ return group_iter def mark_for_invalidation(self, repo_name): - """Puts cache invalidation task into db for + """ + Puts cache invalidation task into db for further global cache invalidation :param repo_name: this repo that should invalidation take place """ CacheInvalidation.set_invalidate(repo_name) - CacheInvalidation.set_invalidate(repo_name + "_README") def toggle_following_repo(self, follow_repo_id, user_id): diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/model/user.py --- a/rhodecode/model/user.py Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/model/user.py Wed Mar 28 19:54:16 2012 +0200 @@ -29,18 +29,19 @@ from pylons import url from pylons.i18n.translation import _ -from rhodecode.lib import safe_unicode +from rhodecode.lib.utils2 import safe_unicode, generate_api_key from rhodecode.lib.caching_query import FromCache from rhodecode.model import BaseModel from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \ UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \ - Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup + Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup,\ + UsersGroupRepoGroupToPerm from rhodecode.lib.exceptions import DefaultUserException, \ UserOwnsReposException from sqlalchemy.exc import DatabaseError -from rhodecode.lib import generate_api_key + from sqlalchemy.orm import joinedload log = logging.getLogger(__name__) @@ -298,14 +299,16 @@ try: if user.username == 'default': raise DefaultUserException( - _("You can't remove this user since it's" - " crucial for entire application")) + _(u"You can't remove this user since it's" + " crucial for entire application") + ) if user.repositories: - raise UserOwnsReposException(_('This user still owns %s ' - 'repositories and cannot be ' - 'removed. Switch owners or ' - 'remove those repositories') \ - % user.repositories) + repos = [x.repo_name for x in user.repositories] + raise UserOwnsReposException( + _(u'user "%s" still owns %s repositories and cannot be ' + 'removed. Switch owners or remove those repositories. %s') + % (user.username, len(repos), ', '.join(repos)) + ) self.sa.delete(user) except: log.error(traceback.format_exc()) @@ -409,7 +412,7 @@ for perm in default_global_perms: user.permissions[GLOBAL].add(perm.permission.permission_name) - # default for repositories + # defaults for repositories, taken from default user for perm in default_repo_perms: r_k = perm.UserRepoToPerm.repository.repo_name if perm.Repository.private and not (perm.Repository.user_id == uid): @@ -423,17 +426,18 @@ user.permissions[RK][r_k] = p - # default for repositories groups + # defaults for repositories groups taken from default user permission + # on given group for perm in default_repo_groups_perms: rg_k = perm.UserRepoGroupToPerm.group.group_name p = perm.Permission.permission_name user.permissions[GK][rg_k] = p #================================================================== - # overwrite default with user permissions if any + # overwrite defaults with user permissions if any found #================================================================== - # user global + # user global permissions user_perms = self.sa.query(UserToPerm)\ .options(joinedload(UserToPerm.permission))\ .filter(UserToPerm.user_id == uid).all() @@ -441,7 +445,7 @@ for perm in user_perms: user.permissions[GLOBAL].add(perm.permission.permission_name) - # user repositories + # user explicit permissions for repositories user_repo_perms = \ self.sa.query(UserRepoToPerm, Permission, Repository)\ .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ @@ -459,8 +463,8 @@ user.permissions[RK][r_k] = p #================================================================== - # check if user is part of groups for this repository and fill in - # (or replace with higher) permissions + # check if user is part of user groups for this repository and + # fill in (or replace with higher) permissions #================================================================== # users group global @@ -473,7 +477,7 @@ for perm in user_perms_from_users_groups: user.permissions[GLOBAL].add(perm.permission.permission_name) - # users group repositories + # users group for repositories permissions user_repo_perms_from_users_groups = \ self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\ .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\ @@ -495,12 +499,12 @@ # get access for this user for repos group and override defaults #================================================================== - # user repositories groups + # user explicit permissions for repository user_repo_groups_perms = \ self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\ .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\ - .filter(UserRepoToPerm.user_id == uid)\ + .filter(UserRepoGroupToPerm.user_id == uid)\ .all() for perm in user_repo_groups_perms: @@ -510,6 +514,30 @@ if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]: user.permissions[GK][rg_k] = p + #================================================================== + # check if user is part of user groups for this repo group and + # fill in (or replace with higher) permissions + #================================================================== + + # users group for repositories permissions + user_repo_group_perms_from_users_groups = \ + self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\ + .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\ + .join((Permission, UsersGroupRepoGroupToPerm.permission_id == Permission.permission_id))\ + .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\ + .filter(UsersGroupMember.user_id == uid)\ + .all() + + for perm in user_repo_group_perms_from_users_groups: + g_k = perm.UsersGroupRepoGroupToPerm.group.group_name + print perm, g_k + p = perm.Permission.permission_name + cur_perm = user.permissions[GK][g_k] + # overwrite permission only if it's greater than permission + # given from other sources + if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]: + user.permissions[GK][g_k] = p + return user def has_perm(self, user, perm): diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/public/css/style.css --- a/rhodecode/public/css/style.css Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/public/css/style.css Wed Mar 28 19:54:16 2012 +0200 @@ -185,6 +185,41 @@ border-bottom-right-radius: 8px; } +.top-left-rounded-corner-mid { + -webkit-border-top-left-radius: 4px; + -khtml-border-radius-topleft: 4px; + -moz-border-radius-topleft: 4px; + border-top-left-radius: 4px; +} + +.top-right-rounded-corner-mid { + -webkit-border-top-right-radius: 4px; + -khtml-border-radius-topright: 4px; + -moz-border-radius-topright: 4px; + border-top-right-radius: 4px; +} + +.bottom-left-rounded-corner-mid { + -webkit-border-bottom-left-radius: 4px; + -khtml-border-radius-bottomleft: 4px; + -moz-border-radius-bottomleft: 4px; + border-bottom-left-radius: 4px; +} + +.bottom-right-rounded-corner-mid { + -webkit-border-bottom-right-radius: 4px; + -khtml-border-radius-bottomright: 4px; + -moz-border-radius-bottomright: 4px; + border-bottom-right-radius: 4px; +} + +.help-block { + color: #999999; + display: block; + margin-bottom: 0; + margin-top: 5px; +} + #header { margin: 0; padding: 0 10px; @@ -197,18 +232,16 @@ -moz-border-radius: 0px 0px 8px 8px; border-radius: 0px 0px 8px 8px; height: 37px; - background-color: #eedc94; + background-color: #003B76; background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), - to(#eedc94) ); + background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) ); background-image: -moz-linear-gradient(top, #003b76, #00376e); background-image: -ms-linear-gradient(top, #003b76, #00376e); background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) ); background-image: -webkit-linear-gradient(top, #003b76, #00376e); background-image: -o-linear-gradient(top, #003b76, #00376e); background-image: linear-gradient(top, #003b76, #00376e); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', - endColorstr='#00376e', GradientType=0 ); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 ); box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6); } @@ -260,9 +293,9 @@ min-height: 44px; clear: both; position: relative; - background-color: #eedc94; + background-color: #003B76; background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),to(#eedc94) ); + background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) ); background-image: -moz-linear-gradient(top, #003b76, #00376e); background-image: -ms-linear-gradient(top, #003b76, #00376e); background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),color-stop(100%, #00376e) ); @@ -289,6 +322,14 @@ -moz-border-radius: 0px 0px 0px 0px; border-radius: 0px 0px 0px 0px; } + +.ie7 #header #header-inner.hover, +.ie8 #header #header-inner.hover, +.ie9 #header #header-inner.hover +{ + z-index: auto !important; +} + #header #header-inner #home a { height: 40px; width: 46px; @@ -997,9 +1038,9 @@ #content div.box div.title { clear: both; overflow: hidden; - background-color: #eedc94; + background-color: #003B76; background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94) ); + background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) ); background-image: -moz-linear-gradient(top, #003b76, #00376e); background-image: -ms-linear-gradient(top, #003b76, #00376e); background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) ); @@ -1758,33 +1799,21 @@ } #footer div#footer-inner { - background-color: #eedc94; background-repeat : repeat-x; - background-image : -khtml-gradient( linear, left top, left bottom, - from( #fceec1), to( #eedc94)); background-image : -moz-linear-gradient( - top, #003b76, #00376e); background-image : -ms-linear-gradient( top, - #003b76, #00376e); background-image : -webkit-gradient( linear, left - top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e)); + background-color: #003B76; + background-repeat : repeat-x; + background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E)); + background-image : -moz-linear-gradient(top, #003b76, #00376e); + background-image : -ms-linear-gradient( top, #003b76, #00376e); + background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e)); background-image : -webkit-linear-gradient( top, #003b76, #00376e)); background-image : -o-linear-gradient( top, #003b76, #00376e)); - background-image : linear-gradient( top, #003b76, #00376e); filter : - progid : DXImageTransform.Microsoft.gradient ( startColorstr = - '#003b76', endColorstr = '#00376e', GradientType = 0); + background-image : linear-gradient( top, #003b76, #00376e); + filter :progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0); 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; -moz-border-radius: 4px 4px 4px 4px; border-radius: 4px 4px 4px 4px; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), - to(#eedc94) ); - background-image: -moz-linear-gradient(top, #003b76, #00376e); - background-image: -ms-linear-gradient(top, #003b76, #00376e); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) ); - background-image: -webkit-linear-gradient(top, #003b76, #00376e); - background-image: -o-linear-gradient(top, #003b76, #00376e); - background-image: linear-gradient(top, #003b76, #00376e); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', - endColorstr='#00376e', GradientType=0 ); } #footer div#footer-inner p { @@ -1808,30 +1837,18 @@ clear: both; overflow: hidden; position: relative; - background-color: #eedc94; background-repeat : repeat-x; - background-image : -khtml-gradient( linear, left top, left bottom, - from( #fceec1), to( #eedc94)); background-image : -moz-linear-gradient( - top, #003b76, #00376e); background-image : -ms-linear-gradient( top, - #003b76, #00376e); background-image : -webkit-gradient( linear, left - top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e)); + background-color: #003B76; + background-repeat : repeat-x; + background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E)); + background-image : -moz-linear-gradient( top, #003b76, #00376e); + background-image : -ms-linear-gradient( top, #003b76, #00376e); + background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e)); background-image : -webkit-linear-gradient( top, #003b76, #00376e)); background-image : -o-linear-gradient( top, #003b76, #00376e)); - background-image : linear-gradient( top, #003b76, #00376e); filter : - progid : DXImageTransform.Microsoft.gradient ( startColorstr = - '#003b76', endColorstr = '#00376e', GradientType = 0); + background-image : linear-gradient( top, #003b76, #00376e); + filter : progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0); margin: 0 auto; padding: 0; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), - to(#eedc94) ); - background-image: -moz-linear-gradient(top, #003b76, #00376e); - background-image: -ms-linear-gradient(top, #003b76, #00376e); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) ); - background-image: -webkit-linear-gradient(top, #003b76, #00376e); - background-image: -o-linear-gradient(top, #003b76, #00376e); - background-image: linear-gradient(top, #003b76, #00376e); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', - endColorstr='#00376e', GradientType=0 ); } #login div.inner { @@ -1908,16 +1925,14 @@ width: 278px; background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), - to(#eedc94) ); + background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) ); background-image: -moz-linear-gradient(top, #003b76, #00376e); background-image: -ms-linear-gradient(top, #003b76, #00376e); background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) ); background-image: -webkit-linear-gradient(top, #003b76, #00376e); background-image: -o-linear-gradient(top, #003b76, #00376e); background-image: linear-gradient(top, #003b76, #00376e); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', - endColorstr='#00376e', GradientType=0 ); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 ); z-index: 999; -webkit-border-radius: 0px 0px 4px 4px; @@ -2060,10 +2075,9 @@ clear: both; overflow: hidden; position: relative; - background-color: #eedc94; + background-color: #003B76; background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), - to(#eedc94) ); + background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) ); background-image: -moz-linear-gradient(top, #003b76, #00376e); background-image: -ms-linear-gradient(top, #003b76, #00376e); background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) ); @@ -2794,12 +2808,12 @@ } .ac .yui-ac { - position: relative; + position: inherit; font-size: 100%; } .ac .perm_ac { - width: 15em; + width: 20em; } .ac .yui-ac-input { @@ -2809,16 +2823,15 @@ .ac .yui-ac-container { position: absolute; top: 1.6em; - width: 100%; + width: auto; } .ac .yui-ac-content { position: absolute; - width: 100%; border: 1px solid gray; background: #fff; - overflow: hidden; z-index: 9050; + } .ac .yui-ac-shadow { @@ -2827,7 +2840,7 @@ background: #000; -moz-opacity: 0.1px; opacity: .10; - filter: alpha(opacity = 10); + filter: alpha(opacity = 10); z-index: 9049; margin: .3em; } @@ -2836,6 +2849,7 @@ width: 100%; margin: 0; padding: 0; + z-index: 9050; } .ac .yui-ac-content li { @@ -2843,15 +2857,28 @@ white-space: nowrap; margin: 0; padding: 2px 5px; + height: 18px; + z-index: 9050; + display: block; + width: auto !important; +} + +.ac .yui-ac-content li .ac-container-wrap{ + width: auto; } .ac .yui-ac-content li.yui-ac-prehighlight { background: #B3D4FF; + z-index: 9050; } .ac .yui-ac-content li.yui-ac-highlight { background: #556CB5; color: #FFF; + z-index: 9050; +} +.ac .yui-ac-bd{ + z-index: 9050; } .follow { @@ -3006,17 +3033,14 @@ .error_msg { background-color: #c43c35; background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), - to(#c43c35) ); + background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35) ); background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35); background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), - color-stop(100%, #c43c35) ); + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35) ); background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35); background-image: -o-linear-gradient(top, #ee5f5b, #c43c35); background-image: linear-gradient(top, #ee5f5b, #c43c35); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', - endColorstr='#c43c35', GradientType=0 ); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#c43c35', GradientType=0 ); border-color: #c43c35 #c43c35 #882a25; } @@ -3024,51 +3048,42 @@ color: #404040 !important; background-color: #eedc94; background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), - to(#eedc94) ); + background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94) ); background-image: -moz-linear-gradient(top, #fceec1, #eedc94); background-image: -ms-linear-gradient(top, #fceec1, #eedc94); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), - color-stop(100%, #eedc94) ); + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94) ); background-image: -webkit-linear-gradient(top, #fceec1, #eedc94); background-image: -o-linear-gradient(top, #fceec1, #eedc94); background-image: linear-gradient(top, #fceec1, #eedc94); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', - endColorstr='#eedc94', GradientType=0 ); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0 ); border-color: #eedc94 #eedc94 #e4c652; } .success_msg { background-color: #57a957; background-repeat: repeat-x !important; - background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), - to(#57a957) ); + background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957) ); background-image: -moz-linear-gradient(top, #62c462, #57a957); background-image: -ms-linear-gradient(top, #62c462, #57a957); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), - color-stop(100%, #57a957) ); + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957) ); background-image: -webkit-linear-gradient(top, #62c462, #57a957); background-image: -o-linear-gradient(top, #62c462, #57a957); background-image: linear-gradient(top, #62c462, #57a957); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', - endColorstr='#57a957', GradientType=0 ); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0 ); border-color: #57a957 #57a957 #3d773d; } .notice_msg { background-color: #339bb9; background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), - to(#339bb9) ); + background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9) ); background-image: -moz-linear-gradient(top, #5bc0de, #339bb9); background-image: -ms-linear-gradient(top, #5bc0de, #339bb9); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), - color-stop(100%, #339bb9) ); + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9) ); background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9); background-image: -o-linear-gradient(top, #5bc0de, #339bb9); background-image: linear-gradient(top, #5bc0de, #339bb9); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', - endColorstr='#339bb9', GradientType=0 ); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0 ); border-color: #339bb9 #339bb9 #22697d; } @@ -3096,8 +3111,7 @@ } #msg_close { - background: transparent url("../icons/cross_grey_small.png") no-repeat - scroll 0 0; + background: transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0; cursor: pointer; height: 16px; position: absolute; @@ -3105,7 +3119,12 @@ top: 5px; width: 16px; } - +div#legend_data{ + padding-left:10px; +} +div#legend_container table{ + border: none !important; +} div#legend_container table,div#legend_choices table { width: auto !important; } @@ -4115,6 +4134,56 @@ padding:5px 0px 5px 38px; } +/**** + PERMS +*****/ +#perms .perms_section_head { + padding:10px 10px 10px 0px; + font-size:16px; + font-weight: bold; +} + +#perms .perm_tag{ + padding: 1px 3px 1px 3px; + font-size: 10px; + font-weight: bold; + text-transform: uppercase; + white-space: nowrap; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +#perms .perm_tag.admin{ + background-color: #B94A48; + color: #ffffff; +} + +#perms .perm_tag.write{ + background-color: #B94A48; + color: #ffffff; +} + +#perms .perm_tag.read{ + background-color: #468847; + color: #ffffff; +} + +#perms .perm_tag.none{ + background-color: #bfbfbf; + color: #ffffff; +} + +.perm-gravatar{ + vertical-align:middle; + padding:2px; +} +.perm-gravatar-ac{ + vertical-align:middle; + padding:2px; + width: 14px; + height: 14px; +} /***************************************************************************** DIFFS CSS diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/public/js/rhodecode.js --- a/rhodecode/public/js/rhodecode.js Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/public/js/rhodecode.js Wed Mar 28 19:54:16 2012 +0200 @@ -609,6 +609,178 @@ }; +/** MEMBERS AUTOCOMPLETE WIDGET **/ + +var MembersAutoComplete = function (users_list, groups_list, group_lbl, members_lbl) { + var myUsers = users_list; + var myGroups = groups_list; + + // Define a custom search function for the DataSource of users + var matchUsers = function (sQuery) { + // Case insensitive matching + var query = sQuery.toLowerCase(); + var i = 0; + var l = myUsers.length; + var matches = []; + + // Match against each name of each contact + for (; i < l; i++) { + contact = myUsers[i]; + if ((contact.fname.toLowerCase().indexOf(query) > -1) || (contact.lname.toLowerCase().indexOf(query) > -1) || (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) { + matches[matches.length] = contact; + } + } + return matches; + }; + + // Define a custom search function for the DataSource of usersGroups + var matchGroups = function (sQuery) { + // Case insensitive matching + var query = sQuery.toLowerCase(); + var i = 0; + var l = myGroups.length; + var matches = []; + + // Match against each name of each contact + for (; i < l; i++) { + matched_group = myGroups[i]; + if (matched_group.grname.toLowerCase().indexOf(query) > -1) { + matches[matches.length] = matched_group; + } + } + return matches; + }; + + //match all + var matchAll = function (sQuery) { + u = matchUsers(sQuery); + g = matchGroups(sQuery); + return u.concat(g); + }; + + // DataScheme for members + var memberDS = new YAHOO.util.FunctionDataSource(matchAll); + memberDS.responseSchema = { + fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk"] + }; + + // DataScheme for owner + var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers); + ownerDS.responseSchema = { + fields: ["id", "fname", "lname", "nname", "gravatar_lnk"] + }; + + // Instantiate AutoComplete for perms + var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS); + membersAC.useShadow = false; + membersAC.resultTypeList = false; + + // Instantiate AutoComplete for owner + var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS); + ownerAC.useShadow = false; + ownerAC.resultTypeList = false; + + + // Helper highlight function for the formatter + var highlightMatch = function (full, snippet, matchindex) { + return full.substring(0, matchindex) + + "" + + full.substr(matchindex, snippet.length) + + "" + full.substring(matchindex + snippet.length); + }; + + // Custom formatter to highlight the matching letters + var custom_formatter = function (oResultData, sQuery, sResultMatch) { + var query = sQuery.toLowerCase(); + var _gravatar = function(res, em, group){ + if (group !== undefined){ + em = '/images/icons/group.png' + } + tmpl = '
{1}
' + return tmpl.format(em,res) + } + // group + if (oResultData.grname != undefined) { + var grname = oResultData.grname; + var grmembers = oResultData.grmembers; + var grnameMatchIndex = grname.toLowerCase().indexOf(query); + var grprefix = "{0}: ".format(group_lbl); + var grsuffix = " (" + grmembers + " )"; + var grsuffix = " ({0} {1})".format(grmembers, members_lbl); + + if (grnameMatchIndex > -1) { + return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,true); + } + return _gravatar(grprefix + oResultData.grname + grsuffix, null,true); + // Users + } else if (oResultData.fname != undefined) { + var fname = oResultData.fname, + lname = oResultData.lname, + nname = oResultData.nname || "", + // Guard against null value + fnameMatchIndex = fname.toLowerCase().indexOf(query), + lnameMatchIndex = lname.toLowerCase().indexOf(query), + nnameMatchIndex = nname.toLowerCase().indexOf(query), + displayfname, displaylname, displaynname; + + if (fnameMatchIndex > -1) { + displayfname = highlightMatch(fname, query, fnameMatchIndex); + } else { + displayfname = fname; + } + + if (lnameMatchIndex > -1) { + displaylname = highlightMatch(lname, query, lnameMatchIndex); + } else { + displaylname = lname; + } + + if (nnameMatchIndex > -1) { + displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")"; + } else { + displaynname = nname ? "(" + nname + ")" : ""; + } + + return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk); + } else { + return ''; + } + }; + membersAC.formatResult = custom_formatter; + ownerAC.formatResult = custom_formatter; + + var myHandler = function (sType, aArgs) { + + var myAC = aArgs[0]; // reference back to the AC instance + var elLI = aArgs[1]; // reference to the selected LI element + var oData = aArgs[2]; // object literal of selected item's result data + //fill the autocomplete with value + if (oData.nname != undefined) { + //users + myAC.getInputEl().value = oData.nname; + YUD.get('perm_new_member_type').value = 'user'; + } else { + //groups + myAC.getInputEl().value = oData.grname; + YUD.get('perm_new_member_type').value = 'users_group'; + } + }; + + membersAC.itemSelectEvent.subscribe(myHandler); + if(ownerAC.itemSelectEvent){ + ownerAC.itemSelectEvent.subscribe(myHandler); + } + + return { + memberDS: memberDS, + ownerDS: ownerDS, + membersAC: membersAC, + ownerAC: ownerAC, + }; +} + + + /** * QUICK REPO MENU */ @@ -700,6 +872,19 @@ return compState; }; +var permNameSort = function(a, b, desc, field) { + var a_ = fromHTML(a.getData(field)); + var b_ = fromHTML(b.getData(field)); + // extract name from table + + a_ = a_.children[0].innerHTML; + b_ = b_.children[0].innerHTML; + + var comp = YAHOO.util.Sort.compare; + var compState = comp(a_, b_, desc); + return compState; +}; + var groupNameSort = function(a, b, desc, field) { var a_ = fromHTML(a.getData(field)); var b_ = fromHTML(b.getData(field)); diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/templates/_data_table/_dt_elements.html --- a/rhodecode/templates/_data_table/_dt_elements.html Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/templates/_data_table/_dt_elements.html Wed Mar 28 19:54:16 2012 +0200 @@ -40,7 +40,14 @@ -<%def name="repo_name(name,rtype,private,fork_of)"> +<%def name="repo_name(name,rtype,private,fork_of,short_name=False, admin=False)"> + <% + def get_name(name,short_name=short_name): + if short_name: + return name.split('/')[-1] + else: + return name + %>
##TYPE OF REPO %if h.is_hg(rtype): @@ -57,7 +64,11 @@ %endif ##NAME - ${h.link_to(name,h.url('summary_home',repo_name=name),class_="repo_name")} + %if admin: + ${h.link_to(get_name(name),h.url('edit_repo',repo_name=name),class_="repo_name")} + %else: + ${h.link_to(get_name(name),h.url('summary_home',repo_name=name),class_="repo_name")} + %endif %if fork_of: ${_('fork')} diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/templates/admin/repos/repo_add_base.html --- a/rhodecode/templates/admin/repos/repo_add_base.html Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/templates/admin/repos/repo_add_base.html Wed Mar 28 19:54:16 2012 +0200 @@ -21,6 +21,7 @@
${h.text('clone_uri',class_="small")} + ${_('Optional http[s] url from which repository should be cloned.')}
@@ -28,7 +29,8 @@
- ${h.select('repo_group','',c.repo_groups,class_="medium")} + ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")} + ${_('Optional select a group to put this repository into.')}
@@ -37,6 +39,7 @@
${h.select('repo_type','hg',c.backends,class_="small")} + ${_('Type of repository to create.')}
@@ -44,15 +47,17 @@
- ${h.textarea('description',cols=23,rows=5)} + ${h.textarea('description')} + ${_('Keep it short and to the point. Use a README file for longer descriptions.')}
- +
${h.checkbox('private',value="True")} + ${_('Private repositories are only visible to people explicitly added as collaborators.')}
diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/templates/admin/repos/repo_edit.html --- a/rhodecode/templates/admin/repos/repo_edit.html Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/templates/admin/repos/repo_edit.html Wed Mar 28 19:54:16 2012 +0200 @@ -41,6 +41,7 @@
${h.text('clone_uri',class_="medium")} + ${_('Optional http[s] url from which repository should be cloned.')}
@@ -49,6 +50,7 @@
${h.select('repo_group','',c.repo_groups,class_="medium")} + ${_('Optional select a group to put this repository into.')}
@@ -64,16 +66,18 @@
- ${h.textarea('description',cols=23,rows=5)} + ${h.textarea('description')} + ${_('Keep it short and to the point. Use a README file for longer descriptions.')}
- +
${h.checkbox('private',value="True")} + ${_('Private repositories are only visible to people explicitly added as collaborators.')}
@@ -82,6 +86,7 @@
${h.checkbox('enable_statistics',value="True")} + ${_('Enable statistics window on summary page.')}
@@ -90,15 +95,17 @@
${h.checkbox('enable_downloads',value="True")} + ${_('Enable download menu on summary page.')}
-
+
${h.text('user',class_='yui-ac-input')} + ${_('Change owner of this repository.')}
diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/templates/admin/repos/repo_edit_perms.html --- a/rhodecode/templates/admin/repos/repo_edit_perms.html Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/templates/admin/repos/repo_edit_perms.html Wed Mar 28 19:54:16 2012 +0200 @@ -25,7 +25,7 @@ ${h.radio('u_perm_%s' % r2p.user.username,'repository.write')} ${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')} - ${r2p.user.username} + ${r2p.user.username} %if r2p.user.username !='default': @@ -46,7 +46,7 @@ ${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')} ${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')} - ${g2p.users_group.users_group_name} + ${g2p.users_group.users_group_name} @@ -117,165 +117,12 @@ YUD.setStyle('add_perm', 'opacity', '0.6'); YUD.setStyle('add_perm', 'cursor', 'default'); }); + MembersAutoComplete( + ${c.users_array|n}, + ${c.users_groups_array|n}, + "${_('Group')}", + "${_('members')}" + ); }); -YAHOO.example.FnMultipleFields = function () { - var myUsers = ${c.users_array|n}; - var myGroups = ${c.users_groups_array|n}; - - // Define a custom search function for the DataSource of users - var matchUsers = function (sQuery) { - // Case insensitive matching - var query = sQuery.toLowerCase(); - var i = 0; - var l = myUsers.length; - var matches = []; - - // Match against each name of each contact - for (; i < l; i++) { - contact = myUsers[i]; - if ((contact.fname.toLowerCase().indexOf(query) > -1) || (contact.lname.toLowerCase().indexOf(query) > -1) || (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) { - matches[matches.length] = contact; - } - } - return matches; - }; - - // Define a custom search function for the DataSource of usersGroups - var matchGroups = function (sQuery) { - // Case insensitive matching - var query = sQuery.toLowerCase(); - var i = 0; - var l = myGroups.length; - var matches = []; - - // Match against each name of each contact - for (; i < l; i++) { - matched_group = myGroups[i]; - if (matched_group.grname.toLowerCase().indexOf(query) > -1) { - matches[matches.length] = matched_group; - } - } - return matches; - }; - - //match all - var matchAll = function (sQuery) { - u = matchUsers(sQuery); - g = matchGroups(sQuery); - return u.concat(g); - }; - - // DataScheme for members - var memberDS = new YAHOO.util.FunctionDataSource(matchAll); - memberDS.responseSchema = { - fields: ["id", "fname", "lname", "nname", "grname", "grmembers"] - }; - - // DataScheme for owner - var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers); - ownerDS.responseSchema = { - fields: ["id", "fname", "lname", "nname"] - }; - - // Instantiate AutoComplete for perms - var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS); - membersAC.useShadow = false; - membersAC.resultTypeList = false; - - // Instantiate AutoComplete for owner - var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS); - ownerAC.useShadow = false; - ownerAC.resultTypeList = false; - - - // Helper highlight function for the formatter - var highlightMatch = function (full, snippet, matchindex) { - return full.substring(0, matchindex) + "" + full.substr(matchindex, snippet.length) + "" + full.substring(matchindex + snippet.length); - }; - - // Custom formatter to highlight the matching letters - var custom_formatter = function (oResultData, sQuery, sResultMatch) { - var query = sQuery.toLowerCase(); - - if (oResultData.grname != undefined) { - var grname = oResultData.grname; - var grmembers = oResultData.grmembers; - var grnameMatchIndex = grname.toLowerCase().indexOf(query); - var grprefix = "${_('Group')}: "; - var grsuffix = " (" + grmembers + " ${_('members')})"; - - if (grnameMatchIndex > -1) { - return grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix; - } - - return grprefix + oResultData.grname + grsuffix; - } else if (oResultData.fname != undefined) { - - var fname = oResultData.fname, - lname = oResultData.lname, - nname = oResultData.nname || "", - // Guard against null value - fnameMatchIndex = fname.toLowerCase().indexOf(query), - lnameMatchIndex = lname.toLowerCase().indexOf(query), - nnameMatchIndex = nname.toLowerCase().indexOf(query), - displayfname, displaylname, displaynname; - - if (fnameMatchIndex > -1) { - displayfname = highlightMatch(fname, query, fnameMatchIndex); - } else { - displayfname = fname; - } - - if (lnameMatchIndex > -1) { - displaylname = highlightMatch(lname, query, lnameMatchIndex); - } else { - displaylname = lname; - } - - if (nnameMatchIndex > -1) { - displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")"; - } else { - displaynname = nname ? "(" + nname + ")" : ""; - } - - return displayfname + " " + displaylname + " " + displaynname; - } else { - return ''; - } - }; - membersAC.formatResult = custom_formatter; - ownerAC.formatResult = custom_formatter; - - var myHandler = function (sType, aArgs) { - - var myAC = aArgs[0]; // reference back to the AC instance - var elLI = aArgs[1]; // reference to the selected LI element - var oData = aArgs[2]; // object literal of selected item's result data - //fill the autocomplete with value - if (oData.nname != undefined) { - //users - myAC.getInputEl().value = oData.nname; - YUD.get('perm_new_member_type').value = 'user'; - } else { - //groups - myAC.getInputEl().value = oData.grname; - YUD.get('perm_new_member_type').value = 'users_group'; - } - - }; - - membersAC.itemSelectEvent.subscribe(myHandler); - if(ownerAC.itemSelectEvent){ - ownerAC.itemSelectEvent.subscribe(myHandler); - } - - return { - memberDS: memberDS, - ownerDS: ownerDS, - membersAC: membersAC, - ownerAC: ownerAC, - }; -}(); - diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/templates/admin/repos/repos.html --- a/rhodecode/templates/admin/repos/repos.html Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/templates/admin/repos/repos.html Wed Mar 28 19:54:16 2012 +0200 @@ -48,7 +48,7 @@ ${dt.quick_menu(repo['name'])} - ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))} + ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'), admin=True)} ##DESCRIPTION diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html --- a/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html Wed Mar 28 19:54:16 2012 +0200 @@ -15,7 +15,7 @@ ${h.radio('u_perm_%s' % r2p.user.username,'group.write')} ${h.radio('u_perm_%s' % r2p.user.username,'group.admin')} - ${r2p.user.username} + ${r2p.user.username} %if r2p.user.username !='default': @@ -35,7 +35,7 @@ ${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')} ${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')} - ${g2p.users_group.users_group_name} + ${g2p.users_group.users_group_name} @@ -68,7 +68,7 @@ diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/templates/admin/repos_groups/repos_groups.html --- a/rhodecode/templates/admin/repos_groups/repos_groups.html Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/templates/admin/repos_groups/repos_groups.html Wed Mar 28 19:54:16 2012 +0200 @@ -17,5 +17,5 @@ ${self.menu('admin')} <%def name="main()"> - <%include file="/index_base.html" args="parent=self"/> + <%include file="/index_base.html" args="parent=self,short_repo_names=True"/> diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/templates/admin/repos_groups/repos_groups_show.html --- a/rhodecode/templates/admin/repos_groups/repos_groups_show.html Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/templates/admin/repos_groups/repos_groups_show.html Wed Mar 28 19:54:16 2012 +0200 @@ -44,7 +44,7 @@
${_('Repositories group')} - ${h.link_to(h.literal(' » '.join([g.name for g in gr.parents+[gr]])),url('edit_repos_group',id=gr.group_id))} + ${h.link_to(h.literal(' » '.join(map(h.safe_unicode,[g.name for g in gr.parents+[gr]]))),url('edit_repos_group',id=gr.group_id))}
${gr.group_description} diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/templates/admin/users/user_edit_my_account.html --- a/rhodecode/templates/admin/users/user_edit_my_account.html Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/templates/admin/users/user_edit_my_account.html Wed Mar 28 19:54:16 2012 +0200 @@ -113,52 +113,45 @@
- ${_('My repositories')} + ${_('My repos')} / ${_('My permissions')}
%if h.HasPermissionAny('hg.admin','hg.create.repository')(): %endif
-
- +
+
+
+ - - + + + + <%namespace name="dt" file="/_data_table/_dt_elements.html"/> %if c.user_repos: %for repo in c.user_repos: - - + ##QUICK MENU + + ##REPO NAME AND ICONS + + ##LAST REVISION +
${_('Name')}${_('revision')}${_('action')}${_('Revision')}${_('Action')}${_('Action')}
- %if h.is_hg(repo['dbrepo']['repo_type']): - ${_('Mercurial repository')} - %elif h.is_git(repo['dbrepo']['repo_type']): - ${_('Git repository')} - %else: - - %endif - %if repo['dbrepo']['private']: - ${_('private')} - %else: - ${_('public')} - %endif - - ${h.link_to(repo['name'], h.url('summary_home',repo_name=repo['name']),class_="repo_name")} - %if repo['dbrepo_fork']: - - ${_('public')} - %endif - ${("r%s:%s") % (repo['rev'],h.short_id(repo['tip']))} + ${dt.quick_menu(repo['name'])} + + ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))} + + ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])} + ${_('private')} ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')} @@ -177,14 +170,144 @@ %endif
+
+
+
diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/templates/admin/users_groups/users_groups.html --- a/rhodecode/templates/admin/users_groups/users_groups.html Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/templates/admin/users_groups/users_groups.html Wed Mar 28 19:54:16 2012 +0200 @@ -37,7 +37,7 @@ %for cnt,u_group in enumerate(c.users_groups_list): ${h.link_to(u_group.users_group_name,h.url('edit_users_group', id=u_group.users_group_id))} - ${len(u_group.members)} + ${len(u_group.members)} ${h.bool2icon(u_group.users_group_active)} ${h.form(url('users_group', id=u_group.users_group_id),method='delete')} diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/templates/base/root.html --- a/rhodecode/templates/base/root.html Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/templates/base/root.html Wed Mar 28 19:54:16 2012 +0200 @@ -1,5 +1,5 @@ ## -*- coding: utf-8 -*- - + ${self.title()} @@ -37,7 +37,7 @@ ## JAVASCRIPT ## <%def name="js()"> - @@ -130,6 +130,17 @@ ${self.js()} - ${next.body()} + ## IE hacks + + + + + ${next.body()} diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/templates/changelog/changelog.html --- a/rhodecode/templates/changelog/changelog.html Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/templates/changelog/changelog.html Wed Mar 28 19:54:16 2012 +0200 @@ -93,11 +93,11 @@ %endif %if h.is_hg(c.rhodecode_repo) and cs.branch: - ${h.link_to(cs.branch,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))} + ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))} %endif %for tag in cs.tags: - ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))} + ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))} %endfor diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/templates/changeset/changeset.html --- a/rhodecode/templates/changeset/changeset.html Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/templates/changeset/changeset.html Wed Mar 28 19:54:16 2012 +0200 @@ -36,8 +36,8 @@
- ${c.ignorews_url()} - ${c.context_url()} + ${c.ignorews_url(request.GET)} + ${c.context_url(request.GET)}
${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})
@@ -91,14 +91,14 @@ - ${_('%s files affected with %s additions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)} + ${_('%s files affected with %s insertions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
%for change,filenode,diff,cs1,cs2,stat in c.changes:
%if change != 'removed': - ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path)+"_target")} + ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path,request.GET)+"_target")} %else: ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))} %endif diff -r 8fd6650bb436 -r dc2584ba5fbc rhodecode/templates/changeset/diff_block.html --- a/rhodecode/templates/changeset/diff_block.html Sat Mar 03 03:41:19 2012 +0200 +++ b/rhodecode/templates/changeset/diff_block.html Wed Mar 28 19:54:16 2012 +0200 @@ -7,7 +7,7 @@ %for change,filenode,diff,cs1,cs2,stat in changes: %if change !='removed': -
+
@@ -19,8 +19,8 @@ - ${c.ignorews_url(h.FID(filenode.changeset.raw_id,filenode.path))} - ${c.context_url(h.FID(filenode.changeset.raw_id,filenode.path))} + ${c.ignorews_url(request.GET, h.FID(filenode.changeset.raw_id,filenode.path))} + ${c.context_url(request.GET, h.FID(filenode.changeset.raw_id,filenode.path))}