Commits

Marcin Kuzminski  committed a437a98 Merge

merged beta into stable

  • Participants
  • Parent commits deb816e, 7903988
  • Branches default

Comments (0)

Files changed (68)

File docs/changelog.rst

 Changelog
 =========
 
+1.3.5 (**2012-05-10**)
+----------------------
+
+news
+++++
+
+- use ext_json for json module
+- unified annotation view with file source view
+- notification improvements, better inbox + css
+- #419 don't strip passwords for login forms, make rhodecode 
+  more compatible with LDAP servers
+- Added HTTP_X_FORWARDED_FOR as another method of extracting 
+  IP for pull/push logs. - moved all to base controller  
+- #415: Adding comment to changeset causes reload. 
+  Comments are now added via ajax and doesn't reload the page
+- #374 LDAP config is discarded when LDAP can't be activated
+- limited push/pull operations are now logged for git in the journal
+- bumped mercurial to 2.2.X series
+- added support for displaying submodules in file-browser
+- #421 added bookmarks in changelog view
+
+fixes
++++++
+
+- fixed dev-version marker for stable when served from source codes
+- fixed missing permission checks on show forks page
+- #418 cast to unicode fixes in notification objects
+- #426 fixed mention extracting regex
+- fixed remote-pulling for git remotes remopositories
+- fixed #434: Error when accessing files or changesets of a git repository 
+  with submodules
+- fixed issue with empty APIKEYS for users after registration ref. #438
+- fixed issue with getting README files from git repositories
 
 1.3.4 (**2012-03-28**)
 ----------------------

File docs/conf.py

 # All configuration values have a default; values that are commented out
 # serve to show the default.
 
-import sys, os
+import sys
+import os
+import datetime
 
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.viewcode']
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest',
+              'sphinx.ext.intersphinx', 'sphinx.ext.todo',
+              'sphinx.ext.viewcode']
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
 
 # General information about the project.
 project = u'RhodeCode'
-copyright = u'2010, Marcin Kuzminski'
+copyright = u'%s, Marcin Kuzminski' % (datetime.datetime.now().year)
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the

File requires.txt

File contents unchanged.

File rhodecode/__init__.py

 import sys
 import platform
 
-VERSION = (1, 3, 4)
+VERSION = (1, 3, 5)
 
 try:
     from rhodecode.lib import get_current_revision
 PLATFORM_WIN = ('Windows')
 PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS')
 
+is_windows = __platform__ in PLATFORM_WIN
+is_unix = __platform__ in PLATFORM_OTHERS
+
 requirements = [
     "Pylons==1.0.0",
     "Beaker==1.6.3",
     "WebHelpers==1.3",
     "formencode==1.2.4",
     "SQLAlchemy==0.7.6",
-    "Mako==0.6.2",
+    "Mako==0.7.0",
     "pygments>=1.4",
-    "whoosh>=2.3.0,<2.4",
+    "whoosh>=2.4.0,<2.5",
     "celery>=2.2.5,<2.3",
     "babel",
     "python-dateutil>=1.5.0,<2.0.0",
-    "dulwich>=0.8.4,<0.9.0",
+    "dulwich>=0.8.5,<0.9.0",
     "webob==1.0.8",
     "markdown==2.1.1",
     "docutils==0.8.1",
     requirements.append("simplejson")
     requirements.append("pysqlite")
 
-if __platform__ in PLATFORM_WIN:
-    requirements.append("mercurial>=2.1,<2.2")
+if is_windows:
+    requirements.append("mercurial>=2.2.1,<2.3")
 else:
     requirements.append("py-bcrypt")
-    requirements.append("mercurial>=2.1,<2.2")
+    requirements.append("mercurial>=2.2.1,<2.3")
 
 
 def get_version():

File rhodecode/config/routing.py

 
     rmap.connect('files_annotate_home',
                  '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
-                 controller='files', action='annotate', revision='tip',
-                 f_path='', conditions=dict(function=check_repo))
+                 controller='files', action='index', revision='tip',
+                 f_path='', annotate=True, conditions=dict(function=check_repo))
 
     rmap.connect('files_edit_home',
                  '/{repo_name:.*}/edit/{revision}/{f_path:.*}',

File rhodecode/controllers/admin/ldap_settings.py

         _form = LdapSettingsForm([x[0] for x in self.tls_reqcert_choices],
                                  [x[0] for x in self.search_scope_choices],
                                  [x[0] for x in self.tls_kind_choices])()
+        # check the ldap lib
+        ldap_active = False
+        try:
+            import ldap
+            ldap_active = True
+        except ImportError:
+            pass
 
         try:
             form_result = _form.to_python(dict(request.POST))
+
             try:
 
                 for k, v in form_result.items():
                     if k.startswith('ldap_'):
+                        if k == 'ldap_active':
+                            v = ldap_active
                         setting = RhodeCodeSetting.get_by_name(k)
                         setting.app_settings_value = v
                         self.sa.add(setting)
 
                 self.sa.commit()
                 h.flash(_('Ldap settings updated successfully'),
-                    category='success')
+                        category='success')
+                if not ldap_active:
+                    #if ldap is missing send an info to user
+                    h.flash(_('Unable to activate ldap. The "python-ldap" library '
+                              'is missing.'), category='warning')
+
             except (DatabaseError,):
                 raise
-        except LdapImportError:
-            h.flash(_('Unable to activate ldap. The "python-ldap" library '
-                      'is missing.'), category='warning')
 
         except formencode.Invalid, errors:
             e = errors.error_dict or {}

File rhodecode/controllers/admin/notifications.py

 from pylons import tmpl_context as c, url
 from pylons.controllers.util import redirect
 
+from webhelpers.paginate import Page
+
 from rhodecode.lib.base import BaseController, render
 from rhodecode.model.db import Notification
 
         """GET /_admin/notifications: All items in the collection"""
         # url('notifications')
         c.user = self.rhodecode_user
-        c.notifications = NotificationModel()\
-                            .get_for_user(self.rhodecode_user.user_id)
+        notif = NotificationModel().get_for_user(self.rhodecode_user.user_id)
+        p = int(request.params.get('page', 1))
+        c.notifications = Page(notif, page=p, items_per_page=10)
         return render('admin/notifications/notifications.html')
 
     def mark_all_read(self):
             nm.mark_all_read_for_user(self.rhodecode_user.user_id)
             Session.commit()
             c.user = self.rhodecode_user
-            c.notifications = nm.get_for_user(self.rhodecode_user.user_id)
+            notif = nm.get_for_user(self.rhodecode_user.user_id)
+            c.notifications = Page(notif, page=1, items_per_page=10)
             return render('admin/notifications/notifications_data.html')
 
     def create(self):

File rhodecode/controllers/admin/settings.py

 import logging
 import traceback
 import formencode
+import pkg_resources
+import platform
 
 from sqlalchemy import func
 from formencode import htmlfill
     def __before__(self):
         c.admin_user = session.get('admin_user')
         c.admin_username = session.get('admin_username')
+        c.modules = sorted([(p.project_name, p.version)
+                            for p in pkg_resources.working_set],
+                           key=lambda k: k[0].lower())
+        c.py_version = platform.python_version()
+        c.platform = platform.platform()
         super(SettingsController, self).__before__()
 
     @HasPermissionAllDecorator('hg.admin')
 
         defaults = RhodeCodeSetting.get_app_settings()
         defaults.update(self.get_hg_ui_settings())
+
         return htmlfill.render(
             render('admin/settings/settings.html'),
             defaults=defaults,

File rhodecode/controllers/changelog.py

                 data.append(['', vtx, edges])
 
         elif repo.alias == 'hg':
-            c.dag = graphmod.colored(graphmod.dagwalker(repo._repo, revs))
+            dag = graphmod.dagwalker(repo._repo, revs)
+            c.dag = graphmod.colored(dag, repo._repo)
             for (id, type, ctx, vtx, edges) in c.dag:
                 if type != graphmod.CHANGESET:
                     continue

File rhodecode/controllers/changeset.py

 
         return render('changeset/raw_changeset.html')
 
+    @jsonify
     def comment(self, repo_name, revision):
-        ChangesetCommentsModel().create(text=request.POST.get('text'),
-                                        repo_id=c.rhodecode_db_repo.repo_id,
-                                        user_id=c.rhodecode_user.user_id,
-                                        revision=revision,
-                                        f_path=request.POST.get('f_path'),
-                                        line_no=request.POST.get('line'))
+        comm = ChangesetCommentsModel().create(
+            text=request.POST.get('text'),
+            repo_id=c.rhodecode_db_repo.repo_id,
+            user_id=c.rhodecode_user.user_id,
+            revision=revision,
+            f_path=request.POST.get('f_path'),
+            line_no=request.POST.get('line')
+        )
         Session.commit()
-        return redirect(h.url('changeset_home', repo_name=repo_name,
-                              revision=revision))
+        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
+            return redirect(h.url('changeset_home', repo_name=repo_name,
+                                  revision=revision))
+
+        data = {
+           'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
+        }
+        if comm:
+            c.co = comm
+            data.update(comm.get_dict())
+            data.update({'rendered_text':
+                         render('changeset/changeset_comment_block.html')})
+
+        return data
 
     @jsonify
     def delete_comment(self, repo_name, comment_id):

File rhodecode/controllers/files.py

 
 from rhodecode.model.repo import RepoModel
 from rhodecode.model.scm import ScmModel
+from rhodecode.model.db import Repository
 
 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
     _context_url, get_line_ctx, get_ignore_ws
 
     @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
                                    'repository.admin')
-    def index(self, repo_name, revision, f_path):
+    def index(self, repo_name, revision, f_path, annotate=False):
         # redirect to given revision from form if given
         post_revision = request.POST.get('at_rev', None)
         if post_revision:
         c.changeset = self.__get_cs_or_redirect(revision, repo_name)
         c.branch = request.GET.get('branch', None)
         c.f_path = f_path
-
+        c.annotate = annotate
         cur_rev = c.changeset.revision
 
         # prev link
         file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
 
         response.content_disposition = 'attachment; filename=%s' % \
-            safe_str(f_path.split(os.sep)[-1])
+            safe_str(f_path.split(Repository.url_sep())[-1])
 
         response.content_type = file_node.mimetype
         return file_node.content
         response.content_type = mimetype
         return file_node.content
 
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
-    def annotate(self, repo_name, revision, f_path):
-        c.cs = self.__get_cs_or_redirect(revision, repo_name)
-        c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
-
-        c.file_history = self._get_node_history(c.cs, f_path)
-        c.f_path = f_path
-        return render('files/files_annotate.html')
-
     @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
     def edit(self, repo_name, revision, f_path):
         r_post = request.POST
 
             try:
                 self.scm_model.create_node(repo=c.rhodecode_repo,
-                                             repo_name=repo_name, cs=c.cs,
-                                             user=self.rhodecode_user,
-                                             author=author, message=message,
-                                             content=content, f_path=node_path)
+                                           repo_name=repo_name, cs=c.cs,
+                                           user=self.rhodecode_user,
+                                           author=author, message=message,
+                                           content=content, f_path=node_path)
                 h.flash(_('Successfully committed to %s' % node_path),
                         category='success')
             except NodeAlreadyExistsError, e:

File rhodecode/controllers/forks.py

 
 from rhodecode.lib.helpers import Page
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
-    NotAnonymous
+    NotAnonymous, HasRepoPermissionAny
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
 from rhodecode.model.repo import RepoModel
     def forks(self, repo_name):
         p = int(request.params.get('page', 1))
         repo_id = c.rhodecode_db_repo.repo_id
-        d = Repository.get_repo_forks(repo_id)
+        d = []
+        for r in Repository.get_repo_forks(repo_id):
+            if not HasRepoPermissionAny(
+                'repository.read', 'repository.write', 'repository.admin'
+            )(r.repo_name, 'get forks check'):
+                continue
+            d.append(r)
         c.forks_pager = Page(d, page=p, items_per_page=20)
 
         c.forks_data = render('/forks/forks_data.html')

File rhodecode/controllers/summary.py

         if c.enable_downloads:
             c.download_options = self._get_download_links(c.rhodecode_repo)
 
-        c.readme_data, c.readme_file = self.__get_readme_data(c.rhodecode_db_repo)
+        c.readme_data, c.readme_file = self.__get_readme_data(
+            c.rhodecode_db_repo.repo_name, c.rhodecode_repo
+        )
         return render('summary/summary.html')
 
-    def __get_readme_data(self, repo):
+    def __get_readme_data(self, repo_name, repo):
 
         @cache_region('long_term')
         def _get_readme_from_cache(key):
             readme_file = None
             log.debug('Fetching readme file')
             try:
-                cs = repo.get_changeset('tip')
+                cs = repo.get_changeset()  # fetches TIP
                 renderer = MarkupRenderer()
                 for f in README_FILES:
                     try:
                     except NodeDoesNotExistError:
                         continue
             except ChangesetError:
+                log.error(traceback.format_exc())
                 pass
             except EmptyRepositoryError:
                 pass
 
             return readme_data, readme_file
 
-        key = repo.repo_name + '_README'
+        key = repo_name + '_README'
         inv = CacheInvalidation.invalidate(key)
         if inv is not None:
             region_invalidate(_get_readme_from_cache, None, key)

File rhodecode/lib/base.py

 
         return True
 
+    def _get_ip_addr(self, environ):
+        proxy_key = 'HTTP_X_REAL_IP'
+        proxy_key2 = 'HTTP_X_FORWARDED_FOR'
+        def_key = 'REMOTE_ADDR'
+
+        return environ.get(proxy_key2,
+                           environ.get(proxy_key,
+                                       environ.get(def_key, '0.0.0.0')
+                            )
+                        )
+
     def __call__(self, environ, start_response):
         start = time.time()
         try:

File rhodecode/lib/celerylib/tasks.py

 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
 from rhodecode.lib.utils import add_cache, action_logger
 from rhodecode.lib.compat import json, OrderedDict
