changeset 1614:59ae82850e76 beta

Merge with upstream
author Liad Shani <liadff@gmail.com>
date Sun, 09 Oct 2011 23:49:00 +0200
parents 6cab36e31f09 (current diff) c307c920f94c (diff)
children 51bd5404529c
files rhodecode/lib/auth.py rhodecode/lib/base.py rhodecode/lib/middleware/simplehg.py rhodecode/lib/odict.py rhodecode/lib/oset.py
diffstat 50 files changed, 1307 insertions(+), 749 deletions(-) [+]
line wrap: on
line diff
--- a/README.rst	Tue Sep 27 22:20:24 2011 +0300
+++ b/README.rst	Sun Oct 09 23:49:00 2011 +0200
@@ -2,10 +2,11 @@
 Welcome to RhodeCode (RhodiumCode) documentation!
 =================================================
 
-``RhodeCode`` (formerly hg-app) is a Pylons framework based Mercurial repository 
+``RhodeCode`` is a Pylons framework based Mercurial repository 
 browser/management tool with a built in push/pull server and full text search.
 It works on http/https and has a built in permission/authentication system with 
-the ability to authenticate via LDAP.
+the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also supports
+simple API so it's easy integrable with existing systems.
 
 RhodeCode is similar in some respects to github or bitbucket_, 
 however RhodeCode can be run as standalone hosted application on your own server.  
--- a/docs/api/api.rst	Tue Sep 27 22:20:24 2011 +0300
+++ b/docs/api/api.rst	Sun Oct 09 23:49:00 2011 +0200
@@ -6,11 +6,12 @@
 
 
 Starting from RhodeCode version 1.2 a simple API was implemented.
-There's one schema for calling all api methods. API is implemented
-with JSON protocol both ways. 
+There's a single schema for calling all api methods. API is implemented
+with JSON protocol both ways. An url to send API request in RhodeCode is 
+<your_server>/_admin/api
 
 
-Clients need to send JSON data in such format::
+All clients need to send JSON data in such format::
 
     {
         "api_key":"<api_key>",
@@ -18,16 +19,20 @@
         "args":{"<arg_key>":"<arg_val>"}
     }
 
-Simply provide api_key for access and permission validation
-method is name of method to call
-and args is an key:value list of arguments to pass to method
+Example call for autopulling remotes repos using curl::
+    curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
+
+Simply provide 
+ - *api_key* for access and permission validation.
+ - *method* is name of method to call
+ - *args* is an key:value list of arguments to pass to method
     
 .. note::
     
     api_key can be found in your user account page    
     
     
-And will receive JSON formatted answer::
+RhodeCode API will return always a JSON formatted answer::
     
     {
         "result": "<result>", 
@@ -35,7 +40,7 @@
     }
 
 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
-calling api **error** key from response will contain failure description 
+calling api *error* key from response will contain failure description 
 and result will be null.
 
 API METHODS
@@ -46,11 +51,61 @@
 ----
 
 Pulls given repo from remote location. Can be used to automatically keep 
-remote repos upto date. This command can be executed only using admin users
-api_key
+remote repos up to date. This command can be executed only using api_key 
+belonging to user with admin rights
 
-::
-    
+INPUT::
+
+    api_key:"<api_key>"
     method: "pull"
     args: {"repo":<repo_name>}
 
+OUTPUT::
+
+    result:"Pulled from <repo_name>"
+    error:null
+
+    
+create_user
+-----------
+
+Creates new user in RhodeCode. This command can be executed only using api_key 
+belonging to user with admin rights
+
+INPUT::
+
+    api_key:"<api_key>"
+    method: "create_user"
+    args: {"username": "<username>", 
+           "password": "<password>", 
+           "active":   "<bool>", 
+           "admin":    "<bool>", 
+           "name":     "<firstname>", 
+           "lastname": "<lastname>", 
+           "email":    "<useremail>"}
+
+OUTPUT::
+
+    result:{"id": <newuserid>,
+            "msg":"created new user <username>"}
+    error:null
+    
+    
+create_users_group
+------------------
+
+creates new users group. This command can be executed only using api_key 
+belonging to user with admin rights
+
+INPUT::
+
+    api_key:"<api_key>"
+    method: "create_user"
+    args: {"name":  "<groupname>", 
+           "active":"<bool>"}
+
+OUTPUT::
+
+    result:{"id": <newusersgroupid>,
+            "msg":"created new users group <groupname>"}
+    error:null    
--- a/docs/changelog.rst	Tue Sep 27 22:20:24 2011 +0300
+++ b/docs/changelog.rst	Sun Oct 09 23:49:00 2011 +0200
@@ -3,7 +3,8 @@
 Changelog
 =========
 
-1.2.0 (**2011-XX-XX**)
+
+1.3.0 (**XXXX-XX-XX**)
 ======================
 
 :status: in-progress
@@ -12,6 +13,29 @@
 news
 ----
 
+fixes
+-----
+
+1.2.1 (**2011-10-08**)
+======================
+
+news
+----
+
+
+fixes
+-----
+
+- fixed problems with basic auth and push problems 
+- gui fixes
+- fixed logger
+
+1.2.0 (**2011-10-07**)
+======================
+
+news
+----
+
 - implemented #47 repository groups
 - implemented #89 Can setup google analytics code from settings menu
 - implemented #91 added nicer looking archive urls with more download options
Binary file docs/images/screenshot1_main_page.png has changed
Binary file docs/images/screenshot2_summary_page.png has changed
Binary file docs/images/screenshot3_changelog_page.png has changed
--- a/rhodecode/__init__.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/__init__.py	Sun Oct 09 23:49:00 2011 +0200
@@ -25,9 +25,9 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import platform
 
-VERSION = (1, 2, 0, 'beta')
+VERSION = (1, 3, 0, 'beta')
 __version__ = '.'.join((str(each) for each in VERSION[:4]))
-__dbversion__ = 3 #defines current db version for migrations
+__dbversion__ = 4 #defines current db version for migrations
 __platform__ = platform.system()
 __license__ = 'GPLv3'
 
--- a/rhodecode/config/middleware.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/config/middleware.py	Sun Oct 09 23:49:00 2011 +0200
@@ -51,6 +51,9 @@
         from rhodecode.lib.profiler import ProfilingMiddleware
         app = ProfilingMiddleware(app)
 
+
+    # we want our low level middleware to get to the request ASAP. We don't
+    # need any pylons stack middleware in them
     app = SimpleHg(app, config)
     app = SimpleGit(app, config)
 
@@ -77,6 +80,7 @@
         app = Cascade([static_app, app])
         app = make_gzip_middleware(app, global_conf, compress_level=1)
 
+
     app.config = config
 
     return app
--- a/rhodecode/config/routing.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/config/routing.py	Sun Oct 09 23:49:00 2011 +0200
@@ -7,7 +7,7 @@
 """
 from __future__ import with_statement
 from routes import Mapper
-from rhodecode.lib.utils import check_repo_fast as cr
+
 
 # prefix for non repository related links needs to be prefixed with `/`
 ADMIN_PREFIX = '/_admin'
@@ -19,23 +19,36 @@
                  always_scan=config['debug'])
     rmap.minimization = False
     rmap.explicit = False
-
+    
+    from rhodecode.lib.utils import is_valid_repo
+    from rhodecode.lib.utils import is_valid_repos_group
+    
     def check_repo(environ, match_dict):
         """
         check for valid repository for proper 404 handling
+        
         :param environ:
         :param match_dict:
         """
+         
         repo_name = match_dict.get('repo_name')
-        return not cr(repo_name, config['base_path'])
+        return is_valid_repo(repo_name, config['base_path'])
+
+    def check_group(environ, match_dict):
+        """
+        check for valid repositories group for proper 404 handling
+        
+        :param environ:
+        :param match_dict:
+        """
+        repos_group_name = match_dict.get('group_name')
+        
+        return is_valid_repos_group(repos_group_name, config['base_path'])
 
 
     def check_int(environ, match_dict):
         return match_dict.get('id').isdigit()
 
-
-
-
     # The ErrorController route (handles 404/500 error pages); it should
     # likely stay at the top, ensuring it can always be resolved
     rmap.connect('/error/{action}', controller='error')
@@ -319,6 +332,14 @@
     #==========================================================================
     # REPOSITORY ROUTES
     #==========================================================================
+    rmap.connect('summary_home', '/{repo_name:.*}',
+                controller='summary', 
+                conditions=dict(function=check_repo))
+    
+#    rmap.connect('repo_group_home', '/{group_name:.*}',
+#                controller='admin/repos_groups',action="show_by_name", 
+#                conditions=dict(function=check_group))
+    
     rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
                 controller='changeset', revision='tip',
                 conditions=dict(function=check_repo))
@@ -328,9 +349,6 @@
                  controller='changeset', action='raw_changeset',
                  revision='tip', conditions=dict(function=check_repo))
 
-    rmap.connect('summary_home', '/{repo_name:.*}',
-                controller='summary', conditions=dict(function=check_repo))
-
     rmap.connect('summary_home', '/{repo_name:.*}/summary',
                 controller='summary', conditions=dict(function=check_repo))
 
--- a/rhodecode/controllers/admin/permissions.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/controllers/admin/permissions.py	Sun Oct 09 23:49:00 2011 +0200
@@ -23,19 +23,21 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import logging
+import traceback
+import formencode
 from formencode import htmlfill
+
 from pylons import request, session, tmpl_context as c, url
 from pylons.controllers.util import abort, redirect
 from pylons.i18n.translation import _
+
 from rhodecode.lib import helpers as h
 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
 from rhodecode.lib.base import BaseController, render
-from rhodecode.model.forms import LdapSettingsForm, DefaultPermissionsForm
+from rhodecode.model.forms import DefaultPermissionsForm
 from rhodecode.model.permission import PermissionModel
-from rhodecode.model.user import UserModel
-import formencode
-import logging
-import traceback
+from rhodecode.model.db import User
 
 log = logging.getLogger(__name__)
 
@@ -142,7 +144,7 @@
         c.create_choices = self.create_choices
 
         if id == 'default':
-            default_user = UserModel().get_by_username('default')
+            default_user = User.get_by_username('default')
             defaults = {'_method': 'put',
                         'anonymous': default_user.active}
 
--- a/rhodecode/controllers/admin/repos.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/controllers/admin/repos.py	Sun Oct 09 23:49:00 2011 +0200
@@ -89,7 +89,7 @@
         """
         self.__load_defaults()
 
-        c.repo_info = db_repo = Repository.by_repo_name(repo_name)
+        c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
         repo = scm_repo = db_repo.scm_instance
 
         if c.repo_info is None:
@@ -101,7 +101,7 @@
 
             return redirect(url('repos'))
 
-        c.default_user_id = User.by_username('default').user_id
+        c.default_user_id = User.get_by_username('default').user_id
         c.in_public_journal = self.sa.query(UserFollowing)\
             .filter(UserFollowing.user_id == c.default_user_id)\
             .filter(UserFollowing.follows_repository == c.repo_info).scalar()
@@ -381,8 +381,8 @@
         token = get_token()
         if cur_token == token:
             try:
-                repo_id = Repository.by_repo_name(repo_name).repo_id
-                user_id = User.by_username('default').user_id
+                repo_id = Repository.get_by_repo_name(repo_name).repo_id
+                user_id = User.get_by_username('default').user_id
                 self.scm_model.toggle_following_repo(repo_id, user_id)
                 h.flash(_('Updated repository visibility in public journal'),
                         category='success')
--- a/rhodecode/controllers/admin/settings.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/controllers/admin/settings.py	Sun Oct 09 23:49:00 2011 +0200
@@ -46,6 +46,7 @@
     ApplicationUiSettingsForm
 from rhodecode.model.scm import ScmModel
 from rhodecode.model.user import UserModel
+from rhodecode.model.db import User
 
 log = logging.getLogger(__name__)
 
@@ -299,7 +300,7 @@
         """
         # url('admin_settings_my_account')
 
-        c.user = UserModel().get(self.rhodecode_user.user_id, cache=False)
+        c.user = User.get(self.rhodecode_user.user_id)
         all_repos = self.sa.query(Repository)\
                      .filter(Repository.user_id == c.user.user_id)\
                      .order_by(func.lower(Repository.repo_name)).all()
@@ -340,8 +341,7 @@
                     category='success')
 
         except formencode.Invalid, errors:
-            c.user = user_model.get(self.rhodecode_user.user_id, cache=False)
-            c.user = UserModel().get(self.rhodecode_user.user_id, cache=False)
+            c.user = User.get(self.rhodecode_user.user_id)
             all_repos = self.sa.query(Repository)\
                 .filter(Repository.user_id == c.user.user_id)\
                 .order_by(func.lower(Repository.repo_name))\
--- a/rhodecode/controllers/api/__init__.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/controllers/api/__init__.py	Sun Oct 09 23:49:00 2011 +0200
@@ -26,10 +26,12 @@
 # MA  02110-1301, USA.
 
 import inspect
-import json
 import logging
 import types
 import urllib
+import traceback
+
+from rhodecode.lib.compat import izip_longest, json
 
 from paste.response import replace_header
 
@@ -39,7 +41,7 @@
 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
 HTTPBadRequest, HTTPError
 
-from rhodecode.model.user import User
+from rhodecode.model.db import User
 from rhodecode.lib.auth import AuthUser
 
 log = logging.getLogger('JSONRPC')
@@ -85,10 +87,9 @@
         Parse the request body as JSON, look up the method on the
         controller and if it exists, dispatch to it.
         """
-
         if 'CONTENT_LENGTH' not in environ:
             log.debug("No Content-Length")
-            return jsonrpc_error(0, "No Content-Length")
+            return jsonrpc_error(message="No Content-Length in request")
         else:
             length = environ['CONTENT_LENGTH'] or 0
             length = int(environ['CONTENT_LENGTH'])
@@ -96,20 +97,18 @@
 
         if length == 0:
             log.debug("Content-Length is 0")
-            return jsonrpc_error(0, "Content-Length is 0")
+            return jsonrpc_error(message="Content-Length is 0")
 
         raw_body = environ['wsgi.input'].read(length)
 
         try:
             json_body = json.loads(urllib.unquote_plus(raw_body))
-        except ValueError as e:
+        except ValueError, e:
             #catch JSON errors Here
-            return jsonrpc_error("JSON parse error ERR:%s RAW:%r" \
+            return jsonrpc_error(message="JSON parse error ERR:%s RAW:%r" \
                                  % (e, urllib.unquote_plus(raw_body)))
 
-
         #check AUTH based on API KEY
-
         try:
             self._req_api_key = json_body['api_key']
             self._req_method = json_body['method']
@@ -117,47 +116,61 @@
             log.debug('method: %s, params: %s',
                       self._req_method,
                       self._req_params)
-        except KeyError as e:
+        except KeyError, e:
             return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
 
         #check if we can find this session using api_key
         try:
             u = User.get_by_api_key(self._req_api_key)
             auth_u = AuthUser(u.user_id, self._req_api_key)
-        except Exception as e:
+        except Exception, e:
             return jsonrpc_error(message='Invalid API KEY')
 
         self._error = None
         try:
             self._func = self._find_method()
         except AttributeError, e:
-            return jsonrpc_error(str(e))
+            return jsonrpc_error(message=str(e))
 
         # now that we have a method, add self._req_params to
         # self.kargs and dispatch control to WGIController
-        arglist = inspect.getargspec(self._func)[0][1:]
+        argspec = inspect.getargspec(self._func)
+        arglist = argspec[0][1:]
+        defaults = argspec[3] or []
+        default_empty = types.NotImplementedType
+
+        kwarglist = list(izip_longest(reversed(arglist), reversed(defaults),
+                                fillvalue=default_empty))
 
         # this is little trick to inject logged in user for 
         # perms decorators to work they expect the controller class to have
-        # rhodecode_user set
+        # rhodecode_user attribute set
         self.rhodecode_user = auth_u
 
-        if 'user' not in arglist:
-            return jsonrpc_error('This method [%s] does not support '
-                                 'authentication (missing user param)' %
-                                 self._func.__name__)
+        # This attribute will need to be first param of a method that uses
+        # api_key, which is translated to instance of user at that name
+        USER_SESSION_ATTR = 'apiuser'
+
+        if USER_SESSION_ATTR not in arglist:
+            return jsonrpc_error(message='This method [%s] does not support '
+                                 'authentication (missing %s param)' %
+                                 (self._func.__name__, USER_SESSION_ATTR))
 
         # get our arglist and check if we provided them as args
-        for arg in arglist:
-            if arg == 'user':
-                # user is something translated from api key and this is
-                # checked before
+        for arg, default in kwarglist:
+            if arg == USER_SESSION_ATTR:
+                # USER_SESSION_ATTR is something translated from api key and 
+                # this is checked before so we don't need validate it
                 continue
 
-            if not self._req_params or arg not in self._req_params:
-                return jsonrpc_error('Missing %s arg in JSON DATA' % arg)
+            # skip the required param check if it's default value is 
+            # NotImplementedType (default_empty)
+            if not self._req_params or (type(default) == default_empty
+                                        and arg not in self._req_params):
+                return jsonrpc_error(message=('Missing non optional %s arg '
+                                              'in JSON DATA') % arg)
 
-        self._rpc_args = dict(user=u)
+        self._rpc_args = {USER_SESSION_ATTR:u}
         self._rpc_args.update(self._req_params)
 
         self._rpc_args['action'] = self._req_method
@@ -186,13 +199,13 @@
         """
         try:
             raw_response = self._inspect_call(self._func)
-            print raw_response
             if isinstance(raw_response, HTTPError):
                 self._error = str(raw_response)
-        except JSONRPCError as e:
+        except JSONRPCError, e:
             self._error = str(e)
-        except Exception as e:
-            log.debug('Encountered unhandled exception: %s', repr(e))
+        except Exception, e:
+            log.error('Encountered unhandled exception: %s' \
+                      % traceback.format_exc())
             json_exc = JSONRPCError('Internal server error')
             self._error = str(json_exc)
 
@@ -226,3 +239,4 @@
             return func
         else:
             raise AttributeError("No such method: %s" % self._req_method)
+
--- a/rhodecode/controllers/api/api.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/controllers/api/api.py	Sun Oct 09 23:49:00 2011 +0200
@@ -1,7 +1,14 @@
+import traceback
+import logging
+
 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
 from rhodecode.lib.auth import HasPermissionAllDecorator
 from rhodecode.model.scm import ScmModel
 
+from rhodecode.model.db import User, UsersGroup, Repository
+
+log = logging.getLogger(__name__)
+
 
 class ApiController(JSONRPCController):
     """
@@ -20,15 +27,18 @@
     """
 
     @HasPermissionAllDecorator('hg.admin')
-    def pull(self, user, repo):
+    def pull(self, apiuser, repo):
         """
         Dispatch pull action on given repo
         
         
-        param user:
-        param repo:
+        :param user:
+        :param repo:
         """
 
+        if Repository.is_valid(repo) is False:
+            raise JSONRPCError('Unknown repo "%s"' % repo)
+        
         try:
             ScmModel().pull_changes(repo, self.rhodecode_user.username)
             return 'Pulled from %s' % repo
@@ -36,5 +46,53 @@
             raise JSONRPCError('Unable to pull changes from "%s"' % repo)
 
 
+    @HasPermissionAllDecorator('hg.admin')
+    def create_user(self, apiuser, username, password, active, admin, name, 
+                    lastname, email):
+        """
+        Creates new user
+        
+        :param apiuser:
+        :param username:
+        :param password:
+        :param active:
+        :param admin:
+        :param name:
+        :param lastname:
+        :param email:
+        """
+        
+        form_data = dict(username=username,
+                         password=password,
+                         active=active,
+                         admin=admin,
+                         name=name,
+                         lastname=lastname,
+                         email=email)
+        try:
+            u = User.create(form_data)
+            return {'id':u.user_id,
+                    'msg':'created new user %s' % name}
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError('failed to create user %s' % name)
 
 
+    @HasPermissionAllDecorator('hg.admin')
+    def create_users_group(self, apiuser, name, active):
+        """
+        Creates an new usergroup
+        
+        :param name:
+        :param active:
+        """
+        form_data = {'users_group_name':name,
+                     'users_group_active':active}
+        try:
+            ug = UsersGroup.create(form_data)
+            return {'id':ug.users_group_id,
+                    'msg':'created new users group %s' % name}
+        except Exception:
+            log.error(traceback.format_exc())
+            raise JSONRPCError('failed to create group %s' % name)
+        
\ No newline at end of file
--- a/rhodecode/controllers/branches.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/controllers/branches.py	Sun Oct 09 23:49:00 2011 +0200
@@ -30,7 +30,7 @@
 
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
-from rhodecode.lib.odict import OrderedDict
+from rhodecode.lib.compat import OrderedDict
 from rhodecode.lib import safe_unicode
 log = logging.getLogger(__name__)
 
--- a/rhodecode/controllers/changelog.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/controllers/changelog.py	Sun Oct 09 23:49:00 2011 +0200
@@ -25,18 +25,13 @@
 
 import logging
 
-try:
-    import json
-except ImportError:
-    #python 2.5 compatibility
-    import simplejson as json
-
 from mercurial import graphmod
 from pylons import request, session, tmpl_context as c
 
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.lib.helpers import RepoPage
+from rhodecode.lib.compat import json
 
 log = logging.getLogger(__name__)
 
--- a/rhodecode/controllers/changeset.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/controllers/changeset.py	Sun Oct 09 23:49:00 2011 +0200
@@ -34,7 +34,7 @@
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.lib.utils import EmptyChangeset
-from rhodecode.lib.odict import OrderedDict
+from rhodecode.lib.compat import OrderedDict
 
 from vcs.exceptions import RepositoryError, ChangesetError, \
 ChangesetDoesNotExistError
--- a/rhodecode/controllers/files.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/controllers/files.py	Sun Oct 09 23:49:00 2011 +0200
@@ -316,6 +316,13 @@
                 filename = file_obj.filename
                 content = file_obj.file
 
+            #TODO: REMOVE THIS !!
+            ################################
+            import ipdb;ipdb.set_trace()
+            print 'setting ipdb debuggin for rhodecode.controllers.files.FilesController.add'
+            ################################
+
+
             node_path = os.path.join(location, filename)
             author = self.rhodecode_user.full_contact
 
--- a/rhodecode/controllers/login.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/controllers/login.py	Sun Oct 09 23:49:00 2011 +0200
@@ -64,8 +64,7 @@
                 c.form_result = login_form.to_python(dict(request.POST))
                 #form checks for username/password, now we're authenticated
                 username = c.form_result['username']
-                user = User.by_username(username,
-                                                   case_insensitive=True)
+                user = User.get_by_username(username, case_insensitive=True)
                 auth_user = AuthUser(user.user_id)
                 auth_user.set_authenticated()
                 session['rhodecode_user'] = auth_user
@@ -95,8 +94,7 @@
     def register(self):
         user_model = UserModel()
         c.auto_active = False
-        for perm in user_model.get_by_username('default',
-                                               cache=False).user_perms:
+        for perm in User.get_by_username('default').user_perms:
             if perm.permission.permission_name == 'hg.register.auto_activate':
                 c.auto_active = True
                 break
--- a/rhodecode/controllers/summary.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/controllers/summary.py	Sun Oct 09 23:49:00 2011 +0200
@@ -39,18 +39,13 @@
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.lib.utils import EmptyChangeset
-from rhodecode.lib.odict import OrderedDict
 
 from rhodecode.lib.celerylib import run_task
 from rhodecode.lib.celerylib.tasks import get_commits_stats, \
     LANGUAGES_EXTENSIONS_MAP
 from rhodecode.lib.helpers import RepoPage
+from rhodecode.lib.compat import json, OrderedDict
 
-try:
-    import json
-except ImportError:
-    #python 2.5 compatibility
-    import simplejson as json
 log = logging.getLogger(__name__)
 
 
@@ -139,9 +134,9 @@
             c.commit_data = stats.commit_activity
             c.overview_data = stats.commit_activity_combined
 
-            lang_stats = [(x, {"count": y,
+            lang_stats = ((x, {"count": y,
                                "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
-                          for x, y in lang_stats_d.items()]
+                          for x, y in lang_stats_d.items())
 
             c.trending_languages = json.dumps(OrderedDict(
                                        sorted(lang_stats, reverse=True,
--- a/rhodecode/controllers/tags.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/controllers/tags.py	Sun Oct 09 23:49:00 2011 +0200
@@ -28,7 +28,7 @@
 
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
-from rhodecode.lib.odict import OrderedDict
+from rhodecode.lib.compat import OrderedDict
 
 log = logging.getLogger(__name__)
 
--- a/rhodecode/lib/__init__.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/lib/__init__.py	Sun Oct 09 23:49:00 2011 +0200
@@ -23,14 +23,6 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-
-try:
-    import json
-except ImportError:
-    #python 2.5 compatibility
-    import simplejson as json
-
-
 def __get_lem():
     from pygments import lexers
     from string import lower
@@ -157,44 +149,69 @@
     return hashlib.sha1(username + salt).hexdigest()
 
 
-def safe_unicode(_str, from_encoding='utf8'):
+def safe_unicode(str_, from_encoding='utf8'):
     """
-    safe unicode function. In case of UnicodeDecode error we try to return
-    unicode with errors replaceed
+    safe unicode function. Does few trick to turn str_ into unicode
+     
+    In case of UnicodeDecode error we try to return it with encoding detected
+    by chardet library if it fails fallback to unicode with errors replaced
 
-    :param _str: string to decode
+    :param str_: string to decode
     :rtype: unicode
     :returns: unicode object
     """
+    if isinstance(str_, unicode):
+        return str_
 
-    if isinstance(_str, unicode):
-        return _str
+    try:
+        return unicode(str_)
+    except UnicodeDecodeError:
+        pass
+
+    try:
+        return unicode(str_, from_encoding)
+    except UnicodeDecodeError:
+        pass
 
     try:
-        u_str = unicode(_str, from_encoding)
-    except UnicodeDecodeError:
-        u_str = unicode(_str, from_encoding, 'replace')
-
-    return u_str
-
+        import chardet
+        encoding = chardet.detect(str_)['encoding']
+        if encoding is None:
+            raise Exception()
+        return str_.decode(encoding)
+    except (ImportError, UnicodeDecodeError, Exception):
+        return unicode(str_, from_encoding, 'replace')
 
-def safe_str(_unicode, to_encoding='utf8'):
+def safe_str(unicode_, to_encoding='utf8'):
     """
-    safe str function. In case of UnicodeEncode error we try to return
-    str with errors replaceed
+    safe str function. Does few trick to turn unicode_ into string
+     
+    In case of UnicodeEncodeError we try to return it with encoding detected
+    by chardet library if it fails fallback to string with errors replaced
 
-    :param _unicode: unicode to encode
+    :param unicode_: unicode to encode
     :rtype: str
     :returns: str object
     """
 
-    if isinstance(_unicode, str):
-        return _unicode
+    if isinstance(unicode_, str):
+        return unicode_
+
+    try:
+        return unicode_.encode(to_encoding)
+    except UnicodeEncodeError:
+        pass
 
     try:
-        safe_str = str(_unicode)
-    except UnicodeEncodeError:
-        safe_str = _unicode.encode(to_encoding, 'replace')
+        import chardet
+        encoding = chardet.detect(unicode_)['encoding']
+        print encoding
+        if encoding is None:
+            raise UnicodeEncodeError()
+
+        return unicode_.encode(encoding)
+    except (ImportError, UnicodeEncodeError):
+        return unicode_.encode(to_encoding, 'replace')
 
     return safe_str
 
@@ -361,4 +378,4 @@
     except RepositoryError:
         from rhodecode.lib.utils import EmptyChangeset
         cs = EmptyChangeset(requested_revision=rev)
-    return cs
\ No newline at end of file
+    return cs
--- a/rhodecode/lib/auth.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/lib/auth.py	Sun Oct 09 23:49:00 2011 +0200
@@ -6,8 +6,8 @@
     authentication and permission libraries
 
     :created_on: Apr 4, 2010
-    :copyright: (c) 2010 by marcink.
-    :license: LICENSE_NAME, see LICENSE_FILE for more details.
+    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :license: GPLv3, see COPYING for more details.
 """
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -48,7 +48,7 @@
 
 from rhodecode.model import meta
 from rhodecode.model.user import UserModel
-from rhodecode.model.db import Permission, RhodeCodeSettings
+from rhodecode.model.db import Permission, RhodeCodeSettings, User
 
 log = logging.getLogger(__name__)
 
@@ -151,7 +151,7 @@
     """
 
     user_model = UserModel()
-    user = user_model.get_by_username(username, cache=False)
+    user = User.get_by_username(username)
 
     log.debug('Authenticating user using RhodeCode account')
     if user is not None and not user.ldap_dn:
@@ -170,8 +170,7 @@
 
     else:
         log.debug('Regular authentication failed')
-        user_obj = user_model.get_by_username(username, cache=False,
-                                            case_insensitive=True)
+        user_obj = User.get_by_username(username, case_insensitive=True)
 
         if user_obj is not None and not user_obj.ldap_dn:
             log.debug('this user already exists as non ldap')
@@ -252,7 +251,7 @@
 
     def propagate_data(self):
         user_model = UserModel()
-        self.anonymous_user = user_model.get_by_username('default', cache=True)
+        self.anonymous_user = User.get_by_username('default')
         is_user_loaded = False
         if self._api_key and self._api_key != self.anonymous_user.api_key:
             #try go get user by api key
@@ -269,7 +268,7 @@
             self.username = self.username.partition('@')[0]
 
             log.debug('Auth User lookup by USER NAME %s', self.username)
-            dbuser = user_model.get_by_username(self.username)
+            dbuser = User.get_by_username(self.username)
             if dbuser is not None and dbuser.active:
                 for k, v in dbuser.get_dict().items():
                     setattr(self, k, v)
--- a/rhodecode/lib/backup_manager.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/lib/backup_manager.py	Sun Oct 09 23:49:00 2011 +0200
@@ -7,8 +7,8 @@
     repositories and send it to backup server using RSA key via ssh.
 
     :created_on: Feb 28, 2010
-    :copyright: (c) 2010 by marcink.
-    :license: LICENSE_NAME, see LICENSE_FILE for more details.
+    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
--- a/rhodecode/lib/base.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/lib/base.py	Sun Oct 09 23:49:00 2011 +0200
@@ -76,7 +76,7 @@
         super(BaseRepoController, self).__before__()
         if c.repo_name:
 
-            c.rhodecode_db_repo = Repository.by_repo_name(c.repo_name)
+            c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
             c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
 
             if c.rhodecode_repo is None:
--- a/rhodecode/lib/celerylib/tasks.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/lib/celerylib/tasks.py	Sun Oct 09 23:49:00 2011 +0200
@@ -43,7 +43,8 @@
 from rhodecode.lib.helpers import person
 from rhodecode.lib.smtp_mailer import SmtpMailer
 from rhodecode.lib.utils import add_cache
-from rhodecode.lib.odict import OrderedDict
+from rhodecode.lib.compat import json, OrderedDict
+
 from rhodecode.model import init_model
 from rhodecode.model import meta
 from rhodecode.model.db import RhodeCodeUi, Statistics, Repository
@@ -54,11 +55,7 @@
 
 add_cache(config)
 
-try:
-    import json
-except ImportError:
-    #python 2.5 compatibility
-    import simplejson as json
+
 
 __all__ = ['whoosh_index', 'get_commits_stats',
            'reset_user_password', 'send_email']
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/compat.py	Sun Oct 09 23:49:00 2011 +0200
@@ -0,0 +1,360 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.lib.compat
+    ~~~~~~~~~~~~~~~~~~~~
+
+    Python backward compatibility functions and common libs
+    
+    
+    :created_on: Oct 7, 2011
+    :author: marcink
+    :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>    
+    :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 <http://www.gnu.org/licenses/>.
+
+#==============================================================================
+# json
+#==============================================================================
+try:
+    import json
+except ImportError:
+    import simplejson as json
+
+
+#==============================================================================
+# izip_longest
+#==============================================================================
+try:
+    from itertools import izip_longest
+except ImportError:
+    import itertools
+
+    def izip_longest(*args, **kwds): # noqa
+        fillvalue = kwds.get("fillvalue")
+
+        def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
+            yield counter() # yields the fillvalue, or raises IndexError
+
+        fillers = itertools.repeat(fillvalue)
+        iters = [itertools.chain(it, sentinel(), fillers)
+                    for it in args]
+        try:
+            for tup in itertools.izip(*iters):
+                yield tup
+        except IndexError:
+            pass
+
+
+#==============================================================================
+# OrderedDict
+#==============================================================================
+
+# Python Software Foundation License
+
+# XXX: it feels like using the class with "is" and "is not" instead of "==" and
+# "!=" should be faster.
+class _Nil(object):
+
+    def __repr__(self):
+        return "nil"
+
+    def __eq__(self, other):
+        if (isinstance(other, _Nil)):
+            return True
+        else:
+            return NotImplemented
+
+    def __ne__(self, other):
+        if (isinstance(other, _Nil)):
+            return False
+        else:
+            return NotImplemented
+
+_nil = _Nil()
+
+class _odict(object):
+    """Ordered dict data structure, with O(1) complexity for dict operations
+    that modify one element.
+    
+    Overwriting values doesn't change their original sequential order.
+    """
+
+    def _dict_impl(self):
+        return None
+
+    def __init__(self, data=(), **kwds):
+        """This doesn't accept keyword initialization as normal dicts to avoid
+        a trap - inside a function or method the keyword args are accessible
+        only as a dict, without a defined order, so their original order is
+        lost.
+        """
+        if kwds:
+            raise TypeError("__init__() of ordered dict takes no keyword "
+                            "arguments to avoid an ordering trap.")
+        self._dict_impl().__init__(self)
+        # If you give a normal dict, then the order of elements is undefined
+        if hasattr(data, "iteritems"):
+            for key, val in data.iteritems():
+                self[key] = val
+        else:
+            for key, val in data:
+                self[key] = val
+
+    # Double-linked list header
+    def _get_lh(self):
+        dict_impl = self._dict_impl()
+        if not hasattr(self, '_lh'):
+            dict_impl.__setattr__(self, '_lh', _nil)
+        return dict_impl.__getattribute__(self, '_lh')
+
+    def _set_lh(self, val):
+        self._dict_impl().__setattr__(self, '_lh', val)
+
+    lh = property(_get_lh, _set_lh)
+
+    # Double-linked list tail
+    def _get_lt(self):
+        dict_impl = self._dict_impl()
+        if not hasattr(self, '_lt'):
+            dict_impl.__setattr__(self, '_lt', _nil)
+        return dict_impl.__getattribute__(self, '_lt')
+
+    def _set_lt(self, val):
+        self._dict_impl().__setattr__(self, '_lt', val)
+
+    lt = property(_get_lt, _set_lt)
+
+    def __getitem__(self, key):
+        return self._dict_impl().__getitem__(self, key)[1]
+
+    def __setitem__(self, key, val):
+        dict_impl = self._dict_impl()
+        try:
+            dict_impl.__getitem__(self, key)[1] = val
+        except KeyError, e:
+            new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
+            dict_impl.__setitem__(self, key, new)
+            if dict_impl.__getattribute__(self, 'lt') == _nil:
+                dict_impl.__setattr__(self, 'lh', key)
+            else:
+                dict_impl.__getitem__(
+                    self, dict_impl.__getattribute__(self, 'lt'))[2] = key
+            dict_impl.__setattr__(self, 'lt', key)
+
+    def __delitem__(self, key):
+        dict_impl = self._dict_impl()
+        pred, _ , succ = self._dict_impl().__getitem__(self, key)
+        if pred == _nil:
+            dict_impl.__setattr__(self, 'lh', succ)
+        else:
+            dict_impl.__getitem__(self, pred)[2] = succ
+        if succ == _nil:
+            dict_impl.__setattr__(self, 'lt', pred)
+        else:
+            dict_impl.__getitem__(self, succ)[0] = pred
+        dict_impl.__delitem__(self, key)
+
+    def __contains__(self, key):
+        return key in self.keys()
+
+    def __len__(self):
+        return len(self.keys())
+
+    def __str__(self):
+        pairs = ("%r: %r" % (k, v) for k, v in self.iteritems())
+        return "{%s}" % ", ".join(pairs)
+
+    def __repr__(self):
+        if self:
+            pairs = ("(%r, %r)" % (k, v) for k, v in self.iteritems())
+            return "odict([%s])" % ", ".join(pairs)
+        else:
+            return "odict()"
+
+    def get(self, k, x=None):
+        if k in self:
+            return self._dict_impl().__getitem__(self, k)[1]
+        else:
+            return x
+
+    def __iter__(self):
+        dict_impl = self._dict_impl()
+        curr_key = dict_impl.__getattribute__(self, 'lh')
+        while curr_key != _nil:
+            yield curr_key
+            curr_key = dict_impl.__getitem__(self, curr_key)[2]
+
+    iterkeys = __iter__
+
+    def keys(self):
+        return list(self.iterkeys())
+
+    def itervalues(self):
+        dict_impl = self._dict_impl()
+        curr_key = dict_impl.__getattribute__(self, 'lh')
+        while curr_key != _nil:
+            _, val, curr_key = dict_impl.__getitem__(self, curr_key)
+            yield val
+
+    def values(self):
+        return list(self.itervalues())
+
+    def iteritems(self):
+        dict_impl = self._dict_impl()
+        curr_key = dict_impl.__getattribute__(self, 'lh')
+        while curr_key != _nil:
+            _, val, next_key = dict_impl.__getitem__(self, curr_key)
+            yield curr_key, val
+            curr_key = next_key
+
+    def items(self):
+        return list(self.iteritems())
+
+    def sort(self, cmp=None, key=None, reverse=False):
+        items = [(k, v) for k, v in self.items()]
+        if cmp is not None:
+            items = sorted(items, cmp=cmp)
+        elif key is not None:
+            items = sorted(items, key=key)
+        else:
+            items = sorted(items, key=lambda x: x[1])
+        if reverse:
+            items.reverse()
+        self.clear()
+        self.__init__(items)
+
+    def clear(self):
+        dict_impl = self._dict_impl()
+        dict_impl.clear(self)
+        dict_impl.__setattr__(self, 'lh', _nil)
+        dict_impl.__setattr__(self, 'lt', _nil)
+
+    def copy(self):
+        return self.__class__(self)
+
+    def update(self, data=(), **kwds):
+        if kwds:
+            raise TypeError("update() of ordered dict takes no keyword "
+                            "arguments to avoid an ordering trap.")
+        if hasattr(data, "iteritems"):
+            data = data.iteritems()
+        for key, val in data:
+            self[key] = val
+
+    def setdefault(self, k, x=None):
+        try:
+            return self[k]
+        except KeyError:
+            self[k] = x
+            return x
+
+    def pop(self, k, x=_nil):
+        try:
+            val = self[k]
+            del self[k]
+            return val
+        except KeyError:
+            if x == _nil:
+                raise
+            return x
+
+    def popitem(self):
+        try:
+            dict_impl = self._dict_impl()
+            key = dict_impl.__getattribute__(self, 'lt')
+            return key, self.pop(key)
+        except KeyError:
+            raise KeyError("'popitem(): ordered dictionary is empty'")
+
+    def riterkeys(self):
+        """To iterate on keys in reversed order.
+        """
+        dict_impl = self._dict_impl()
+        curr_key = dict_impl.__getattribute__(self, 'lt')
+        while curr_key != _nil:
+            yield curr_key
+            curr_key = dict_impl.__getitem__(self, curr_key)[0]
+
+    __reversed__ = riterkeys
+
+    def rkeys(self):
+        """List of the keys in reversed order.
+        """
+        return list(self.riterkeys())
+
+    def ritervalues(self):
+        """To iterate on values in reversed order.
+        """
+        dict_impl = self._dict_impl()
+        curr_key = dict_impl.__getattribute__(self, 'lt')
+        while curr_key != _nil:
+            curr_key, val, _ = dict_impl.__getitem__(self, curr_key)
+            yield val
+
+    def rvalues(self):
+        """List of the values in reversed order.
+        """
+        return list(self.ritervalues())
+
+    def riteritems(self):
+        """To iterate on (key, value) in reversed order.
+        """
+        dict_impl = self._dict_impl()
+        curr_key = dict_impl.__getattribute__(self, 'lt')
+        while curr_key != _nil:
+            pred_key, val, _ = dict_impl.__getitem__(self, curr_key)
+            yield curr_key, val
+            curr_key = pred_key
+
+    def ritems(self):
+        """List of the (key, value) in reversed order.
+        """
+        return list(self.riteritems())
+
+    def firstkey(self):
+        if self:
+            return self._dict_impl().__getattribute__(self, 'lh')
+        else:
+            raise KeyError("'firstkey(): ordered dictionary is empty'")
+
+    def lastkey(self):
+        if self:
+            return self._dict_impl().__getattribute__(self, 'lt')
+        else:
+            raise KeyError("'lastkey(): ordered dictionary is empty'")
+
+    def as_dict(self):
+        return self._dict_impl()(self.items())
+
+    def _repr(self):
+        """_repr(): low level repr of the whole data contained in the odict.
+        Useful for debugging.
+        """
+        dict_impl = self._dict_impl()
+        form = "odict low level repr lh,lt,data: %r, %r, %s"
+        return form % (dict_impl.__getattribute__(self, 'lh'),
+                       dict_impl.__getattribute__(self, 'lt'),
+                       dict_impl.__repr__(self))
+
+class OrderedDict(_odict, dict):
+
+    def _dict_impl(self):
+        return dict
+
+
+#==============================================================================
+# OrderedSet
+#==============================================================================
+from sqlalchemy.util import OrderedSet
--- a/rhodecode/lib/db_manage.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/lib/db_manage.py	Sun Oct 09 23:49:00 2011 +0200
@@ -160,7 +160,9 @@
             def step_3(self):
                 print ('Adding additional settings into RhodeCode db')
                 self.klass.fix_settings()
-
+                print ('Adding ldap defaults')
+                self.klass.create_ldap_options(skip_existing=True)
+                
         upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
 
         #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
@@ -307,7 +309,7 @@
             self.sa.rollback()
             raise
 
-    def create_ldap_options(self):
+    def create_ldap_options(self,skip_existing=False):
         """Creates ldap settings"""
 
         try:
@@ -319,6 +321,9 @@
                         ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
                         ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
 
+                if skip_existing and RhodeCodeSettings.get_by_name(k) != None:
+                    log.debug('Skipping option %s' % k)
+                    continue
                 setting = RhodeCodeSettings(k, v)
                 self.sa.add(setting)
             self.sa.commit()
@@ -411,42 +416,30 @@
 
     def create_user(self, username, password, email='', admin=False):
         log.info('creating administrator user %s', username)
-        new_user = User()
-        new_user.username = username
-        new_user.password = get_crypt_password(password)
-        new_user.api_key = generate_api_key(username)
-        new_user.name = 'RhodeCode'
-        new_user.lastname = 'Admin'
-        new_user.email = email
-        new_user.admin = admin
-        new_user.active = True
+        
+        form_data = dict(username=username,
+                         password=password,
+                         active=True,
+                         admin=admin,
+                         name='RhodeCode',
+                         lastname='Admin',
+                         email=email)
+        User.create(form_data)
 
-        try:
-            self.sa.add(new_user)
-            self.sa.commit()
-        except:
-            self.sa.rollback()
-            raise
 
     def create_default_user(self):
         log.info('creating default user')
         #create default user for handling default permissions.
-        def_user = User()
-        def_user.username = 'default'
-        def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
-        def_user.api_key = generate_api_key('default')
-        def_user.name = 'Anonymous'
-        def_user.lastname = 'User'
-        def_user.email = 'anonymous@rhodecode.org'
-        def_user.admin = False
-        def_user.active = False
-        try:
-            self.sa.add(def_user)
-            self.sa.commit()
-        except:
-            self.sa.rollback()
-            raise
 
+        form_data = dict(username='default',
+                         password=str(uuid.uuid1())[:8],
+                         active=False,
+                         admin=False,
+                         name='Anonymous',
+                         lastname='User',
+                         email='anonymous@rhodecode.org')
+        User.create(form_data)
+        
     def create_permissions(self):
         #module.(access|create|change|delete)_[name]
         #module.(read|write|owner)
--- a/rhodecode/lib/dbmigrate/versions/003_version_1_2_0.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/lib/dbmigrate/versions/003_version_1_2_0.py	Sun Oct 09 23:49:00 2011 +0200
@@ -76,6 +76,14 @@
     #==========================================================================
     from rhodecode.model.db import Repository
 
+    #ADD clone_uri column#
+
+    clone_uri = Column("clone_uri", String(length=255, convert_unicode=False,
+                                           assert_unicode=None),
+                        nullable=True, unique=False, default=None)
+
+    clone_uri.create(Repository().__table__)
+    
     #ADD downloads column#
     enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
     enable_downloads.create(Repository().__table__)
@@ -92,21 +100,16 @@
     group_id.create(Repository().__table__)
 
 
-    #ADD clone_uri column#
-
-    clone_uri = Column("clone_uri", String(length=255, convert_unicode=False,
-                                           assert_unicode=None),
-                        nullable=True, unique=False, default=None)
-
-    clone_uri.create(Repository().__table__)
-
-
     #==========================================================================
     # Upgrade of `user_followings` table
     #==========================================================================
 
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    follows_from.create(Repository().__table__)
+    from rhodecode.model.db import UserFollowing
+
+    follows_from = Column('follows_from', DateTime(timezone=False), 
+                          nullable=True, unique=None, 
+                          default=datetime.datetime.now)
+    follows_from.create(UserFollowing().__table__)
 
     return
 
--- a/rhodecode/lib/exceptions.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/lib/exceptions.py	Sun Oct 09 23:49:00 2011 +0200
@@ -6,8 +6,8 @@
     Set of custom exceptions used in RhodeCode
 
     :created_on: Nov 17, 2010
-    :copyright: (c) 2010 by marcink.
-    :license: LICENSE_NAME, see LICENSE_FILE for more details.
+    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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
--- a/rhodecode/lib/middleware/simplegit.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/lib/middleware/simplegit.py	Sun Oct 09 23:49:00 2011 +0200
@@ -44,11 +44,11 @@
           get_tagged=self.get_tagged)
 
         # Do they want any objects?
-        if len(objects_iter) == 0:
+        if objects_iter is None or len(objects_iter) == 0:
             return
 
         self.progress("counting objects: %d, done.\n" % len(objects_iter))
-        dulserver.write_pack_data(dulserver.ProtocolFile(None, write),
+        dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
                                   objects_iter, len(objects_iter))
         messages = []
         messages.append('thank you for using rhodecode')
@@ -71,8 +71,8 @@
 
 from rhodecode.lib import safe_str
 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
-from rhodecode.lib.utils import invalidate_cache, check_repo_fast
-from rhodecode.model.user import UserModel
+from rhodecode.lib.utils import invalidate_cache, is_valid_repo
+from rhodecode.model.db import User
 
 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
 
@@ -96,12 +96,10 @@
     def __init__(self, application, config):
         self.application = application
         self.config = config
-        #authenticate this git request using
+        # base path of repo locations
+        self.basepath = self.config['base_path']
+        #authenticate this mercurial request using authfunc
         self.authenticate = AuthBasicAuthenticator('', authfunc)
-        self.ipaddr = '0.0.0.0'
-        self.repo_name = None
-        self.username = None
-        self.action = None
 
     def __call__(self, environ, start_response):
         if not is_git(environ):
@@ -109,31 +107,34 @@
 
         proxy_key = 'HTTP_X_REAL_IP'
         def_key = 'REMOTE_ADDR'
-        self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
+        ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
+        username = None
         # skip passing error to error controller
         environ['pylons.status_code_redirect'] = True
 
         #======================================================================
+        # EXTRACT REPOSITORY NAME FROM ENV
+        #======================================================================
+        try:
+            repo_name = self.__get_repository(environ)
+            log.debug('Extracted repo name is %s' % repo_name)
+        except:
+            return HTTPInternalServerError()(environ, start_response)
+
+        #======================================================================
         # GET ACTION PULL or PUSH
         #======================================================================
-        self.action = self.__get_action(environ)
-        try:
-            #==================================================================
-            # GET REPOSITORY NAME
-            #==================================================================
-            self.repo_name = self.__get_repository(environ)
-        except:
-            return HTTPInternalServerError()(environ, start_response)
+        action = self.__get_action(environ)
 
         #======================================================================
         # CHECK ANONYMOUS PERMISSION
         #======================================================================
-        if self.action in ['pull', 'push']:
+        if action in ['pull', 'push']:
             anonymous_user = self.__get_user('default')
-            self.username = anonymous_user.username
-            anonymous_perm = self.__check_permission(self.action,
+            username = anonymous_user.username
+            anonymous_perm = self.__check_permission(action,
                                                      anonymous_user,
-                                                     self.repo_name)
+                                                     repo_name)
 
             if anonymous_perm is not True or anonymous_user.active is False:
                 if anonymous_perm is not True:
@@ -162,56 +163,66 @@
                 # BASIC AUTH
                 #==============================================================
 
-                if self.action in ['pull', 'push']:
+                if action in ['pull', 'push']:
                     username = REMOTE_USER(environ)
                     try:
                         user = self.__get_user(username)
-                        self.username = user.username
+                        username = user.username
                     except:
                         log.error(traceback.format_exc())
                         return HTTPInternalServerError()(environ,
                                                          start_response)
 
                     #check permissions for this repository
-                    perm = self.__check_permission(self.action, user,
-                                                   self.repo_name)
+                    perm = self.__check_permission(action, user,
+                                                   repo_name)
                     if perm is not True:
                         return HTTPForbidden()(environ, start_response)
 
-        self.extras = {'ip': self.ipaddr,
-                       'username': self.username,
-                       'action': self.action,
-                       'repository': self.repo_name}
+        extras = {'ip': ipaddr,
+                  'username': username,
+                  'action': action,
+                  'repository': repo_name}
 
         #===================================================================
         # GIT REQUEST HANDLING
         #===================================================================
-        self.basepath = self.config['base_path']
-        self.repo_path = os.path.join(self.basepath, self.repo_name)
-        #quick check if that dir exists...
-        if check_repo_fast(self.repo_name, self.basepath):
+
+        repo_path = safe_str(os.path.join(self.basepath, 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:
-            app = self.__make_app()
-        except:
+            #invalidate cache on push
+            if action == 'push':
+                self.__invalidate_cache(repo_name)
+
+            app = self.__make_app(repo_name, repo_path)
+            return app(environ, start_response)
+        except Exception:
             log.error(traceback.format_exc())
             return HTTPInternalServerError()(environ, start_response)
 
-        #invalidate cache on push
-        if self.action == 'push':
-            self.__invalidate_cache(self.repo_name)
+    def __make_app(self, repo_name, repo_path):
+        """
+        Make an wsgi application using dulserver
+        
+        :param repo_name: name of the repository
+        :param repo_path: full path to the repository
+        """
 
-        return app(environ, start_response)
-
-    def __make_app(self):
-        _d = {'/' + self.repo_name: Repo(self.repo_path)}
+        _d = {'/' + repo_name: Repo(repo_path)}
         backend = dulserver.DictBackend(_d)
         gitserve = HTTPGitApplication(backend)
 
         return gitserve
 
     def __check_permission(self, action, user, repo_name):
-        """Checks permissions using action (push/pull) user and repository
+        """
+        Checks permissions using action (push/pull) user and repository
         name
 
         :param action: push or pull action
@@ -235,7 +246,8 @@
         return True
 
     def __get_repository(self, environ):
-        """Get's repository name out of PATH_INFO header
+        """
+        Get's repository name out of PATH_INFO header
 
         :param environ: environ where PATH_INFO is stored
         """
@@ -250,7 +262,7 @@
         return repo_name
 
     def __get_user(self, username):
-        return UserModel().get_by_username(username, cache=True)
+        return User.get_by_username(username)
 
     def __get_action(self, environ):
         """Maps git request commands into a pull or push command.
@@ -274,3 +286,4 @@
         invalidate the cache to see the changes right away but only for
         push requests"""
         invalidate_cache('get_repo_cached_%s' % repo_name)
+
--- a/rhodecode/lib/middleware/simplehg.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/lib/middleware/simplehg.py	Sun Oct 09 23:49:00 2011 +0200
@@ -29,8 +29,7 @@
 import traceback
 
 from mercurial.error import RepoError
-from mercurial.hgweb import hgweb
-from mercurial.hgweb.request import wsgiapplication
+from mercurial.hgweb import hgweb_mod
 
 from paste.auth.basic import AuthBasicAuthenticator
 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
@@ -38,8 +37,8 @@
 from rhodecode.lib import safe_str
 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
 from rhodecode.lib.utils import make_ui, invalidate_cache, \
-    check_repo_fast, ui_sections
-from rhodecode.model.user import UserModel
+    is_valid_repo, ui_sections
+from rhodecode.model.db import User
 
 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
 
@@ -61,12 +60,11 @@
     def __init__(self, application, config):
         self.application = application
         self.config = config
+        # base path of repo locations
+        self.basepath = self.config['base_path']
         #authenticate this mercurial request using authfunc
         self.authenticate = AuthBasicAuthenticator('', authfunc)
         self.ipaddr = '0.0.0.0'
-        self.repo_name = None
-        self.username = None
-        self.action = None
 
     def __call__(self, environ, start_response):
         if not is_mercurial(environ):
@@ -74,31 +72,35 @@
 
         proxy_key = 'HTTP_X_REAL_IP'
         def_key = 'REMOTE_ADDR'
-        self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
+        ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
+
         # skip passing error to error controller
         environ['pylons.status_code_redirect'] = True
 
         #======================================================================
+        # EXTRACT REPOSITORY NAME FROM ENV
+        #======================================================================
+        try:
+            repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
+            log.debug('Extracted repo name is %s' % repo_name)
+        except:
+            return HTTPInternalServerError()(environ, start_response)
+
+        #======================================================================
         # GET ACTION PULL or PUSH
         #======================================================================
-        self.action = self.__get_action(environ)
-        try:
-            #==================================================================
-            # GET REPOSITORY NAME
-            #==================================================================
-            self.repo_name = self.__get_repository(environ)
-        except:
-            return HTTPInternalServerError()(environ, start_response)
+        action = self.__get_action(environ)
 
         #======================================================================
         # CHECK ANONYMOUS PERMISSION
         #======================================================================
-        if self.action in ['pull', 'push']:
+        if action in ['pull', 'push']:
             anonymous_user = self.__get_user('default')
-            self.username = anonymous_user.username
-            anonymous_perm = self.__check_permission(self.action,
+
+            username = anonymous_user.username
+            anonymous_perm = self.__check_permission(action,
                                                      anonymous_user,
-                                                     self.repo_name)
+                                                     repo_name)
 
             if anonymous_perm is not True or anonymous_user.active is False:
                 if anonymous_perm is not True:
@@ -127,43 +129,52 @@
                 # BASIC AUTH
                 #==============================================================
 
-                if self.action in ['pull', 'push']:
+                if action in ['pull', 'push']:
                     #Removing realm from username
                     username = REMOTE_USER(environ).partition('@')[0]
                     try:
                         user = self.__get_user(username)
                         if user is None:
                             return HTTPForbidden()(environ, start_response)
-                        self.username = user.username
+                        username = user.username
                     except:
                         log.error(traceback.format_exc())
                         return HTTPInternalServerError()(environ,
                                                          start_response)
 
                     #check permissions for this repository
-                    perm = self.__check_permission(self.action, user,
-                                                   self.repo_name)
+                    perm = self.__check_permission(action, user,
+                                                   repo_name)
                     if perm is not True:
                         return HTTPForbidden()(environ, start_response)
 
-        self.extras = {'ip': self.ipaddr,
-                       'username': self.username,
-                       'action': self.action,
-                       'repository': self.repo_name}
+        extras = {'ip': ipaddr,
+                  'username': username,
+                  'action': action,
+                  'repository': repo_name}
 
         #======================================================================
         # MERCURIAL REQUEST HANDLING
         #======================================================================
-        environ['PATH_INFO'] = '/'  # since we wrap into hgweb, reset the path
-        self.baseui = make_ui('db')
-        self.basepath = self.config['base_path']
-        self.repo_path = os.path.join(self.basepath, self.repo_name)
+
+        repo_path = safe_str(os.path.join(self.basepath, repo_name))
+        log.debug('Repository path is %s' % repo_path)
+
+        baseui = make_ui('db')
+        self.__inject_extras(repo_path, baseui, extras)
+
 
-        #quick check if that dir exists...
-        if check_repo_fast(self.repo_name, self.basepath):
+        # quick check if that dir exists...
+        if is_valid_repo(repo_name, self.basepath) is False:
             return HTTPNotFound()(environ, start_response)
+
         try:
-            app = wsgiapplication(self.__make_app)
+            #invalidate cache on push
+            if action == 'push':
+                self.__invalidate_cache(repo_name)
+
+            app = self.__make_app(repo_path, baseui, extras)
+            return app(environ, start_response)
         except RepoError, e:
             if str(e).find('not found') != -1:
                 return HTTPNotFound()(environ, start_response)
@@ -171,19 +182,12 @@
             log.error(traceback.format_exc())
             return HTTPInternalServerError()(environ, start_response)
 
-        #invalidate cache on push
-        if self.action == 'push':
-            self.__invalidate_cache(self.repo_name)
-
-        return app(environ, start_response)
-
-    def __make_app(self):
+    def __make_app(self, repo_name, baseui, extras):
         """
         Make an wsgi application using hgweb, and inject generated baseui
         instance, additionally inject some extras into ui object
         """
-        self.__inject_extras(self.baseui, self.extras)
-        return hgweb(str(self.repo_path), baseui=self.baseui)
+        return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
 
 
     def __check_permission(self, action, user, repo_name):
@@ -228,7 +232,7 @@
         return repo_name
 
     def __get_user(self, username):
-        return UserModel().get_by_username(username, cache=True)
+        return User.get_by_username(username)
 
     def __get_action(self, environ):
         """
@@ -257,7 +261,7 @@
         push requests"""
         invalidate_cache('get_repo_cached_%s' % repo_name)
 
-    def __inject_extras(self, baseui, extras={}):
+    def __inject_extras(self, repo_path, baseui, extras={}):
         """
         Injects some extra params into baseui instance
         
@@ -267,7 +271,10 @@
         :param extras: dict with extra params to put into baseui
         """
 
-        hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
+        hgrc = os.path.join(repo_path, '.hg', 'hgrc')
+
+        # make our hgweb quiet so it doesn't print output
+        baseui.setconfig('ui', 'quiet', 'true')
 
         #inject some additional parameters that will be available in ui
         #for hooks
@@ -281,3 +288,4 @@
             for section in ui_sections:
                 for k, v in repoui.configitems(section):
                     baseui.setconfig(section, k, v)
+
--- a/rhodecode/lib/odict.py	Tue Sep 27 22:20:24 2011 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,291 +0,0 @@
-# Python Software Foundation License
-
-# XXX: it feels like using the class with "is" and "is not" instead of "==" and
-# "!=" should be faster.
-class _Nil(object):
-
-    def __repr__(self):
-        return "nil"
-
-    def __eq__(self, other):
-        if (isinstance(other, _Nil)):
-            return True
-        else:
-            return NotImplemented
-
-    def __ne__(self, other):
-        if (isinstance(other, _Nil)):
-            return False
-        else:
-            return NotImplemented
-
-_nil = _Nil()
-
-class _odict(object):
-    """Ordered dict data structure, with O(1) complexity for dict operations
-    that modify one element.
-    
-    Overwriting values doesn't change their original sequential order.
-    """
-
-    def _dict_impl(self):
-        return None
-
-    def __init__(self, data=(), **kwds):
-        """This doesn't accept keyword initialization as normal dicts to avoid
-        a trap - inside a function or method the keyword args are accessible
-        only as a dict, without a defined order, so their original order is
-        lost.
-        """
-        if kwds:
-            raise TypeError("__init__() of ordered dict takes no keyword "
-                            "arguments to avoid an ordering trap.")
-        self._dict_impl().__init__(self)
-        # If you give a normal dict, then the order of elements is undefined
-        if hasattr(data, "iteritems"):
-            for key, val in data.iteritems():
-                self[key] = val
-        else:
-            for key, val in data:
-                self[key] = val
-
-    # Double-linked list header
-    def _get_lh(self):
-        dict_impl = self._dict_impl()
-        if not hasattr(self, '_lh'):
-            dict_impl.__setattr__(self, '_lh', _nil)
-        return dict_impl.__getattribute__(self, '_lh')
-
-    def _set_lh(self, val):
-        self._dict_impl().__setattr__(self, '_lh', val)
-
-    lh = property(_get_lh, _set_lh)
-
-    # Double-linked list tail
-    def _get_lt(self):
-        dict_impl = self._dict_impl()
-        if not hasattr(self, '_lt'):
-            dict_impl.__setattr__(self, '_lt', _nil)
-        return dict_impl.__getattribute__(self, '_lt')
-
-    def _set_lt(self, val):
-        self._dict_impl().__setattr__(self, '_lt', val)
-
-    lt = property(_get_lt, _set_lt)
-
-    def __getitem__(self, key):
-        return self._dict_impl().__getitem__(self, key)[1]
-
-    def __setitem__(self, key, val):
-        dict_impl = self._dict_impl()
-        try:
-            dict_impl.__getitem__(self, key)[1] = val
-        except KeyError, e:
-            new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
-            dict_impl.__setitem__(self, key, new)
-            if dict_impl.__getattribute__(self, 'lt') == _nil:
-                dict_impl.__setattr__(self, 'lh', key)
-            else:
-                dict_impl.__getitem__(
-                    self, dict_impl.__getattribute__(self, 'lt'))[2] = key
-            dict_impl.__setattr__(self, 'lt', key)
-
-    def __delitem__(self, key):
-        dict_impl = self._dict_impl()
-        pred, _ , succ = self._dict_impl().__getitem__(self, key)
-        if pred == _nil:
-            dict_impl.__setattr__(self, 'lh', succ)
-        else:
-            dict_impl.__getitem__(self, pred)[2] = succ
-        if succ == _nil:
-            dict_impl.__setattr__(self, 'lt', pred)
-        else:
-            dict_impl.__getitem__(self, succ)[0] = pred
-        dict_impl.__delitem__(self, key)
-
-    def __contains__(self, key):
-        return key in self.keys()
-
-    def __len__(self):
-        return len(self.keys())
-
-    def __str__(self):
-        pairs = ("%r: %r" % (k, v) for k, v in self.iteritems())
-        return "{%s}" % ", ".join(pairs)
-
-    def __repr__(self):
-        if self:
-            pairs = ("(%r, %r)" % (k, v) for k, v in self.iteritems())
-            return "odict([%s])" % ", ".join(pairs)
-        else:
-            return "odict()"
-
-    def get(self, k, x=None):
-        if k in self:
-            return self._dict_impl().__getitem__(self, k)[1]
-        else:
-            return x
-
-    def __iter__(self):
-        dict_impl = self._dict_impl()
-        curr_key = dict_impl.__getattribute__(self, 'lh')
-        while curr_key != _nil:
-            yield curr_key
-            curr_key = dict_impl.__getitem__(self, curr_key)[2]
-
-    iterkeys = __iter__
-
-    def keys(self):
-        return list(self.iterkeys())
-
-    def itervalues(self):
-        dict_impl = self._dict_impl()
-        curr_key = dict_impl.__getattribute__(self, 'lh')
-        while curr_key != _nil:
-            _, val, curr_key = dict_impl.__getitem__(self, curr_key)
-            yield val
-
-    def values(self):
-        return list(self.itervalues())
-
-    def iteritems(self):
-        dict_impl = self._dict_impl()
-        curr_key = dict_impl.__getattribute__(self, 'lh')
-        while curr_key != _nil:
-            _, val, next_key = dict_impl.__getitem__(self, curr_key)
-            yield curr_key, val
-            curr_key = next_key
-
-    def items(self):
-        return list(self.iteritems())
-
-    def sort(self, cmp=None, key=None, reverse=False):
-        items = [(k, v) for k, v in self.items()]
-        if cmp is not None:
-            items = sorted(items, cmp=cmp)
-        elif key is not None:
-            items = sorted(items, key=key)
-        else:
-            items = sorted(items, key=lambda x: x[1])
-        if reverse:
-            items.reverse()
-        self.clear()
-        self.__init__(items)
-
-    def clear(self):
-        dict_impl = self._dict_impl()
-        dict_impl.clear(self)
-        dict_impl.__setattr__(self, 'lh', _nil)
-        dict_impl.__setattr__(self, 'lt', _nil)
-
-    def copy(self):
-        return self.__class__(self)
-
-    def update(self, data=(), **kwds):
-        if kwds:
-            raise TypeError("update() of ordered dict takes no keyword "
-                            "arguments to avoid an ordering trap.")
-        if hasattr(data, "iteritems"):
-            data = data.iteritems()
-        for key, val in data:
-            self[key] = val
-
-    def setdefault(self, k, x=None):
-        try:
-            return self[k]
-        except KeyError:
-            self[k] = x
-            return x
-
-    def pop(self, k, x=_nil):
-        try:
-            val = self[k]
-            del self[k]
-            return val
-        except KeyError:
-            if x == _nil:
-                raise
-            return x
-
-    def popitem(self):
-        try:
-            dict_impl = self._dict_impl()
-            key = dict_impl.__getattribute__(self, 'lt')
-            return key, self.pop(key)
-        except KeyError:
-            raise KeyError("'popitem(): ordered dictionary is empty'")
-
-    def riterkeys(self):
-        """To iterate on keys in reversed order.
-        """
-        dict_impl = self._dict_impl()
-        curr_key = dict_impl.__getattribute__(self, 'lt')
-        while curr_key != _nil:
-            yield curr_key
-            curr_key = dict_impl.__getitem__(self, curr_key)[0]
-
-    __reversed__ = riterkeys
-
-    def rkeys(self):
-        """List of the keys in reversed order.
-        """
-        return list(self.riterkeys())
-
-    def ritervalues(self):
-        """To iterate on values in reversed order.
-        """
-        dict_impl = self._dict_impl()
-        curr_key = dict_impl.__getattribute__(self, 'lt')
-        while curr_key != _nil:
-            curr_key, val, _ = dict_impl.__getitem__(self, curr_key)
-            yield val
-
-    def rvalues(self):
-        """List of the values in reversed order.
-        """
-        return list(self.ritervalues())
-
-    def riteritems(self):
-        """To iterate on (key, value) in reversed order.
-        """
-        dict_impl = self._dict_impl()
-        curr_key = dict_impl.__getattribute__(self, 'lt')
-        while curr_key != _nil:
-            pred_key, val, _ = dict_impl.__getitem__(self, curr_key)
-            yield curr_key, val
-            curr_key = pred_key
-
-    def ritems(self):
-        """List of the (key, value) in reversed order.
-        """
-        return list(self.riteritems())
-
-    def firstkey(self):
-        if self:
-            return self._dict_impl().__getattribute__(self, 'lh')
-        else:
-            raise KeyError("'firstkey(): ordered dictionary is empty'")
-
-    def lastkey(self):
-        if self:
-            return self._dict_impl().__getattribute__(self, 'lt')
-        else:
-            raise KeyError("'lastkey(): ordered dictionary is empty'")
-
-    def as_dict(self):
-        return self._dict_impl()(self.items())
-
-    def _repr(self):
-        """_repr(): low level repr of the whole data contained in the odict.
-        Useful for debugging.
-        """
-        dict_impl = self._dict_impl()
-        form = "odict low level repr lh,lt,data: %r, %r, %s"
-        return form % (dict_impl.__getattribute__(self, 'lh'),
-                       dict_impl.__getattribute__(self, 'lt'),
-                       dict_impl.__repr__(self))
-
-class OrderedDict(_odict, dict):
-
-    def _dict_impl(self):
-        return dict
--- a/rhodecode/lib/oset.py	Tue Sep 27 22:20:24 2011 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-KEY, PREV, NEXT = range(3)
-import collections
-
-class OrderedSet(collections.MutableSet):
-
-    def __init__(self, iterable=None):
-        self.end = end = []
-        end += [None, end, end]         # sentinel node for doubly linked list
-        self.map = {}                   # key --> [key, prev, next]
-        if iterable is not None:
-            self |= iterable
-
-    def __len__(self):
-        return len(self.map)
-
-    def __contains__(self, key):
-        return key in self.map
-
-    def add(self, key):
-        if key not in self.map:
-            end = self.end
-            curr = end[PREV]
-            curr[NEXT] = end[PREV] = self.map[key] = [key, curr, end]
-
-    def discard(self, key):
-        if key in self.map:
-            key, prev, next = self.map.pop(key)
-            prev[NEXT] = next
-            next[PREV] = prev
-
-    def __iter__(self):
-        end = self.end
-        curr = end[NEXT]
-        while curr is not end:
-            yield curr[KEY]
-            curr = curr[NEXT]
-
-    def __reversed__(self):
-        end = self.end
-        curr = end[PREV]
-        while curr is not end:
-            yield curr[KEY]
-            curr = curr[PREV]
-
-    def pop(self, last=True):
-        if not self:
-            raise KeyError('set is empty')
-        key = next(reversed(self)) if last else next(iter(self))
-        self.discard(key)
-        return key
-
-    def __repr__(self):
-        if not self:
-            return '%s()' % (self.__class__.__name__,)
-        return '%s(%r)' % (self.__class__.__name__, list(self))
-
-    def __eq__(self, other):
-        if isinstance(other, OrderedSet):
-            return len(self) == len(other) and list(self) == list(other)
-        return set(self) == set(other)
-
-    def __del__(self):
-        self.clear()                    # remove circular references
-
--- a/rhodecode/lib/smtp_mailer.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/lib/smtp_mailer.py	Sun Oct 09 23:49:00 2011 +0200
@@ -6,9 +6,21 @@
     Simple smtp mailer used in RhodeCode
 
     :created_on: Sep 13, 2010
-    :copyright: (c) 2011 by marcink.
-    :license: LICENSE_NAME, see LICENSE_FILE for more details.
+    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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 <http://www.gnu.org/licenses/>.
 
 import logging
 import smtplib
--- a/rhodecode/lib/utils.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/lib/utils.py	Sun Oct 09 23:49:00 2011 +0200
@@ -33,23 +33,21 @@
 
 from paste.script.command import Command, BadCommand
 
-from UserDict import DictMixin
-
-from mercurial import ui, config, hg
-from mercurial.error import RepoError
+from mercurial import ui, config
 
 from webhelpers.text import collapse, remove_formatting, strip_tags
 
+from vcs import get_backend
 from vcs.backends.base import BaseChangeset
 from vcs.utils.lazy import LazyProperty
-from vcs import get_backend
+from vcs.utils.helpers import get_scm
+from vcs.exceptions import VCSError
 
 from rhodecode.model import meta
 from rhodecode.model.caching_query import FromCache
 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group, \
     RhodeCodeSettings
 from rhodecode.model.repo import RepoModel
-from rhodecode.model.user import UserModel
 
 log = logging.getLogger(__name__)
 
@@ -111,11 +109,10 @@
         sa = meta.Session()
 
     try:
-        um = UserModel()
         if hasattr(user, 'user_id'):
             user_obj = user
         elif isinstance(user, basestring):
-            user_obj = um.get_by_username(user, cache=False)
+            user_obj = User.get_by_username(user)
         else:
             raise Exception('You have to provide user object or username')
 
@@ -183,37 +180,40 @@
     return _get_repos(path)
 
 
-def check_repo_fast(repo_name, base_path):
+def is_valid_repo(repo_name, base_path):
     """
-    Check given path for existence of directory
+    Returns True if given path is a valid repository False otherwise
     :param repo_name:
     :param base_path:
 
-    :return False: if this directory is present
+    :return True: if given path is a valid repository
     """
-    if os.path.isdir(os.path.join(base_path, repo_name)):
-        return False
-    return True
-
-
-def check_repo(repo_name, base_path, verify=True):
-
-    repo_path = os.path.join(base_path, repo_name)
+    full_path = os.path.join(base_path, repo_name)
 
     try:
-        if not check_repo_fast(repo_name, base_path):
-            return False
-        r = hg.repository(ui.ui(), repo_path)
-        if verify:
-            hg.verify(r)
-        #here we hnow that repo exists it was verified
-        log.info('%s repo is already created', repo_name)
+        get_scm(full_path)
+        return True
+    except VCSError:
         return False
-    except RepoError:
-        #it means that there is no valid repo there...
-        log.info('%s repo is free for creation', repo_name)
+
+def is_valid_repos_group(repos_group_name, base_path):
+    """
+    Returns True if given path is a repos group False otherwise
+    
+    :param repo_name:
+    :param base_path:
+    """
+    full_path = os.path.join(base_path, repos_group_name)
+
+    # check if it's not a repo
+    if is_valid_repo(repos_group_name, base_path):
+        return False
+
+    # check if it's a valid path
+    if os.path.isdir(full_path):
         return True
 
+    return False
 
 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
     while True:
--- a/rhodecode/model/db.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/model/db.py	Sun Oct 09 23:49:00 2011 +0200
@@ -38,12 +38,14 @@
 
 from vcs import get_backend
 from vcs.utils.helpers import get_scm
-from vcs.exceptions import RepositoryError, VCSError
+from vcs.exceptions import VCSError
 from vcs.utils.lazy import LazyProperty
-from vcs.nodes import FileNode
 
+from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
+    generate_api_key
 from rhodecode.lib.exceptions import UsersGroupsAssignedException
-from rhodecode.lib import str2bool, json, safe_str, get_changeset_safe
+from rhodecode.lib.compat import json
+
 from rhodecode.model.meta import Base, Session
 from rhodecode.model.caching_query import FromCache
 
@@ -279,17 +281,16 @@
             return self.__class__.__name__
 
     @classmethod
-    def by_username(cls, username, case_insensitive=False):
+    def get_by_username(cls, username, case_insensitive=False):
         if case_insensitive:
-            return Session.query(cls).filter(cls.username.like(username)).one()
+            return Session.query(cls).filter(cls.username.like(username)).scalar()
         else:
-            return Session.query(cls).filter(cls.username == username).one()
+            return Session.query(cls).filter(cls.username == username).scalar()
 
     @classmethod
     def get_by_api_key(cls, api_key):
         return Session.query(cls).filter(cls.api_key == api_key).one()
 
-
     def update_lastlogin(self):
         """Update user lastlogin"""
 
@@ -298,6 +299,25 @@
         Session.commit()
         log.debug('updated user %s lastlogin', self.username)
 
+    @classmethod
+    def create(cls, form_data):
+        from rhodecode.lib.auth import get_crypt_password
+
+        try:
+            new_user = cls()
+            for k, v in form_data.items():
+                if k == 'password':
+                    v = get_crypt_password(v)
+                setattr(new_user, k, v)
+
+            new_user.api_key = generate_api_key(form_data['username'])
+            Session.add(new_user)
+            Session.commit()
+            return new_user
+        except:
+            log.error(traceback.format_exc())
+            Session.rollback()
+            raise
 
 class UserLog(Base, BaseModel):
     __tablename__ = 'user_logs'
@@ -362,6 +382,7 @@
 
             Session.add(new_users_group)
             Session.commit()
+            return new_users_group
         except:
             log.error(traceback.format_exc())
             Session.rollback()
@@ -465,7 +486,7 @@
                                   self.repo_id, self.repo_name)
 
     @classmethod
-    def by_repo_name(cls, repo_name):
+    def get_by_repo_name(cls, repo_name):
         q = Session.query(cls).filter(cls.repo_name == repo_name)
 
         q = q.options(joinedload(Repository.fork))\
@@ -478,6 +499,17 @@
     def get_repo_forks(cls, repo_id):
         return Session.query(cls).filter(Repository.fork_id == repo_id)
 
+    @classmethod
+    def base_path(cls):
+        """
+        Returns base path when all repos are stored
+        
+        :param cls:
+        """
+        q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
+        q.options(FromCache("sql_cache_short", "repository_repo_path"))
+        return q.one().ui_value
+
     @property
     def just_name(self):
         return self.repo_name.split(os.sep)[-1]
@@ -549,6 +581,19 @@
 
         return baseui
 
+    @classmethod
+    def is_valid(cls, repo_name):
+        """
+        returns True if given repo name is a valid filesystem repository
+         
+        @param cls:
+        @param repo_name:
+        """
+        from rhodecode.lib.utils import is_valid_repo
+
+        return is_valid_repo(repo_name, cls.base_path())
+
+
     #==========================================================================
     # SCM PROPERTIES
     #==========================================================================
--- a/rhodecode/model/forms.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/model/forms.py	Sun Oct 09 23:49:00 2011 +0200
@@ -32,6 +32,7 @@
 from pylons.i18n.translation import _
 from webhelpers.pylonslib.secure_form import authentication_token
 
