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
self.message = safe_unicode(commit.message[:-1])
# Always strip last eol
except UnicodeDecodeError:
self.message = commit.message[:-1].decode(commit.encoding
or 'utf-8')
#self.branch = None
self.tags = []
#tree = self.repository.get_object(self._tree_id)
self.nodes = {}
self._paths = {}
@LazyProperty
def author(self):
return safe_unicode(self._commit.committer)
def date(self):
return date_fromtimestamp(self._commit.commit_time,
self._commit.commit_timezone)
def status(self):
Returns modified, added, removed, deleted files for current changeset
return self.changed, self.added, self.removed
def branch(self):
heads = self.repository._heads(reverse=False)
ref = heads.get(self.raw_id)
if ref:
return safe_unicode(ref)
def _fix_path(self, path):
Paths are stored without trailing slash so we need to get rid off it if
needed.
if path.endswith('/'):
path = path.rstrip('/')
return path
def _get_id_for_path(self, path):
# FIXME: Please, spare a couple of minutes and make those codes cleaner;
if not path in self._paths:
path = path.strip('/')
# set root tree
tree = self.repository._repo[self._commit.tree]
if path == '':
self._paths[''] = tree.id
return tree.id
splitted = path.split('/')
dirs, name = splitted[:-1], splitted[-1]
curdir = ''
# initially extract things from root dir
for item, stat, id in tree.iteritems():
if curdir:
name = '/'.join((curdir, item))
else:
name = item
self._paths[name] = id
self._stat_modes[name] = stat
for dir in dirs:
@@ -236,193 +237,199 @@ class GitChangeset(BaseChangeset):
which file at given ``path`` has been modified.
TODO: This function now uses os underlying 'git' and 'grep' commands
which is generally not good. Should be replaced with algorithm
iterating commits.
cmd = 'log --pretty="format: %%H" --name-status -p %s -- "%s"' % (
self.id, path
)
so, se = self.repository.run_git_command(cmd)
ids = re.findall(r'\w{40}', so)
return [self.repository.get_changeset(id) for id in ids]
def get_file_annotate(self, path):
Returns a list of three element tuples with lineno,changeset and line
TODO: This function now uses os underlying 'git' command which is
generally not good. Should be replaced with algorithm iterating
commits.
cmd = 'blame -l --root -r %s -- "%s"' % (self.id, path)
# -l ==> outputs long shas (and we need all 40 characters)
# --root ==> doesn't put '^' character for bounderies
# -r sha ==> blames for the given revision
annotate = []
for i, blame_line in enumerate(so.split('\n')[:-1]):
ln_no = i + 1
id, line = re.split(r' \(.+?\) ', blame_line, 1)
annotate.append((ln_no, self.repository.get_changeset(id), line))
return annotate
def fill_archive(self, stream=None, kind='tgz', prefix=None,
subrepos=False):
Fills up given stream.
:param stream: file like object.
:param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
Default: ``tgz``.
:param prefix: name of root directory in archive.
Default is repository name and changeset's raw_id joined with dash
(``repo-tip.<KIND>``).
:param subrepos: include subrepos in this archive.
:raise ImproperArchiveTypeError: If given kind is wrong.
:raise VcsError: If given stream is None
allowed_kinds = settings.ARCHIVE_SPECS.keys()
if kind not in allowed_kinds:
raise ImproperArchiveTypeError('Archive kind not supported use one'
'of %s', allowed_kinds)
if prefix is None:
prefix = '%s-%s' % (self.repository.name, self.short_id)
elif prefix.startswith('/'):
raise VCSError("Prefix cannot start with leading slash")
elif prefix.strip() == '':
raise VCSError("Prefix cannot be empty")
if kind == 'zip':
frmt = 'zip'
frmt = 'tar'
cmd = 'git archive --format=%s --prefix=%s/ %s' % (frmt, prefix,
self.raw_id)
if kind == 'tgz':
cmd += ' | gzip -9'
elif kind == 'tbz2':
cmd += ' | bzip2 -9'
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))
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')
if not path in self.nodes:
except ChangesetError:
raise NodeDoesNotExistError("Cannot find one of parents' "
"directories for a given path: %s" % path)
node = RootNode(changeset=self)
node = DirNode(path, changeset=self)
node._tree = obj
node = FileNode(path, changeset=self)
node._blob = obj
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 affected_files(self):
Get's a fast accessible file changes for given changeset
return self.added + self.changed
def _diff_name_status(self):
output = []
for parent in self.parents:
cmd = 'diff --name-status %s %s --encoding=utf8' % (parent.raw_id, self.raw_id)
output.append(so.strip())
return '\n'.join(output)
def _get_paths_for_status(self, status):
Returns sorted list of paths for given ``status``.
:param status: one of: *added*, *modified* or *deleted*
paths = set()
char = status[0].upper()
for line in self._diff_name_status.splitlines():
if not line:
if line.startswith(char):
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))
_path = splitted[1].strip()
paths.add(_path)
return sorted(paths)
def added(self):
Returns list of added ``FileNode`` objects.
if not self.parents:
return list(self._get_file_nodes())
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
def tags(self):
return map(safe_unicode, self._ctx.tags())
return safe_unicode(self._ctx.branch())
def message(self):
return safe_unicode(self._ctx.description())
return safe_unicode(self._ctx.user())
return date_fromtimestamp(*self._ctx.date())
return self.repository._repo.status(self._ctx.p1().node(),
self._ctx.node())
def _file_paths(self):
return list(self._ctx)
def _dir_paths(self):
p = list(set(get_dirs_for_path(*self._file_paths)))
p.insert(0, '')
return p
def _paths(self):
return self._dir_paths + self._file_paths
def id(self):
if self.last:
return u'tip'
return self.short_id
def short_id(self):
return self.raw_id[:12]
def parents(self):
Returns list of parents changesets.
return [self.repository.get_changeset(parent.rev())
for parent in self._ctx.parents() if parent.rev() >= 0]
def next(self, branch=None):
if branch and self.branch != branch:
raise VCSError('Branch option used on changeset not belonging '
'to that branch')
def _next(changeset, branch):
next_ = changeset.revision + 1
next_rev = changeset.repository.revisions[next_]
except IndexError:
raise ChangesetDoesNotExistError
cs = changeset.repository.get_changeset(next_rev)
if branch and branch != cs.branch:
return _next(cs, branch)
return cs
return _next(self, branch)
def prev(self, branch=None):
def _prev(changeset, branch):
prev_ = changeset.revision - 1
if prev_ < 0:
raise IndexError
prev_rev = changeset.repository.revisions[prev_]
cs = changeset.repository.get_changeset(prev_rev)
return _prev(cs, branch)
return _prev(self, branch)
needed. Also mercurial keeps filenodes as str so we need to decode
from unicode to str
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()
def get_file_changeset(self, path):
Returns last commit of the file at the given ``path``.
node = self.get_node(path)
return node.history[0]
def get_file_history(self, path):
Returns history of file as reversed list of ``Changeset`` objects for
nodes = [fctx.filectx(x).node() for x in fctx.filelog()]
changesets = [self.repository.get_changeset(hex(node))
for node in reversed(nodes)]
return changesets
for i, annotate_data in enumerate(fctx.annotate()):
annotate.append((ln_no, self.repository\
.get_changeset(hex(annotate_data[0].node())),
annotate_data[1],))
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.
elif path in self._dir_paths or path in self._dir_paths:
"at the given path: %r at revision %r"
return self._ctx.files()
@property
return AddedFileNodesGenerator([n for n in self.status[1]], self)
def changed(self):
Returns list of modified ``FileNode`` objects.
return ChangedFileNodesGenerator([n for n in self.status[0]], self)
def removed(self):
Returns list of removed ``FileNode`` objects.
return RemovedFileNodesGenerator([n for n in self.status[2]], self)
# -*- 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):
return [n for n in self]
def __getslice__(self, i, j):
for p in self.current_paths[i:j]:
yield self.cs.get_node(p)
def __len__(self):
return len(self.current_paths)
def __iter__(self):
for p in self.current_paths:
class AddedFileNodesGenerator(NodeGeneratorBase):
Class holding Added files for current changeset
pass
class ChangedFileNodesGenerator(NodeGeneratorBase):
Class holding Changed files for current changeset
class RemovedFileNodesGenerator(NodeGeneratorBase):
Class holding removed files for current changeset
yield RemovedFileNode(path=p)
class Node(object):
Simplest class representing file or directory on repository. SCM backends
should use ``FileNode`` and ``DirNode`` subclasses rather than ``Node``
directly.
Node's ``path`` cannot start with slash as we operate on *relative* paths
only. Moreover, every single node is identified by the ``path`` attribute,
so it cannot end with slash, too. Otherwise, path could lead to mistakes.
def __init__(self, path, kind):
if path.startswith('/'):
raise NodeError("Cannot initialize Node objects with slash at "
"the beginning as only relative paths are supported")
self.path = path.rstrip('/')
if path == '' and kind != NodeKind.DIR:
raise NodeError("Only DirNode and its subclasses may be "
"initialized with empty path")
self.kind = kind
#self.dirs, self.files = [], []
if self.is_root() and not self.is_dir():
raise NodeError("Root node cannot be FILE kind")
def parent(self):
parent_path = self.get_parent_path()
if parent_path:
if self.changeset:
return self.changeset.get_node(parent_path)
return DirNode(parent_path)
return None
def unicode_path(self):
return safe_unicode(self.path)
def name(self):
Returns name of the node so if its path
then only last part is returned.
return safe_unicode(self.path.rstrip('/').split('/')[-1])
def _get_kind(self):
return self._kind
def _set_kind(self, kind):
if hasattr(self, '_kind'):
raise NodeError("Cannot change node's kind")
self._kind = kind
# Post setter check (path's trailing slash)
if self.path.endswith('/'):
raise NodeError("Node's path cannot end with slash")
kind = property(_get_kind, _set_kind)
def __cmp__(self, other):
Comparator using name of the node, needed for quick list sorting.
kind_cmp = cmp(self.kind, other.kind)
if kind_cmp:
return kind_cmp
return cmp(self.name, other.name)
def __eq__(self, other):
for attr in ['name', 'path', 'kind']:
if getattr(self, attr) != getattr(other, attr):
return False
if self.is_file():
if self.content != other.content:
# For DirNode's check without entering each dir
self_nodes_paths = list(sorted(n.path for n in self.nodes))
other_nodes_paths = list(sorted(n.path for n in self.nodes))
if self_nodes_paths != other_nodes_paths:
return True
def __nq__(self, other):
return not self.__eq__(other)
def __repr__(self):
return '<%s %r>' % (self.__class__.__name__, self.path)
def __str__(self):
return self.__repr__()
def __unicode__(self):
return self.name
def get_parent_path(self):
Returns node's parent path or empty string if node is root.
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
return self.state is NodeState.ADDED
return self.state is NodeState.CHANGED
def not_changed(self):
return self.state is NodeState.NOT_CHANGED
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
:attribute: mode: octal stat mode for a node. Default is 0100644.
def __init__(self, path, content=None, changeset=None, mode=None):
Only one of ``content`` and ``changeset`` may be given. Passing both
would raise ``NodeError`` exception.
:param path: relative path to the node
:param content: content may be passed to constructor
:param changeset: if given, will use it to lazily fetch content
:param mode: octal representation of ST_MODE (i.e. 0100644)
if content and changeset:
raise NodeError("Cannot use both content and changeset")
super(FileNode, self).__init__(path, kind=NodeKind.FILE)
self.changeset = changeset
self._content = content
self._mode = mode or 0100644
def mode(self):
Returns lazily mode of the FileNode. If ``changeset`` is not set, would
use value given at initialization or 0100644 (default).
mode = self.changeset.get_file_mode(self.path)
mode = self._mode
return mode
def content(self):
Returns lazily content of the FileNode. If possible, would try to
decode content from UTF-8.
content = self.changeset.get_file_content(self.path)
content = self._content
if bool(content and '\0' in content):
return content
return safe_unicode(content)
def size(self):
return self.changeset.get_file_size(self.path)
raise NodeError("Cannot retrieve size of the file without related "
"changeset attribute")
return self.last_changeset.message
raise NodeError("Cannot retrieve message of the file without related "
def last_changeset(self):
return self.changeset.get_file_changeset(self.path)
raise NodeError("Cannot retrieve last changeset of the file without "
"related changeset attribute")
def get_mimetype(self):
Mimetype is calculated based on the file's content. If ``_mimetype``
@@ -468,96 +477,124 @@ class DirNode(Node):
raise NodeError("%s represents a dir and has no ``content`` attribute"
% self)
def nodes(self):
nodes = self.changeset.get_nodes(self.path)
nodes = self._nodes
self._nodes_dict = dict((node.path, node) for node in nodes)
return sorted(nodes)
def files(self):
return sorted((node for node in self.nodes if node.is_file()))
def dirs(self):
return sorted((node for node in self.nodes if node.is_dir()))
for node in self.nodes:
yield node
Returns node from within this particular ``DirNode``, so it is now
allowed to fetch, i.e. node located at 'docs/api/index.rst' from node
'docs'. In order to access deeper nodes one must fetch nodes between
them first - this would work::
docs = root.get_node('docs')
docs.get_node('api').get_node('index.rst')
:param: path - relative to the current node
.. note::
To access lazily (as in example above) node have to be initialized
with related changeset object - without it node is out of
context and may know nothing about anything else than nearest
(located at same level) nodes.
raise NodeError("Cannot retrieve node without path")
self.nodes # access nodes first in order to set _nodes_dict
paths = path.split('/')
if len(paths) == 1:
if not self.is_root():
path = '/'.join((self.path, paths[0]))
path = paths[0]
return self._nodes_dict[path]
elif len(paths) > 1:
if self.changeset is None:
raise NodeError("Cannot access deeper "
"nodes without changeset")
path1, path2 = paths[0], '/'.join(paths[1:])
return self.get_node(path1).get_node(path2)
raise KeyError
raise NodeError("Node does not exist at %s" % path)
def state(self):
raise NodeError("Cannot access state of DirNode")
size = 0
for root, dirs, files in self.changeset.walk(self.path):
for f in files:
size += f.size
return size
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':
org = safe_unicode(self.path.rstrip('/').split('/')[-1])
return u'%s @ %s' % (org, self.changeset[:12])
@@ -2625,192 +2625,200 @@ div.browserblock #node_filter_box {
div.browserblock .search_activate {
float: left
}
div.browserblock .add_node {
float: left;
padding-left: 5px;
div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover
{
text-decoration: none !important;
div.browserblock .browser-body {
background: #EEE;
border-top: 1px solid #CCC;
table.code-browser {
border-collapse: collapse;
width: 100%;
table.code-browser tr {
margin: 3px;
table.code-browser thead th {
background-color: #EEE;
height: 20px;
font-size: 1.1em;
font-weight: 700;
text-align: left;
padding-left: 10px;
table.code-browser tbody td {
table.code-browser .browser-file {
background: url("../images/icons/document_16.png") no-repeat scroll 3px;
height: 16px;
padding-left: 20px;
.diffblock .changeset_header {
.diffblock .changeset_file {
background: url("../images/icons/file.png") no-repeat scroll 3px;
padding: 2px 0px 2px 22px;
.diffblock .diff-menu-wrapper{
.diffblock .diff-menu{
position: absolute;
background: none repeat scroll 0 0 #FFFFFF;
border-color: #003367 #666666 #666666;
border-right: 1px solid #666666;
border-style: solid solid solid;
border-width: 1px;
box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
margin-top:5px;
margin-left:1px;
.diffblock .diff-actions {
padding: 2px 0px 0px 2px;
.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;
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 {
margin-left: 25px;
.box .search div.search_path div.link a {
color: #003367;
cursor: pointer;
#path_unlock {
color: red;
font-size: 1.2em;
padding-left: 4px;
.info_box span {
margin-left: 3px;
margin-right: 3px;
.info_box .rev {
font-size: 1.6em;
font-weight: bold;
vertical-align: sub;
.info_box input#at_rev,.info_box input#size {
background: #FFF;
border-top: 1px solid #b3b3b3;
border-left: 1px solid #b3b3b3;
border-right: 1px solid #eaeaea;
border-bottom: 1px solid #eaeaea;
color: #000;
font-size: 12px;
padding: 1px 5px 1px;
.info_box input#view {
text-align: center;
padding: 4px 3px 2px 2px;
.yui-overlay,.yui-panel-container {
visibility: hidden;
z-index: 2;
.yui-tt {
color: #666;
background-color: #FFF;
border: 2px solid #003367;
font: 100% sans-serif;
width: auto;
opacity: 1px;
padding: 8px;
white-space: pre-wrap;
-webkit-border-radius: 8px 8px 8px 8px;
-khtml-border-radius: 8px 8px 8px 8px;
-moz-border-radius: 8px 8px 8px 8px;
border-radius: 8px 8px 8px 8px;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
.ac {
vertical-align: top;
.ac .yui-ac {
position: inherit;
font-size: 100%;
.ac .perm_ac {
width: 20em;
<%def name="file_class(node)">
%if node.is_file():
<%return "browser-file" %>
%else:
<%return "browser-dir"%>
%endif
</%def>
<div id="body" class="browserblock">
<div class="browser-header">
<div class="browser-nav">
${h.form(h.url.current())}
<div class="info_box">
<span class="rev">${_('view')}@rev</span>
<a class="ui-btn" href="${c.url_prev}" title="${_('previous revision')}">«</a>
${h.text('at_rev',value=c.changeset.revision,size=5)}
<a class="ui-btn" href="${c.url_next}" title="${_('next revision')}">»</a>
## ${h.submit('view',_('view'),class_="ui-btn")}
</div>
${h.end_form()}
<div class="browser-branch">
${h.checkbox('stay_at_branch',c.changeset.branch,c.changeset.branch==c.branch)}
<label>${_('follow current branch')}</label>
<div class="browser-search">
<div id="search_activate_id" class="search_activate">
<a class="ui-btn" id="filter_activate" href="#">${_('search file list')}</a>
% if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
<div id="add_node_id" class="add_node">
<a class="ui-btn" href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path)}">${_('add new file')}</a>
% endif
<div>
<div id="node_filter_box_loading" style="display:none">${_('Loading file list...')}</div>
<div id="node_filter_box" style="display:none">
${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}/<input class="init" type="text" value="type to search..." name="filter" size="25" id="node_filter" autocomplete="off">
<div class="browser-body">
<table class="code-browser">
<thead>
<tr>
<th>${_('Name')}</th>
<th>${_('Size')}</th>
<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>
%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")}
${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:
${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>
<span class="tooltip" title="${node.last_changeset.date}">
${h.age(node.last_changeset.date)}</span>
<span title="${node.last_changeset.author}">
${h.person(node.last_changeset.author)}
</span>
%endfor
</tbody>
<tbody id="tbody_filtered" style="display:none">
</table>
Status change: