changeset 2194:b567f5fde13e demo

merged stable into demo
author Marcin Kuzminski <marcin@python-works.com>
date Sat, 03 Mar 2012 03:41:38 +0200
parents 1ea54d1ac8b2 (current diff) 8fd6650bb436 (diff)
children 6ee253bb18c1
files rhodecode/controllers/admin/settings.py rhodecode/model/forms.py rhodecode/model/user.py rhodecode/templates/index_base.html
diffstat 43 files changed, 268 insertions(+), 147 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Tue Feb 28 20:55:14 2012 +0200
+++ b/.hgtags	Sat Mar 03 03:41:38 2012 +0200
@@ -49,3 +49,4 @@
 dbc82e3362a25d2aece42060089824c4342efd17 v1.3.0
 79a95f338fd0115b2cdb77118f39e17d22ff505c v1.3.1
 9ab21c5ddb84935bea5c743b4e147ed5a398b30c v1.3.2
+934906f028b582a254e0028ba25e5d20dd32b9cd v1.3.3
--- a/CONTRIBUTORS	Tue Feb 28 20:55:14 2012 +0200
+++ b/CONTRIBUTORS	Sat Mar 03 03:41:38 2012 +0200
@@ -17,3 +17,4 @@
     Matt Zuba <matt.zuba@goodwillaz.org>
     Aras Pranckevicius <aras@unity3d.com>
     Tony Bussieres <t.bussieres@gmail.com>
+    Erwin Kroon <e.kroon@smartmetersolutions.nl>
\ No newline at end of file
--- a/docs/api/api.rst	Tue Feb 28 20:55:14 2012 +0200
+++ b/docs/api/api.rst	Sat Mar 03 03:41:38 2012 +0200
@@ -1,6 +1,6 @@
 .. _api:
 
-
+===
 API
 ===
 
--- a/docs/api/models.rst	Tue Feb 28 20:55:14 2012 +0200
+++ b/docs/api/models.rst	Sat Mar 03 03:41:38 2012 +0200
@@ -1,5 +1,6 @@
 .. _models:
 
+========================
 The :mod:`models` Module
 ========================
 
--- a/docs/changelog.rst	Tue Feb 28 20:55:14 2012 +0200
+++ b/docs/changelog.rst	Sat Mar 03 03:41:38 2012 +0200
@@ -1,9 +1,36 @@
 .. _changelog:
 
+=========
 Changelog
 =========
 
 
+
+1.3.3 (**2012-03-02**)
+----------------------
+
+news
+++++
+
+
+fixes
++++++
+
+- fixed some python2.5 compatibility issues 
+- fixed issues with removed repos was accidentally added as groups, after
+  full rescan of paths
+- fixes #376 Cannot edit user (using container auth)
+- fixes #378 Invalid image urls on changeset screen with proxy-prefix 
+  configuration
+- fixed initial sorting of repos inside repo group
+- fixes issue when user tried to resubmit same permission into user/user_groups
+- bumped beaker version that fixes #375 leap error bug
+- fixed raw_changeset for git. It was generated with hg patch headers
+- fixed vcs issue with last_changeset for filenodes
+- fixed missing commit after hook delete
+- fixed #372 issues with git operation detection that caused a security issue 
+  for git repos
+
 1.3.2 (**2012-02-28**)
 ----------------------
 
--- a/docs/contributing.rst	Tue Feb 28 20:55:14 2012 +0200
+++ b/docs/contributing.rst	Sat Mar 03 03:41:38 2012 +0200
@@ -1,5 +1,6 @@
 .. _contributing:
 
+=========================
 Contributing to RhodeCode
 =========================
 
@@ -14,16 +15,19 @@
 stable, and not the other way. Finally, when you are finished making a change, 
 please send me a pull request.
 
-To run RhodeCode in a development version you always need to install the tip
-version of RhodeCode and the VCS library.
+To run RhodeCode in a development version you always need to install the latest
+required libs from `requires.txt` file.
 
-after downloading RhodeCode make sure you run::
+after downloading/pulling RhodeCode make sure you run::
 
     python setup.py develop
 
-command to install all required packages, and prepare development enviroment
+command to install/verify all required packages, and prepare development 
+enviroment.
 
 
+After finishing your changes make sure all tests passes ok. You can run
+the testsuite running nosetest from the project root.
 
 | Thank you for any contributions!
 |  Marcin
--- a/docs/installation.rst	Tue Feb 28 20:55:14 2012 +0200
+++ b/docs/installation.rst	Sat Mar 03 03:41:38 2012 +0200
@@ -1,5 +1,6 @@
 .. _installation:
 
+============
 Installation
 ============
 
--- a/docs/setup.rst	Tue Feb 28 20:55:14 2012 +0200
+++ b/docs/setup.rst	Sat Mar 03 03:41:38 2012 +0200
@@ -1,5 +1,6 @@
 .. _setup:
 
+=====
 Setup
 =====
 
@@ -648,6 +649,7 @@
         threads=4 \
         python-path=/home/web/rhodecode/pyenv/lib/python2.6/site-packages
     WSGIScriptAlias / /home/web/rhodecode/dispatch.wsgi
+    WSGIPassAuthorization On
 
 Example wsgi dispatch script::
 
--- a/docs/upgrade.rst	Tue Feb 28 20:55:14 2012 +0200
+++ b/docs/upgrade.rst	Sat Mar 03 03:41:38 2012 +0200
@@ -1,5 +1,6 @@
 .. _upgrade:
 
+=======
 Upgrade
 =======
 
@@ -25,16 +26,17 @@
  
 This will display any changes made by the new version of RhodeCode to your
 current configuration. It will try to perform an automerge. It's always better
-to make a backup of your configuration file before hand and recheck the 
+to make a backup of your configuration file before hand and re check the 
 content after the automerge.
 
 .. note::
-   The next steps only apply to upgrading from non bugfix releases eg. from
-   any minor or major releases. Bugfix releases (eg. 1.1.2->1.1.3) will 
-   not have any database schema changes or whoosh library updates.
+   Please always make sure your .ini files are up to date. Often errors are
+   caused by missing params added in new versions.
+
 
 It is also recommended that you rebuild the whoosh index after upgrading since 
-the new whoosh version could introduce some incompatible index changes.
+the new whoosh version could introduce some incompatible index changes. Please
+Read the changelog to see if there were any changes to whoosh.
 
 
 The final step is to upgrade the database. To do this simply run::
--- a/docs/usage/backup.rst	Tue Feb 28 20:55:14 2012 +0200
+++ b/docs/usage/backup.rst	Sat Mar 03 03:41:38 2012 +0200
@@ -1,5 +1,6 @@
 .. _backup:
 
+====================
 Backing up RhodeCode
 ====================
 
--- a/docs/usage/general.rst	Tue Feb 28 20:55:14 2012 +0200
+++ b/docs/usage/general.rst	Sat Mar 03 03:41:38 2012 +0200
@@ -1,5 +1,6 @@
 .. _general:
 
+=======================
 General RhodeCode usage
 =======================
 
--- a/docs/usage/git_support.rst	Tue Feb 28 20:55:14 2012 +0200
+++ b/docs/usage/git_support.rst	Sat Mar 03 03:41:38 2012 +0200
@@ -1,5 +1,6 @@
 .. _git_support:
 
+===========
 GIT support
 ===========
 
--- a/docs/usage/statistics.rst	Tue Feb 28 20:55:14 2012 +0200
+++ b/docs/usage/statistics.rst	Sat Mar 03 03:41:38 2012 +0200
@@ -1,6 +1,6 @@
 .. _statistics:
 
-
+==========
 Statistics
 ==========
 
--- a/requires.txt	Tue Feb 28 20:55:14 2012 +0200
+++ b/requires.txt	Sat Mar 03 03:41:38 2012 +0200
@@ -1,5 +1,5 @@
 Pylons==1.0.0
-Beaker==1.6.2
+Beaker==1.6.3
 WebHelpers>=1.2
 formencode==1.2.4
 SQLAlchemy==0.7.4
--- a/rhodecode/__init__.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/__init__.py	Sat Mar 03 03:41:38 2012 +0200
@@ -26,7 +26,7 @@
 import sys
 import platform
 
-VERSION = (1, 3, 2)
+VERSION = (1, 3, 3)
 __version__ = '.'.join((str(each) for each in VERSION[:4]))
 __dbversion__ = 5  # defines current db version for migrations
 __platform__ = platform.system()
@@ -38,7 +38,7 @@
 
 requirements = [
     "Pylons==1.0.0",
-    "Beaker==1.6.2",
+    "Beaker==1.6.3",
     "WebHelpers>=1.2",
     "formencode==1.2.4",
     "SQLAlchemy==0.7.4",
--- a/rhodecode/controllers/admin/settings.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/controllers/admin/settings.py	Sat Mar 03 03:41:38 2012 +0200
@@ -248,7 +248,7 @@
 
                 if update:
                     h.flash(_('Updated hooks'), category='success')
-                Session.commit()
+                self.sa.commit()
             except:
                 log.error(traceback.format_exc())
                 h.flash(_('error occurred during hook creation'),
@@ -285,7 +285,7 @@
         if setting_id == 'hooks':
             hook_id = request.POST.get('hook_id')
             RhodeCodeUi.delete(hook_id)
-
+            self.sa.commit()
 
     @HasPermissionAllDecorator('hg.admin')
     def show(self, setting_id, format='html'):
--- a/rhodecode/controllers/changeset.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/controllers/changeset.py	Sat Mar 03 03:41:38 2012 +0200
@@ -53,7 +53,7 @@
 
 def anchor_url(revision, path):
     fid = h.FID(revision, path)
-    return h.url.current(anchor=fid, **request.GET)
+    return h.url.current(anchor=fid, **dict(request.GET))
 
 
 def get_ignore_ws(fid, GET):
@@ -93,7 +93,7 @@
         params[ctx_key] += [ctx_val]
 
     params['anchor'] = fileid
-    img = h.image('/images/icons/text_strikethrough.png', lbl, class_='icon')
+    img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
     return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
 
 
@@ -144,7 +144,7 @@
     lbl = _('%s line context') % ln_ctx
 
     params['anchor'] = fileid
-    img = h.image('/images/icons/table_add.png', lbl, class_='icon')
+    img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
     return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
 
 
@@ -221,17 +221,20 @@
                 lim = self.cut_off_limit
                 if cumulative_diff > self.cut_off_limit:
                     lim = -1
-                size, cs1, cs2, diff, st = wrapped_diff(filenode_old=None,
-                                         filenode_new=node,
-                                         cut_off_limit=lim,
-                                         ignore_whitespace=ign_whitespace_lcl,
-                                         line_context=line_context_lcl,
-                                         enable_comments=enable_comments)
+                size, cs1, cs2, diff, st = wrapped_diff(
+                    filenode_old=None,
+                    filenode_new=node,
+                    cut_off_limit=lim,
+                    ignore_whitespace=ign_whitespace_lcl,
+                    line_context=line_context_lcl,
+                    enable_comments=enable_comments
+                )
                 cumulative_diff += size
                 c.lines_added += st[0]
                 c.lines_deleted += st[1]
-                c.changes[changeset.raw_id].append(('added', node, diff,
-                                                    cs1, cs2, st))
+                c.changes[changeset.raw_id].append(
+                    ('added', node, diff, cs1, cs2, st)
+                )
 
             #==================================================================
             # CHANGED FILES
@@ -249,24 +252,27 @@
                 lim = self.cut_off_limit
                 if cumulative_diff > self.cut_off_limit:
                     lim = -1
-                size, cs1, cs2, diff, st = wrapped_diff(filenode_old=filenode_old,
-                                         filenode_new=node,
-                                         cut_off_limit=lim,
-                                         ignore_whitespace=ign_whitespace_lcl,
-                                         line_context=line_context_lcl,
-                                         enable_comments=enable_comments)
+                size, cs1, cs2, diff, st = wrapped_diff(
+                    filenode_old=filenode_old,
+                    filenode_new=node,
+                    cut_off_limit=lim,
+                    ignore_whitespace=ign_whitespace_lcl,
+                    line_context=line_context_lcl,
+                    enable_comments=enable_comments
+                )
                 cumulative_diff += size
                 c.lines_added += st[0]
                 c.lines_deleted += st[1]
-                c.changes[changeset.raw_id].append(('changed', node, diff,
-                                                    cs1, cs2, st))
-
+                c.changes[changeset.raw_id].append(
+                    ('changed', node, diff, cs1, cs2, st)
+                )
             #==================================================================
             # REMOVED FILES
             #==================================================================
             for node in changeset.removed:
-                c.changes[changeset.raw_id].append(('removed', node, None,
-                                                    None, None, (0, 0)))
+                c.changes[changeset.raw_id].append(
+                    ('removed', node, None, None, None, (0, 0))
+                )
 
         # count inline comments
         for path, lines in c.inline_comments:
@@ -311,7 +317,7 @@
                                                 format='gitdiff').raw_diff()
 
                 cs1 = None
-                cs2 = node.last_changeset.raw_id
+                cs2 = node.changeset.raw_id
                 c.changes.append(('added', node, diff, cs1, cs2))
 
             for node in c.changeset.changed:
@@ -325,8 +331,8 @@
                     diff = diffs.DiffProcessor(f_gitdiff,
                                                 format='gitdiff').raw_diff()
 
-                cs1 = filenode_old.last_changeset.raw_id
-                cs2 = node.last_changeset.raw_id
+                cs1 = filenode_old.changeset.raw_id
+                cs2 = node.changeset.raw_id
                 c.changes.append(('changed', node, diff, cs1, cs2))
 
         response.content_type = 'text/plain'
@@ -335,8 +341,8 @@
             response.content_disposition = 'attachment; filename=%s.patch' \
                                             % revision
 
-        c.parent_tmpl = ''.join(['# Parent  %s\n' % x.raw_id for x in
-                                                 c.changeset.parents])
+        c.parent_tmpl = ''.join(['# Parent  %s\n' % x.raw_id
+                                 for x in c.changeset.parents])
 
         c.diffs = ''
         for x in c.changes:
--- a/rhodecode/controllers/files.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/controllers/files.py	Sat Mar 03 03:41:38 2012 +0200
@@ -428,8 +428,9 @@
 
             diff_name = '%s_vs_%s.diff' % (diff1, diff2)
             response.content_type = 'text/plain'
-            response.content_disposition = 'attachment; filename=%s' \
-                                                    % diff_name
+            response.content_disposition = (
+                'attachment; filename=%s' % diff_name
+            )
             return diff.raw_diff()
 
         elif c.action == 'raw':
--- a/rhodecode/controllers/summary.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/controllers/summary.py	Sat Mar 03 03:41:38 2012 +0200
@@ -28,8 +28,8 @@
 import logging
 from time import mktime
 from datetime import timedelta, date
-from itertools import product
 from urlparse import urlparse
+from rhodecode.lib.compat import product
 
 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
     NodeDoesNotExistError
--- a/rhodecode/lib/compat.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/lib/compat.py	Sat Mar 03 03:41:38 2012 +0200
@@ -379,3 +379,21 @@
 
 else:
     kill = os.kill
+
+
+#==============================================================================
+# itertools.product
+#==============================================================================
+
+try:
+    from itertools import product
+except ImportError:
+    def product(*args, **kwds):
+        # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
+        # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
+        pools = map(tuple, args) * kwds.get('repeat', 1)
+        result = [[]]
+        for pool in pools:
+            result = [x + [y] for x in result for y in pool]
+        for prod in result:
+            yield tuple(prod)
--- a/rhodecode/lib/diffs.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/lib/diffs.py	Sat Mar 03 03:41:38 2012 +0200
@@ -83,8 +83,8 @@
     if not diff:
         diff = wrap_to_table(_('No changes detected'))
 
-    cs1 = filenode_old.last_changeset.raw_id
-    cs2 = filenode_new.last_changeset.raw_id
+    cs1 = filenode_old.changeset.raw_id
+    cs2 = filenode_new.changeset.raw_id
 
     return size, cs1, cs2, diff, stats
 
--- a/rhodecode/lib/middleware/simplegit.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/lib/middleware/simplegit.py	Sat Mar 03 03:41:38 2012 +0200
@@ -121,6 +121,7 @@
         #======================================================================
         # CHECK ANONYMOUS PERMISSION
         #======================================================================
+
         if action in ['pull', 'push']:
             anonymous_user = self.__get_user('default')
             username = anonymous_user.username
@@ -169,15 +170,13 @@
                                                          start_response)
 
                     #check permissions for this repository