+from rhodecode.config.routing import ADMIN_PREFIX
 from rhodecode.lib.utils import repo_name_slug
 from rhodecode.lib.auth import authenticate, get_crypt_password
 from rhodecode.lib.exceptions import LdapImportError
@@ -70,8 +71,7 @@
                 old_un = UserModel().get(old_data.get('user_id')).username
 
             if old_un != value or not edit:
-                if UserModel().get_by_username(value, cache=False,
-                                               case_insensitive=True):
+                if User.get_by_username(value, case_insensitive=True):
                     raise formencode.Invalid(_('This username already '
                                                'exists') , value, state)
 
@@ -206,7 +206,7 @@
     def validate_python(self, value, state):
         password = value['password']
         username = value['username']
-        user = UserModel().get_by_username(username)
+        user = User.get_by_username(username)
 
         if authenticate(username, password):
             return value
@@ -241,7 +241,7 @@
             repo_name = value.get('repo_name')
 
             slug = repo_name_slug(repo_name)
-            if slug in ['_admin', '']:
+            if slug in [ADMIN_PREFIX, '']:
                 e_dict = {'repo_name': _('This repository name is disallowed')}
                 raise formencode.Invalid('', value, state, error_dict=e_dict)
 
@@ -283,6 +283,19 @@
 def ValidForkName():
     class _ValidForkName(formencode.validators.FancyValidator):
         def to_python(self, value, state):
+
+            repo_name = value.get('fork_name')
+
+            slug = repo_name_slug(repo_name)
+            if slug in [ADMIN_PREFIX, '']:
+                e_dict = {'repo_name': _('This repository name is disallowed')}
+                raise formencode.Invalid('', value, state, error_dict=e_dict)
+
+            if RepoModel().get_by_repo_name(repo_name):
+                e_dict = {'fork_name':_('This repository '
+                                        'already exists')}
+                raise formencode.Invalid('', value, state,
+                                         error_dict=e_dict)
             return value
     return _ValidForkName
 
--- a/rhodecode/model/repo.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/model/repo.py	Sun Oct 09 23:49:00 2011 +0200
@@ -102,7 +102,7 @@
             for member, perm, member_type in form_data['perms_updates']:
                 if member_type == 'user':
                     r2p = self.sa.query(RepoToPerm)\
-                            .filter(RepoToPerm.user == User.by_username(member))\
+                            .filter(RepoToPerm.user == User.get_by_username(member))\
                             .filter(RepoToPerm.repository == cur_repo)\
                             .one()
 
@@ -127,7 +127,7 @@
                 if member_type == 'user':
                     r2p = RepoToPerm()
                     r2p.repository = cur_repo
-                    r2p.user = User.by_username(member)
+                    r2p.user = User.get_by_username(member)
 
                     r2p.permission = self.sa.query(Permission)\
                                         .filter(Permission.
@@ -147,7 +147,7 @@
             #update current repo
             for k, v in form_data.items():
                 if k == 'user':
-                    cur_repo.user = User.by_username(v)
+                    cur_repo.user = User.get_by_username(v)
                 elif k == 'repo_name':
                     cur_repo.repo_name = form_data['repo_name_full']
                 elif k == 'repo_group':
@@ -192,6 +192,9 @@
                 if k == 'repo_group':
                     k = 'group_id'
 
+                if k == 'description':
+                    v = v or repo_name
+
                 setattr(new_repo, k, v)
 
             if fork:
@@ -205,8 +208,7 @@
             #create default permission
             repo_to_perm = RepoToPerm()
             default = 'repository.read'
-            for p in UserModel(self.sa).get_by_username('default',
-                                                    cache=False).user_perms:
+            for p in User.get_by_username('default').user_perms:
                 if p.permission.permission_name.startswith('repository.'):
                     default = p.permission.permission_name
                     break
@@ -218,8 +220,7 @@
                     .one().permission_id
 
             repo_to_perm.repository = new_repo
-            repo_to_perm.user_id = UserModel(self.sa)\
-                .get_by_username('default', cache=False).user_id
+            repo_to_perm.user_id = User.get_by_username('default').user_id
 
             self.sa.add(repo_to_perm)
 
@@ -301,7 +302,7 @@
         :param parent_id:
         :param clone_uri:
         """
-        from rhodecode.lib.utils import check_repo
+        from rhodecode.lib.utils import is_valid_repo
 
         if new_parent_id:
             paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
@@ -312,7 +313,7 @@
         repo_path = os.path.join(*map(lambda x:safe_str(x),
                                 [self.repos_path, new_parent_path, repo_name]))
 
-        if check_repo(repo_path, self.repos_path):
+        if is_valid_repo(repo_path, self.repos_path) is False:
             log.info('creating repo %s in %s @ %s', repo_name, repo_path,
                      clone_uri)
             backend = get_backend(alias)
@@ -332,8 +333,8 @@
         old_path = os.path.join(self.repos_path, old)
         new_path = os.path.join(self.repos_path, new)
         if os.path.isdir(new_path):
-            raise Exception('Was trying to rename to already existing dir %s',
-                            new_path)
+            raise Exception('Was trying to rename to already existing dir %s' \
+            		     % new_path)
         shutil.move(old_path, new_path)
 
     def __delete_repo(self, repo):
--- a/rhodecode/model/scm.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/model/scm.py	Sun Oct 09 23:49:00 2011 +0200
@@ -22,7 +22,6 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
-import os
 import time
 import traceback
 import logging
@@ -41,9 +40,8 @@
 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
     action_logger, EmptyChangeset
 from rhodecode.model import BaseModel
-from rhodecode.model.user import UserModel
 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
-    UserFollowing, UserLog
+    UserFollowing, UserLog, User
 
 log = logging.getLogger(__name__)
 
@@ -283,7 +281,7 @@
         return f is not None
 
     def is_following_user(self, username, user_id, cache=False):
-        u = UserModel(self.sa).get_by_username(username)
+        u = User.get_by_username(username)
 
         f = self.sa.query(UserFollowing)\
             .filter(UserFollowing.follows_user == u)\
@@ -293,20 +291,24 @@
 
     def get_followers(self, repo_id):
         if not isinstance(repo_id, int):
-            repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
+            repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
 
         return self.sa.query(UserFollowing)\
                 .filter(UserFollowing.follows_repo_id == repo_id).count()
 
     def get_forks(self, repo_id):
         if not isinstance(repo_id, int):
-            repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
+            repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
 
         return self.sa.query(Repository)\
                 .filter(Repository.fork_id == repo_id).count()
 
     def pull_changes(self, repo_name, username):
-        dbrepo = Repository.by_repo_name(repo_name)
+        dbrepo = Repository.get_by_repo_name(repo_name)
+        clone_uri = dbrepo.clone_uri
+        if not clone_uri:
+            raise Exception("This repository doesn't have a clone uri")
+
         repo = dbrepo.scm_instance
         try:
             extras = {'ip': '',
@@ -318,13 +320,12 @@
             for k, v in extras.items():
                 repo._repo.ui.setconfig('rhodecode_extras', k, v)
 
-            repo.pull(dbrepo.clone_uri)
+            repo.pull(clone_uri)
             self.mark_for_invalidation(repo_name)
         except:
             log.error(traceback.format_exc())
             raise
 
-
     def commit_change(self, repo, repo_name, cs, user, author, message, content,
                       f_path):
 
@@ -360,12 +361,12 @@
             from vcs.backends.git import GitInMemoryChangeset as IMC
         # decoding here will force that we have proper encoded values
         # in any other case this will throw exceptions and deny commit
-        
-        if isinstance(content,(basestring,)):
+
+        if isinstance(content, (basestring,)):
             content = safe_str(content)
-        elif isinstance(content,file):
+        elif isinstance(content, file):
             content = content.read()
-            
+
         message = safe_str(message)
         path = safe_str(f_path)
         author = safe_str(author)
--- a/rhodecode/model/user.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/model/user.py	Sun Oct 09 23:49:00 2011 +0200
@@ -28,6 +28,7 @@
 
 from pylons.i18n.translation import _
 
+from rhodecode.lib import safe_unicode
 from rhodecode.model import BaseModel
 from rhodecode.model.caching_query import FromCache
 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
@@ -111,7 +112,7 @@
                 new_user.api_key = generate_api_key(username)
                 new_user.email = attrs['email']
                 new_user.active = True
-                new_user.ldap_dn = user_dn
+                new_user.ldap_dn = safe_unicode(user_dn)
                 new_user.name = attrs['name']
                 new_user.lastname = attrs['lastname']
 
--- a/rhodecode/public/css/style.css	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/public/css/style.css	Sun Oct 09 23:49:00 2011 +0200
@@ -1466,7 +1466,7 @@
 overflow:hidden;
 text-align:right;
 margin:0;
-padding:10px 14px 3px 5px;
+padding:10px 14px 0px 5px;
 }
 
 #quick_login div.form div.links {
@@ -2555,7 +2555,7 @@
 border-left:1px solid #c6c6c6;
 border-right:1px solid #DDD;
 border-bottom:1px solid #c6c6c6;
-color:#515151;
+color:#515151 !important;
 outline:none;
 margin:0;
 padding:6px 12px;
--- a/rhodecode/tests/functional/test_login.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/tests/functional/test_login.py	Sun Oct 09 23:49:00 2011 +0200
@@ -246,7 +246,7 @@
 
         # GOOD KEY
 
-        key = User.by_username(username).api_key
+        key = User.get_by_username(username).api_key
 
         response = self.app.get(url(controller='login',
                                     action='password_reset_confirmation',
--- a/rhodecode/tests/functional/test_summary.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/tests/functional/test_summary.py	Sun Oct 09 23:49:00 2011 +0200
@@ -41,7 +41,7 @@
 
 
     def _enable_stats(self):
-        r = Repository.by_repo_name(HG_REPO)
+        r = Repository.get_by_repo_name(HG_REPO)
         r.enable_statistics = True
         self.sa.add(r)
         self.sa.commit()
--- a/rhodecode/tests/rhodecode_crawler.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/tests/rhodecode_crawler.py	Sun Oct 09 23:49:00 2011 +0200
@@ -102,7 +102,7 @@
 
     repo = vcs.get_repo(jn(PROJECT_PATH, PROJECT))
 
-    from rhodecode.lib.oset import OrderedSet
+    from rhodecode.lib.compat import OrderedSet
 
     paths_ = OrderedSet([''])
     try:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/test_concurency.py	Sun Oct 09 23:49:00 2011 +0200
@@ -0,0 +1,189 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.tests.test_hg_operations
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Test suite for making push/pull operations
+
+    :created_on: Dec 30, 2010
+    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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 <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+import shutil
+import logging
+from os.path import join as jn
+from os.path import dirname as dn
+
+from tempfile import _RandomNameSequence
+from subprocess import Popen, PIPE
+
+from paste.deploy import appconfig
+from pylons import config
+from sqlalchemy import engine_from_config
+
+from rhodecode.lib.utils import add_cache
+from rhodecode.model import init_model
+from rhodecode.model import meta
+from rhodecode.model.db import User, Repository
+from rhodecode.lib.auth import get_crypt_password
+
+from rhodecode.tests import TESTS_TMP_PATH, NEW_HG_REPO, HG_REPO
+from rhodecode.config.environment import load_environment
+
+rel_path = dn(dn(dn(os.path.abspath(__file__))))
+conf = appconfig('config:development.ini', relative_to=rel_path)
+load_environment(conf.global_conf, conf.local_conf)
+
+add_cache(conf)
+
+USER = 'test_admin'
+PASS = 'test12'
+HOST = '127.0.0.1:5000'
+DEBUG = True
+log = logging.getLogger(__name__)
+
+
+class Command(object):
+
+    def __init__(self, cwd):
+        self.cwd = cwd
+
+    def execute(self, cmd, *args):
+        """Runs command on the system with given ``args``.
+        """
+
+        command = cmd + ' ' + ' '.join(args)
+        log.debug('Executing %s' % command)
+        if DEBUG:
+            print command
+        p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
+        stdout, stderr = p.communicate()
+        if DEBUG:
+            print stdout, stderr
+        return stdout, stderr
+
+def get_session():
+    engine = engine_from_config(conf, 'sqlalchemy.db1.')
+    init_model(engine)
+    sa = meta.Session()
+    return sa
+
+
+def create_test_user(force=True):
+    print 'creating test user'
+    sa = get_session()
+
+    user = sa.query(User).filter(User.username == USER).scalar()
+
+    if force and user is not None:
+        print 'removing current user'
+        for repo in sa.query(Repository).filter(Repository.user == user).all():
+            sa.delete(repo)
+        sa.delete(user)
+        sa.commit()
+
+    if user is None or force:
+        print 'creating new one'
+        new_usr = User()
+        new_usr.username = USER
+        new_usr.password = get_crypt_password(PASS)
+        new_usr.email = 'mail@mail.com'
+        new_usr.name = 'test'
+        new_usr.lastname = 'lasttestname'
+        new_usr.active = True
+        new_usr.admin = True
+        sa.add(new_usr)
+        sa.commit()
+
+    print 'done'
+
+
+def create_test_repo(force=True):
+    print 'creating test repo'
+    from rhodecode.model.repo import RepoModel
+    sa = get_session()
+
+    user = sa.query(User).filter(User.username == USER).scalar()
+    if user is None:
+        raise Exception('user not found')
+
+
+    repo = sa.query(Repository).filter(Repository.repo_name == HG_REPO).scalar()
+
+    if repo is None:
+        print 'repo not found creating'
+
+        form_data = {'repo_name':HG_REPO,
+                     'repo_type':'hg',
+                     'private':False,
+                     'clone_uri':'' }
+        rm = RepoModel(sa)
+        rm.base_path = '/home/hg'
+        rm.create(form_data, user)
+
+    print 'done'
+
+def set_anonymous_access(enable=True):
+    sa = get_session()
+    user = sa.query(User).filter(User.username == 'default').one()
+    user.active = enable
+    sa.add(user)
+    sa.commit()
+
+def get_anonymous_access():
+    sa = get_session()
+    return sa.query(User).filter(User.username == 'default').one().active
+
+
+#==============================================================================
+# TESTS
+#==============================================================================
+def test_clone_with_credentials(no_errors=False, repo=HG_REPO):
+    cwd = path = jn(TESTS_TMP_PATH, repo)
+
+
+    try:
+        shutil.rmtree(path, ignore_errors=True)
+        os.makedirs(path)
+        #print 'made dirs %s' % jn(path)
+    except OSError:
+        raise
+
+
+    clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
+                  {'user':USER,
+                   'pass':PASS,
+                   'host':HOST,
+                   'cloned_repo':repo,
+                   'dest':path + _RandomNameSequence().next()}
+
+    stdout, stderr = Command(cwd).execute('hg clone', clone_url)
+
+    if no_errors is False:
+        assert """adding file changes""" in stdout, 'no messages about cloning'
+        assert """abort""" not in stderr , 'got error from clone'
+
+if __name__ == '__main__':
+    try:
+        create_test_user(force=False)
+
+        for i in range(int(sys.argv[2])):
+            test_clone_with_credentials(repo=sys.argv[1])
+
+    except Exception, e:
+        sys.exit('stop on %s' % e)
--- a/rhodecode/tests/test_hg_operations.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/tests/test_hg_operations.py	Sun Oct 09 23:49:00 2011 +0200
@@ -6,13 +6,28 @@
     Test suite for making push/pull operations
 
     :created_on: Dec 30, 2010
-    :copyright: (c) 2010 by marcink.
-    :license: LICENSE_NAME, see LICENSE_FILE for more details.
+    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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 <http://www.gnu.org/licenses/>.
 
 import os
+import time
+import sys
 import shutil
 import logging
+
 from os.path import join as jn
 from os.path import dirname as dn
 
@@ -26,7 +41,7 @@
 from rhodecode.lib.utils import add_cache
 from rhodecode.model import init_model
 from rhodecode.model import meta
-from rhodecode.model.db import User, Repository
+from rhodecode.model.db import User, Repository, UserLog
 from rhodecode.lib.auth import get_crypt_password
 
 from rhodecode.tests import TESTS_TMP_PATH, NEW_HG_REPO, HG_REPO
@@ -41,7 +56,8 @@
 USER = 'test_admin'
 PASS = 'test12'
 HOST = '127.0.0.1:5000'
-DEBUG = True
+DEBUG = True if sys.argv[1:] else False
+print 'DEBUG:', DEBUG
 log = logging.getLogger(__name__)
 
 
@@ -64,28 +80,44 @@
             print stdout, stderr
         return stdout, stderr
 
+
+def test_wrapp(func):
+
+    def __wrapp(*args, **kwargs):
+        print '>>>%s' % func.__name__
+        try:
+            res = func(*args, **kwargs)
+        except Exception, e:
+            print ('###############\n-'
+                   '--%s failed %s--\n'
+                   '###############\n' % (func.__name__, e))
+            sys.exit()
+        print '++OK++'
+        return res
+    return __wrapp
+
 def get_session():
     engine = engine_from_config(conf, 'sqlalchemy.db1.')
     init_model(engine)
-    sa = meta.Session()
+    sa = meta.Session
     return sa
 
 
 def create_test_user(force=True):
-    print 'creating test user'
+    print '\tcreating test user'
     sa = get_session()
 
     user = sa.query(User).filter(User.username == USER).scalar()
 
     if force and user is not None:
-        print 'removing current user'
+        print '\tremoving current user'
         for repo in sa.query(Repository).filter(Repository.user == user).all():
             sa.delete(repo)
         sa.delete(user)
         sa.commit()
 
     if user is None or force:
-        print 'creating new one'
+        print '\tcreating new one'
         new_usr = User()
         new_usr.username = USER
         new_usr.password = get_crypt_password(PASS)
@@ -97,7 +129,7 @@
         sa.add(new_usr)
         sa.commit()
 
-    print 'done'
+    print '\tdone'
 
 
 def create_test_repo(force=True):
@@ -112,7 +144,7 @@
     repo = sa.query(Repository).filter(Repository.repo_name == HG_REPO).scalar()
 
     if repo is None:
-        print 'repo not found creating'
+        print '\trepo not found creating'
 
         form_data = {'repo_name':HG_REPO,
                      'repo_type':'hg',
@@ -126,19 +158,27 @@
 def set_anonymous_access(enable=True):
     sa = get_session()
     user = sa.query(User).filter(User.username == 'default').one()
+    sa.expire(user)
     user.active = enable
     sa.add(user)
     sa.commit()
+    sa.remove()
+    import time;time.sleep(3)
+    print '\tanonymous access is now:', enable
+
 
 def get_anonymous_access():
     sa = get_session()
-    return sa.query(User).filter(User.username == 'default').one().active
+    obj1 = sa.query(User).filter(User.username == 'default').one()
+    sa.expire(obj1)
+    return obj1.active
 
 
 #==============================================================================
 # TESTS
 #==============================================================================
-def test_clone(no_errors=False):
+@test_wrapp
+def test_clone_with_credentials(no_errors=False):
     cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
 
     try:
@@ -148,6 +188,12 @@
     except OSError:
         raise
 
+    print '\tchecking if anonymous access is enabled'
+    anonymous_access = get_anonymous_access()
+    if anonymous_access:
+        print '\tenabled, disabling it '
+        set_anonymous_access(enable=False)
+        time.sleep(1)
 
     clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
                   {'user':USER,
@@ -163,8 +209,8 @@
         assert """abort""" not in stderr , 'got error from clone'
 
 
-
-def test_clone_anonymous_ok():
+@test_wrapp
+def test_clone_anonymous():
     cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
 
     try:
@@ -175,11 +221,12 @@
         raise
 
 
-    print 'checking if anonymous access is enabled'
+    print '\tchecking if anonymous access is enabled'
     anonymous_access = get_anonymous_access()
     if not anonymous_access:
-        print 'not enabled, enabling it '
+        print '\tnot enabled, enabling it '
         set_anonymous_access(enable=True)
+        time.sleep(1)
 
     clone_url = 'http://%(host)s/%(cloned_repo)s %(dest)s' % \
                   {'user':USER,
@@ -189,18 +236,16 @@
                    'dest':path}
 
     stdout, stderr = Command(cwd).execute('hg clone', clone_url)
-    print stdout, stderr
-
 
     assert """adding file changes""" in stdout, 'no messages about cloning'
     assert """abort""" not in stderr , 'got error from clone'
 
     #disable if it was enabled
     if not anonymous_access:
-        print 'disabling anonymous access'
+        print '\tdisabling anonymous access'
         set_anonymous_access(enable=False)
 
-
+@test_wrapp
 def test_clone_wrong_credentials():
     cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
 
@@ -211,6 +256,11 @@
     except OSError:
         raise
 
+    print '\tchecking if anonymous access is enabled'
+    anonymous_access = get_anonymous_access()
+    if anonymous_access:
+        print '\tenabled, disabling it '
+        set_anonymous_access(enable=False)
 
     clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
                   {'user':USER + 'error',
@@ -221,12 +271,14 @@
 
     stdout, stderr = Command(cwd).execute('hg clone', clone_url)
 
-    assert """abort: authorization failed""" in stderr , 'no error from wrong credentials'
+    if not """abort: authorization failed"""  in stderr:
+        raise Exception('Failure')
 
-
+@test_wrapp
 def test_pull():
     pass
 
+@test_wrapp
 def test_push_modify_file(f_name='setup.py'):
     cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
     modified_file = jn(TESTS_TMP_PATH, HG_REPO, f_name)
@@ -239,10 +291,11 @@
 
     Command(cwd).execute('hg push %s' % jn(TESTS_TMP_PATH, HG_REPO))
 
+@test_wrapp
 def test_push_new_file(commits=15, with_clone=True):
 
     if with_clone:
-        test_clone(no_errors=True)
+        test_clone_with_credentials(no_errors=True)
 
     cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
     added_file = jn(path, '%ssetupążźć.py' % _RandomNameSequence().next())
@@ -269,6 +322,7 @@
 
     Command(cwd).execute('hg push --verbose --debug %s' % push_url)
 
+@test_wrapp
 def test_push_wrong_credentials():
     cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
     clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
@@ -288,6 +342,7 @@
 
     Command(cwd).execute('hg push %s' % clone_url)
 
+@test_wrapp
 def test_push_wrong_path():
     cwd = path = jn(TESTS_TMP_PATH, HG_REPO)
     added_file = jn(path, 'somefile.py')
@@ -295,7 +350,7 @@
     try:
         shutil.rmtree(path, ignore_errors=True)
         os.makedirs(path)
-        print 'made dirs %s' % jn(path)
+        print '\tmade dirs %s' % jn(path)
     except OSError:
         raise
 
@@ -318,20 +373,40 @@
                    'dest':jn(TESTS_TMP_PATH, HG_REPO)}
 
     stdout, stderr = Command(cwd).execute('hg push %s' % clone_url)
-    assert """abort: HTTP Error 403: Forbidden"""  in stderr
+    if not """abort: HTTP Error 403: Forbidden"""  in stderr:
+        raise Exception('Failure')
+
+@test_wrapp
+def get_logs():
+    sa = get_session()
+    return len(sa.query(UserLog).all())
+
+@test_wrapp
+def test_logs(initial):
+    sa = get_session()
+    logs = sa.query(UserLog).all()
+    operations = 7
+    if initial + operations != len(logs):
+        raise Exception("missing number of logs %s vs %s" % (initial, len(logs)))
 
 
 if __name__ == '__main__':
     create_test_user(force=False)
     create_test_repo()
-    #test_push_modify_file()
-    #test_clone()
-    #test_clone_anonymous_ok()
+
+    initial_logs = get_logs()
 
-    #test_clone_wrong_credentials()
+#    test_push_modify_file()
+    test_clone_with_credentials()
+    test_clone_wrong_credentials()
 
-    test_pull()
+
     test_push_new_file(commits=2, with_clone=True)
 
-    #test_push_wrong_path()
-    #test_push_wrong_credentials()
+    test_clone_anonymous()
+    test_push_wrong_path()
+
+
+    test_push_wrong_credentials()
+
+    test_logs(initial_logs)
--- a/rhodecode/tests/test_libs.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/rhodecode/tests/test_libs.py	Sun Oct 09 23:49:00 2011 +0200
@@ -7,9 +7,21 @@
     Package for testing various lib/helper functions in rhodecode
     
     :created_on: Jun 9, 2011
-    :copyright: (c) 2011 by marcink.
-    :license: LICENSE_NAME, see LICENSE_FILE for more details.
+    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
+    :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 <http://www.gnu.org/licenses/>.
 
 
 
--- a/setup.py	Tue Sep 27 22:20:24 2011 +0300
+++ b/setup.py	Sun Oct 09 23:49:00 2011 +0200
@@ -12,6 +12,7 @@
 requirements = [
         "Pylons==1.0.0",
         "WebHelpers>=1.2",
+        "formencode==1.2.4",
         "SQLAlchemy>=0.7.2,<0.8",
         "Mako>=0.4.2",
         "pygments>=1.4",
@@ -22,12 +23,12 @@
         "python-dateutil>=1.5.0,<2.0.0",
         "dulwich>=0.8.0",
         "vcs>=0.2.1.dev",
-        "webob==1.0.8"    
+        "webob==1.0.8"
     ]
 
 dependency_links = [
-    "https://secure.rhodecode.org/vcs/archive/default.zip#egg=vcs-0.2.1.dev",
-    "https://bitbucket.org/marcinkuzminski/vcs/get/default.zip#egg=vcs-0.2.1.dev",
+    "https://secure.rhodecode.org/vcs/archive/default.zip#egg=vcs-0.2.2.dev",
+    "https://bitbucket.org/marcinkuzminski/vcs/get/default.zip#egg=vcs-0.2.2.dev",
 ]
 
 classifiers = ['Development Status :: 4 - Beta',
--- a/test.ini	Tue Sep 27 22:20:24 2011 +0300
+++ b/test.ini	Sun Oct 09 23:49:00 2011 +0200
@@ -23,6 +23,7 @@
 #smtp_password = 
 #smtp_port = 
 #smtp_use_tls = false
+#smtp_use_ssl = true
 
 [server:main]
 ##nr of threads to spawn
@@ -49,6 +50,7 @@
 cut_off_limit = 256000
 force_https = false
 commit_parse_limit = 25
+use_gravatar = true
 
 ####################################
 ###        CELERY CONFIG        ####
@@ -93,7 +95,6 @@
 beaker.cache.long_term.type=memory
 beaker.cache.long_term.expire=36000
 
-
 beaker.cache.sql_cache_short.type=memory
 beaker.cache.sql_cache_short.expire=10
 
@@ -150,13 +151,13 @@
 ### LOGGING CONFIGURATION   ####
 ################################
 [loggers]
-keys = root, routes, rhodecode, sqlalchemy,beaker,templates
+keys = root, routes, rhodecode, sqlalchemy, beaker, templates
 
 [handlers]
 keys = console
 
 [formatters]
-keys = generic,color_formatter
+keys = generic, color_formatter
 
 #############
 ## LOGGERS ##
@@ -167,9 +168,10 @@
 
 [logger_routes]
 level = ERROR
-handlers = console
+handlers = 
 qualname = routes.middleware
 # "level = DEBUG" logs the route matched and routing variables.
+propagate = 1
 
 [logger_beaker]
 level = DEBUG
@@ -185,9 +187,9 @@
 
 [logger_rhodecode]
 level = ERROR
-handlers = console
+handlers = 
 qualname = rhodecode
-propagate = 0
+propagate = 1
 
 [logger_sqlalchemy]
 level = ERROR
@@ -203,7 +205,7 @@
 class = StreamHandler
 args = (sys.stderr,)
 level = NOTSET
-formatter = color_formatter
+formatter = generic
 
 ################
 ## FORMATTERS ##