+from rhodecode.lib.hooks import log_create_repository
 
 from rhodecode.model.db import Statistics, Repository, User
 
 
     base_path = Repository.base_path()
 
-    RepoModel(DBS).create(form_data, cur_user, just_db=True, fork=True)
+    fork_repo = RepoModel(DBS).create(form_data, cur_user,
+                                      just_db=True, fork=True)
 
     alias = form_data['repo_type']
     org_repo_name = form_data['org_path']
     backend(safe_str(destination_fork_path), create=True,
             src_url=safe_str(source_repo_path),
             update_after_clone=update_after_clone)
+    log_create_repository(fork_repo.get_dict(), created_by=cur_user.username)
+
     action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
                    org_repo_name, '', DBS)
 
     # finally commit at latest possible stage
     DBS.commit()
 
+
 def __get_codes_stats(repo_name):
     from rhodecode.config.conf import  LANGUAGES_EXTENSIONS_MAP
     repo = Repository.get_by_repo_name(repo_name).scm_instance

File rhodecode/lib/compat.py

 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
-import datetime
-import functools
-import decimal
 from rhodecode import __platform__, PLATFORM_WIN
 
 #==============================================================================
 # json
 #==============================================================================
-
-
-def _is_aware(value):
-    """
-    Determines if a given datetime.time is aware.
-
-    The logic is described in Python's docs:
-    http://docs.python.org/library/datetime.html#datetime.tzinfo
-    """
-    return (value.tzinfo is not None
-            and value.tzinfo.utcoffset(value) is not None)
-
-
-def _obj_dump(obj):
-    """
-    Custom function for dumping objects to JSON, if obj has __json__ attribute
-    or method defined it will be used for serialization
-
-    :param obj:
-    """
-
-    if isinstance(obj, complex):
-        return [obj.real, obj.imag]
-    # See "Date Time String Format" in the ECMA-262 specification.
-    # some code borrowed from django 1.4
-    elif isinstance(obj, datetime.datetime):
-        r = obj.isoformat()
-        if obj.microsecond:
-            r = r[:23] + r[26:]
-        if r.endswith('+00:00'):
-            r = r[:-6] + 'Z'
-        return r
-    elif isinstance(obj, datetime.date):
-        return obj.isoformat()
-    elif isinstance(obj, decimal.Decimal):
-        return str(obj)
-    elif isinstance(obj, datetime.time):
-        if _is_aware(obj):
-            raise ValueError("JSON can't represent timezone-aware times.")
-        r = obj.isoformat()
-        if obj.microsecond:
-            r = r[:12]
-        return r
-    elif isinstance(obj, set):
-        return list(obj)
-    elif isinstance(obj, OrderedDict):
-        return obj.as_dict()
-    elif hasattr(obj, '__json__'):
-        if callable(obj.__json__):
-            return obj.__json__()
-        else:
-            return obj.__json__
-    else:
-        raise NotImplementedError
-
-try:
-    import json
-
-    # extended JSON encoder for json
-    class ExtendedEncoder(json.JSONEncoder):
-        def default(self, obj):
-            try:
-                return _obj_dump(obj)
-            except NotImplementedError:
-                pass
-            return json.JSONEncoder.default(self, obj)
-    # monkey-patch JSON encoder to use extended version
-    json.dumps = functools.partial(json.dumps, cls=ExtendedEncoder)
-except ImportError:
-    import simplejson as json
-
-    def extended_encode(obj):
-        try:
-            return _obj_dump(obj)
-        except NotImplementedError:
-            pass
-        raise TypeError("%r is not JSON serializable" % (obj,))
-    json.dumps = functools.partial(json.dumps, default=extended_encode)
+from rhodecode.lib.ext_json import json
 
 
 #==============================================================================

File rhodecode/lib/dbmigrate/versions/003_version_1_2_0.py

     is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False)
     is_ldap.drop(User().__table__)
 
-
     #==========================================================================
     # Upgrade of `repositories` table
     #==========================================================================
 
     group_id.create(Repository().__table__)
 
-
     #==========================================================================
     # Upgrade of `user_followings` table
     #==========================================================================

File rhodecode/lib/diffs.py

 from pylons.i18n.translation import _
 
 from rhodecode.lib.vcs.exceptions import VCSError
-from rhodecode.lib.vcs.nodes import FileNode
-
+from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
+from rhodecode.lib.helpers import escape
 from rhodecode.lib.utils import EmptyChangeset
 
 
                                'diff menu to display this diff'))
         stats = (0, 0)
         size = 0
-
     if not diff:
-        diff = wrap_to_table(_('No changes detected'))
+        submodules = filter(lambda o: isinstance(o, SubModuleNode),
+                            [filenode_new, filenode_old])
+        if submodules:
+            diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
+        else:
+            diff = wrap_to_table(_('No changes detected'))
 
     cs1 = filenode_old.changeset.raw_id
     cs2 = filenode_new.changeset.raw_id
     """
     # make sure we pass in default context
     context = context or 3
+    submodules = filter(lambda o: isinstance(o, SubModuleNode),
+                        [filenode_new, filenode_old])
+    if submodules:
+        return ''
 
     for filenode in (filenode_old, filenode_new):
         if not isinstance(filenode, FileNode):
 
     vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
                                  ignore_whitespace, context)
-
     return vcs_gitdiff
 
 

File rhodecode/lib/ext_json.py

+import datetime
+import functools
+import decimal
+
+__all__ = ['json', 'simplejson', 'stdjson']
+
+
+def _is_aware(value):
+    """
+    Determines if a given datetime.time is aware.
+
+    The logic is described in Python's docs:
+    http://docs.python.org/library/datetime.html#datetime.tzinfo
+    """
+    return (value.tzinfo is not None
+            and value.tzinfo.utcoffset(value) is not None)
+
+
+def _obj_dump(obj):
+    """
+    Custom function for dumping objects to JSON, if obj has __json__ attribute
+    or method defined it will be used for serialization
+
+    :param obj:
+    """
+
+    if isinstance(obj, complex):
+        return [obj.real, obj.imag]
+    # See "Date Time String Format" in the ECMA-262 specification.
+    # some code borrowed from django 1.4
+    elif isinstance(obj, datetime.datetime):
+        r = obj.isoformat()
+        if obj.microsecond:
+            r = r[:23] + r[26:]
+        if r.endswith('+00:00'):
+            r = r[:-6] + 'Z'
+        return r
+    elif isinstance(obj, datetime.date):
+        return obj.isoformat()
+    elif isinstance(obj, decimal.Decimal):
+        return str(obj)
+    elif isinstance(obj, datetime.time):
+        if _is_aware(obj):
+            raise ValueError("JSON can't represent timezone-aware times.")
+        r = obj.isoformat()
+        if obj.microsecond:
+            r = r[:12]
+        return r
+    elif isinstance(obj, set):
+        return list(obj)
+    elif hasattr(obj, '__json__'):
+        if callable(obj.__json__):
+            return obj.__json__()
+        else:
+            return obj.__json__
+    else:
+        raise NotImplementedError
+
+
+# Import simplejson
+try:
+    # import simplejson initially
+    import simplejson as _sj
+
+    def extended_encode(obj):
+        try:
+            return _obj_dump(obj)
+        except NotImplementedError:
+            pass
+        raise TypeError("%r is not JSON serializable" % (obj,))
+    # we handle decimals our own it makes unified behavior of json vs
+    # simplejson
+    _sj.dumps = functools.partial(_sj.dumps, default=extended_encode,
+                                  use_decimal=False)
+    _sj.dump = functools.partial(_sj.dump, default=extended_encode,
+                                 use_decimal=False)
+    simplejson = _sj
+
+except ImportError:
+    # no simplejson set it to None
+    _sj = None
+
+
+# simplejson not found try out regular json module
+import json as _json
+
+
+# extended JSON encoder for json
+class ExtendedEncoder(_json.JSONEncoder):
+    def default(self, obj):
+        try:
+            return _obj_dump(obj)
+        except NotImplementedError:
+            pass
+        return _json.JSONEncoder.default(self, obj)
+# monkey-patch JSON encoder to use extended version
+_json.dumps = functools.partial(_json.dumps, cls=ExtendedEncoder)
+_json.dump = functools.partial(_json.dump, cls=ExtendedEncoder)
+stdlib = _json
+
+# set all available json modules
+simplejson = _sj
+stdjson = _json
+json = _sj if _sj else _json

File rhodecode/lib/helpers.py

     if not token_key in session:
         try:
             token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
-        except AttributeError: # Python < 2.4
+        except AttributeError:  # Python < 2.4
             token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
         session[token_key] = token
         if hasattr(session, 'save'):
                         revision=rev.raw_id),
                     title=tooltip(message(rev)), class_='tooltip')
         )
-        # get only max revs_top_limit of changeset for performance/ui reasons
-        revs = [
-            x for x in repo.get_changesets(revs_ids[0],
-                                           revs_ids[:revs_top_limit][-1])
-        ]
+
+        revs = []
+        if len(filter(lambda v: v != '', revs_ids)) > 0:
+            # get only max revs_top_limit of changeset for performance/ui reasons
+            revs = [
+                x for x in repo.get_changesets(revs_ids[0],
+                                               revs_ids[:revs_top_limit][-1])
+            ]
 
         cs_links = []
         cs_links.append(" " + ', '.join(

File rhodecode/lib/hooks.py

 from inspect import isfunction
 
 
+def _get_scm_size(alias, root_path):
+
+    if not alias.startswith('.'):
+        alias += '.'
+
+    size_scm, size_root = 0, 0
+    for path, dirs, files in os.walk(root_path):
+        if path.find(alias) != -1:
+            for f in files:
+                try:
+                    size_scm += os.path.getsize(os.path.join(path, f))
+                except OSError:
+                    pass
+        else:
+            for f in files:
+                try:
+                    size_root += os.path.getsize(os.path.join(path, f))
+                except OSError:
+                    pass
+
+    size_scm_f = h.format_byte_size(size_scm)
+    size_root_f = h.format_byte_size(size_root)
+    size_total_f = h.format_byte_size(size_root + size_scm)
+
+    return size_scm_f, size_root_f, size_total_f
+
+
 def repo_size(ui, repo, hooktype=None, **kwargs):
     """
     Presents size of repository after push
     :param hooktype:
     """
 
-    size_hg, size_root = 0, 0
-    for path, dirs, files in os.walk(repo.root):
-        if path.find('.hg') != -1:
-            for f in files:
-                try:
-                    size_hg += os.path.getsize(os.path.join(path, f))
-                except OSError:
-                    pass
-        else:
-            for f in files:
-                try:
-                    size_root += os.path.getsize(os.path.join(path, f))
-                except OSError:
-                    pass
-
-    size_hg_f = h.format_byte_size(size_hg)
-    size_root_f = h.format_byte_size(size_root)
-    size_total_f = h.format_byte_size(size_root + size_hg)
+    size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
 
     last_cs = repo[len(repo) - 1]
 
     extras = dict(repo.ui.configitems('rhodecode_extras'))
     username = extras['username']
     repository = extras['repository']
+    scm = extras['scm']
     action = 'pull'
 
     action_logger(username, action, repository, extras['ip'], commit=True)
     Maps user last push action to new changeset id, from mercurial
 
     :param ui:
-    :param repo:
+    :param repo: repo object containing the `ui` object
     """
 
     extras = dict(repo.ui.configitems('rhodecode_extras'))
     username = extras['username']
     repository = extras['repository']
     action = extras['action'] + ':%s'
-    node = kwargs['node']
+    scm = extras['scm']
 
-    def get_revs(repo, rev_opt):
-        if rev_opt:
-            revs = revrange(repo, rev_opt)
+    if scm == 'hg':
+        node = kwargs['node']
 
-            if len(revs) == 0:
-                return (nullrev, nullrev)
-            return (max(revs), min(revs))
-        else:
-            return (len(repo) - 1, 0)
+        def get_revs(repo, rev_opt):
+            if rev_opt:
+                revs = revrange(repo, rev_opt)
 
-    stop, start = get_revs(repo, [node + ':'])
+                if len(revs) == 0:
+                    return (nullrev, nullrev)
+                return (max(revs), min(revs))
+            else:
+                return (len(repo) - 1, 0)
 
-    revs = (str(repo[r]) for r in xrange(start, stop + 1))
+        stop, start = get_revs(repo, [node + ':'])
+
+        revs = (str(repo[r]) for r in xrange(start, stop + 1))
+    elif scm == 'git':
+        revs = []
 
     action = action % ','.join(revs)
 

File rhodecode/lib/markup_renderer.py

 import re
 import logging
 
-from rhodecode.lib.utils2 import safe_unicode
+from rhodecode.lib.utils2 import safe_unicode, MENTIONS_REGEX
 
 log = logging.getLogger(__name__)
 
 
     @classmethod
     def rst_with_mentions(cls, source):
-        mention_pat = re.compile(r'(?:^@|\s@)(\w+)')
+        mention_pat = re.compile(MENTIONS_REGEX)
 
         def wrapp(match_obj):
             uname = match_obj.groups()[0]
-            return ' **@%(uname)s** ' % {'uname':uname}
+            return ' **@%(uname)s** ' % {'uname': uname}
         mention_hl = mention_pat.sub(wrapp, source).strip()
         return cls.rst(mention_hl)

File rhodecode/lib/middleware/simplegit.py

           graph_walker.determine_wants, graph_walker, self.progress,
           get_tagged=self.get_tagged)
 
-        # Do they want any objects?
-        if objects_iter is None or len(objects_iter) == 0:
+        # Did the process short-circuit (e.g. in a stateless RPC call)? Note
+        # that the client still expects a 0-object pack in most cases.
+        if objects_iter is None:
             return
 
         self.progress("counting objects: %d, done.\n" % len(objects_iter))
         dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
-                                  objects_iter, len(objects_iter))
+                                     objects_iter)
         messages = []
         messages.append('thank you for using rhodecode')
 
         # we are done
         self.proto.write("0000")
 
+
 dulserver.DEFAULT_HANDLERS = {
   'git-upload-pack': SimpleGitUploadPackHandler,
   'git-receive-pack': dulserver.ReceivePackHandler,
 from rhodecode.lib.utils2 import safe_str
 from rhodecode.lib.base import BaseVCSController
 from rhodecode.lib.auth import get_container_username
-from rhodecode.lib.utils import is_valid_repo
+from rhodecode.lib.utils import is_valid_repo, make_ui
 from rhodecode.model.db import User
 
 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
         if not is_git(environ):
             return self.application(environ, start_response)
 
-        proxy_key = 'HTTP_X_REAL_IP'
-        def_key = 'REMOTE_ADDR'
-        ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
+        ipaddr = self._get_ip_addr(environ)
         username = None
+        self._git_first_op = False
         # skip passing error to error controller
         environ['pylons.status_code_redirect'] = True
 
                     perm = self._check_permission(action, user, repo_name)
                     if perm is not True:
                         return HTTPForbidden()(environ, start_response)
+        extras = {
+            'ip': ipaddr,
+            'username': username,
+            'action': action,
+            'repository': repo_name,
+            'scm': 'git',
+        }
 
         #===================================================================
         # GIT REQUEST HANDLING
         repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
         log.debug('Repository path is %s' % repo_path)
 
+        baseui = make_ui('db')
+        self.__inject_extras(repo_path, baseui, extras)
+
+
         try:
-            #invalidate cache on push
+            # invalidate cache on push
             if action == 'push':
                 self._invalidate_cache(repo_name)
+            self._handle_githooks(repo_name, action, baseui, environ)
+
             log.info('%s action on GIT repo "%s"' % (action, repo_name))
             app = self.__make_app(repo_name, repo_path)
             return app(environ, start_response)
             # operation is pull/push
             op = getattr(self, '_git_stored_op', 'pull')
         return op
+
+    def _handle_githooks(self, repo_name, action, baseui, environ):
+        from rhodecode.lib.hooks import log_pull_action, log_push_action
+        service = environ['QUERY_STRING'].split('=')
+        if len(service) < 2:
+            return
+
+        from rhodecode.model.db import Repository
+        _repo = Repository.get_by_repo_name(repo_name)
+        _repo = _repo.scm_instance
+        _repo._repo.ui = baseui
+
+        push_hook = 'pretxnchangegroup.push_logger'
+        pull_hook = 'preoutgoing.pull_logger'
+        _hooks = dict(baseui.configitems('hooks')) or {}
+        if action == 'push' and _hooks.get(push_hook):
+            log_push_action(ui=baseui, repo=_repo._repo)
+        elif action == 'pull' and _hooks.get(pull_hook):
+            log_pull_action(ui=baseui, repo=_repo._repo)
+
+    def __inject_extras(self, repo_path, baseui, extras={}):
+        """
+        Injects some extra params into baseui instance
+
+        :param baseui: baseui instance
+        :param extras: dict with extra params to put into baseui
+        """
+
+        # 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
+        for k, v in extras.items():
+            baseui.setconfig('rhodecode_extras', k, v)

File rhodecode/lib/middleware/simplehg.py

         if not is_mercurial(environ):
             return self.application(environ, start_response)
 
-        proxy_key = 'HTTP_X_REAL_IP'
-        def_key = 'REMOTE_ADDR'
-        ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
+        ipaddr = self._get_ip_addr(environ)
 
         # skip passing error to error controller
         environ['pylons.status_code_redirect'] = True
             'ip': ipaddr,
             'username': username,
             'action': action,
-            'repository': repo_name
+            'repository': repo_name,
+            'scm': 'hg',
         }
 
         #======================================================================

File rhodecode/lib/utils.py

 
         user_log = UserLog()
         user_log.user_id = user_obj.user_id
-        user_log.action = action
+        user_log.action = safe_unicode(action)
 
         user_log.repository_id = repo_obj.repo_id
         user_log.repository_name = repo_name

File rhodecode/lib/utils2.py

     return cs
 
 
+MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
+
+
 def extract_mentioned_users(s):
     """
     Returns unique usernames from given string s that have @mention
 
     :param s: string to get mentions
     """
-    usrs = {}
-    for username in re.findall(r'(?:^@|\s@)(\w+)', s):
-        usrs[username] = username
+    usrs = set()
+    for username in re.findall(MENTIONS_REGEX, s):
+        usrs.add(username)
 
-    return sorted(usrs.keys())
+    return sorted(list(usrs), key=lambda k: k.lower())

File rhodecode/lib/vcs/backends/base.py

         :raises ``CommitError``: if any error occurs while committing
         """
         raise NotImplementedError
+
+
+class EmptyChangeset(BaseChangeset):
+    """
+    An dummy empty changeset. It's possible to pass hash when creating
+    an EmptyChangeset
+    """
+
+    def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
+                 alias=None):
+        self._empty_cs = cs
+        self.revision = -1
+        self.message = ''
+        self.author = ''
+        self.date = ''
+        self.repository = repo
+        self.requested_revision = requested_revision
+        self.alias = alias
+
+    @LazyProperty
+    def raw_id(self):
+        """
+        Returns raw string identifying this changeset, useful for web
+        representation.
+        """
+
+        return self._empty_cs
+
+    @LazyProperty
+    def branch(self):
+        from rhodecode.lib.vcs.backends import get_backend
+        return get_backend(self.alias).DEFAULT_BRANCH_NAME
+
+    @LazyProperty
+    def short_id(self):
+        return self.raw_id[:12]
+
+    def get_file_changeset(self, path):
+        return self
+
+    def get_file_content(self, path):
+        return u''
+
+    def get_file_size(self, path):
+        return 0

File rhodecode/lib/vcs/backends/git/changeset.py

 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
 from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError
 from rhodecode.lib.vcs.backends.base import BaseChangeset
-from rhodecode.lib.vcs.nodes import FileNode, DirNode, NodeKind, RootNode, RemovedFileNode
+from rhodecode.lib.vcs.nodes import FileNode, DirNode, NodeKind, RootNode, \
+    RemovedFileNode, SubModuleNode
 from rhodecode.lib.vcs.utils import safe_unicode
 from rhodecode.lib.vcs.utils import date_fromtimestamp
 from rhodecode.lib.vcs.utils.lazy import LazyProperty
 
     @LazyProperty
     def branch(self):
-        # TODO: Cache as we walk (id <-> branch name mapping)
-        refs = self.repository._repo.get_refs()
-        heads = {}
-        for key, val in refs.items():
-            for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
-                if key.startswith(ref_key):
-                    n = key[len(ref_key):]
-                    if n not in ['HEAD']:
-                        heads[n] = val
 
-        for name, id in heads.iteritems():
-            walker = self.repository._repo.object_store.get_graph_walker([id])
-            while True:
-                id_ = walker.next()
-                if not id_:
-                    break
-                if id_ == self.id:
-                    return safe_unicode(name)
-        raise ChangesetError("This should not happen... Have you manually "
-                             "change id of the changeset?")
+        heads = self.repository._heads(reverse=False)
+
+        ref = heads.get(self.raw_id)
+        if ref:
+            return safe_unicode(ref)
 
     def _fix_path(self, path):
         """
                         name = item
                     self._paths[name] = id
                     self._stat_modes[name] = stat
