Changeset - 9265958e33bb
[Not reviewed]
codereview
0 5 0
Marcin Kuzminski - 14 years ago 2012-05-17 00:02:52
marcin@python-works.com
Show changes of status inside comments
- comments that change status are now associated with status object, and showed in comments blocks
5 files changed with 32 insertions and 13 deletions:
0 comments (0 inline, 0 general)
rhodecode/controllers/changeset.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.controllers.changeset
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    changeset controller for pylons showoing changes beetween
 
    revisions
 

	
 
    :created_on: Apr 25, 2010
 
    :author: marcink
 
    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
import logging
 
import traceback
 
from collections import defaultdict
 
from webob.exc import HTTPForbidden
 

	
 
from pylons import tmpl_context as c, url, request, response
 
from pylons.i18n.translation import _
 
from pylons.controllers.util import redirect
 
from pylons.decorators import jsonify
 

	
 
from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
 
    ChangesetDoesNotExistError
 
from rhodecode.lib.vcs.nodes import FileNode
 

	
 
import rhodecode.lib.helpers as h
 
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 
from rhodecode.lib.base import BaseRepoController, render
 
from rhodecode.lib.utils import EmptyChangeset
 
from rhodecode.lib.compat import OrderedDict
 
from rhodecode.lib import diffs
 
from rhodecode.model.db import ChangesetComment, ChangesetStatus
 
from rhodecode.model.comment import ChangesetCommentsModel
 
from rhodecode.model.changeset_status import ChangesetStatusModel
 
from rhodecode.model.meta import Session
 
from rhodecode.lib.diffs import wrapped_diff
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def _update_with_GET(params, GET):
 
    for k in ['diff1', 'diff2', 'diff']:
 
        params[k] += GET.getall(k)
 

	
 

	
 
def anchor_url(revision, path, GET):
 
    fid = h.FID(revision, path)
 
    return h.url.current(anchor=fid, **dict(GET))
 

	
 

	
 
def get_ignore_ws(fid, GET):
 
    ig_ws_global = GET.get('ignorews')
 
    ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
 
    if ig_ws:
 
        try:
 
            return int(ig_ws[0].split(':')[-1])
 
        except:
 
            pass
 
    return ig_ws_global
 

	
 

	
 
def _ignorews_url(GET, fileid=None):
 
    fileid = str(fileid) if fileid else None
 
    params = defaultdict(list)
 
    _update_with_GET(params, GET)
 
    lbl = _('show white space')
 
    ig_ws = get_ignore_ws(fileid, GET)
 
    ln_ctx = get_line_ctx(fileid, GET)
 
    # global option
 
    if fileid is None:
 
        if ig_ws is None:
 
            params['ignorews'] += [1]
 
            lbl = _('ignore white space')
 
        ctx_key = 'context'
 
        ctx_val = ln_ctx
 
    # per file options
 
    else:
 
        if ig_ws is None:
 
            params[fileid] += ['WS:1']
 
            lbl = _('ignore white space')
 

	
 
        ctx_key = fileid
 
        ctx_val = 'C:%s' % ln_ctx
 
    # if we have passed in ln_ctx pass it along to our params
 
    if ln_ctx:
 
        params[ctx_key] += [ctx_val]
 

	
 
    params['anchor'] = fileid
 
    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')
 

	
 

	
 
def get_line_ctx(fid, GET):
 
    ln_ctx_global = GET.get('context')
 
    ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
 

	
 
    if ln_ctx:
 
        retval = ln_ctx[0].split(':')[-1]
 
    else:
 
        retval = ln_ctx_global
 

	
 
    try:
 
        return int(retval)
 
    except:
 
        return
 

	
 

	
 
def _context_url(GET, fileid=None):
 
    """
 
    Generates url for context lines
 

	
 
    :param fileid:
 
    """
 

	
 
    fileid = str(fileid) if fileid else None
 
    ig_ws = get_ignore_ws(fileid, GET)
 
    ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
 

	
 
    params = defaultdict(list)
 
    _update_with_GET(params, GET)
 

	
 
    # global option
 
    if fileid is None:
 
        if ln_ctx > 0:
 
            params['context'] += [ln_ctx]
 

	
 
        if ig_ws:
 
            ig_ws_key = 'ignorews'
 
            ig_ws_val = 1
 

	
 
    # per file option
 
    else:
 
        params[fileid] += ['C:%s' % ln_ctx]
 
        ig_ws_key = fileid
 
        ig_ws_val = 'WS:%s' % 1
 

	
 
    if ig_ws:
 
        params[ig_ws_key] += [ig_ws_val]
 

	
 
    lbl = _('%s line context') % ln_ctx
 

	
 
    params['anchor'] = fileid
 
    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')
 

	
 

	
 
class ChangesetController(BaseRepoController):
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def __before__(self):
 
        super(ChangesetController, self).__before__()
 
        c.affected_files_cut_off = 60
 

	
 
    def index(self, revision):
 

	
 
        c.anchor_url = anchor_url
 
        c.ignorews_url = _ignorews_url
 
        c.context_url = _context_url
 
        limit_off = request.GET.get('fulldiff')
 
        #get ranges of revisions if preset
 
        rev_range = revision.split('...')[:2]
 
        enable_comments = True
 
        try:
 
            if len(rev_range) == 2:
 
                enable_comments = False
 
                rev_start = rev_range[0]
 
                rev_end = rev_range[1]
 
                rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
 
                                                            end=rev_end)
 
            else:
 
                rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
 

	
 
            c.cs_ranges = list(rev_ranges)
 
            if not c.cs_ranges:
 
                raise RepositoryError('Changeset range returned empty result')
 

	
 
        except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
 
            log.error(traceback.format_exc())
 
            h.flash(str(e), category='warning')
 
            return redirect(url('home'))
 

	
 
        c.changes = OrderedDict()
 

	
 
        c.lines_added = 0  # count of lines added
 
        c.lines_deleted = 0  # count of lines removes
 

	
 
        cumulative_diff = 0
 
        c.cut_off = False  # defines if cut off limit is reached
 
        c.changeset_statuses = ChangesetStatus.STATUSES
 
        c.comments = []
 
        c.statuses = []
 
        c.inline_comments = []
 
        c.inline_cnt = 0
 
        # Iterate over ranges (default changeset view is always one changeset)
 
        for changeset in c.cs_ranges:
 

	
 
            c.statuses.extend([ChangesetStatusModel()\
 
                              .get_status(c.rhodecode_db_repo.repo_id,
 
                                          changeset.raw_id)])
 

	
 
            c.comments.extend(ChangesetCommentsModel()\
 
                              .get_comments(c.rhodecode_db_repo.repo_id,
 
                                            changeset.raw_id))
 
            inlines = ChangesetCommentsModel()\
 
                        .get_inline_comments(c.rhodecode_db_repo.repo_id,
 
                                             changeset.raw_id)
 
            c.inline_comments.extend(inlines)
 
            c.changes[changeset.raw_id] = []
 
            try:
 
                changeset_parent = changeset.parents[0]
 
            except IndexError:
 
                changeset_parent = None
 

	
 
            #==================================================================
 
            # ADDED FILES
 
            #==================================================================
 
            for node in changeset.added:
 
                fid = h.FID(revision, node.path)
 
                line_context_lcl = get_line_ctx(fid, request.GET)
 
                ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
 
                lim = self.cut_off_limit
 
                if cumulative_diff > self.cut_off_limit:
 
                    lim = -1 if limit_off is None else None
 
                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)
 
                )
 

	
 
            #==================================================================
 
            # CHANGED FILES
 
            #==================================================================
 
            for node in changeset.changed:
 
                try:
 
                    filenode_old = changeset_parent.get_node(node.path)
 
                except ChangesetError:
 
                    log.warning('Unable to fetch parent node for diff')
 
                    filenode_old = FileNode(node.path, '', EmptyChangeset())
 

	
 
                fid = h.FID(revision, node.path)
 
                line_context_lcl = get_line_ctx(fid, request.GET)
 
                ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
 
                lim = self.cut_off_limit
 
                if cumulative_diff > self.cut_off_limit:
 
                    lim = -1 if limit_off is None else None
 
                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)
 
                )
 
            #==================================================================
 
            # REMOVED FILES
 
            #==================================================================
 
            for node in changeset.removed:
 
                c.changes[changeset.raw_id].append(
 
                    ('removed', node, None, None, None, (0, 0))
 
                )
 

	
 
        # count inline comments
 
        for path, lines in c.inline_comments:
 
            for comments in lines.values():
 
                c.inline_cnt += len(comments)
 

	
 
        if len(c.cs_ranges) == 1:
 
            c.changeset = c.cs_ranges[0]
 
            c.changes = c.changes[c.changeset.raw_id]
 

	
 
            return render('changeset/changeset.html')
 
        else:
 
            return render('changeset/changeset_range.html')
 

	
 
    def raw_changeset(self, revision):
 

	
 
        method = request.GET.get('diff', 'show')
 
        ignore_whitespace = request.GET.get('ignorews') == '1'
 
        line_context = request.GET.get('context', 3)
 
        try:
 
            c.scm_type = c.rhodecode_repo.alias
 
            c.changeset = c.rhodecode_repo.get_changeset(revision)
 
        except RepositoryError:
 
            log.error(traceback.format_exc())
 
            return redirect(url('home'))
 
        else:
 
            try:
 
                c.changeset_parent = c.changeset.parents[0]
 
            except IndexError:
 
                c.changeset_parent = None
 
            c.changes = []
 

	
 
            for node in c.changeset.added:
 
                filenode_old = FileNode(node.path, '')
 
                if filenode_old.is_binary or node.is_binary:
 
                    diff = _('binary file') + '\n'
 
                else:
 
                    f_gitdiff = diffs.get_gitdiff(filenode_old, node,
 
                                           ignore_whitespace=ignore_whitespace,
 
                                           context=line_context)
 
                    diff = diffs.DiffProcessor(f_gitdiff,
 
                                                format='gitdiff').raw_diff()
 

	
 
                cs1 = None
 
                cs2 = node.changeset.raw_id
 
                c.changes.append(('added', node, diff, cs1, cs2))
 

	
 
            for node in c.changeset.changed:
 
                filenode_old = c.changeset_parent.get_node(node.path)
 
                if filenode_old.is_binary or node.is_binary:
 
                    diff = _('binary file')
 
                else:
 
                    f_gitdiff = diffs.get_gitdiff(filenode_old, node,
 
                                           ignore_whitespace=ignore_whitespace,
 
                                           context=line_context)
 
                    diff = diffs.DiffProcessor(f_gitdiff,
 
                                                format='gitdiff').raw_diff()
 

	
 
                cs1 = filenode_old.changeset.raw_id
 
                cs2 = node.changeset.raw_id
 
                c.changes.append(('changed', node, diff, cs1, cs2))
 

	
 
        response.content_type = 'text/plain'
 

	
 
        if method == 'download':
 
            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.diffs = ''
 
        for x in c.changes:
 
            c.diffs += x[2]
 

	
 
        return render('changeset/raw_changeset.html')
 

	
 
    @jsonify
 
    def comment(self, repo_name, revision):
 
        comm = ChangesetCommentsModel().create(
 
            text=request.POST.get('text'),
 
            repo_id=c.rhodecode_db_repo.repo_id,
 
            user_id=c.rhodecode_user.user_id,
 
            revision=revision,
 
            f_path=request.POST.get('f_path'),
 
            line_no=request.POST.get('line')
 
        )
 

	
 
        # get status if set !
 
        status = request.POST.get('changeset_status')
 
        if status and request.POST.get('change_changeset_status'):
 
            ChangesetStatusModel().set_status(
 
                c.rhodecode_db_repo.repo_id, 
 
                c.rhodecode_db_repo.repo_id,
 
                revision,
 
                status,
 
                c.rhodecode_user.user_id,
 
                comm,
 
            )
 

	
 
        Session.commit()
 
        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            return redirect(h.url('changeset_home', repo_name=repo_name,
 
                                  revision=revision))
 

	
 
        data = {
 
           'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
 
        }
 
        if comm:
 
            c.co = comm
 
            data.update(comm.get_dict())
 
            data.update({'rendered_text':
 
                         render('changeset/changeset_comment_block.html')})
 

	
 
        return data
 

	
 
    @jsonify
 
    def delete_comment(self, repo_name, comment_id):
 
        co = ChangesetComment.get(comment_id)
 
        owner = lambda: co.author.user_id == c.rhodecode_user.user_id
 
        if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
 
            ChangesetCommentsModel().delete(comment=co)
 
            Session.commit()
 
            return True
 
        else:
 
            raise HTTPForbidden()
rhodecode/model/changeset_status.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.model.changeset_status
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 

	
 
    :created_on: Apr 30, 2012
 
    :author: marcink
 
    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 

	
 
import logging
 
import traceback
 

	
 
from pylons.i18n.translation import _
 

	
 
from rhodecode.lib.utils2 import safe_unicode
 
from rhodecode.model import BaseModel
 
from rhodecode.model.db import ChangesetStatus, Repository, User
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class ChangesetStatusModel(BaseModel):
 

	
 
    def __get_changeset_status(self, changeset_status):
 
        return self._get_instance(ChangesetStatus, changeset_status)
 

	
 
    def __get_repo(self, repository):
 
        return self._get_instance(Repository, repository,
 
                                  callback=Repository.get_by_repo_name)
 

	
 
    def __get_user(self, user):
 
        return self._get_instance(User, user, callback=User.get_by_username)
 

	
 
    def get_status(self, repo, revision):
 
        """
 
        Returns status of changeset for given revision
 

	
 
        :param repo:
 
        :type repo:
 
        :param revision: 40char hash
 
        :type revision: str
 
        """
 
        repo = self.__get_repo(repo)
 

	
 
        status = ChangesetStatus.query()\
 
            .filter(ChangesetStatus.repo == repo)\
 
            .filter(ChangesetStatus.revision == revision).scalar()
 
        status = status.status if status else status
 
        st = status or ChangesetStatus.DEFAULT
 
        return str(st)
 

	
 
    def set_status(self, repo, revision, status, user):
 
    def set_status(self, repo, revision, status, user, comment):
 
        """
 
        Creates new status for changeset or updates the old one
 

	
 
        :param repo:
 
        :type repo:
 
        :param revision:
 
        :type revision:
 
        :param status:
 
        :type status:
 
        :param user:
 
        :type user:
 
        :param comment:
 
        :type comment:
 
        """
 
        repo = self.__get_repo(repo)
 

	
 
        cur_status = ChangesetStatus.query()\
 
            .filter(ChangesetStatus.repo == repo)\
 
            .filter(ChangesetStatus.revision == revision)\
 
            .scalar()
 
        new_status = cur_status or ChangesetStatus()
 
        new_status.author = self.__get_user(user)
 
        new_status.repo = self.__get_repo(repo)
 
        new_status.status = status
 
        new_status.revision = revision
 
        new_status.comment = comment
 
        self.sa.add(new_status)
 
        return new_status
 

	
rhodecode/model/db.py
Show inline comments
 
@@ -830,528 +830,531 @@ class RepoGroup(Base, BaseModel):
 
                break
 
            if cnt == parents_recursion_limit:
 
                # this will prevent accidental infinit loops
 
                log.error('group nested more than %s' %
 
                          parents_recursion_limit)
 
                break
 

	
 
            groups.insert(0, gr)
 
        return groups
 

	
 
    @property
 
    def children(self):
 
        return RepoGroup.query().filter(RepoGroup.parent_group == self)
 

	
 
    @property
 
    def name(self):
 
        return self.group_name.split(RepoGroup.url_sep())[-1]
 

	
 
    @property
 
    def full_path(self):
 
        return self.group_name
 

	
 
    @property
 
    def full_path_splitted(self):
 
        return self.group_name.split(RepoGroup.url_sep())
 

	
 
    @property
 
    def repositories(self):
 
        return Repository.query()\
 
                .filter(Repository.group == self)\
 
                .order_by(Repository.repo_name)
 

	
 
    @property
 
    def repositories_recursive_count(self):
 
        cnt = self.repositories.count()
 

	
 
        def children_count(group):
 
            cnt = 0
 
            for child in group.children:
 
                cnt += child.repositories.count()
 
                cnt += children_count(child)
 
            return cnt
 

	
 
        return cnt + children_count(self)
 

	
 
    def get_new_name(self, group_name):
 
        """
 
        returns new full group name based on parent and new name
 

	
 
        :param group_name:
 
        """
 
        path_prefix = (self.parent_group.full_path_splitted if
 
                       self.parent_group else [])
 
        return RepoGroup.url_sep().join(path_prefix + [group_name])
 

	
 

	
 