-                    perm = self._check_permission(action, user,
-                                                   repo_name)
+                    perm = self._check_permission(action, user, repo_name)
                     if perm is not True:
                         return HTTPForbidden()(environ, start_response)
 
         #===================================================================
         # GIT REQUEST HANDLING
         #===================================================================
-
         repo_path = safe_str(os.path.join(self.basepath, repo_name))
         log.debug('Repository path is %s' % repo_path)
 
@@ -203,7 +202,6 @@
         :param repo_name: name of the repository
         :param repo_path: full path to the repository
         """
-
         _d = {'/' + repo_name: Repo(repo_path)}
         backend = dulserver.DictBackend(_d)
         gitserve = HTTPGitApplication(backend)
@@ -229,19 +227,24 @@
         return User.get_by_username(username)
 
     def __get_action(self, environ):
-        """Maps git request commands into a pull or push command.
+        """
+        Maps git request commands into a pull or push command.
 
         :param environ:
         """
         service = environ['QUERY_STRING'].split('=')
+
         if len(service) > 1:
             service_cmd = service[1]
             mapping = {
                 'git-receive-pack': 'push',
                 'git-upload-pack': 'pull',
             }
-
-            return mapping.get(service_cmd,
-                               service_cmd if service_cmd else 'other')
+            op = mapping[service_cmd]
+            self._git_stored_op = op
+            return op
         else:
-            return 'other'
+            # try to fallback to stored variable as we don't know if the last
+            # operation is pull/push
+            op = getattr(self, '_git_stored_op', 'pull')
+        return op
--- a/rhodecode/lib/utils.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/lib/utils.py	Sat Mar 03 03:41:38 2012 +0200
@@ -24,6 +24,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
+import re
 import logging
 import datetime
 import traceback
@@ -56,6 +57,8 @@
 
 log = logging.getLogger(__name__)
 
+REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
+
 
 def recursive_replace(str_, replace=' '):
     """Recursive replace of given sign to just one instance
@@ -393,6 +396,10 @@
 #            group = rgm.create(group_name, desc, parent, just_db=True)
 #            sa.commit()
 
+        # skip folders that are now removed repos
+        if REMOVED_REPO_PAT.match(group_name):
+            break
+
         if group is None:
             log.debug('creating group level: %s group_name: %s' % (lvl, group_name))
             group = RepoGroup(group_name, parent)
--- a/rhodecode/lib/vcs/backends/git/changeset.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/lib/vcs/backends/git/changeset.py	Sat Mar 03 03:41:38 2012 +0200
@@ -247,7 +247,7 @@
         iterating commits.
         """
         cmd = 'log --pretty="format: %%H" --name-status -p %s -- "%s"' % (
-                  '', path
+                  self.id, path
                )
         so, se = self.repository.run_git_command(cmd)
         ids = re.findall(r'\w{40}', so)
--- a/rhodecode/lib/vcs/backends/hg/changeset.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/lib/vcs/backends/hg/changeset.py	Sat Mar 03 03:41:38 2012 +0200
@@ -187,9 +187,8 @@
         """
         Returns last commit of the file at the given ``path``.
         """
-        fctx = self._get_filectx(path)
-        changeset = self.repository.get_changeset(fctx.linkrev())
-        return changeset
+        node = self.get_node(path)
+        return node.history[0]
 
     def get_file_history(self, path):
         """
--- a/rhodecode/lib/vcs/nodes.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/lib/vcs/nodes.py	Sat Mar 03 03:41:38 2012 +0200
@@ -306,14 +306,14 @@
         attribute to indicate that type should *NOT* be calculated).
         """
         if hasattr(self, '_mimetype'):
-            if (isinstance(self._mimetype,(tuple,list,)) and
+            if (isinstance(self._mimetype, (tuple, list,)) and
                 len(self._mimetype) == 2):
                 return self._mimetype
             else:
                 raise NodeError('given _mimetype attribute must be an 2 '
                                'element list or tuple')
 
-        mtype,encoding = mimetypes.guess_type(self.name)
+        mtype, encoding = mimetypes.guess_type(self.name)
 
         if mtype is None:
             if self.is_binary:
@@ -322,7 +322,7 @@
             else:
                 mtype = 'text/plain'
                 encoding = None
-        return mtype,encoding
+        return mtype, encoding
 
     @LazyProperty
     def mimetype(self):
@@ -392,8 +392,8 @@
         """
         Returns True if file has binary content.
         """
-        bin = '\0' in self.content
-        return bin
+        _bin = '\0' in self.content
+        return _bin
 
     @LazyProperty
     def extension(self):
@@ -406,6 +406,10 @@
         """
         return bool(self.mode & stat.S_IXUSR)
 
+    def __repr__(self):
+        return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
+                                 self.changeset.short_id)
+
 
 class RemovedFileNode(FileNode):
     """
@@ -537,6 +541,10 @@
 
         return size
 
+    def __repr__(self):
+        return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
+                                 self.changeset.short_id)
+
 
 class RootNode(DirNode):
     """
--- a/rhodecode/lib/vcs/utils/diffs.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/lib/vcs/utils/diffs.py	Sat Mar 03 03:41:38 2012 +0200
@@ -15,17 +15,17 @@
 from rhodecode.lib.vcs.nodes import FileNode, NodeError
 
 
-def get_udiff(filenode_old, filenode_new,show_whitespace=True):
+def get_udiff(filenode_old, filenode_new, show_whitespace=True):
     """
     Returns unified diff between given ``filenode_old`` and ``filenode_new``.
     """
     try:
-        filenode_old_date = filenode_old.last_changeset.date
+        filenode_old_date = filenode_old.changeset.date
     except NodeError:
         filenode_old_date = None
 
     try:
-        filenode_new_date = filenode_new.last_changeset.date
+        filenode_new_date = filenode_new.changeset.date
     except NodeError:
         filenode_new_date = None
 
--- a/rhodecode/model/comment.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/model/comment.py	Sat Mar 03 03:41:38 2012 +0200
@@ -67,7 +67,7 @@
             repo = Repository.get(repo_id)
             cs = repo.scm_instance.get_changeset(revision)
             desc = cs.message
-            author = cs.author_email
+            author_email = cs.author_email
             comment = ChangesetComment()
             comment.repo = repo
             comment.user_id = user_id
@@ -92,22 +92,27 @@
                                    )
                              )
             body = text
+
+            # get the current participants of this changeset
             recipients = ChangesetComment.get_users(revision=revision)
-            # add changeset author
-            recipients += [User.get_by_email(author)]
 
-            NotificationModel().create(created_by=user_id, subject=subj,
-                                   body=body, recipients=recipients,
-                                   type_=Notification.TYPE_CHANGESET_COMMENT)
+            # add changeset author if it's in rhodecode system
+            recipients += [User.get_by_email(author_email)]
+
+            NotificationModel().create(
+              created_by=user_id, subject=subj, body=body,
+              recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT
+            )
 
             mention_recipients = set(self._extract_mentions(body))\
                                     .difference(recipients)
             if mention_recipients:
                 subj = _('[Mention]') + ' ' + subj
-                NotificationModel().create(created_by=user_id, subject=subj,
-                                    body=body,
-                                    recipients=mention_recipients,
-                                    type_=Notification.TYPE_CHANGESET_COMMENT)
+                NotificationModel().create(
+                    created_by=user_id, subject=subj, body=body,
+                    recipients=mention_recipients,
+                    type_=Notification.TYPE_CHANGESET_COMMENT
+                )
 
             return comment
 
--- a/rhodecode/model/db.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/model/db.py	Sat Mar 03 03:41:38 2012 +0200
@@ -807,7 +807,9 @@
 
     @property
     def repositories(self):
-        return Repository.query().filter(Repository.group == self)
+        return Repository.query()\
+                .filter(Repository.group == self)\
+                .order_by(Repository.repo_name)
 
     @property
     def repositories_recursive_count(self):
--- a/rhodecode/model/forms.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/model/forms.py	Sat Mar 03 03:41:38 2012 +0200
@@ -487,7 +487,7 @@
     class _UniqSystemEmail(formencode.validators.FancyValidator):
         def to_python(self, value, state):
             value = value.lower()
-            if old_data.get('email', '').lower() != value:
+            if (old_data.get('email') or '').lower() != value:
                 user = User.get_by_email(value, case_insensitive=True)
                 if user:
                     raise formencode.Invalid(
--- a/rhodecode/model/notification.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/model/notification.py	Sat Mar 03 03:41:38 2012 +0200
@@ -85,13 +85,19 @@
                 if obj:
                     recipients_objs.append(obj)
             recipients_objs = set(recipients_objs)
+            log.debug('sending notifications %s to %s' % (
+                type_, recipients_objs)
+            )
         else:
             # empty recipients means to all admins
             recipients_objs = User.query().filter(User.admin == True).all()
-
-        notif = Notification.create(created_by=created_by_obj, subject=subject,
-                                    body=body, recipients=recipients_objs,
-                                    type_=type_)
+            log.debug('sending notifications %s to admins: %s' % (
+                type_, recipients_objs)
+            )
+        notif = Notification.create(
+            created_by=created_by_obj, subject=subject,
+            body=body, recipients=recipients_objs, type_=type_
+        )
 
         if with_email is False:
             return notif
@@ -163,10 +169,12 @@
         of notification object
         """
 
-        _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
-                notification.TYPE_MESSAGE:_('sent message'),
-                notification.TYPE_MENTION:_('mentioned you'),
-                notification.TYPE_REGISTRATION:_('registered in RhodeCode')}
+        _map = {
+            notification.TYPE_CHANGESET_COMMENT: _('commented on commit'),
+            notification.TYPE_MESSAGE: _('sent message'),
+            notification.TYPE_MENTION: _('mentioned you'),
+            notification.TYPE_REGISTRATION: _('registered in RhodeCode')
+        }
 
         DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
 
@@ -176,9 +184,10 @@
         else:
             DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
             when = DTF(notification.created_on)
-        data = dict(user=notification.created_by_user.username,
-                    action=_map[notification.type_],
-                    when=when)
+        data = dict(
+            user=notification.created_by_user.username,
+            action=_map[notification.type_], when=when,
+        )
         return tmpl % data
 
 
@@ -194,10 +203,10 @@
         self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
 
         self.email_types = {
-            self.TYPE_CHANGESET_COMMENT:'email_templates/changeset_comment.html',
-            self.TYPE_PASSWORD_RESET:'email_templates/password_reset.html',
-            self.TYPE_REGISTRATION:'email_templates/registration.html',
-            self.TYPE_DEFAULT:'email_templates/default.html'
+         self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
+         self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
+         self.TYPE_REGISTRATION: 'email_templates/registration.html',
+         self.TYPE_DEFAULT: 'email_templates/default.html'
         }
 
     def get_email_tmpl(self, type_, **kwargs):
@@ -210,7 +219,7 @@
         base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
         email_template = self._tmpl_lookup.get_template(base)
         # translator inject
-        _kwargs = {'_':_}
+        _kwargs = {'_': _}
         _kwargs.update(kwargs)
         log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
         return email_template.render(**_kwargs)
--- a/rhodecode/model/scm.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/model/scm.py	Sat Mar 03 03:41:38 2012 +0200
@@ -38,7 +38,7 @@
 from rhodecode.lib import safe_str
 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
-    action_logger, EmptyChangeset
+    action_logger, EmptyChangeset, REMOVED_REPO_PAT
 from rhodecode.model import BaseModel
 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
     UserFollowing, UserLog, User, RepoGroup
@@ -182,6 +182,9 @@
         repos = {}
 
         for name, path in get_filesystem_repos(repos_path, recursive=True):
+            # skip removed repos
+            if REMOVED_REPO_PAT.match(name):
+                continue
 
             # name need to be decomposed and put back together using the /
             # since this is internal storage separator for rhodecode
--- a/rhodecode/model/user.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/model/user.py	Sat Mar 03 03:41:38 2012 +0200
@@ -533,6 +533,13 @@
         """
         user = self.__get_user(user)
         perm = self.__get_perm(perm)
+        # if this permission is already granted skip it
+        _perm = UserToPerm.query()\
+            .filter(UserToPerm.user == user)\
+            .filter(UserToPerm.permission == perm)\
+            .scalar()
+        if _perm:
+            return
         new = UserToPerm()
         new.user = user
         new.permission = perm
@@ -548,7 +555,9 @@
         user = self.__get_user(user)
         perm = self.__get_perm(perm)
 
-        obj = UserToPerm.query().filter(UserToPerm.user == user)\
-                .filter(UserToPerm.permission == perm).scalar()
+        obj = UserToPerm.query()\
+                .filter(UserToPerm.user == user)\
+                .filter(UserToPerm.permission == perm)\
+                .scalar()
         if obj:
             self.sa.delete(obj)
--- a/rhodecode/model/users_group.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/model/users_group.py	Sat Mar 03 03:41:38 2012 +0200
@@ -172,6 +172,14 @@
 
         users_group = self.__get_users_group(users_group)
 
+        # if this permission is already granted skip it
+        _perm = UsersGroupToPerm.query()\
+            .filter(UsersGroupToPerm.users_group == users_group)\
+            .filter(UsersGroupToPerm.permission == perm)\
+            .scalar()
+        if _perm:
+            return
+
         new = UsersGroupToPerm()
         new.users_group = users_group
         new.permission = perm
--- a/rhodecode/public/js/rhodecode.js	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/public/js/rhodecode.js	Sat Mar 03 03:41:38 2012 +0200
@@ -613,16 +613,20 @@
  * QUICK REPO MENU
  */
 var quick_repo_menu = function(){
-    YUE.on(YUQ('.quick_repo_menu'),'click',function(e){
-        var menu = e.currentTarget.firstElementChild.firstElementChild;
-        if(YUD.hasClass(menu,'hidden')){
-            YUD.addClass(e.currentTarget,'active');
-            YUD.removeClass(menu,'hidden');
-        }else{
-            YUD.removeClass(e.currentTarget,'active');
-            YUD.addClass(menu,'hidden');
-        }
-    })
+    YUE.on(YUQ('.quick_repo_menu'),'mouseenter',function(e){
+            var menu = e.currentTarget.firstElementChild.firstElementChild;
+            if(YUD.hasClass(menu,'hidden')){
+                YUD.replaceClass(e.currentTarget,'hidden', 'active');
+                YUD.replaceClass(menu, 'hidden', 'active');
+            }
+        })
+    YUE.on(YUQ('.quick_repo_menu'),'mouseleave',function(e){
+            var menu = e.currentTarget.firstElementChild.firstElementChild;
+            if(YUD.hasClass(menu,'active')){
+                YUD.replaceClass(e.currentTarget, 'active', 'hidden');
+                YUD.replaceClass(menu, 'active', 'hidden');
+            }
+        })
 };
 
 
--- a/rhodecode/templates/admin/repos/repos.html	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/templates/admin/repos/repos.html	Sat Mar 03 03:41:38 2012 +0200
@@ -42,8 +42,8 @@
           </tr>
          </thead>
 
-          %for cnt,repo in enumerate(c.repos_list,1):
-          <tr class="parity${cnt%2}">
+          %for cnt,repo in enumerate(c.repos_list):
+          <tr class="parity${(cnt+1)%2}">
               <td class="quick_repo_menu">
                 ${dt.quick_menu(repo['name'])}
               </td>
--- a/rhodecode/templates/changeset/raw_changeset.html	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/templates/changeset/raw_changeset.html	Sat Mar 03 03:41:38 2012 +0200
@@ -1,8 +1,9 @@
+%if h.is_hg(c.scm_type):
 # ${c.scm_type.upper()} changeset patch
 # User ${c.changeset.author|n}
 # Date ${c.changeset.date}
 # Node ID ${c.changeset.raw_id}
 ${c.parent_tmpl}
 ${c.changeset.message}
-
+%endif
 ${c.diffs|n}
--- a/rhodecode/templates/files/files_annotate.html	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/templates/files/files_annotate.html	Sat Mar 03 03:41:38 2012 +0200
@@ -34,8 +34,8 @@
 			    <dd>
 			        <div>
 			        ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
-			        ${h.hidden('diff2',c.file.last_changeset.raw_id)}
-			        ${h.select('diff1',c.file.last_changeset.raw_id,c.file_history)}
+			        ${h.hidden('diff2',c.file.changeset.raw_id)}
+			        ${h.select('diff1',c.file.changeset.raw_id,c.file_history)}
 			        ${h.submit('diff','diff to revision',class_="ui-btn")}
 			        ${h.submit('show_rev','show at revision',class_="ui-btn")}
 			        ${h.end_form()}
@@ -46,7 +46,7 @@
                 <div class="code-header">
                     <div class="stats">
                         <div class="left"><img src="${h.url('/images/icons/file.png')}"/></div>
-                        <div class="left item">${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))}</div>
+                        <div class="left item">${h.link_to("r%s:%s" % (c.file.changeset.revision,h.short_id(c.file.changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id))}</div>
                         <div class="left item">${h.format_byte_size(c.file.size,binary=True)}</div>
                         <div class="left item last">${c.file.mimetype}</div>
                         <div class="buttons">
--- a/rhodecode/templates/files/files_browser.html	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/templates/files/files_browser.html	Sat Mar 03 03:41:38 2012 +0200
@@ -47,7 +47,7 @@
 		                 <th>${_('Name')}</th>
 		                 <th>${_('Size')}</th>
 		                 <th>${_('Mimetype')}</th>
-		                 <th>${_('Revision')}</th>
+		                 <th>${_('Last Revision')}</th>
 		                 <th>${_('Last modified')}</th>
 		                 <th>${_('Last commiter')}</th>
 		             </tr>
@@ -70,7 +70,7 @@
 		    %for cnt,node in enumerate(c.file):
 				<tr class="parity${cnt%2}">
 		             <td>
-						${h.link_to(node.name,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=h.safe_unicode(node.path)),class_=file_class(node)+" ypjax-link")}
+                        ${h.link_to(node.name,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=h.safe_unicode(node.path)),class_=file_class(node)+" ypjax-link")}
 		             </td>
 		             <td>
 		             %if node.is_file():
--- a/rhodecode/templates/files/files_edit.html	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/templates/files/files_edit.html	Sat Mar 03 03:41:38 2012 +0200
@@ -42,7 +42,7 @@
             <div class="code-header">
                 <div class="stats">
                     <div class="left"><img src="${h.url('/images/icons/file.png')}"/></div>
-                    <div class="left item">${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))}</div>
+                    <div class="left item">${h.link_to("r%s:%s" % (c.file.changeset.revision,h.short_id(c.file.changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id))}</div>
                     <div class="left item">${h.format_byte_size(c.file.size,binary=True)}</div>
                     <div class="left item last">${c.file.mimetype}</div>
                     <div class="buttons">
--- a/rhodecode/templates/files/files_source.html	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/templates/files/files_source.html	Sat Mar 03 03:41:38 2012 +0200
@@ -3,8 +3,8 @@
 	<dd>
 		<div>
 		${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
-		${h.hidden('diff2',c.file.last_changeset.raw_id)}
-		${h.select('diff1',c.file.last_changeset.raw_id,c.file_history)}
+		${h.hidden('diff2',c.file.changeset.raw_id)}
+		${h.select('diff1',c.file.changeset.raw_id,c.file_history)}
 		${h.submit('diff','diff to revision',class_="ui-btn")}
 		${h.submit('show_rev','show at revision',class_="ui-btn")}
 		${h.end_form()}
@@ -16,27 +16,27 @@
 	<div class="code-header">
         <div class="stats">
             <div class="left img"><img src="${h.url('/images/icons/file.png')}"/></div>
-            <div class="left item"><pre>${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))}</pre></div>
+            <div class="left item"><pre>${h.link_to("r%s:%s" % (c.file.changeset.revision,h.short_id(c.file.changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id))}</pre></div>
             <div class="left item"><pre>${h.format_byte_size(c.file.size,binary=True)}</pre></div>
             <div class="left item last"><pre>${c.file.mimetype}</pre></div>
             <div class="buttons">
-              ${h.link_to(_('show annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
-              ${h.link_to(_('show as raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
-              ${h.link_to(_('download as raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
+              ${h.link_to(_('show annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
+              ${h.link_to(_('show as raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
+              ${h.link_to(_('download as raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
               % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
                % if not c.file.is_binary:
-                ${h.link_to(_('edit'),h.url('files_edit_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
+                ${h.link_to(_('edit'),h.url('files_edit_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
                % endif
               % endif
             </div>
         </div>
         <div class="author">
             <div class="gravatar">
-                <img alt="gravatar" src="${h.gravatar_url(h.email(c.file.last_changeset.author),16)}"/>
+                <img alt="gravatar" src="${h.gravatar_url(h.email(c.file.changeset.author),16)}"/>
             </div>
-            <div title="${c.file.last_changeset.author}" class="user">${h.person(c.file.last_changeset.author)}</div>
+            <div title="${c.file.changeset.author}" class="user">${h.person(c.file.changeset.author)}</div>
         </div>
-		<div class="commit">${h.urlify_commit(c.file.last_changeset.message,c.repo_name)}</div>
+		<div class="commit">${h.urlify_commit(c.file.changeset.message,c.repo_name)}</div>
 	</div>
 	<div class="code-body">
 	   %if c.file.is_binary:
@@ -46,7 +46,7 @@
 			${h.pygmentize(c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
 		%else:
 			${_('File is too big to display')} ${h.link_to(_('show as raw'),
-			h.url('files_raw_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id,f_path=c.f_path))}
+			h.url('files_raw_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id,f_path=c.f_path))}
 		%endif
        <script type="text/javascript">
            function highlight_lines(lines){
--- a/rhodecode/templates/index_base.html	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/templates/index_base.html	Sat Mar 03 03:41:38 2012 +0200
@@ -126,8 +126,8 @@
                 </tr>
             </thead>
             <tbody>
-            %for cnt,repo in enumerate(c.repos_list,1):
-                <tr class="parity${cnt%2}">
+            %for cnt,repo in enumerate(c.repos_list):
+                <tr class="parity${(cnt+1)%2}">
                     ##QUICK MENU
                     <td class="quick_repo_menu">
                       ${dt.quick_menu(repo['name'])}
@@ -172,7 +172,7 @@
         </div>
     </div>
     <script>
-      YUD.get('repo_count').innerHTML = ${cnt};
+      YUD.get('repo_count').innerHTML = ${cnt+1};
       var func = function(node){
           return node.parentNode.parentNode.parentNode.parentNode;
       }
--- a/rhodecode/tests/functional/test_files.py	Tue Feb 28 20:55:14 2012 +0200
+++ b/rhodecode/tests/functional/test_files.py	Sat Mar 03 03:41:38 2012 +0200
@@ -75,7 +75,7 @@
 
         #test or history
         response.mustcontain("""<optgroup label="Changesets">
-<option selected="selected" value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
+<option value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
@@ -110,23 +110,20 @@
 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
 </optgroup>
 <optgroup label="Branches">
-<option value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
+<option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
 <option value="97e8b885c04894463c51898e14387d80c30ed1ee">git</option>
 <option value="2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</option>
 </optgroup>
 <optgroup label="Tags">
-<option value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</option>
+<option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</option>
 <option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">0.1.4</option>
 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
-</optgroup>""")
+</optgroup>
+""")
 
-        response.mustcontain("""<div class="commit">Partially implemented #16. filecontent/commit message/author/node name are safe_unicode now.
-In addition some other __str__ are unicode as well
-Added test for unicode
-Improved test to clone into uniq repository.
-removed extra unicode conversion in diff.</div>""")
+        response.mustcontain("""<div class="commit">merge</div>""")
 
         response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""")
 
@@ -139,7 +136,7 @@
 
 
         response.mustcontain("""<optgroup label="Changesets">
-<option selected="selected" value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
+<option value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
@@ -174,18 +171,17 @@
 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
 </optgroup>
 <optgroup label="Branches">
-<option value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
+<option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
 <option value="97e8b885c04894463c51898e14387d80c30ed1ee">git</option>
 <option value="2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</option>
 </optgroup>
 <optgroup label="Tags">
-<option value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</option>
+<option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</option>
 <option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">0.1.4</option>
 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
-</optgroup>
-""")
+</optgroup>""")
 
         response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""")