-
             if not path in self._paths:
                 raise NodeDoesNotExistError("There is no file nor directory "
                     "at the given path %r at revision %r"
         tree = self.repository._repo[id]
         dirnodes = []
         filenodes = []
+        als = self.repository.alias
         for name, stat, id in tree.iteritems():
+            if objects.S_ISGITLINK(stat):
+                dirnodes.append(SubModuleNode(name, url=None, changeset=id,
+                                              alias=als))
+                continue
+
             obj = self.repository._repo.get_object(id)
             if path != '':
                 obj_path = '/'.join((path, name))
         path = self._fix_path(path)
         if not path in self.nodes:
             try:
-                id = self._get_id_for_path(path)
+                id_ = self._get_id_for_path(path)
             except ChangesetError:
                 raise NodeDoesNotExistError("Cannot find one of parents' "
                     "directories for a given path: %s" % path)
-            obj = self.repository._repo.get_object(id)
-            if isinstance(obj, objects.Tree):
-                if path == '':
-                    node = RootNode(changeset=self)
+
+            als = self.repository.alias
+            _GL = lambda m: m and objects.S_ISGITLINK(m)
+            if _GL(self._stat_modes.get(path)):
+                node = SubModuleNode(path, url=None, changeset=id_, alias=als)
+            else:
+                obj = self.repository._repo.get_object(id_)
+
+                if isinstance(obj, objects.Tree):
+                    if path == '':
+                        node = RootNode(changeset=self)
+                    else:
+                        node = DirNode(path, changeset=self)
+                    node._tree = obj
+                elif isinstance(obj, objects.Blob):
+                    node = FileNode(path, changeset=self)
+                    node._blob = obj
                 else:
-                    node = DirNode(path, changeset=self)
-                node._tree = obj
-            elif isinstance(obj, objects.Blob):
-                node = FileNode(path, changeset=self)
-                node._blob = obj
-            else:
-                raise NodeDoesNotExistError("There is no file nor directory "
-                    "at the given path %r at revision %r"
-                    % (path, self.short_id))
+                    raise NodeDoesNotExistError("There is no file nor directory "
+                        "at the given path %r at revision %r"
+                        % (path, self.short_id))
             # cache node
             self.nodes[path] = node
         return self.nodes[path]
     def _diff_name_status(self):
         output = []
         for parent in self.parents:
-            cmd = 'diff --name-status %s %s' % (parent.raw_id, self.raw_id)
+            cmd = 'diff --name-status %s %s --encoding=utf8' % (parent.raw_id, self.raw_id)
             so, se = self.repository.run_git_command(cmd)
             output.append(so.strip())
         return '\n'.join(output)
         for line in self._diff_name_status.splitlines():
             if not line:
                 continue
+
             if line.startswith(char):
-                splitted = line.split(char,1)
+                splitted = line.split(char, 1)
                 if not len(splitted) == 2:
                     raise VCSError("Couldn't parse diff result:\n%s\n\n and "
                         "particularly that line: %s" % (self._diff_name_status,
                         line))
-                paths.add(splitted[1].strip())
+                _path = splitted[1].strip()
+                paths.add(_path)
         return sorted(paths)
 
     @LazyProperty

File rhodecode/lib/vcs/backends/git/inmemory.py

 from dulwich.repo import Repo
 from rhodecode.lib.vcs.backends.base import BaseInMemoryChangeset
 from rhodecode.lib.vcs.exceptions import RepositoryError
+from rhodecode.lib.vcs.utils import safe_str
 
 
 class GitInMemoryChangeset(BaseInMemoryChangeset):
 
     def commit(self, message, author, parents=None, branch=None, date=None,
-            **kwargs):
+               **kwargs):
         """
         Performs in-memory commit (doesn't check workdir in any way) and
         returns newly created ``Changeset``. Updates repository's
         commit = objects.Commit()
         commit.tree = commit_tree.id
         commit.parents = [p._commit.id for p in self.parents if p]
-        commit.author = commit.committer = author
+        commit.author = commit.committer = safe_str(author)
         commit.encoding = ENCODING
-        commit.message = message + ' '
+        commit.message = safe_str(message) + ' '
 
         # Compute date
         if date is None:

File rhodecode/lib/vcs/backends/git/repository.py

 
         self.path = abspath(repo_path)
         self._repo = self._get_repo(create, src_url, update_after_clone, bare)
+        #temporary set that to now at later we will move it to constructor
+        baseui = None
+        if baseui is None:
+            from mercurial.ui import ui
+            baseui = ui()
+        # patch the instance of GitRepo with an "FAKE" ui object to add
+        # compatibility layer with Mercurial
+        setattr(self._repo, 'ui', baseui)
+
         try:
             self.head = self._repo.head()
         except KeyError:
 
         :param cmd: git command to be executed
         """
-        #cmd = '(cd %s && git %s)' % (self.path, cmd)
+
+        _copts = ['-c', 'core.quotepath=false', ]
+        _str_cmd = False
         if isinstance(cmd, basestring):
-            cmd = 'git %s' % cmd
-        else:
-            cmd = ['git'] + cmd
+            cmd = [cmd]
+            _str_cmd = True
+
+        cmd = ['GIT_CONFIG_NOGLOBAL=1', 'git'] + _copts + cmd
+        if _str_cmd:
+            cmd = ' '.join(cmd)
         try:
             opts = dict(
                 shell=isinstance(cmd, basestring),
             if ref.startswith('refs/heads/') and not ref.endswith('/HEAD')]
         return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
 
+    def _heads(self, reverse=False):
+        refs = self._repo.get_refs()
+        heads = {}
+
+        for key, val in refs.items():
+            for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
+                if key.startswith(ref_key):
+                    n = key[len(ref_key):]
+                    if n not in ['HEAD']:
+                        heads[n] = val
+
+        return heads if reverse else dict((y,x) for x,y in heads.iteritems())
+
     def _get_tags(self):
         if not self.revisions:
             return {}
             yield self.get_changeset(rev)
 
     def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
-            context=3):
+                 context=3):
         """
         Returns (git like) *diff*, as plain text. Shows changes introduced by
         ``rev2`` since ``rev1``.
         # If error occurs run_git_command raises RepositoryError already
         self.run_git_command(cmd)
 
+    def pull(self, url):
+        """
+        Tries to pull changes from external location.
+        """
+        url = self._get_url(url)
+        cmd = ['pull']
+        cmd.append("--ff-only")
+        cmd.append(url)
+        cmd = ' '.join(cmd)
+        # If error occurs run_git_command raises RepositoryError already
+        self.run_git_command(cmd)
+
     @LazyProperty
     def workdir(self):
         """

File rhodecode/lib/vcs/backends/hg/changeset.py

 from rhodecode.lib.vcs.conf import settings
 from rhodecode.lib.vcs.exceptions import  ChangesetDoesNotExistError, \
     ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, VCSError
-from rhodecode.lib.vcs.nodes import AddedFileNodesGenerator, ChangedFileNodesGenerator, \
-    DirNode, FileNode, NodeKind, RemovedFileNodesGenerator, RootNode
+from rhodecode.lib.vcs.nodes import AddedFileNodesGenerator, \
+    ChangedFileNodesGenerator, DirNode, FileNode, NodeKind, \
+    RemovedFileNodesGenerator, RootNode, SubModuleNode
 
 from rhodecode.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp
 from rhodecode.lib.vcs.utils.lazy import LazyProperty
         return  safe_unicode(self._ctx.branch())
 
     @LazyProperty
+    def bookmarks(self):
+        return map(safe_unicode, self._ctx.bookmarks())
+
+    @LazyProperty
     def message(self):
         return safe_unicode(self._ctx.description())
 
                 " %r" % (self.revision, path))
         return self._ctx.filectx(path)
 
+    def _extract_submodules(self):
+        """
+        returns a dictionary with submodule information from substate file
+        of hg repository
+        """
+        return self._ctx.substate
+
     def get_file_mode(self, path):
         """
         Returns stat mode of the file at the given ``path``.
             raise ChangesetError("Directory does not exist for revision %r at "
                 " %r" % (self.revision, path))
         path = self._fix_path(path)
+
         filenodes = [FileNode(f, changeset=self) for f in self._file_paths
             if os.path.dirname(f) == path]
         dirs = path == '' and '' or [d for d in self._dir_paths
             if d and posixpath.dirname(d) == path]
         dirnodes = [DirNode(d, changeset=self) for d in dirs
             if os.path.dirname(d) == path]
+
+        als = self.repository.alias
+        for k, vals in self._extract_submodules().iteritems():
+            #vals = url,rev,type
+            loc = vals[0]
+            cs = vals[1]
+            dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
+                                          alias=als))
         nodes = dirnodes + filenodes
         # cache nodes
         for node in nodes:
             self.nodes[node.path] = node
         nodes.sort()
+
         return nodes
 
     def get_node(self, path):

File rhodecode/lib/vcs/backends/hg/inmemory.py

 from rhodecode.lib.vcs.backends.base import BaseInMemoryChangeset
 from rhodecode.lib.vcs.exceptions import RepositoryError
 
-from ...utils.hgcompat import memfilectx, memctx, hex
+from ...utils.hgcompat import memfilectx, memctx, hex, tolocal
 
 
 class MercurialInMemoryChangeset(BaseInMemoryChangeset):
         self.check_integrity(parents)
 
         from .repository import MercurialRepository
-        if not isinstance(message, str) or not isinstance(author, str):
+        if not isinstance(message, unicode) or not isinstance(author, unicode):
             raise RepositoryError('Given message and author needs to be '
-                                  'an <str> instance')
+                                  'an <unicode> instance')
 
         if branch is None:
             branch = MercurialRepository.DEFAULT_BRANCH_NAME
                         copied=False)
 
             raise RepositoryError("Given path haven't been marked as added,"
-                "changed or removed (%s)" % path)
+                                  "changed or removed (%s)" % path)
 
         parents = [None, None]
         for i, parent in enumerate(self.parents):
             date=date,
             extra=kwargs)
 
+        loc = lambda u: tolocal(u.encode('utf-8'))
+
         # injecting given _repo params
-        commit_ctx._text = message
-        commit_ctx._user = author
+        commit_ctx._text = loc(message)
+        commit_ctx._user = loc(author)
         commit_ctx._date = date
 
         # TODO: Catch exceptions!

File rhodecode/lib/vcs/nodes.py

     :created_on: Apr 8, 2010
     :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
 """
+import os
 import stat
 import posixpath
 import mimetypes
 
+from pygments import lexers
+
 from rhodecode.lib.vcs.utils.lazy import LazyProperty
-from rhodecode.lib.vcs.utils import safe_unicode
+from rhodecode.lib.vcs.utils import safe_unicode, safe_str
 from rhodecode.lib.vcs.exceptions import NodeError
 from rhodecode.lib.vcs.exceptions import RemovedFileNodeError
-
-from pygments import lexers
+from rhodecode.lib.vcs.backends.base import EmptyChangeset
 
 
 class NodeKind:
+    SUBMODULE = -1
     DIR = 1
     FILE = 2
 
         return None
 
     @LazyProperty
+    def unicode_path(self):
+        return safe_unicode(self.path)
+
+    @LazyProperty
     def name(self):
         """
         Returns name of the node so if its path
         """
         return self.kind == NodeKind.DIR and self.path == ''
 
+    def is_submodule(self):
+        """
+        Returns ``True`` if node's kind is ``NodeKind.SUBMODULE``, ``False``
+        otherwise.
+        """
+        return self.kind == NodeKind.SUBMODULE
+
     @LazyProperty
     def added(self):
         return self.state is NodeState.ADDED
 
     def __repr__(self):
         return '<%s>' % self.__class__.__name__
+
+
+class SubModuleNode(Node):
+    """
+    represents a SubModule of Git or SubRepo of Mercurial
+    """
+    is_binary = False
+    size = 0
+
+    def __init__(self, name, url=None, changeset=None, alias=None):
+        self.path = name
+        self.kind = NodeKind.SUBMODULE
+        self.alias = alias
+        # we have to use emptyChangeset here since this can point to svn/git/hg
+        # submodules we cannot get from repository
+        self.changeset = EmptyChangeset(str(changeset), alias=alias)
+        self.url = url or self._extract_submodule_url()
+
+    def __repr__(self):
+        return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
+                                 self.changeset.short_id)
+
+    def _extract_submodule_url(self):
+        if self.alias == 'git':
+            #TODO: find a way to parse gits submodule file and extract the
+            # linking URL
+            return self.path
+        if self.alias == 'hg':
+            return self.path
+
+    @LazyProperty
+    def name(self):
+        """
+        Returns name of the node so if its path
+        then only last part is returned.
+        """
+        org = safe_unicode(self.path.rstrip('/').split('/')[-1])
+        return u'%s @ %s' % (org, self.changeset.short_id)

File rhodecode/lib/vcs/utils/hgcompat.py

-"""Mercurial libs compatibility
+"""
+Mercurial libs compatibility
+"""
 
-"""
 from mercurial import archival, merge as hg_merge, patch, ui
 from mercurial.commands import clone, nullid, pull
 from mercurial.context import memctx, memfilectx
 from mercurial.match import match
 from mercurial.mdiff import diffopts
 from mercurial.node import hex
+from mercurial.encoding import tolocal

File rhodecode/model/comment.py

 from pylons.i18n.translation import _
 from sqlalchemy.util.compat import defaultdict
 
-from rhodecode.lib.utils2 import extract_mentioned_users
+from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode
 from rhodecode.lib import helpers as h
 from rhodecode.model import BaseModel
 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
         if text:
             repo = Repository.get(repo_id)
             cs = repo.scm_instance.get_changeset(revision)
-            desc = cs.message
+            desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
             author_email = cs.author_email
             comment = ChangesetComment()
             comment.repo = repo
             line = ''
             if line_no:
                 line = _('on line %s') % line_no
-            subj = h.link_to('Re commit: %(commit_desc)s %(line)s' % \
-                                    {'commit_desc': desc, 'line': line},
-                             h.url('changeset_home', repo_name=repo.repo_name,
-                                   revision=revision,
-                                   anchor='comment-%s' % comment.comment_id,
-                                   qualified=True,
-                                   )
-                             )
+            subj = safe_unicode(
+                h.link_to('Re commit: %(commit_desc)s %(line)s' % \
+                          {'commit_desc': desc, 'line': line},
+                          h.url('changeset_home', repo_name=repo.repo_name,
+                                revision=revision,
+                                anchor='comment-%s' % comment.comment_id,
+                                qualified=True,
+                          )
+                )
+            )
+
             body = text
 
             # get the current participants of this changeset
             .filter(ChangesetComment.repo_id == repo_id)\
             .filter(ChangesetComment.revision == revision)\
             .filter(ChangesetComment.line_no != None)\
-            .filter(ChangesetComment.f_path != None).all()
+            .filter(ChangesetComment.f_path != None)\
+            .order_by(ChangesetComment.comment_id.asc())\
+            .all()
 
         paths = defaultdict(lambda: defaultdict(list))
 

File rhodecode/model/db.py

     # SCM PROPERTIES
     #==========================================================================
 
-    def get_changeset(self, rev):
+    def get_changeset(self, rev=None):
         return get_changeset_safe(self.scm_instance, rev)
 
     @property
     @property
     def recipients(self):
         return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self).all()]
+                .filter(UserNotification.notification == self)\
+                .order_by(UserNotification.user).all()]
 
     @classmethod
     def create(cls, created_by, subject, body, recipients, type_=None):

File rhodecode/model/forms.py

     )
 
     password = UnicodeString(
-        strip=True,
+        strip=False,
         min=3,
         not_empty=True,
         messages={
         username = All(UnicodeString(strip=True, min=1, not_empty=True),
                        ValidUsername(edit, old_data))
         if edit:
-            new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
-            password_confirmation = All(UnicodeString(strip=True, min=6,
+            new_password = All(UnicodeString(strip=False, min=6, not_empty=False))
+            password_confirmation = All(UnicodeString(strip=False, min=6,
                                                       not_empty=False))
             admin = StringBoolean(if_missing=False)
         else:
-            password = All(UnicodeString(strip=True, min=6, not_empty=True))
-            password_confirmation = All(UnicodeString(strip=True, min=6,
+            password = All(UnicodeString(strip=False, min=6, not_empty=True))
+            password_confirmation = All(UnicodeString(strip=False, min=6,
                                                       not_empty=False))
 
         active = StringBoolean(if_missing=False)
         filter_extra_fields = True
         username = All(ValidUsername(edit, old_data),
                        UnicodeString(strip=True, min=1, not_empty=True))
-        password = All(UnicodeString(strip=True, min=6, not_empty=True))
-        password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
+        password = All(UnicodeString(strip=False, min=6, not_empty=True))
+        password_confirmation = All(UnicodeString(strip=False, min=6, not_empty=True))
         active = StringBoolean(if_missing=False)
         name = UnicodeString(strip=True, min=1, not_empty=False)
         lastname = UnicodeString(strip=True, min=1, not_empty=False)
     class _LdapSettingsForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = True
-        pre_validators = [LdapLibValidator]
+        #pre_validators = [LdapLibValidator]
         ldap_active = StringBoolean(if_missing=False)
         ldap_host = UnicodeString(strip=True,)
         ldap_port = Number(strip=True,)

File rhodecode/model/repo.py

                 self.__create_repo(repo_name, form_data['repo_type'],
                                    form_data['repo_group'],
                                    form_data['clone_uri'])
+                log_create_repository(new_repo.get_dict(),
+                                      created_by=cur_user.username)
 
             # now automatically start following this repository as owner
             ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
                                                     cur_user.user_id)
-            log_create_repository(new_repo.get_dict(),
-                                  created_by=cur_user.username)
             return new_repo
         except:
             log.error(traceback.format_exc())

File rhodecode/model/scm.py

 
 from rhodecode import BACKENDS
 from rhodecode.lib import helpers as h
-from rhodecode.lib.utils2 import safe_str
+from rhodecode.lib.utils2 import safe_str, safe_unicode
 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
     action_logger, EmptyChangeset, REMOVED_REPO_PAT
 
         repo = dbrepo.scm_instance
         try:
-            extras = {'ip': '',
-                      'username': username,
-                      'action': 'push_remote',
-                      'repository': repo_name}
+            extras = {
+                'ip': '',
+                'username': username,
+                'action': 'push_remote',
+                'repository': repo_name,
+                'scm': repo.alias,
+            }
 
-            #inject ui extra param to log this action via push logger
+            # inject ui extra param to log this action via push logger
             for k, v in extras.items():
                 repo._repo.ui.setconfig('rhodecode_extras', k, v)
 
                       content, f_path):
 
         if repo.alias == 'hg':
-            from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
+            from rhodecode.lib.vcs.backends.hg import \
+                MercurialInMemoryChangeset as IMC
         elif repo.alias == 'git':
-            from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
+            from rhodecode.lib.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
         content = safe_str(content)
-        message = safe_str(message)
         path = safe_str(f_path)
-        author = safe_str(author)
+        # message and author needs to be unicode
+        # proper backend should then translate that into required type
+        message = safe_unicode(message)
+        author = safe_unicode(author)
         m = IMC(repo)
         m.change(FileNode(path, content))
         tip = m.commit(message=message,
-                 author=author,
-                 parents=[cs], branch=cs.branch)
+                       author=author,
+                       parents=[cs], branch=cs.branch)
 
         new_cs = tip.short_id
         action = 'push_local:%s' % new_cs
                 type(content)
             ))
 
-        message = safe_str(message)
+        message = safe_unicode(message)
+        author = safe_unicode(author)
         path = safe_str(f_path)
-        author = safe_str(author)
         m = IMC(repo)
 
         if isinstance(cs, EmptyChangeset):
-            # Emptychangeset means we we're editing empty repository
+            # EmptyChangeset means we we're editing empty repository
             parents = None
         else:
             parents = [cs]
 
         m.add(FileNode(path, content=content))
         tip = m.commit(message=message,
-                 author=author,
-                 parents=parents, branch=cs.branch)
+                       author=author,
+                       parents=parents, branch=cs.branch)
         new_cs = tip.short_id
         action = 'push_local:%s' % new_cs
 

File rhodecode/model/user.py

         from rhodecode.model.notification import NotificationModel