class Permission(Base, BaseModel):
 
    __tablename__ = 'permissions'
 
    __table_args__ = (
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 
    permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 

	
 
    def __unicode__(self):
 
        return u"<%s('%s:%s')>" % (
 
            self.__class__.__name__, self.permission_id, self.permission_name
 
        )
 

	
 
    @classmethod
 
    def get_by_key(cls, key):
 
        return cls.query().filter(cls.permission_name == key).scalar()
 

	
 
    @classmethod
 
    def get_default_perms(cls, default_user_id):
 
        q = Session.query(UserRepoToPerm, Repository, cls)\
 
         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
 
         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
 
         .filter(UserRepoToPerm.user_id == default_user_id)
 

	
 
        return q.all()
 

	
 
    @classmethod
 
    def get_default_group_perms(cls, default_user_id):
 
        q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
 
         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
 
         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
 
         .filter(UserRepoGroupToPerm.user_id == default_user_id)
 

	
 
        return q.all()
 

	
 

	
 
class UserRepoToPerm(Base, BaseModel):
 
    __tablename__ = 'repo_to_perm'
 
    __table_args__ = (
 
        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'}
 
    )
 
    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
 
    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 
    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
 

	
 
    user = relationship('User')
 
    repository = relationship('Repository')
 
    permission = relationship('Permission')
 

	
 
    @classmethod
 
    def create(cls, user, repository, permission):
 
        n = cls()
 
        n.user = user
 
        n.repository = repository
 
        n.permission = permission
 
        Session.add(n)
 
        return n
 

	
 
    def __unicode__(self):
 
        return u'<user:%s => %s >' % (self.user, self.repository)
 

	
 

	
 
class UserToPerm(Base, BaseModel):
 
    __tablename__ = 'user_to_perm'
 
    __table_args__ = (
 
        UniqueConstraint('user_id', 'permission_id'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'}
 
    )
 
    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
 
    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 

	
 
    user = relationship('User')
 
    permission = relationship('Permission', lazy='joined')
 

	
 

	
 
class UsersGroupRepoToPerm(Base, BaseModel):
 
    __tablename__ = 'users_group_repo_to_perm'
 
    __table_args__ = (
 
        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'}
 
    )
 
    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
 
    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 
    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
 

	
 
    users_group = relationship('UsersGroup')
 
    permission = relationship('Permission')
 
    repository = relationship('Repository')
 

	
 
    @classmethod
 
    def create(cls, users_group, repository, permission):
 
        n = cls()
 
        n.users_group = users_group
 
        n.repository = repository
 
        n.permission = permission
 
        Session.add(n)
 
        return n
 

	
 
    def __unicode__(self):
 
        return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
 

	
 

	
 
class UsersGroupToPerm(Base, BaseModel):
 
    __tablename__ = 'users_group_to_perm'
 
    __table_args__ = (
 
        UniqueConstraint('users_group_id', 'permission_id',),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'}
 
    )
 
    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
 
    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 

	
 
    users_group = relationship('UsersGroup')
 
    permission = relationship('Permission')
 

	
 

	
 
class UserRepoGroupToPerm(Base, BaseModel):
 
    __tablename__ = 'user_repo_group_to_perm'
 
    __table_args__ = (
 
        UniqueConstraint('user_id', 'group_id', 'permission_id'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'}
 
    )
 

	
 
    group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
 
    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
 
    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 

	
 
    user = relationship('User')
 
    group = relationship('RepoGroup')
 
    permission = relationship('Permission')
 

	
 

	
 
class UsersGroupRepoGroupToPerm(Base, BaseModel):
 
    __tablename__ = 'users_group_repo_group_to_perm'
 
    __table_args__ = (
 
        UniqueConstraint('users_group_id', 'group_id'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'}
 
    )
 

	
 
    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
 
    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
 
    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 

	
 
    users_group = relationship('UsersGroup')
 
    permission = relationship('Permission')
 
    group = relationship('RepoGroup')
 

	
 

	
 
class Statistics(Base, BaseModel):
 
    __tablename__ = 'statistics'
 
    __table_args__ = (
 
         UniqueConstraint('repository_id'),
 
         {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
          'mysql_charset': 'utf8'}
 
    )
 
    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
 
    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
 
    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
 
    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
 
    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
 

	
 
    repository = relationship('Repository', single_parent=True)
 

	
 

	
 
class UserFollowing(Base, BaseModel):
 
    __tablename__ = 'user_followings'
 
    __table_args__ = (
 
        UniqueConstraint('user_id', 'follows_repository_id'),
 
        UniqueConstraint('user_id', 'follows_user_id'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'}
 
    )
 

	
 
    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
 
    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
 
    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
 
    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
 

	
 
    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
 

	
 
    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
 
    follows_repository = relationship('Repository', order_by='Repository.repo_name')
 

	
 
    @classmethod
 
    def get_repo_followers(cls, repo_id):
 
        return cls.query().filter(cls.follows_repo_id == repo_id)
 

	
 

	
 
class CacheInvalidation(Base, BaseModel):
 
    __tablename__ = 'cache_invalidation'
 
    __table_args__ = (
 
        UniqueConstraint('cache_key'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 
    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
 

	
 
    def __init__(self, cache_key, cache_args=''):
 
        self.cache_key = cache_key
 
        self.cache_args = cache_args
 
        self.cache_active = False
 

	
 
    def __unicode__(self):
 
        return u"<%s('%s:%s')>" % (self.__class__.__name__,
 
                                  self.cache_id, self.cache_key)
 
    @classmethod
 
    def clear_cache(cls):
 
        cls.query().delete()
 

	
 
    @classmethod
 
    def _get_key(cls, key):
 
        """
 
        Wrapper for generating a key, together with a prefix
 

	
 
        :param key:
 
        """
 
        import rhodecode
 
        prefix = ''
 
        iid = rhodecode.CONFIG.get('instance_id')
 
        if iid:
 
            prefix = iid
 
        return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
 

	
 
    @classmethod
 
    def get_by_key(cls, key):
 
        return cls.query().filter(cls.cache_key == key).scalar()
 

	
 
    @classmethod
 
    def _get_or_create_key(cls, key, prefix, org_key):
 
        inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
 
        if not inv_obj:
 
            try:
 
                inv_obj = CacheInvalidation(key, org_key)
 
                Session.add(inv_obj)
 
                Session.commit()
 
            except Exception:
 
                log.error(traceback.format_exc())
 
                Session.rollback()
 
        return inv_obj
 

	
 
    @classmethod
 
    def invalidate(cls, key):
 
        """
 
        Returns Invalidation object if this given key should be invalidated
 
        None otherwise. `cache_active = False` means that this cache
 
        state is not valid and needs to be invalidated
 

	
 
        :param key:
 
        """
 

	
 
        key, _prefix, _org_key = cls._get_key(key)
 
        inv = cls._get_or_create_key(key, _prefix, _org_key)
 

	
 
        if inv and inv.cache_active is False:
 
            return inv
 

	
 
    @classmethod
 
    def set_invalidate(cls, key):
 
        """
 
        Mark this Cache key for invalidation
 

	
 
        :param key:
 
        """
 

	
 
        key, _prefix, _org_key = cls._get_key(key)
 
        inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
 
        log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
 
                                                             _org_key))
 
        try:
 
            for inv_obj in inv_objs:
 
                if inv_obj:
 
                    inv_obj.cache_active = False
 

	
 
                Session.add(inv_obj)
 
            Session.commit()
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            Session.rollback()
 

	
 
    @classmethod
 
    def set_valid(cls, key):
 
        """
 
        Mark this cache key as active and currently cached
 

	
 
        :param key:
 
        """
 
        inv_obj = cls.get_by_key(key)
 
        inv_obj.cache_active = True
 
        Session.add(inv_obj)
 
        Session.commit()
 

	
 

	
 
class ChangesetComment(Base, BaseModel):
 
    __tablename__ = 'changeset_comments'
 
    __table_args__ = (
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 
    comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
 
    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
 
    revision = Column('revision', String(40), nullable=False)
 
    line_no = Column('line_no', Unicode(10), nullable=True)
 
    f_path = Column('f_path', Unicode(1000), nullable=True)
 
    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
 
    text = Column('text', Unicode(25000), nullable=False)
 
    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
 

	
 
    author = relationship('User', lazy='joined')
 
    repo = relationship('Repository')
 
    status_change = relationship('ChangesetStatus', uselist=False)
 

	
 
    @classmethod
 
    def get_users(cls, revision):
 
        """
 
        Returns user associated with this changesetComment. ie those
 
        who actually commented
 

	
 
        :param cls:
 
        :param revision:
 
        """
 
        return Session.query(User)\
 
                .filter(cls.revision == revision)\
 
                .join(ChangesetComment.author).all()
 

	
 

	
 
class ChangesetStatus(Base, BaseModel):
 
    __tablename__ = 'changeset_statuses'
 
    __table_args__ = (
 
        UniqueConstraint('repo_id', 'revision'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'}
 
    )
 

	
 
    STATUSES = [
 
        ('not_reviewed', _("Not Reviewed")),  # (no icon) and default
 
        ('approved', _("Approved")),
 
        ('rejected', _("Rejected")),
 
        ('under_review', _("Under Review")),
 
    ]
 
    DEFAULT = STATUSES[0][0]
 

	
 
    changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
 
    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
 
    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
 
    revision = Column('revision', String(40), nullable=False)
 
    status = Column('status', String(128), nullable=False, default=DEFAULT)
 
    changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
 
    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
 

	
 
    author = relationship('User', lazy='joined')
 
    repo = relationship('Repository')
 
    comment = relationship('ChangesetComment', lazy='joined')
 

	
 
    @property
 
    def status_lbl(self):
 
        return dict(self.STATUSES).get(self.status)
 

	
 

	
 
class ChangesetStatusHistory(Base, BaseModel):
 
    __tablename__ = 'changeset_statuses_history'
 
    __table_args__ = (
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'}
 
    )
 
    #TODO: check if sqla has a nice history table implementation
 
    changeset_status_id = Column('changeset_status_id', Integer(), ForeignKey('changeset_statuses.changeset_status_id'), nullable=False, primary_key=True)
 
    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
 
    status = Column('status', String(128), nullable=False)
 
    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
 

	
 

	
 
class Notification(Base, BaseModel):
 
    __tablename__ = 'notifications'
 
    __table_args__ = (
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 

	
 
    TYPE_CHANGESET_COMMENT = u'cs_comment'
 
    TYPE_MESSAGE = u'message'
 
    TYPE_MENTION = u'mention'
 
    TYPE_REGISTRATION = u'registration'
 
    TYPE_PULL_REQUEST = u'pull_request'
 

	
 
    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
 
    subject = Column('subject', Unicode(512), nullable=True)
 
    body = Column('body', Unicode(50000), nullable=True)
 
    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
 
    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
    type_ = Column('type', Unicode(256))
 

	
 
    created_by_user = relationship('User')
 
    notifications_to_users = relationship('UserNotification', lazy='joined',
 
                                          cascade="all, delete, delete-orphan")
 

	
 
    @property
 
    def recipients(self):
 
        return [x.user for x in UserNotification.query()\
 
                .filter(UserNotification.notification == self)\
 
                .order_by(UserNotification.user).all()]
 

	
 
    @classmethod
 
    def create(cls, created_by, subject, body, recipients, type_=None):
 
        if type_ is None:
 
            type_ = Notification.TYPE_MESSAGE
 

	
 
        notification = cls()
 
        notification.created_by_user = created_by
 
        notification.subject = subject
 
        notification.body = body
 
        notification.type_ = type_
 
        notification.created_on = datetime.datetime.now()
 

	
 
        for u in recipients:
 
            assoc = UserNotification()
 
            assoc.notification = notification
 
            u.notifications.append(assoc)
 
        Session.add(notification)
 
        return notification
 

	
 
    @property
 
    def description(self):
 
        from rhodecode.model.notification import NotificationModel
 
        return NotificationModel().make_description(self)
 

	
 

	
 
class UserNotification(Base, BaseModel):
 
    __tablename__ = 'user_to_notification'
 
    __table_args__ = (
 
        UniqueConstraint('user_id', 'notification_id'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'}
 
    )
 
    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
 
    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
 
    read = Column('read', Boolean, default=False)
 
    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
 

	
 
    user = relationship('User', lazy="joined")
 
    notification = relationship('Notification', lazy="joined",
 
                                order_by=lambda: Notification.created_on.desc(),)
 

	
 
    def mark_as_read(self):
 
        self.read = True
 
        Session.add(self)
 

	
 

	
 
class DbMigrateVersion(Base, BaseModel):
 
    __tablename__ = 'db_migrate_version'
 
    __table_args__ = (
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 
    repository_id = Column('repository_id', String(250), primary_key=True)
 
    repository_path = Column('repository_path', Text)
 
    version = Column('version', Integer)
rhodecode/public/css/style.css
Show inline comments
 
@@ -2066,788 +2066,788 @@ div.form div.fields div.field div.button
 
    color: #fff;
 
    display: block;
 
    padding: 2px;
 
}
 
#quick_login ol.links li a:HOVER{
 
    background-color: inherit !important;
 
}
 
 
#register div.title {
 
	clear: both;
 
	overflow: hidden;
 
	position: relative;
 
    background-color: #003B76;
 
    background-repeat: repeat-x;
 
    background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
 
    background-image: -moz-linear-gradient(top, #003b76, #00376e);
 
    background-image: -ms-linear-gradient(top, #003b76, #00376e);
 
    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
 
    background-image: -webkit-linear-gradient(top, #003b76, #00376e);
 
    background-image: -o-linear-gradient(top, #003b76, #00376e);
 
    background-image: linear-gradient(top, #003b76, #00376e);
 
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
 
        endColorstr='#00376e', GradientType=0 );
 
	margin: 0 auto;
 
	padding: 0;
 
}
 
 
#register div.inner {
 
	background: #FFF;
 
	border-top: none;
 
	border-bottom: none;
 
	margin: 0 auto;
 
	padding: 20px;
 
}
 
 
#register div.form div.fields div.field div.label {
 
	width: 135px;
 
	float: left;
 
	text-align: right;
 
	margin: 2px 10px 0 0;
 
	padding: 5px 0 0 5px;
 
}
 
 
#register div.form div.fields div.field div.input input {
 
	width: 300px;
 
	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: 11px;
 
	margin: 0;
 
	padding: 7px 7px 6px;
 
}
 
 
#register div.form div.fields div.buttons {
 
	clear: both;
 
	overflow: hidden;
 
	border-top: 1px solid #DDD;
 
	text-align: left;
 
	margin: 0;
 
	padding: 10px 0 0 150px;
 
}
 
 
#register div.form div.activation_msg {
 
	padding-top: 4px;
 
	padding-bottom: 4px;
 
}
 
 
#journal .journal_day {
 
	font-size: 20px;
 
	padding: 10px 0px;
 
	border-bottom: 2px solid #DDD;
 
	margin-left: 10px;
 
	margin-right: 10px;
 
}
 
 
#journal .journal_container {
 
	padding: 5px;
 
	clear: both;
 
	margin: 0px 5px 0px 10px;
 
}
 
 
#journal .journal_action_container {
 
	padding-left: 38px;
 
}
 
 
#journal .journal_user {
 
	color: #747474;
 
	font-size: 14px;
 
	font-weight: bold;
 
	height: 30px;
 
}
 
 
#journal .journal_icon {
 
	clear: both;
 
	float: left;
 
	padding-right: 4px;
 
	padding-top: 3px;
 
}
 
 
#journal .journal_action {
 
	padding-top: 4px;
 
	min-height: 2px;
 
	float: left
 
}
 
 
#journal .journal_action_params {
 
	clear: left;
 
	padding-left: 22px;
 
}
 
 
#journal .journal_repo {
 
	float: left;
 
	margin-left: 6px;
 
	padding-top: 3px;
 
}
 
 
#journal .date {
 
	clear: both;
 
	color: #777777;
 
	font-size: 11px;
 
	padding-left: 22px;
 
}
 
 
#journal .journal_repo .journal_repo_name {
 
	font-weight: bold;
 
	font-size: 1.1em;
 
}
 
 
#journal .compare_view {
 
	padding: 5px 0px 5px 0px;
 
	width: 95px;
 
}
 
 
.journal_highlight {
 
	font-weight: bold;
 
	padding: 0 2px;
 
	vertical-align: bottom;
 
}
 
 
.trending_language_tbl,.trending_language_tbl td {
 
	border: 0 !important;
 
	margin: 0 !important;
 
	padding: 0 !important;
 
}
 
 
.trending_language_tbl,.trending_language_tbl tr {
 
    border-spacing: 1px;
 
}
 
 
.trending_language {
 
	background-color: #003367;
 
	color: #FFF;
 
	display: block;
 
	min-width: 20px;
 
	text-decoration: none;
 
	height: 12px;
 
	margin-bottom: 0px;
 
	margin-left: 5px;
 
	white-space: pre;
 
	padding: 3px;
 
}
 
 
h3.files_location {
 
	font-size: 1.8em;
 
	font-weight: 700;
 
	border-bottom: none !important;
 
	margin: 10px 0 !important;
 
}
 
 
#files_data dl dt {
 
	float: left;
 
	width: 60px;
 
	margin: 0 !important;
 
	padding: 5px;
 
}
 
 
#files_data dl dd {
 
	margin: 0 !important;
 
	padding: 5px !important;
 
}
 
 
.tablerow0 {
 
	background-color: #F8F8F8;
 
}
 
 
.tablerow1 {
 
    background-color: #FFFFFF;
 
}
 
 
.changeset_id {
 
	font-family: monospace;
 
	color: #666666;
 
}
 
 
.changeset_hash {
 
	color: #000000;
 
}
 
 
#changeset_content {
 
	border-left: 1px solid #CCC;
 
	border-right: 1px solid #CCC;
 
	border-bottom: 1px solid #CCC;
 
	padding: 5px;
 
}
 
 
#changeset_compare_view_content {
 
	border: 1px solid #CCC;
 
	padding: 5px;
 
}
 
 
#changeset_content .container {
 
	min-height: 100px;
 
	font-size: 1.2em;
 
	overflow: hidden;
 
}
 
 
#changeset_compare_view_content .compare_view_commits {
 
	width: auto !important;
 
}
 
 
#changeset_compare_view_content .compare_view_commits td {
 
	padding: 0px 0px 0px 12px !important;
 
}
 
 
#changeset_content .container .right {
 
	float: right;
 
	width: 20%;
 
	text-align: right;
 
}
 
 
#changeset_content .container .left .message {
 
	white-space: pre-wrap;
 
}
 
#changeset_content .container .left .message a:hover {
 
	text-decoration: none;
 
}
 
.cs_files .cur_cs {
 
	margin: 10px 2px;
 
	font-weight: bold;
 
}
 
 
.cs_files .node {
 
	float: left;
 
}
 
 
.cs_files .changes {
 
	float: right;
 
	color:#003367;
 
	
 
}
 
 
.cs_files .changes .added {
 
	background-color: #BBFFBB;
 
	float: left;
 
	text-align: center;
 
	font-size: 9px;
 
    padding: 2px 0px 2px 0px;
 
}
 
 
.cs_files .changes .deleted {
 
	background-color: #FF8888;
 
	float: left;
 
	text-align: center;
 
	font-size: 9px;
 
    padding: 2px 0px 2px 0px;
 
}
 
 
.cs_files .cs_added {
 
	background: url("../images/icons/page_white_add.png") no-repeat scroll
 
		3px;
 
	height: 16px;
 
	padding-left: 20px;
 
	margin-top: 7px;
 
	text-align: left;
 
}
 
 
.cs_files .cs_changed {
 
	background: url("../images/icons/page_white_edit.png") no-repeat scroll
 
		3px;
 
	height: 16px;
 
	padding-left: 20px;
 
	margin-top: 7px;
 
	text-align: left;
 
}
 
 
.cs_files .cs_removed {
 
	background: url("../images/icons/page_white_delete.png") no-repeat
 
		scroll 3px;
 
	height: 16px;
 
	padding-left: 20px;
 
	margin-top: 7px;
 
	text-align: left;
 
}
 
 
#graph {
 
	overflow: hidden;
 
}
 
 
#graph_nodes {
 
	float: left;
 
	margin-right: -6px;
 
	margin-top: 0px;
 
}
 
 
#graph_content {
 
	width: 80%;
 
	float: left;
 
}
 
 
#graph_content .container_header {
 
	border-bottom: 1px solid #DDD;
 
	padding: 10px;
 
	height: 25px;
 
}
 
 
#graph_content #rev_range_container {
 
	padding: 7px 20px;
 
	float: left;
 
}
 
 
#graph_content .container {
 
	border-bottom: 1px solid #DDD;
 
	height: 56px;
 
	overflow: hidden;
 
}
 
 
#graph_content .container .right {
 
	float: right;
 
	width: 23%;
 
	text-align: right;
 
}
 
 
#graph_content .container .left {
 
	float: left;
 
	width: 25%;
 
	padding-left: 5px;
 
}
 
 
#graph_content .container .mid {
 
	float: left;
 
	width: 49%;
 
}
 
 
 
#graph_content .container .left .date {
 
	color: #666;
 
	padding-left: 22px;
 
	font-size: 10px;
 
}
 
 
#graph_content .container .left .author {
 
	height: 22px;
 
}
 
 
#graph_content .container .left .author .user {
 
	color: #444444;
 
	float: left;
 
	margin-left: -4px;
 
	margin-top: 4px;
 
}
 
 
#graph_content .container .mid .message {
 
	white-space: pre-wrap;
 
}
 
 
#graph_content .container .mid .message a:hover{
 
	text-decoration: none;
 
}
 
#content #graph_content .message .revision-link,
 
#changeset_content .container .message .revision-link
 
 {
 
	color:#3F6F9F;
 
    font-weight: bold !important;
 
}
 
 
#content #graph_content .message .issue-tracker-link,
 
#changeset_content .container .message .issue-tracker-link{
 
    color:#3F6F9F;
 
    font-weight: bold !important;
 
}
 
 
.right .changeset-status-container{
 
.changeset-status-container{
 
    padding-right: 5px;
 
    margin-top:1px;
 
    float:right;
 
    height:14px;
 
}
 
.code-header .changeset-status-container{
 
	float:left;
 
	padding:2px 0px 0px 2px;
 
}
 
.right .changeset-status-container .changeset-status-lbl{
 
.changeset-status-container .changeset-status-lbl{
 
	color: rgb(136, 136, 136);
 
    float: left;
 
    padding: 0px 4px 0px 0px;	
 
    padding: 3px 4px 0px 0px
 
}
 
.code-header .changeset-status-container .changeset-status-lbl{
 
    float: left;
 
    padding: 0px 4px 0px 0px;   
 
}
 
.right .changeset-status-container .changeset-status-ico{
 
.changeset-status-container .changeset-status-ico{
 
    float: left;
 
}
 
.code-header .changeset-status-container .changeset-status-ico, .container .changeset-status-ico{
 
    float: left;
 
}
 
.right .comments-container{
 
	padding-right: 5px;
 
	margin-top:1px;
 
	float:right;
 
	height:14px;
 
}
 
 
.right .comments-cnt{
 
    float: left;
 
    color: rgb(136, 136, 136); 
 
    padding-right: 2px; 
 
}
 
 
.right .changes{
 
	clear: both;
 
}
 
 
.right .changes .changed_total {
 
	display: block;
 
	float: right;
 
	text-align: center;
 
	min-width: 45px;
 
	cursor: pointer;
 
	color: #444444;
 
	background: #FEA;
 
	-webkit-border-radius: 0px 0px 0px 6px;
 
	-moz-border-radius: 0px 0px 0px 6px;
 
	border-radius: 0px 0px 0px 6px;
 
	padding: 1px;
 
}
 
 
.right .changes .added,.changed,.removed {
 
	display: block;
 
	padding: 1px;
 
	color: #444444;
 
	float: right;
 
	text-align: center;
 
	min-width: 15px;
 
}
 
 
.right .changes .added {
 
	background: #CFC;
 
}
 
 
.right .changes .changed {
 
	background: #FEA;
 
}
 
 
.right .changes .removed {
 
	background: #FAA;
 
}
 
 
.right .merge {
 
  padding: 1px 3px 1px 3px;
 
  background-color: #fca062;
 
  font-size: 10px;
 
  font-weight: bold;
 
  color: #ffffff;
 
  text-transform: uppercase;
 
  white-space: nowrap;
 
  -webkit-border-radius: 3px;
 
  -moz-border-radius: 3px;
 
  border-radius: 3px;
 
  margin-right: 2px;
 
}
 
 
.right .parent {
 
	color: #666666;
 
	clear:both;
 
}
 
.right .logtags{
 
	padding: 2px 2px 2px 2px;
 
}
 
.right .logtags .branchtag,.right .logtags .tagtag,.right .logtags .booktag{
 
    margin: 0px 2px;
 
}
 
 
.right .logtags .branchtag,.logtags .branchtag {
 
  padding: 1px 3px 1px 3px;
 
  background-color: #bfbfbf;
 
  font-size: 10px;
 
  font-weight: bold;
 
  color: #ffffff;
 
  text-transform: uppercase;
 
  white-space: nowrap;
 
  -webkit-border-radius: 3px;
 
  -moz-border-radius: 3px;
 
  border-radius: 3px;
 
}
 
.right .logtags .branchtag a:hover,.logtags .branchtag a{
 
	color: #ffffff;
 
}
 
.right .logtags .branchtag a:hover,.logtags .branchtag a:hover{
 
	text-decoration: none;
 
	color: #ffffff;
 
}
 
.right .logtags .tagtag,.logtags .tagtag {
 
  padding: 1px 3px 1px 3px;
 
  background-color: #62cffc;
 
  font-size: 10px;
 
  font-weight: bold;
 
  color: #ffffff;
 
  text-transform: uppercase;
 
  white-space: nowrap;
 
  -webkit-border-radius: 3px;
 
  -moz-border-radius: 3px;
 
  border-radius: 3px;
 
}
 
.right .logtags .tagtag a:hover,.logtags .tagtag a{
 
	color: #ffffff;
 
}
 
.right .logtags .tagtag a:hover,.logtags .tagtag a:hover{
 
    text-decoration: none;
 
    color: #ffffff;
 
}
 
.right .logbooks .bookbook,.logbooks .bookbook,.right .logtags .bookbook,.logtags .bookbook {
 
  padding: 1px 3px 1px 3px;
 
  background-color: #46A546;
 
  font-size: 10px;
 
  font-weight: bold;
 
  color: #ffffff;
 
  text-transform: uppercase;
 
  white-space: nowrap;
 
  -webkit-border-radius: 3px;
 
  -moz-border-radius: 3px;
 
  border-radius: 3px;
 
}
 
.right .logbooks .bookbook,.logbooks .bookbook a,.right .logtags .bookbook,.logtags .bookbook a{
 
	color: #ffffff;
 
}
 
.right .logbooks .bookbook,.logbooks .bookbook a:hover,.right .logtags .bookbook,.logtags .bookbook a:hover{
 
    text-decoration: none;
 
    color: #ffffff;
 
}
 
div.browserblock {
 
	overflow: hidden;
 
	border: 1px solid #ccc;
 
	background: #f8f8f8;
 
	font-size: 100%;
 
	line-height: 125%;
 
	padding: 0;
 
    -webkit-border-radius: 6px 6px 0px 0px;
 
    -moz-border-radius: 6px 6px 0px 0px;
 
    border-radius: 6px 6px 0px 0px;	
 
}
 
 
div.browserblock .browser-header {
 
	background: #FFF;
 
	padding: 10px 0px 15px 0px;
 
	width: 100%;
 
}
 
 
div.browserblock .browser-nav {
 
	float: left
 
}
 
 
div.browserblock .browser-branch {
 
	float: left;
 
}
 
 
div.browserblock .browser-branch label {
 
	color: #4A4A4A;
 
	vertical-align: text-top;
 
}
 
 
div.browserblock .browser-header span {
 
	margin-left: 5px;
 
	font-weight: 700;
 
}
 
 
div.browserblock .browser-search {
 
	clear: both;
 
	padding: 8px 8px 0px 5px;
 
	height: 20px;
 
}
 
 
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 {
 
	padding-left: 10px;
 
	height: 20px;
 
}
 
 
table.code-browser .browser-file {
 
	background: url("../images/icons/document_16.png") no-repeat scroll 3px;
 
	height: 16px;
 
	padding-left: 20px;
 
	text-align: left;
 
}
 
.diffblock .changeset_header {
 
    height: 16px;
 
}
 
.diffblock .changeset_file {
 
	background: url("../images/icons/file.png") no-repeat scroll 3px;
 
	text-align: left;
 
	float: left;
 
	padding: 2px 0px 2px 22px;
 
}
 
.diffblock .diff-menu-wrapper{
 
	float: left;
 
}
 
 
.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;
 
    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;
 
    height: 16px;
 
    padding-left: 20px;
 
    text-align: left;
 
}
 
 
 
.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;
 
	text-decoration: none;
 
}
 
 
#path_unlock {
 
	color: red;
 
	font-size: 1.2em;
 
	padding-left: 4px;
 
}
 
 
.info_box span {
 
	margin-left: 3px;
 
	margin-right: 3px;
 
}
 
 
.info_box .rev {
 
	color: #003367;
 
	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;
 
	margin: 0;
 
	padding: 1px 5px 1px;
 
}
 
 
.info_box input#view {
 
	text-align: center;
 
	padding: 4px 3px 2px 2px;
 
}
 
 
.yui-overlay,.yui-panel-container {
 
	visibility: hidden;
 
	position: absolute;
 
	z-index: 2;
 
}
 
 
.yui-tt {
 
	visibility: hidden;
 
	position: absolute;
 
	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;
 
@@ -3535,779 +3535,784 @@ div.form div.fields div.field div.highli
 
#content div.box div.action div.button,#login div.form div.fields div.field div.input div.link,#register div.form div.fields div.field div.input div.link
 
	{
 
	text-align: right;
 
	margin: 6px 0 0;
 
	padding: 0;
 
}
 
 
#content div.box div.action div.button input.ui-state-hover,#login div.form div.fields div.buttons input.ui-state-hover,#register div.form div.fields div.buttons input.ui-state-hover
 
	{
 
	background: #b4b4b4 url("../images/button_selected.png") repeat-x;
 
	border-top: 1px solid #ccc;
 
	border-left: 1px solid #bebebe;
 
	border-right: 1px solid #b1b1b1;
 
	border-bottom: 1px solid #afafaf;
 
	color: #515151;
 
	margin: 0;
 
	padding: 6px 12px;
 
}
 
 
#content div.box div.pagination div.results,#content div.box div.pagination-wh div.results
 
	{
 
	text-align: left;
 
	float: left;
 
	margin: 0;
 
	padding: 0;
 
}
 
 
#content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span
 
	{
 
	height: 1%;
 
	display: block;
 
	float: left;
 
	background: #ebebeb url("../images/pager.png") repeat-x;
 
	border-top: 1px solid #dedede;
 
	border-left: 1px solid #cfcfcf;
 
	border-right: 1px solid #c4c4c4;
 
	border-bottom: 1px solid #c4c4c4;
 
	color: #4A4A4A;
 
	font-weight: 700;
 
	margin: 0;
 
	padding: 6px 8px;
 
}
 
 
#content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled
 
	{
 
	color: #B4B4B4;
 
	padding: 6px;
 
}
 
 
#login,#register {
 
	width: 520px;
 
	margin: 10% auto 0;
 
	padding: 0;
 
}
 
 
#login div.color,#register div.color {
 
	clear: both;
 
	overflow: hidden;
 
	background: #FFF;
 
	margin: 10px auto 0;
 
	padding: 3px 3px 3px 0;
 
}
 
 
#login div.color a,#register div.color a {
 
	width: 20px;
 
	height: 20px;
 
	display: block;
 
	float: left;
 
	margin: 0 0 0 3px;
 
	padding: 0;
 
}
 
 
#login div.title h5,#register div.title h5 {
 
	color: #fff;
 
	margin: 10px;
 
	padding: 0;
 
}
 
 
#login div.form div.fields div.field,#register div.form div.fields div.field
 
	{
 
	clear: both;
 
	overflow: hidden;
 
	margin: 0;
 
	padding: 0 0 10px;
 
}
 
 
#login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message
 
	{
 
	height: 1%;
 
	display: block;
 
	color: red;
 
	margin: 8px 0 0;
 
	padding: 0;
 
	max-width: 320px;
 
}
 
 
#login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label
 
	{
 
	color: #000;
 
	font-weight: 700;
 
}
 
 
#login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input
 
	{
 
	float: left;
 
	margin: 0;
 
	padding: 0;
 
}
 
 
#login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox
 
	{
 
	margin: 0 0 0 184px;
 
	padding: 0;
 
}
 
 
#login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label
 
	{
 
	color: #565656;
 
	font-weight: 700;
 
}
 
 
#login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input
 
	{
 
	color: #000;
 
	font-size: 1em;
 
	font-weight: 700;
 
	margin: 0;
 
}
 
 
#changeset_content .container .wrapper,#graph_content .container .wrapper
 
	{
 
	width: 600px;
 
}
 
 
#changeset_content .container .left {
 
	float: left;
 
	width: 75%;
 
	padding-left: 5px;
 
}
 
 
#changeset_content .container .left .date,.ac .match {
 
	font-weight: 700;
 
	padding-top: 5px;
 
	padding-bottom: 5px;
 
}
 
 
div#legend_container table td,div#legend_choices table td {
 
	border: none !important;
 
	height: 20px !important;
 
	padding: 0 !important;
 
}
 
 
.q_filter_box {
 
	-webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
 
	-webkit-border-radius: 4px;
 
	-moz-border-radius: 4px;
 
	border-radius: 4px;
 
    border: 0 none;
 
    color: #AAAAAA;
 
    margin-bottom: -4px;
 
    margin-top: -4px;
 
    padding-left: 3px;		
 
}
 
 
#node_filter {
 
	border: 0px solid #545454;
 
	color: #AAAAAA;
 
	padding-left: 3px;
 
}
 
 
 
.group_members_wrap{
 
	
 
}
 
 
.group_members .group_member{
 
	height: 30px;
 
	padding:0px 0px 0px 10px;
 
}
 
 
/*README STYLE*/
 
 
div.readme {
 
	padding:0px;
 
}
 
 
div.readme h2 {
 
    font-weight: normal;
 
}
 
 
div.readme .readme_box {
 
    background-color: #fafafa;
 
}
 
 
div.readme .readme_box {
 
clear:both;
 
overflow:hidden;
 
margin:0;
 
padding:0 20px 10px;
 
}
 
 
div.readme .readme_box h1, div.readme .readme_box h2, div.readme .readme_box h3, div.readme .readme_box h4, div.readme .readme_box h5, div.readme .readme_box h6 {
 
border-bottom: 0 !important;
 
margin: 0 !important;
 
padding: 0 !important;
 
line-height: 1.5em !important;
 
}
 
 
 
div.readme .readme_box h1:first-child {
 
padding-top: .25em !important;
 
}
 
 
div.readme .readme_box h2, div.readme .readme_box h3 {
 
margin: 1em 0 !important;
 
}
 
 
div.readme .readme_box h2 {
 
margin-top: 1.5em !important;
 
border-top: 4px solid #e0e0e0 !important;
 
padding-top: .5em !important;
 
}
 
 
div.readme .readme_box p {
 
color: black !important;
 
margin: 1em 0 !important;
 
line-height: 1.5em !important;
 
}
 
 
div.readme .readme_box ul {
 
list-style: disc !important;
 
margin: 1em 0 1em 2em !important;
 
}
 
 
div.readme .readme_box ol {
 
list-style: decimal;
 
margin: 1em 0 1em 2em !important;
 
}
 
 
div.readme .readme_box pre, code {
 
font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
 
}
 
 
div.readme .readme_box code {
 
    font-size: 12px !important;
 
    background-color: ghostWhite !important;
 
    color: #444 !important;
 
    padding: 0 .2em !important;
 
    border: 1px solid #dedede !important;
 
}
 
 
div.readme .readme_box pre code {
 
	padding: 0 !important;
 
	font-size: 12px !important;
 
	background-color: #eee !important;
 
	border: none !important;
 
}
 
 
div.readme .readme_box pre {
 
	margin: 1em 0;
 
	font-size: 12px;
 
	background-color: #eee;
 
	border: 1px solid #ddd;
 
	padding: 5px;
 
	color: #444;
 
	overflow: auto;
 
	-webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
 
	-webkit-border-radius: 3px;
 
	-moz-border-radius: 3px;
 
	border-radius: 3px;
 
}
 
 
 
/** RST STYLE **/
 
 
 
div.rst-block {
 
    padding:0px;
 
}
 
 
div.rst-block h2 {
 
    font-weight: normal;
 
}
 
 
div.rst-block  {
 
    background-color: #fafafa;
 
}
 
 
div.rst-block  {
 
clear:both;
 
overflow:hidden;
 
margin:0;
 
padding:0 20px 10px;
 
}
 
 
div.rst-block  h1, div.rst-block  h2, div.rst-block  h3, div.rst-block  h4, div.rst-block  h5, div.rst-block  h6 {
 
border-bottom: 0 !important;
 
margin: 0 !important;
 
padding: 0 !important;
 
line-height: 1.5em !important;
 
}
 
 
 
div.rst-block  h1:first-child {
 
padding-top: .25em !important;
 
}
 
 
div.rst-block  h2, div.rst-block  h3 {
 
margin: 1em 0 !important;
 
}
 
 
div.rst-block  h2 {
 
margin-top: 1.5em !important;
 
border-top: 4px solid #e0e0e0 !important;
 
padding-top: .5em !important;
 
}
 
 
div.rst-block  p {
 
color: black !important;
 
margin: 1em 0 !important;
 
line-height: 1.5em !important;
 
}
 
 
div.rst-block  ul {
 
list-style: disc !important;
 
margin: 1em 0 1em 2em !important;
 
}
 
 
div.rst-block  ol {
 
list-style: decimal;
 
margin: 1em 0 1em 2em !important;
 
}
 
 
div.rst-block  pre, code {
 
font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
 
}
 
 
div.rst-block  code {
 
    font-size: 12px !important;
 
    background-color: ghostWhite !important;
 
    color: #444 !important;
 
    padding: 0 .2em !important;
 
    border: 1px solid #dedede !important;
 
}
 
 
div.rst-block  pre code {
 
    padding: 0 !important;
 
    font-size: 12px !important;
 
    background-color: #eee !important;
 
    border: none !important;
 
}
 
 
div.rst-block  pre {
 
    margin: 1em 0;
 
    font-size: 12px;
 
    background-color: #eee;
 
    border: 1px solid #ddd;
 
    padding: 5px;
 
    color: #444;
 
    overflow: auto;
 
    -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
 
    -webkit-border-radius: 3px;
 
    -moz-border-radius: 3px;
 
    border-radius: 3px;
 
}
 
 
 
/** comment main **/
 
.comments {
 
    padding:10px 20px;
 
}
 
 
.comments .comment {
 
    border: 1px solid #ddd;
 
    margin-top: 10px;
 
    -webkit-border-radius: 4px;
 
    -moz-border-radius: 4px;
 
    border-radius: 4px;    
 
}
 
 
.comments .comment .meta {
 
    background: #f8f8f8;
 
    padding: 4px;
 
    border-bottom: 1px solid #ddd;
 
    height: 18px;
 
}
 
 
.comments .comment .meta img {
 
    vertical-align: middle;
 
}
 
 
.comments .comment .meta .user {
 
    font-weight: bold;
 
    float: left;
 
    padding: 4px 2px 2px 2px;
 
}
 
 
.comments .comment .meta .date {
 
	float: left;
 
	padding:4px 4px 0px 4px;
 
}
 
 
.comments .comment .text {
 
    background-color: #FAFAFA;
 
}
 
.comment .text div.rst-block p {
 
	margin: 0.5em 0px !important;
 
}
 
 
.comments .comments-number{
 
	padding:0px 0px 10px 0px;
 
	font-weight: bold;
 
	color: #666;
 
	font-size: 16px;
 
}
 
 
/** comment form **/
 
 
.status-block{
 
    height:80px;
 
    clear:both	
 
}
 
 
.comment-form .clearfix{
 
	background: #EEE;
 
    -webkit-border-radius: 4px;
 
    -moz-border-radius: 4px;
 
    border-radius: 4px;
 
    padding: 10px;
 
}
 
 
div.comment-form {
 
    margin-top: 20px;
 
}
 
 
.comment-form strong {
 
    display: block;
 
    margin-bottom: 15px;
 
}
 
 
.comment-form textarea {
 
    width: 100%;
 
    height: 100px;
 
    font-family: 'Monaco', 'Courier', 'Courier New', monospace;
 
}
 
 
form.comment-form {
 
    margin-top: 10px;
 
    margin-left: 10px;
 
}
 
 
.comment-form-submit {
 
    margin-top: 5px;
 
    margin-left: 525px;
 
}
 
 
.file-comments {
 
    display: none;
 
}
 
 
.comment-form .comment {
 
    margin-left: 10px;
 
}
 
 
.comment-form .comment-help{
 
    padding: 0px 0px 5px 0px;
 
    color: #666;
 
}
 
 
.comment-form .comment-button{
 
	padding-top:5px;
 
}
 
 
.add-another-button {
 
    margin-left: 10px;
 
    margin-top: 10px;
 
    margin-bottom: 10px;
 
}
 
 
.comment .buttons {
 
	float: right;
 
	padding:2px 2px 0px 0px;
 
}
 
 
 
.show-inline-comments{
 
	position: relative;
 
	top:1px
 
}
 
 
/** comment inline form **/
 
.comment-inline-form .overlay{
 
	display: none;
 
}
 
.comment-inline-form .overlay.submitting{
 
	display:block;
 
    background: none repeat scroll 0 0 white;
 
    font-size: 16px;
 
    opacity: 0.5;
 
    position: absolute;
 
    text-align: center;
 
    vertical-align: top;
 
 
}
 
.comment-inline-form .overlay.submitting .overlay-text{
 
	width:100%;
 
	margin-top:5%;
 
}
 
 
.comment-inline-form .clearfix{
 
    background: #EEE;
 
    -webkit-border-radius: 4px;
 
    -moz-border-radius: 4px;
 
    border-radius: 4px;
 
    padding: 5px;
 
}
 
 
div.comment-inline-form {
 
    margin-top: 5px;
 
    padding:2px 6px 8px 6px;
 
 
}
 
 
.comment-inline-form strong {
 
    display: block;
 
    margin-bottom: 15px;
 
}
 
 
.comment-inline-form textarea {
 
    width: 100%;
 
    height: 100px;
 
    font-family: 'Monaco', 'Courier', 'Courier New', monospace;
 
}
 
 
form.comment-inline-form {
 
    margin-top: 10px;
 
    margin-left: 10px;
 
}
 
 
.comment-inline-form-submit {
 
    margin-top: 5px;
 
    margin-left: 525px;
 
}
 
 
.file-comments {
 
    display: none;
 
}
 
 
.comment-inline-form .comment {
 
    margin-left: 10px;
 
}
 
 
.comment-inline-form .comment-help{
 
    padding: 0px 0px 2px 0px;
 
    color: #666666;
 
    font-size: 10px;
 
}
 
 
.comment-inline-form .comment-button{
 
    padding-top:5px;
 
}
 
 
/** comment inline **/
 
.inline-comments {
 
    padding:10px 20px;
 
}
 
 
.inline-comments div.rst-block  {
 
	clear:both;
 
	overflow:hidden;
 
	margin:0;
 
	padding:0 20px 0px;
 
}
 
.inline-comments .comment {
 
    border: 1px solid #ddd;
 
    -webkit-border-radius: 4px;
 
    -moz-border-radius: 4px;
 
    border-radius: 4px;
 
    margin: 3px 3px 5px 5px;
 
    background-color: #FAFAFA;
 
}
 
.inline-comments .add-comment {
 
	padding: 2px 4px 8px 5px;
 
}
 
 
.inline-comments .comment-wrapp{
 
	padding:1px;
 
}
 
.inline-comments .comment .meta {
 
    background: #f8f8f8;
 
    padding: 4px;
 
    border-bottom: 1px solid #ddd;
 
}
 
 
.inline-comments .comment .meta img {
 
    vertical-align: middle;
 
}
 
 
.inline-comments .comment .meta .user {
 
    font-weight: bold;
 
}
 
 
.inline-comments .comment .meta .date {
 
}
 
 
.inline-comments .comment .text {
 
    background-color: #FAFAFA;
 
}
 
 
.inline-comments .comments-number{
 
    padding:0px 0px 10px 0px;
 
    font-weight: bold;
 
    color: #666;
 
    font-size: 16px;
 
}
 
.inline-comments-button .add-comment{
 
	margin:2px 0px 8px 5px !important
 
}
 
 
 
.notification-paginator{
 
    padding: 0px 0px 4px 16px;
 
    float: left;    	
 
}
 
 
.notifications{
 
    border-radius: 4px 4px 4px 4px;
 
    -webkit-border-radius: 4px;
 
    -moz-border-radius: 4px;    
 
    float: right;
 
    margin: 20px 0px 0px 0px;
 
    position: absolute;
 
    text-align: center;
 
    width: 26px;
 
    z-index: 1000;
 
}
 
.notifications a{
 
	color:#888 !important;
 
	display: block;
 
	font-size: 10px;
 
	background-color: #DEDEDE !important;
 
    border-radius: 2px !important;
 
    -webkit-border-radius: 2px !important;
 
    -moz-border-radius: 2px !important;  	
 
}
 
.notifications a:hover{
 
	text-decoration: none !important;
 
	background-color: #EEEFFF !important;
 
}
 
.notification-header{
 
	padding-top:6px;
 
}
 
.notification-header .desc{
 
	font-size: 16px;
 
    height: 24px;
 
    float: left
 
}
 
.notification-list .container.unread{
 
	background: none repeat scroll 0 0 rgba(255, 255, 180, 0.6);
 
}
 
.notification-header .gravatar{
 
    background: none repeat scroll 0 0 transparent;
 
    padding: 0px 0px 0px 8px;	
 
}
 
.notification-header .desc.unread{
 
    font-weight: bold;
 
    font-size: 17px;
 
}
 
.notification-table{
 
	border: 1px solid #ccc;
 
    -webkit-border-radius: 6px 6px 6px 6px;
 
    -moz-border-radius: 6px 6px 6px 6px;
 
    border-radius: 6px 6px 6px 6px;
 
    clear: both;
 
    margin: 0px 20px 0px 20px;
 
}
 
.notification-header .delete-notifications{
 
    float: right;
 
    padding-top: 8px;
 
    cursor: pointer;
 
}
 
.notification-subject{
 
    clear:both;
 
    border-bottom: 1px solid #eee;
 
    padding:5px 0px 5px 38px;
 
}
 
 
.notification-body{
 
	clear:both;
 
	margin: 34px 2px 2px 8px
 
}
 
 
/****
 
  PERMS
 
*****/
 
#perms .perms_section_head {
 
   padding:10px 10px 10px 0px;
 
   font-size:16px;
 
   font-weight: bold;
 
}
 
 
#perms .perm_tag{
 
  padding: 1px 3px 1px 3px;
 
  font-size: 10px;
 
  font-weight: bold;
 
  text-transform: uppercase;
 
  white-space: nowrap;
 
  -webkit-border-radius: 3px;
 
  -moz-border-radius: 3px;
 
  border-radius: 3px;
 
}
 
 
#perms .perm_tag.admin{
 
  background-color: #B94A48;
 
  color: #ffffff;
 
}
 
 
#perms .perm_tag.write{
 
  background-color: #B94A48;
 
  color: #ffffff;    
 
}
 
 
#perms .perm_tag.read{
 
  background-color: #468847;
 
  color: #ffffff;    
 
}
 
 
#perms .perm_tag.none{
 
  background-color: #bfbfbf;
 
  color: #ffffff;    
 
}
 
 
.perm-gravatar{
 
	vertical-align:middle;
 
	padding:2px;
 
}
 
.perm-gravatar-ac{
 
    vertical-align:middle;
 
    padding:2px;
 
    width: 14px;
 
    height: 14px;	
 
}
 
 
/*****************************************************************************
 
                                  DIFFS CSS
 
******************************************************************************/
 
 
div.diffblock {
 
    overflow: auto;
 
    padding: 0px;
 
    border: 1px solid #ccc;
 
    background: #f8f8f8;
 
    font-size: 100%;
 
    line-height: 100%;
 
    /* new */
 
    line-height: 125%;
 
    -webkit-border-radius: 6px 6px 0px 0px;
 
    -moz-border-radius: 6px 6px 0px 0px;
 
    border-radius: 6px 6px 0px 0px;     
 
}
 
div.diffblock.margined{
 
    margin: 0px 20px 0px 20px;
 
}
 
div.diffblock .code-header{
 
    border-bottom: 1px solid #CCCCCC;
 
    background: #EEEEEE;
 
    padding:10px 0 10px 0;
 
    height: 14px;
 
}
 
div.diffblock .code-header.cv{
 
    height: 34px;
 
}
 
div.diffblock .code-header-title{
 
	padding: 0px 0px 10px 5px !important;
 
	margin: 0 !important;
 
}
 
div.diffblock .code-header .hash{
 
    float: left;
 
    padding: 2px 0 0 2px;
 
}
 
div.diffblock .code-header .date{
 
    float:left;
 
    text-transform: uppercase;
 
    padding: 2px 0px 0px 2px;
rhodecode/templates/changeset/changeset_file_comment.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
## usage:
 
## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
 
## ${comment.comment_block(co)}
 
##
 
<%def name="comment_block(co)">
 
  <div class="comment" id="comment-${co.comment_id}" line="${co.line_no}">
 
    <div class="comment-wrapp">
 
  	<div class="meta">
 
  		<span class="user">
 
  			<img src="${h.gravatar_url(co.author.email, 20)}" />
 
        <div style="float:left"> <img src="${h.gravatar_url(co.author.email, 20)}" /> </div>
 
  		<div class="user">
 
  			${co.author.username}
 
  		</span>
 
  		<span class="date">
 
  		</div>
 
  		<div class="date">
 
  			${h.age(co.modified_at)}
 
  		</span>
 
  		</div>
 
        %if co.status_change:
 
           <div  style="float:left" class="changeset-status-container">
 
             <div style="float:left;padding:0px 2px 0px 2px"><span style="font-size: 18px;">&rsaquo;</span></div>
 
             <div title="${_('Changeset status')}" class="changeset-status-lbl"> ${co.status_change.status_lbl}</div>
 
             <div class="changeset-status-ico"><img src="${h.url(str('/images/icons/flag_status_%s.png' % co.status_change.status))}" /></div>             
 
           </div>
 
        %endif      
 
      %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
 
        <span class="buttons">
 
        <div class="buttons">
 
          <span onClick="deleteComment(${co.comment_id})" class="delete-comment ui-btn">${_('Delete')}</span>
 
        </span>
 
        </div>
 
      %endif
 
  	</div>
 
  	<div class="text">
 
  		${h.rst_w_mentions(co.text)|n}
 
  	</div>
 
    </div>
 
  </div>
 
</%def>
 

	
 

	
 
<%def name="comment_inline_form(changeset)">
 
<div id='comment-inline-form-template' style="display:none">
 
  <div class="comment-inline-form">
 
  %if c.rhodecode_user.username != 'default':
 
    <div class="overlay"><div class="overlay-text">${_('Submitting...')}</div></div>
 
      ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id),class_='inline-form')}
 
      <div class="clearfix">
 
          <div class="comment-help">${_('Commenting on line')} {1}. ${_('Comments parsed using')}
 
          <a href="${h.url('rst_help')}">RST</a> ${_('syntax')} ${_('with')}
 
          <span style="color:#003367" class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span> ${_('support')}
 
          </div>          
 
          <textarea id="text_{1}" name="text"></textarea>
 
      </div>
 
      <div class="comment-button">
 
      <input type="hidden" name="f_path" value="{0}">
 
      <input type="hidden" name="line" value="{1}">
 
      ${h.submit('save', _('Comment'), class_='ui-btn save-inline-form')}
 
      ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
 
      </div>
 
      ${h.end_form()}
 
  %else:
 
      ${h.form('')}
 
      <div class="clearfix">
 
          <div class="comment-help">
 
            ${'You need to be logged in to comment.'} <a href="${h.url('login_home',came_from=h.url.current())}">${_('Login now')}</a>
 
          </div>
 
      </div>
 
      <div class="comment-button">
 
      ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
 
      </div>
 
      ${h.end_form()}
 
  %endif
 
  </div>
 
</div>
 
</%def>
 

	
 

	
 
<%def name="inlines(changeset)">
 
    <div class="comments-number">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
 
    %for path, lines in c.inline_comments:
 
        % for line,comments in lines.iteritems():
 
            <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
 
            %for co in comments:
 
                ${comment_block(co)}
 
            %endfor
 
            </div>
 
        %endfor
 
    %endfor
 

	
 
</%def>
 

	
 
## MAIN COMMENT FORM
 
<%def name="comments(changeset)">
 

	
 
<div class="comments">
 
    <div id="inline-comments-container">
 
     ${inlines(changeset)}
 
    </div>
 

	
 
    %for co in c.comments:
 
        <div id="comment-tr-${co.comment_id}">
 
          ${comment_block(co)}
 
        </div>
 
    %endfor
 
    %if c.rhodecode_user.username != 'default':
 
    <div class="comment-form">
 
        ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id))}
 
        <strong>${_('Leave a comment')}</strong>
 
        <div class="clearfix">
 
            <div class="comment-help">
 
                ${_('Comments parsed using')} <a href="${h.url('rst_help')}">RST</a> ${_('syntax')}
 
                ${_('with')} <span style="color:#003367" class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span> ${_('support')}
 
                | <span class="tooltip" title="${_('Check this to change current status of code-review for this changeset')}"> ${_('change status')}
 
                  <input style="vertical-align: bottom;margin-bottom:-2px" id="show_changeset_status_box" type="checkbox" name="change_changeset_status" />
 
                  </span> 
 
            </div>
 
            <div id="status_block_container" class="status-block" style="display:none">
 
                %for status,lbl in c.changeset_statuses:
 
                    <div class="">
 
                        <img src="${h.url('/images/icons/flag_status_%s.png' % status)}" /> <input ${'checked="checked"' if status == h.changeset_status(c.rhodecode_db_repo, c.changeset.raw_id) else ''}" type="radio" name="changeset_status" value="${status}"> <label>${lbl}</label>
 
                    </div>                    
 
                %endfor
 
            </div>                 
 
            ${h.textarea('text')}
 
        </div>
 
        <div class="comment-button">
 
        ${h.submit('save', _('Comment'), class_='ui-button')}
 
        </div>
 
        ${h.end_form()}
 
    </div>
 
    %endif
 
</div>
 
</%def>
0 comments (0 inline, 0 general)