changeset 2238:09fd90d91b6a codereview

merge with beta
author Marcin Kuzminski <marcin@python-works.com>
date Sat, 05 May 2012 22:22:53 +0200
parents 69404d45f6c1 (current diff) d919631e8390 (diff)
children 493e03a4d268
files rhodecode/__init__.py rhodecode/controllers/changelog.py rhodecode/public/css/style.css rhodecode/templates/changeset/changeset.html
diffstat 18 files changed, 197 insertions(+), 51 deletions(-) [+]
line wrap: on
line diff
--- a/docs/changelog.rst	Thu May 03 01:05:08 2012 +0200
+++ b/docs/changelog.rst	Sat May 05 22:22:53 2012 +0200
@@ -25,6 +25,7 @@
 - #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
 
 fixes
 +++++
@@ -34,6 +35,8 @@
 - #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
 
 1.3.4 (**2012-03-28**)
 ----------------------
--- a/docs/conf.py	Thu May 03 01:05:08 2012 +0200
+++ b/docs/conf.py	Sat May 05 22:22:53 2012 +0200
@@ -27,8 +27,8 @@
 
 # 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', 
+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.
--- a/requires.txt	Thu May 03 01:05:08 2012 +0200
+++ b/requires.txt	Sat May 05 22:22:53 2012 +0200
@@ -5,7 +5,7 @@
 SQLAlchemy==0.7.6
 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
@@ -14,4 +14,4 @@
 markdown==2.1.1
 docutils==0.8.1
 py-bcrypt
-mercurial>=2.2,<2.3
\ No newline at end of file
+mercurial>=2.2.1,<2.3
\ No newline at end of file
--- a/rhodecode/__init__.py	Thu May 03 01:05:08 2012 +0200
+++ b/rhodecode/__init__.py	Sat May 05 22:22:53 2012 +0200
@@ -54,7 +54,7 @@
     "SQLAlchemy==0.7.6",
     "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",
@@ -69,10 +69,10 @@
     requirements.append("pysqlite")
 
 if __platform__ in PLATFORM_WIN:
-    requirements.append("mercurial>=2.2,<2.3")
+    requirements.append("mercurial>=2.2.1,<2.3")
 else:
     requirements.append("py-bcrypt")
-    requirements.append("mercurial>=2.2,<2.3")
+    requirements.append("mercurial>=2.2.1,<2.3")
 
 
 def get_version():
--- a/rhodecode/controllers/changelog.py	Thu May 03 01:05:08 2012 +0200
+++ b/rhodecode/controllers/changelog.py	Sat May 05 22:22:53 2012 +0200
@@ -126,12 +126,7 @@
 
         elif repo.alias == 'hg':
             dag = graphmod.dagwalker(repo._repo, revs)
-            try:
-                c.dag = graphmod.colored(dag)
-            except:
-                #HG 2.2+
-                c.dag = graphmod.colored(dag, repo._repo)
-
+            c.dag = graphmod.colored(dag, repo._repo)
             for (id, type, ctx, vtx, edges) in c.dag:
                 if type != graphmod.CHANGESET:
                     continue
--- a/rhodecode/lib/diffs.py	Thu May 03 01:05:08 2012 +0200
+++ b/rhodecode/lib/diffs.py	Sat May 05 22:22:53 2012 +0200
@@ -33,8 +33,8 @@
 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
 
 
@@ -79,9 +79,13 @@
                                '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
@@ -97,6 +101,10 @@
     """
     # 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):
@@ -109,7 +117,6 @@
 
     vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
                                  ignore_whitespace, context)
-
     return vcs_gitdiff
 
 
--- a/rhodecode/lib/hooks.py	Thu May 03 01:05:08 2012 +0200
+++ b/rhodecode/lib/hooks.py	Sat May 05 22:22:53 2012 +0200
@@ -111,7 +111,7 @@
     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'))
--- a/rhodecode/lib/middleware/simplegit.py	Thu May 03 01:05:08 2012 +0200
+++ b/rhodecode/lib/middleware/simplegit.py	Sat May 05 22:22:53 2012 +0200
@@ -201,7 +201,7 @@
             # invalidate cache on push
             if action == 'push':
                 self._invalidate_cache(repo_name)
-            self._handle_githooks(action, baseui, environ)
+            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)
@@ -264,7 +264,7 @@
             op = getattr(self, '_git_stored_op', 'pull')
         return op
 
-    def _handle_githooks(self, action, baseui, environ):
+    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:
@@ -279,9 +279,9 @@
         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)
+            log_push_action(ui=baseui, repo=_repo._repo)
         elif action == 'pull' and _hooks.get(pull_hook):
-            log_pull_action(ui=baseui, repo=repo._repo)
+            log_pull_action(ui=baseui, repo=_repo._repo)
 
     def __inject_extras(self, repo_path, baseui, extras={}):
         """
--- a/rhodecode/lib/vcs/backends/base.py	Thu May 03 01:05:08 2012 +0200
+++ b/rhodecode/lib/vcs/backends/base.py	Sat May 05 22:22:53 2012 +0200
@@ -909,3 +909,48 @@
         :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
--- a/rhodecode/lib/vcs/backends/git/changeset.py	Thu May 03 01:05:08 2012 +0200
+++ b/rhodecode/lib/vcs/backends/git/changeset.py	Sat May 05 22:22:53 2012 +0200
@@ -10,7 +10,8 @@
 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
@@ -329,7 +330,13 @@
         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))
@@ -357,24 +364,31 @@
         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]
@@ -416,7 +430,6 @@
                         line))
                 _path = splitted[1].strip()
                 paths.add(_path)
-
         return sorted(paths)
 
     @LazyProperty
--- a/rhodecode/lib/vcs/backends/git/repository.py	Thu May 03 01:05:08 2012 +0200
+++ b/rhodecode/lib/vcs/backends/git/repository.py	Sat May 05 22:22:53 2012 +0200
@@ -52,7 +52,7 @@
         if baseui is None:
             from mercurial.ui import ui
             baseui = ui()
-        # patch the instance of GitRepo with an "FAKE" ui object to add 
+        # patch the instance of GitRepo with an "FAKE" ui object to add
         # compatibility layer with Mercurial
         setattr(self._repo, 'ui', baseui)
 
@@ -411,7 +411,7 @@
             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``.
--- a/rhodecode/lib/vcs/backends/hg/changeset.py	Thu May 03 01:05:08 2012 +0200
+++ b/rhodecode/lib/vcs/backends/hg/changeset.py	Sat May 05 22:22:53 2012 +0200
@@ -5,8 +5,9 @@
 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
@@ -159,6 +160,13 @@
                 " %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``.
@@ -271,17 +279,27 @@
             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):
--- a/rhodecode/lib/vcs/nodes.py	Thu May 03 01:05:08 2012 +0200
+++ b/rhodecode/lib/vcs/nodes.py	Sat May 05 22:22:53 2012 +0200
@@ -8,19 +8,22 @@
     :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
 
@@ -209,6 +212,13 @@
         """
         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
@@ -561,3 +571,41 @@
 
     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)
--- a/rhodecode/public/css/style.css	Thu May 03 01:05:08 2012 +0200
+++ b/rhodecode/public/css/style.css	Sat May 05 22:22:53 2012 +0200
@@ -2729,6 +2729,14 @@
 	text-align: left;
 }
 
+table.code-browser .submodule-dir {
+    background: url("../images/icons/disconnect.png") no-repeat scroll 3px;
+    height: 16px;
+    padding-left: 20px;
+    text-align: left;
+}
+
+
 .box .search {
 	clear: both;
 	overflow: hidden;
--- a/rhodecode/templates/changeset/changeset.html	Thu May 03 01:05:08 2012 +0200
+++ b/rhodecode/templates/changeset/changeset.html	Sat May 05 22:22:53 2012 +0200
@@ -81,8 +81,11 @@
                  %if len(c.changeset.parents)>1:
                  <span class="merge">${_('merge')}</span>
                  %endif
-		             <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
-		             ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
+		             %if c.changeset.branch:
+                     <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
+		             ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
+                     </span>
+                     %endif
 		             %for tag in c.changeset.tags:
 		                 <span class="tagtag"  title="${'%s %s' % (_('tag'),tag)}">
 		                 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
--- a/rhodecode/templates/files/files_browser.html	Thu May 03 01:05:08 2012 +0200
+++ b/rhodecode/templates/files/files_browser.html	Sat May 05 22:22:53 2012 +0200
@@ -70,7 +70,11 @@
 		    %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")}
+                        %if node.is_submodule():
+                           ${h.link_to(node.name,node.url or '#',class_="submodule-dir ypjax-link")}
+                        %else:
+                          ${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")}
+                        %endif:
 		             </td>
 		             <td>
 		             %if node.is_file():
--- a/rhodecode/templates/index_base.html	Thu May 03 01:05:08 2012 +0200
+++ b/rhodecode/templates/index_base.html	Sat May 05 22:22:53 2012 +0200
@@ -119,7 +119,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/templates/shortlog/shortlog_data.html	Thu May 03 01:05:08 2012 +0200
+++ b/rhodecode/templates/shortlog/shortlog_data.html	Sat May 05 22:22:53 2012 +0200
@@ -15,7 +15,7 @@
             <div><pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}">r${cs.revision}:${h.short_id(cs.raw_id)}</a></pre></div>
         </td>
         <td>
-            ${h.link_to(h.truncate(cs.message,50),
+            ${h.link_to(h.truncate(cs.message,50) or _('No commit message'),
             h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id),
             title=cs.message)}
         </td>
@@ -25,9 +25,11 @@
 		<td title="${cs.author}">${h.person(cs.author)}</td>
 		<td>
 			<span class="logtags">
+                %if cs.branch:
 				<span class="branchtag">
                     ${cs.branch}
                 </span>
+                %endif
 			</span>
 		</td>
 		<td>