import re
from itertools import chain
from dulwich import objects
from subprocess import Popen, PIPE
from rhodecode.lib.vcs.conf import settings
from rhodecode.lib.vcs.exceptions import RepositoryError
from rhodecode.lib.vcs.exceptions import ChangesetError
from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
from rhodecode.lib.vcs.exceptions import VCSError
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
class GitChangeset(BaseChangeset):
"""
Represents state of the repository at single revision.
def __init__(self, repository, revision):
self._stat_modes = {}
self.repository = repository
self.raw_id = revision
self.revision = repository.revisions.index(revision)
self.short_id = self.raw_id[:12]
self.id = self.raw_id
try:
commit = self.repository._repo.get_object(self.raw_id)
except KeyError:
raise RepositoryError("Cannot get object with id %s" % self.raw_id)
self._commit = commit
self._tree_id = commit.tree
@@ -308,49 +309,55 @@ class GitChangeset(BaseChangeset):
if stream is None:
raise VCSError('You need to pass in a valid stream for filling'
' with archival data')
popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True,
cwd=self.repository.path)
buffer_size = 1024 * 8
chunk = popen.stdout.read(buffer_size)
while chunk:
stream.write(chunk)
# Make sure all descriptors would be read
popen.communicate()
def get_nodes(self, path):
if self._get_kind(path) != NodeKind.DIR:
raise ChangesetError("Directory does not exist for revision %r at "
" %r" % (self.revision, path))
path = self._fix_path(path)
id = self._get_id_for_path(path)
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))
else:
obj_path = name
if obj_path not in self._stat_modes:
self._stat_modes[obj_path] = stat
if isinstance(obj, objects.Tree):
dirnodes.append(DirNode(obj_path, changeset=self))
elif isinstance(obj, objects.Blob):
filenodes.append(FileNode(obj_path, changeset=self, mode=stat))
raise ChangesetError("Requested object should be Tree "
"or Blob, is %r" % type(obj))
nodes = dirnodes + filenodes
for node in nodes:
if not node.path in self.nodes:
self.nodes[node.path] = node
nodes.sort()
return nodes
def get_node(self, path):
if isinstance(path, unicode):
path = path.encode('utf-8')
import os
import posixpath
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.paths import get_dirs_for_path
from ...utils.hgcompat import archival, hex
class MercurialChangeset(BaseChangeset):
Represents state of the repository at the single revision.
self._ctx = repository._repo[revision]
self.revision = self._ctx._rev
self.nodes = {}
@LazyProperty
def tags(self):
return map(safe_unicode, self._ctx.tags())
@@ -138,48 +139,55 @@ class MercurialChangeset(BaseChangeset):
from unicode to str
if path.endswith('/'):
path = path.rstrip('/')
return safe_str(path)
def _get_kind(self, path):
if path in self._file_paths:
return NodeKind.FILE
elif path in self._dir_paths:
return NodeKind.DIR
raise ChangesetError("Node does not exist at the given path %r"
% (path))
def _get_filectx(self, path):
if self._get_kind(path) != NodeKind.FILE:
raise ChangesetError("File does not exist for revision %r at "
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``.
fctx = self._get_filectx(path)
if 'x' in fctx.flags():
return 0100755
return 0100644
def get_file_content(self, path):
Returns content of the file at given ``path``.
return fctx.data()
def get_file_size(self, path):
Returns size of the file at given ``path``.
return fctx.size()
@@ -250,59 +258,69 @@ class MercurialChangeset(BaseChangeset):
archival.archive(self.repository._repo, stream, self.raw_id,
kind, prefix=prefix, subrepos=subrepos)
#stream.close()
if stream.closed and hasattr(stream, 'name'):
stream = open(stream.name, 'rb')
elif hasattr(stream, 'mode') and 'r' not in stream.mode:
stream.seek(0)
Returns combined ``DirNode`` and ``FileNode`` objects list representing
state of changeset at the given ``path``. If node at the given ``path``
is not instance of ``DirNode``, ChangesetError would be raised.
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]
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,
# cache nodes
Returns ``Node`` object from the given ``path``. If there is no node at
the given ``path``, ``ChangesetError`` would be raised.
if not path in self.nodes:
node = FileNode(path, changeset=self)
elif path in self._dir_paths or path in self._dir_paths:
if path == '':
node = RootNode(changeset=self)
node = DirNode(path, changeset=self)
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
# -*- coding: utf-8 -*-
vcs.nodes
~~~~~~~~~
Module holding everything related to vcs nodes.
:created_on: Apr 8, 2010
:copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
import stat
import mimetypes
from pygments import lexers
from rhodecode.lib.vcs.utils import safe_unicode, safe_str
from rhodecode.lib.vcs.exceptions import NodeError
from rhodecode.lib.vcs.exceptions import RemovedFileNodeError
class NodeKind:
SUBMODULE = -1
DIR = 1
FILE = 2
class NodeState:
ADDED = u'added'
CHANGED = u'changed'
NOT_CHANGED = u'not changed'
REMOVED = u'removed'
class NodeGeneratorBase(object):
Base class for removed added and changed filenodes, it's a lazy generator
class that will create filenodes only on iteration or call
The len method doesn't need to create filenodes at all
def __init__(self, current_paths, cs):
self.cs = cs
self.current_paths = current_paths
def __call__(self):
@@ -188,48 +190,55 @@ class Node(object):
if self.is_root():
return ''
return posixpath.dirname(self.path.rstrip('/')) + '/'
def is_file(self):
Returns ``True`` if node's kind is ``NodeKind.FILE``, ``False``
otherwise.
return self.kind == NodeKind.FILE
def is_dir(self):
Returns ``True`` if node's kind is ``NodeKind.DIR``, ``False``
return self.kind == NodeKind.DIR
def is_root(self):
Returns ``True`` if node is a root node and ``False`` otherwise.
return self.kind == NodeKind.DIR and self.path == ''
def is_submodule(self):
Returns ``True`` if node's kind is ``NodeKind.SUBMODULE``, ``False``
return self.kind == NodeKind.SUBMODULE
def added(self):
return self.state is NodeState.ADDED
def changed(self):
return self.state is NodeState.CHANGED
def not_changed(self):
return self.state is NodeState.NOT_CHANGED
def removed(self):
return self.state is NodeState.REMOVED
class FileNode(Node):
Class representing file nodes.
:attribute: path: path to the node, relative to repostiory's root
:attribute: content: if given arbitrary sets content of the file
:attribute: changeset: if given, first time content is accessed, callback
@@ -540,24 +549,52 @@ class DirNode(Node):
def size(self):
size = 0
for root, dirs, files in self.changeset.walk(self.path):
for f in files:
size += f.size
return size
def __repr__(self):
return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
self.changeset.short_id)
class RootNode(DirNode):
DirNode being the root node of the repository.
def __init__(self, nodes=(), changeset=None):
super(RootNode, self).__init__(path='', nodes=nodes,
changeset=changeset)
return '<%s>' % self.__class__.__name__
class SubModuleNode(Node):
represents a SubModule of Git or SubRepo of Mercurial
def __init__(self, name, url=None, changeset=None, alias=None):
self.path = name
self.kind = NodeKind.SUBMODULE
self.alias = alias
# changeset MUST be STR !! since it can point to non-valid SCM
self.changeset = str(changeset)
self.url = url or self._extract_submodule_url()
def _extract_submodule_url(self):
if self.alias == 'git':
return self.path
if self.alias == 'hg':
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[:12])
@@ -2697,48 +2697,56 @@ table.code-browser .browser-file {
}
.diffblock .diff-actions {
padding: 2px 0px 0px 2px;
float: left;
.diffblock .diff-menu ul li {
padding: 0px 0px 0px 0px !important;
.diffblock .diff-menu ul li a{
display: block;
padding: 3px 8px 3px 8px !important;
.diffblock .diff-menu ul li a:hover{
text-decoration: none;
background-color: #EEEEEE;
table.code-browser .browser-dir {
background: url("../images/icons/folder_16.png") no-repeat scroll 3px;
height: 16px;
padding-left: 20px;
text-align: left;
table.code-browser .submodule-dir {
background: url("../images/icons/disconnect.png") no-repeat scroll 3px;
.box .search {
clear: both;
overflow: hidden;
margin: 0;
padding: 0 20px 10px;
.box .search div.search_path {
background: none repeat scroll 0 0 #EEE;
border: 1px solid #CCC;
color: blue;
margin-bottom: 10px;
padding: 10px 0;
.box .search div.search_path div.link {
font-weight: 700;
margin-left: 25px;
.box .search div.search_path div.link a {
color: #003367;
cursor: pointer;
@@ -49,49 +49,53 @@
<th>${_('Mimetype')}</th>
<th>${_('Last Revision')}</th>
<th>${_('Last modified')}</th>
<th>${_('Last commiter')}</th>
</tr>
</thead>
<tbody id="tbody">
%if c.file.parent:
<tr class="parity0">
<td>
${h.link_to('..',h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.file.parent.path),class_="browser-dir ypjax-link")}
</td>
<td></td>
%endif
%for cnt,node in enumerate(c.file):
<tr class="parity${cnt%2}">
${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:
%if node.is_file():
${h.format_byte_size(node.size,binary=True)}
${node.mimetype}
<div class="tooltip" title="${node.last_changeset.message}">
<pre>${'r%s:%s' % (node.last_changeset.revision,node.last_changeset.short_id)}</pre>
</div>
<span class="tooltip" title="${node.last_changeset.date}">
${h.age(node.last_changeset.date)}</span>
Status change: