Changeset - ce4b7023a492
[Not reviewed]
beta
0 11 4
Marcin Kuzminski - 13 years ago 2013-05-08 01:19:18
marcin@python-works.com
diff parser: redefined operations stats for changes

- don't loose info about multiple operations like rename + chmod
- new Binary flag when dealing with binary file operations
- fixed diffs after mercurial 2.6 when GIT binary diffs were fixed
- added more tests for multiple operations

- refactored the way diffprocessor returns data. It's
now easier to extract type of operation on binary files
- diffprocessor doesn't append that information into the diff itself
15 files changed with 389 insertions and 115 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, HTTPBadRequest, HTTPNotFound
 

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

	
 
from rhodecode.lib.vcs.exceptions import RepositoryError, \
 
    ChangesetDoesNotExistError
 

	
 
import rhodecode.lib.helpers as h
 
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
 
    NotAnonymous
 
from rhodecode.lib.base import BaseRepoController, render
 
from rhodecode.lib.utils import action_logger
 
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.model.repo import RepoModel
 
from rhodecode.lib.diffs import LimitedDiffContainer
 
from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
 
from rhodecode.lib.vcs.backends.base import EmptyChangeset
 
from rhodecode.lib.utils2 import safe_unicode
 

	
 
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 Exception:
 
            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')
 
    if fid:
 
        ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
 
    else:
 
        _ln_ctx = filter(lambda k: k.startswith('C'), GET)
 
        ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx  else ln_ctx_global
 
        if ln_ctx:
 
            ln_ctx = [ln_ctx]
 

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

	
 
    try:
 
        return int(retval)
 
    except Exception:
 
        return 3
 

	
 

	
 
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):
 

	
 
    def __before__(self):
 
        super(ChangesetController, self).__before__()
 
        c.affected_files_cut_off = 60
 
        repo_model = RepoModel()
 
        c.users_array = repo_model.get_users_js()
 
        c.users_groups_array = repo_model.get_users_groups_js()
 

	
 
    def _index(self, revision, method):
 
        c.anchor_url = anchor_url
 
        c.ignorews_url = _ignorews_url
 
        c.context_url = _context_url
 
        c.fulldiff = fulldiff = 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='error')
 
            raise HTTPNotFound()
 

	
 
        c.changes = OrderedDict()
 

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

	
 
        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:
 
            inlines = []
 
            if method == 'show':
 
                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,
 
                                                revision=changeset.raw_id))
 

	
 
                #comments from PR
 
                st = ChangesetStatusModel().get_statuses(
 
                            c.rhodecode_db_repo.repo_id, changeset.raw_id,
 
                            with_revisions=True)
 
                # from associated statuses, check the pull requests, and
 
                # show comments from them
 

	
 
                prs = set([x.pull_request for x in
 
                           filter(lambda x: x.pull_request != None, st)])
 

	
 
                for pr in prs:
 
                    c.comments.extend(pr.comments)
 
                inlines = ChangesetCommentsModel()\
 
                            .get_inline_comments(c.rhodecode_db_repo.repo_id,
 
                                                 revision=changeset.raw_id)
 
                c.inline_comments.extend(inlines)
 

	
 
            c.changes[changeset.raw_id] = []
 

	
 
            cs2 = changeset.raw_id
 
            cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset()
 
            context_lcl = get_line_ctx('', request.GET)
 
            ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
 

	
 
            _diff = c.rhodecode_repo.get_diff(cs1, cs2,
 
                ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
 
            diff_limit = self.cut_off_limit if not fulldiff else None
 
            diff_processor = diffs.DiffProcessor(_diff,
 
                                                 vcs=c.rhodecode_repo.alias,
 
                                                 format='gitdiff',
 
                                                 diff_limit=diff_limit)
 
            cs_changes = OrderedDict()
 
            if method == 'show':
 
                _parsed = diff_processor.prepare()
 
                c.limited_diff = False
 
                if isinstance(_parsed, LimitedDiffContainer):
 
                    c.limited_diff = True
 
                for f in _parsed:
 
                    st = f['stats']
 
                    if st[0] != 'b':
 
                        c.lines_added += st[0]
 
                        c.lines_deleted += st[1]
 
                    c.lines_added += st['added']
 
                    c.lines_deleted += st['deleted']
 
                    fid = h.FID(changeset.raw_id, f['filename'])
 
                    diff = diff_processor.as_html(enable_comments=enable_comments,
 
                                                  parsed_lines=[f])
 
                    cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
 
                                       diff, st]
 
            else:
 
                # downloads/raw we only need RAW diff nothing else
 
                diff = diff_processor.as_raw()
 
                cs_changes[''] = [None, None, None, None, diff, None]
 
            c.changes[changeset.raw_id] = cs_changes
 

	
 
        #sort comments by how they were generated
 
        c.comments = sorted(c.comments, key=lambda x: x.comment_id)
 

	
 
        # count inline comments
 
        for __, 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.parent_tmpl = ''.join(['# Parent  %s\n' % x.raw_id
 
                                     for x in c.changeset.parents])
 
        if method == 'download':
 
            response.content_type = 'text/plain'
 
            response.content_disposition = 'attachment; filename=%s.diff' \
 
                                            % revision[:12]
 
            return diff
 
        elif method == 'patch':
 
            response.content_type = 'text/plain'
 
            c.diff = safe_unicode(diff)
 
            return render('changeset/patch_changeset.html')
 
        elif method == 'raw':
 
            response.content_type = 'text/plain'
 
            return diff
 
        elif method == 'show':
 
            if len(c.cs_ranges) == 1:
 
                return render('changeset/changeset.html')
 
            else:
 
                return render('changeset/changeset_range.html')
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def index(self, revision, method='show'):
 
        return self._index(revision, method=method)
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def changeset_raw(self, revision):
 
        return self._index(revision, method='raw')
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def changeset_patch(self, revision):
 
        return self._index(revision, method='patch')
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def changeset_download(self, revision):
 
        return self._index(revision, method='download')
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    @jsonify
 
    def comment(self, repo_name, revision):
 
        status = request.POST.get('changeset_status')
 
        change_status = request.POST.get('change_changeset_status')
 
        text = request.POST.get('text')
 
        if status and change_status:
 
            text = text or (_('Status change -> %s')
 
                            % ChangesetStatus.get_status_lbl(status))
 

	
 
        c.co = comm = ChangesetCommentsModel().create(
 
            text=text,
 
            repo=c.rhodecode_db_repo.repo_id,
 
            user=c.rhodecode_user.user_id,
 
            revision=revision,
 
            f_path=request.POST.get('f_path'),
 
            line_no=request.POST.get('line'),
 
            status_change=(ChangesetStatus.get_status_lbl(status)
 
                           if status and change_status else None)
 
        )
 

	
 
        # get status if set !
 
        if status and change_status:
 
            # if latest status was from pull request and it's closed
 
            # disallow changing status !
 
            # dont_allow_on_closed_pull_request = True !
 

	
 
            try:
 
                ChangesetStatusModel().set_status(
 
                    c.rhodecode_db_repo.repo_id,
 
                    status,
 
                    c.rhodecode_user.user_id,
 
                    comm,
 
                    revision=revision,
 
                    dont_allow_on_closed_pull_request=True
 
                )
 
            except StatusChangeOnClosedPullRequestError:
 
                log.error(traceback.format_exc())
 
                msg = _('Changing status on a changeset associated with '
 
                        'a closed pull request is not allowed')
 
                h.flash(msg, category='warning')
 
                return redirect(h.url('changeset_home', repo_name=repo_name,
 
                                      revision=revision))
 
        action_logger(self.rhodecode_user,
 
                      'user_commented_revision:%s' % revision,
 
                      c.rhodecode_db_repo, self.ip_addr, self.sa)
 

	
 
        Session().commit()
 

	
 
        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            return redirect(h.url('changeset_home', repo_name=repo_name,
 
                                  revision=revision))
 
        #only ajax below
 
        data = {
 
           'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
 
        }
 
        if comm:
 
            data.update(comm.get_dict())
 
            data.update({'rendered_text':
 
                         render('changeset/changeset_comment_block.html')})
 

	
 
        return data
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def preview_comment(self):
 
        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            raise HTTPBadRequest()
 
        text = request.POST.get('text')
 
        if text:
 
            return h.rst_w_mentions(text)
 
        return ''
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    @jsonify
 
    def delete_comment(self, repo_name, comment_id):
 
        co = ChangesetComment.get(comment_id)
 
        owner = 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()
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    @jsonify
 
    def changeset_info(self, repo_name, revision):
 
        if request.is_xhr:
 
            try:
 
                return c.rhodecode_repo.get_changeset(revision)
 
            except ChangesetDoesNotExistError, e:
 
                return EmptyChangeset(message=str(e))
 
        else:
 
            raise HTTPBadRequest()
rhodecode/controllers/compare.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.controllers.compare
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    compare controller for pylons showing differences between two
 
    repos, branches, bookmarks or tips
 

	
 
    :created_on: May 6, 2012
 
    :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
 
import re
 

	
 
from webob.exc import HTTPNotFound
 
from pylons import request, response, session, tmpl_context as c, url
 
from pylons.controllers.util import abort, redirect
 
from pylons.i18n.translation import _
 

	
 
from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
 
from rhodecode.lib.vcs.utils import safe_str
 
from rhodecode.lib.vcs.utils.hgcompat import scmutil
 
from rhodecode.lib import helpers as h
 
from rhodecode.lib.base import BaseRepoController, render
 
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 
from rhodecode.lib import diffs, unionrepo
 

	
 
from rhodecode.model.db import Repository
 
from webob.exc import HTTPBadRequest
 
from rhodecode.lib.diffs import LimitedDiffContainer
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class CompareController(BaseRepoController):
 

	
 
    def __before__(self):
 
        super(CompareController, self).__before__()
 

	
 
    def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
 
                             partial=False):
 
        """
 
        Safe way to get changeset if error occur it redirects to changeset with
 
        proper message. If partial is set then don't do redirect raise Exception
 
        instead
 

	
 
        :param rev: revision to fetch
 
        :param repo: repo instance
 
        """
 

	
 
        try:
 
            type_, rev = rev
 
            return repo.scm_instance.get_changeset(rev)
 
        except EmptyRepositoryError, e:
 
            if not redirect_after:
 
                return None
 
            h.flash(h.literal(_('There are no changesets yet')),
 
                    category='warning')
 
            redirect(url('summary_home', repo_name=repo.repo_name))
 

	
 
        except RepositoryError, e:
 
            log.error(traceback.format_exc())
 
            h.flash(str(e), category='warning')
 
            if not partial:
 
                redirect(h.url('summary_home', repo_name=repo.repo_name))
 
            raise HTTPBadRequest()
 

	
 
    def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
 
        """
 
        Returns a list of changesets that can be merged from org_repo@org_ref
 
        to other_repo@other_ref ... and the ancestor that would be used for merge
 

	
 
        :param org_repo:
 
        :param org_ref:
 
        :param other_repo:
 
        :param other_ref:
 
        :param tmp:
 
        """
 

	
 
        ancestor = None
 

	
 
        if alias == 'hg':
 
            # lookup up the exact node id
 
            _revset_predicates = {
 
                    'branch': 'branch',
 
                    'book': 'bookmark',
 
                    'tag': 'tag',
 
                    'rev': 'id',
 
                }
 

	
 
            org_rev_spec = "max(%s(%%s))" % _revset_predicates[org_ref[0]]
 
            org_revs = org_repo._repo.revs(org_rev_spec, safe_str(org_ref[1]))
 
            org_rev = org_repo._repo[org_revs[-1] if org_revs else -1].hex()
 

	
 
            other_revs_spec = "max(%s(%%s))" % _revset_predicates[other_ref[0]]
 
            other_revs = other_repo._repo.revs(other_revs_spec, safe_str(other_ref[1]))
 
            other_rev = other_repo._repo[other_revs[-1] if other_revs else -1].hex()
 

	
 
            #case two independent repos
 
            if org_repo != other_repo:
 
                hgrepo = unionrepo.unionrepository(other_repo.baseui,
 
                                                   other_repo.path,
 
                                                   org_repo.path)
 
                # all the changesets we are looking for will be in other_repo,
 
                # so rev numbers from hgrepo can be used in other_repo
 

	
 
            #no remote compare do it on the same repository
 
            else:
 
                hgrepo = other_repo._repo
 

	
 
            if merge:
 
                revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
 
                                   other_rev, org_rev, org_rev)
 

	
 
                ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev, other_rev)
 
                if ancestors:
 
                    # pick arbitrary ancestor - but there is usually only one
 
                    ancestor = hgrepo[ancestors[0]].hex()
 
            else:
 
                # TODO: have both + and - changesets
 
                revs = hgrepo.revs("id(%s) :: id(%s) - id(%s)",
 
                                   org_rev, other_rev, org_rev)
 

	
 
            changesets = [other_repo.get_changeset(rev) for rev in revs]
 

	
 
        elif alias == 'git':
 
            if org_repo != other_repo:
 
                raise Exception('Comparing of different GIT repositories is not'
 
                                'allowed. Got %s != %s' % (org_repo, other_repo))
 

	
 
            so, se = org_repo.run_git_command(
 
                'log --reverse --pretty="format: %%H" -s -p %s..%s'
 
                    % (org_ref[1], other_ref[1])
 
            )
 
            changesets = [org_repo.get_changeset(cs)
 
                          for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
 

	
 
        return changesets, ancestor
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
 
        # org_ref will be evaluated in org_repo
 
        org_repo = c.rhodecode_db_repo.repo_name
 
        org_ref = (org_ref_type, org_ref)
 
        # other_ref will be evaluated in other_repo
 
        other_ref = (other_ref_type, other_ref)
 
        other_repo = request.GET.get('other_repo', org_repo)
 
        # If merge is True:
 
        #   Show what org would get if merged with other:
 
        #   List changesets that are ancestors of other but not of org.
 
        #   New changesets in org is thus ignored.
 
        #   Diff will be from common ancestor, and merges of org to other will thus be ignored.
 
        # If merge is False:
 
        #   Make a raw diff from org to other, no matter if related or not.
 
        #   Changesets in one and not in the other will be ignored
 
        merge = bool(request.GET.get('merge'))
 
        # fulldiff disables cut_off_limit
 
        c.fulldiff = request.GET.get('fulldiff')
 
        # partial uses compare_cs.html template directly
 
        partial = request.environ.get('HTTP_X_PARTIAL_XHR')
 
        # as_form puts hidden input field with changeset revisions
 
        c.as_form = partial and request.GET.get('as_form')
 
        # swap url for compare_diff page - never partial and never as_form
 
        c.swap_url = h.url('compare_url',
 
            repo_name=other_repo,
 
            org_ref_type=other_ref[0], org_ref=other_ref[1],
 
            other_repo=org_repo,
 
            other_ref_type=org_ref[0], other_ref=org_ref[1],
 
            merge=merge or '')
 

	
 
        org_repo = Repository.get_by_repo_name(org_repo)
 
        other_repo = Repository.get_by_repo_name(other_repo)
 

	
 
        if org_repo is None:
 
            log.error('Could not find org repo %s' % org_repo)
 
            raise HTTPNotFound
 
        if other_repo is None:
 
            log.error('Could not find other repo %s' % other_repo)
 
            raise HTTPNotFound
 

	
 
        if org_repo != other_repo and h.is_git(org_repo):
 
            log.error('compare of two remote repos not available for GIT REPOS')
 
            raise HTTPNotFound
 

	
 
        if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
 
            log.error('compare of two different kind of remote repos not available')
 
            raise HTTPNotFound
 

	
 
        self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
 
        self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
 

	
 
        c.org_repo = org_repo
 
        c.other_repo = other_repo
 
        c.org_ref = org_ref[1]
 
        c.other_ref = other_ref[1]
 
        c.org_ref_type = org_ref[0]
 
        c.other_ref_type = other_ref[0]
 

	
 
        c.cs_ranges, c.ancestor = self._get_changesets(org_repo.scm_instance.alias,
 
                                                       org_repo.scm_instance, org_ref,
 
                                                       other_repo.scm_instance, other_ref,
 
                                                       merge)
 

	
 
        c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
 
                                                   c.cs_ranges])
 
        if not c.ancestor:
 
            log.warning('Unable to find ancestor revision')
 

	
 
        if partial:
 
            return render('compare/compare_cs.html')
 

	
 
        if c.ancestor:
 
            assert merge
 
            # case we want a simple diff without incoming changesets,
 
            # previewing what will be merged.
 
            # Make the diff on the other repo (which is known to have other_ref)
 
            log.debug('Using ancestor %s as org_ref instead of %s'
 
                      % (c.ancestor, org_ref))
 
            org_ref = ('rev', c.ancestor)
 
            org_repo = other_repo
 

	
 
        diff_limit = self.cut_off_limit if not c.fulldiff else None
 

	
 
        log.debug('running diff between %s and %s in %s'
 
                  % (org_ref, other_ref, org_repo.scm_instance.path))
 
        txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(org_ref[1]), rev2=safe_str(other_ref[1]))
 

	
 
        diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
 
                                             diff_limit=diff_limit)
 
        _parsed = diff_processor.prepare()
 

	
 
        c.limited_diff = False
 
        if isinstance(_parsed, LimitedDiffContainer):
 
            c.limited_diff = True
 

	
 
        c.files = []
 
        c.changes = {}
 
        c.lines_added = 0
 
        c.lines_deleted = 0
 
        for f in _parsed:
 
            st = f['stats']
 
            if st[0] != 'b':
 
                c.lines_added += st[0]
 
                c.lines_deleted += st[1]
 
            if not st['binary']:
 
                c.lines_added += st['added']
 
                c.lines_deleted += st['deleted']
 
            fid = h.FID('', f['filename'])
 
            c.files.append([fid, f['operation'], f['filename'], f['stats']])
 
            htmldiff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
 
            c.changes[fid] = [f['operation'], f['filename'], htmldiff]
 

	
 
        return render('compare/compare_diff.html')
rhodecode/controllers/feed.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.controllers.feed
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    Feed controller for rhodecode
 

	
 
    :created_on: Apr 23, 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
 

	
 
from pylons import url, response, tmpl_context as c
 
from pylons.i18n.translation import _
 

	
 
from beaker.cache import cache_region, region_invalidate
 
from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
 

	
 
from rhodecode.lib import helpers as h
 
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 
from rhodecode.lib.base import BaseRepoController
 
from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer
 
from rhodecode.model.db import CacheInvalidation
 
from rhodecode.lib.utils2 import safe_int, str2bool, safe_unicode
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class FeedController(BaseRepoController):
 

	
 
    @LoginRequired(api_access=True)
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def __before__(self):
 
        super(FeedController, self).__before__()
 
        #common values for feeds
 
        self.description = _('Changes on %s repository')
 
        self.title = self.title = _('%s %s feed') % (c.rhodecode_name, '%s')
 
        self.language = 'en-us'
 
        self.ttl = "5"
 
        import rhodecode
 
        CONF = rhodecode.CONFIG
 
        self.include_diff = str2bool(CONF.get('rss_include_diff', False))
 
        self.feed_nr = safe_int(CONF.get('rss_items_per_page', 20))
 
        # we need to protect from parsing huge diffs here other way
 
        # we can kill the server
 
        self.feed_diff_limit = safe_int(CONF.get('rss_cut_off_limit', 32 * 1024))
 

	
 
    def _get_title(self, cs):
 
        return "%s" % (
 
            h.shorter(cs.message, 160)
 
        )
 

	
 
    def __changes(self, cs):
 
        changes = []
 
        diff_processor = DiffProcessor(cs.diff(),
 
                                       diff_limit=self.feed_diff_limit)
 
        _parsed = diff_processor.prepare(inline_diff=False)
 
        limited_diff = False
 
        if isinstance(_parsed, LimitedDiffContainer):
 
            limited_diff = True
 

	
 
        for st in _parsed:
 
            st.update({'added': st['stats'][0],
 
                       'removed': st['stats'][1]})
 
            st.update({'added': st['stats']['added'],
 
                       'removed': st['stats']['deleted']})
 
            changes.append('\n %(operation)s %(filename)s '
 
                           '(%(added)s lines added, %(removed)s lines removed)'
 
                            % st)
 
        if limited_diff:
 
            changes = changes + ['\n ' +
 
                                 _('Changeset was too big and was cut off...')]
 
        return diff_processor, changes
 

	
 
    def __get_desc(self, cs):
 
        desc_msg = []
 
        desc_msg.append((_('%s committed on %s')
 
                         % (h.person(cs.author), h.fmt_date(cs.date))) + '<br/>')
 
        #branches, tags, bookmarks
 
        if cs.branch:
 
            desc_msg.append('branch: %s<br/>' % cs.branch)
 
        if h.is_hg(c.rhodecode_repo):
 
            for book in cs.bookmarks:
 
                desc_msg.append('bookmark: %s<br/>' % book)
 
        for tag in cs.tags:
 
            desc_msg.append('tag: %s<br/>' % tag)
 
        diff_processor, changes = self.__changes(cs)
 
        # rev link
 
        _url = url('changeset_home', repo_name=cs.repository.name,
 
                   revision=cs.raw_id, qualified=True)
 
        desc_msg.append('changeset: <a href="%s">%s</a>' % (_url, cs.raw_id[:8]))
 

	
 
        desc_msg.append('<pre>')
 
        desc_msg.append(cs.message)
 
        desc_msg.append('\n')
 
        desc_msg.extend(changes)
 
        if self.include_diff:
 
            desc_msg.append('\n\n')
 
            desc_msg.append(diff_processor.as_raw())
 
        desc_msg.append('</pre>')
 
        return map(safe_unicode, desc_msg)
 

	
 
    def atom(self, repo_name):
 
        """Produce an atom-1.0 feed via feedgenerator module"""
 

	
 
        @cache_region('long_term')
 
        def _get_feed_from_cache(key, kind):
 
            feed = Atom1Feed(
 
                 title=self.title % repo_name,
 
                 link=url('summary_home', repo_name=repo_name,
 
                          qualified=True),
 
                 description=self.description % repo_name,
 
                 language=self.language,
 
                 ttl=self.ttl
 
            )
 

	
 
            for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
 
                feed.add_item(title=self._get_title(cs),
 
                              link=url('changeset_home', repo_name=repo_name,
 
                                       revision=cs.raw_id, qualified=True),
 
                              author_name=cs.author,
 
                              description=''.join(self.__get_desc(cs)),
 
                              pubdate=cs.date,
 
                              )
 

	
 
            response.content_type = feed.mime_type
 
            return feed.writeString('utf-8')
 

	
 
        kind = 'ATOM'
 
        valid = CacheInvalidation.test_and_set_valid(repo_name, kind)
 
        if not valid:
 
            region_invalidate(_get_feed_from_cache, None, repo_name, kind)
 
        return _get_feed_from_cache(repo_name, kind)
 

	
 
    def rss(self, repo_name):
 
        """Produce an rss2 feed via feedgenerator module"""
 

	
 
        @cache_region('long_term')
 
        def _get_feed_from_cache(key, kind):
 
            feed = Rss201rev2Feed(
 
                title=self.title % repo_name,
 
                link=url('summary_home', repo_name=repo_name,
 
                         qualified=True),
 
                description=self.description % repo_name,
 
                language=self.language,
 
                ttl=self.ttl
 
            )
 

	
 
            for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
 
                feed.add_item(title=self._get_title(cs),
 
                              link=url('changeset_home', repo_name=repo_name,
 
                                       revision=cs.raw_id, qualified=True),
 
                              author_name=cs.author,
 
                              description=''.join(self.__get_desc(cs)),
 
                              pubdate=cs.date,
 
                             )
 

	
 
            response.content_type = feed.mime_type
 
            return feed.writeString('utf-8')
 

	
 
        kind = 'RSS'
 
        valid = CacheInvalidation.test_and_set_valid(repo_name, kind)
 
        if not valid:
 
            region_invalidate(_get_feed_from_cache, None, repo_name, kind)
 
        return _get_feed_from_cache(repo_name, kind)
rhodecode/controllers/pullrequests.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.controllers.pullrequests
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    pull requests controller for rhodecode for initializing pull requests
 

	
 
    :created_on: May 7, 2012
 
    :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
 
import formencode
 

	
 
from webob.exc import HTTPNotFound, HTTPForbidden
 
from collections import defaultdict
 
from itertools import groupby
 

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

	
 
from rhodecode.lib.compat import json
 
from rhodecode.lib.base import BaseRepoController, render
 
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
 
    NotAnonymous
 
from rhodecode.lib.helpers import Page
 
from rhodecode.lib import helpers as h
 
from rhodecode.lib import diffs
 
from rhodecode.lib.utils import action_logger, jsonify
 
from rhodecode.lib.vcs.utils import safe_str
 
from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
 
from rhodecode.lib.vcs.backends.base import EmptyChangeset
 
from rhodecode.lib.diffs import LimitedDiffContainer
 
from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
 
    ChangesetComment
 
from rhodecode.model.pull_request import PullRequestModel
 
from rhodecode.model.meta import Session
 
from rhodecode.model.repo import RepoModel
 
from rhodecode.model.comment import ChangesetCommentsModel
 
from rhodecode.model.changeset_status import ChangesetStatusModel
 
from rhodecode.model.forms import PullRequestForm
 
from mercurial import scmutil
 
from rhodecode.lib.utils2 import safe_int
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class PullrequestsController(BaseRepoController):
 

	
 
    def __before__(self):
 
        super(PullrequestsController, self).__before__()
 
        repo_model = RepoModel()
 
        c.users_array = repo_model.get_users_js()
 
        c.users_groups_array = repo_model.get_users_groups_js()
 

	
 
    def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
 
        """return a structure with repo's interesting changesets, suitable for
 
        the selectors in pullrequest.html
 

	
 
        rev: a revision that must be in the list somehow and selected by default
 
        branch: a branch that must be in the list and selected by default - even if closed
 
        branch_rev: a revision of which peers should be preferred and available."""
 
        # list named branches that has been merged to this named branch - it should probably merge back
 
        peers = []
 

	
 
        if rev:
 
            rev = safe_str(rev)
 

	
 
        if branch:
 
            branch = safe_str(branch)
 

	
 
        if branch_rev:
 
            branch_rev = safe_str(branch_rev)
 
            # not restricting to merge() would also get branch point and be better
 
            # (especially because it would get the branch point) ... but is currently too expensive
 
            otherbranches = {}
 
            for i in repo._repo.revs(
 
                "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))",
 
                branch_rev, branch_rev):
 
                cs = repo.get_changeset(i)
 
                otherbranches[cs.branch] = cs.raw_id
 
            for abranch, node in otherbranches.iteritems():
 
                selected = 'branch:%s:%s' % (abranch, node)
 
                peers.append((selected, abranch))
 

	
 
        selected = None
 

	
 
        branches = []
 
        for abranch, branchrev in repo.branches.iteritems():
 
            n = 'branch:%s:%s' % (abranch, branchrev)
 
            branches.append((n, abranch))
 
            if rev == branchrev:
 
                selected = n
 
            if branch == abranch:
 
                selected = n
 
                branch = None
 
        if branch: # branch not in list - it is probably closed
 
            revs = repo._repo.revs('max(branch(%s))', branch)
 
            if revs:
 
                cs = repo.get_changeset(revs[0])
 
                selected = 'branch:%s:%s' % (branch, cs.raw_id)
 
                branches.append((selected, branch))
 

	
 
        bookmarks = []
 
        for bookmark, bookmarkrev in repo.bookmarks.iteritems():
 
            n = 'book:%s:%s' % (bookmark, bookmarkrev)
 
            bookmarks.append((n, bookmark))
 
            if rev == bookmarkrev:
 
                selected = n
 

	
 
        tags = []
 
        for tag, tagrev in repo.tags.iteritems():
 
            n = 'tag:%s:%s' % (tag, tagrev)
 
            tags.append((n, tag))
 
            if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
 
                selected = n
 

	
 
        # prio 1: rev was selected as existing entry above
 

	
 
        # prio 2: create special entry for rev; rev _must_ be used
 
        specials = []
 
        if rev and selected is None:
 
            selected = 'rev:%s:%s' % (rev, rev)
 
            specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
 

	
 
        # prio 3: most recent peer branch
 
        if peers and not selected:
 
            selected = peers[0][0][0]
 

	
 
        # prio 4: tip revision
 
        if not selected:
 
            selected = 'tag:tip:%s' % repo.tags['tip']
 

	
 
        groups = [(specials, _("Special")),
 
                  (peers, _("Peer branches")),
 
                  (bookmarks, _("Bookmarks")),
 
                  (branches, _("Branches")),
 
                  (tags, _("Tags")),
 
                  ]
 
        return [g for g in groups if g[0]], selected
 

	
 
    def _get_is_allowed_change_status(self, pull_request):
 
        owner = self.rhodecode_user.user_id == pull_request.user_id
 
        reviewer = self.rhodecode_user.user_id in [x.user_id for x in
 
                                                   pull_request.reviewers]
 
        return (self.rhodecode_user.admin or owner or reviewer)
 

	
 
    def _load_compare_data(self, pull_request, enable_comments=True):
 
        """
 
        Load context data needed for generating compare diff
 

	
 
        :param pull_request:
 
        :type pull_request:
 
        """
 
        org_repo = pull_request.org_repo
 
        (org_ref_type,
 
         org_ref_name,
 
         org_ref_rev) = pull_request.org_ref.split(':')
 

	
 
        other_repo = org_repo
 
        (other_ref_type,
 
         other_ref_name,
 
         other_ref_rev) = pull_request.other_ref.split(':')
 

	
 
        # despite opening revisions for bookmarks/branches/tags, we always
 
        # convert this to rev to prevent changes after bookmark or branch change
 
        org_ref = ('rev', org_ref_rev)
 
        other_ref = ('rev', other_ref_rev)
 

	
 
        c.org_repo = org_repo
 
        c.other_repo = other_repo
 

	
 
        c.fulldiff = fulldiff = request.GET.get('fulldiff')
 

	
 
        c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
 

	
 
        c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
 

	
 
        c.org_ref = org_ref[1]
 
        c.org_ref_type = org_ref[0]
 
        c.other_ref = other_ref[1]
 
        c.other_ref_type = other_ref[0]
 

	
 
        diff_limit = self.cut_off_limit if not fulldiff else None
 

	
 
        # we swap org/other ref since we run a simple diff on one repo
 
        log.debug('running diff between %s and %s in %s'
 
                  % (other_ref, org_ref, org_repo.scm_instance.path))
 
        txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
 

	
 
        diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
 
                                             diff_limit=diff_limit)
 
        _parsed = diff_processor.prepare()
 

	
 
        c.limited_diff = False
 
        if isinstance(_parsed, LimitedDiffContainer):
 
            c.limited_diff = True
 

	
 
        c.files = []
 
        c.changes = {}
 
        c.lines_added = 0
 
        c.lines_deleted = 0
 
        for f in _parsed:
 
            st = f['stats']
 
            if st[0] != 'b':
 
                c.lines_added += st[0]
 
                c.lines_deleted += st[1]
 
            c.lines_added += st['added']
 
            c.lines_deleted += st['deleted']
 
            fid = h.FID('', f['filename'])
 
            c.files.append([fid, f['operation'], f['filename'], f['stats']])
 
            htmldiff = diff_processor.as_html(enable_comments=enable_comments,
 
                                              parsed_lines=[f])
 
            c.changes[fid] = [f['operation'], f['filename'], htmldiff]
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def show_all(self, repo_name):
 
        c.pull_requests = PullRequestModel().get_all(repo_name)
 
        c.repo_name = repo_name
 
        p = safe_int(request.GET.get('page', 1), 1)
 

	
 
        c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
 

	
 
        c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
 

	
 
        if request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            return c.pullrequest_data
 

	
 
        return render('/pullrequests/pullrequest_show_all.html')
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def index(self):
 
        org_repo = c.rhodecode_db_repo
 

	
 
        if org_repo.scm_instance.alias != 'hg':
 
            log.error('Review not available for GIT REPOS')
 
            raise HTTPNotFound
 

	
 
        try:
 
            org_repo.scm_instance.get_changeset()
 
        except EmptyRepositoryError, e:
 
            h.flash(h.literal(_('There are no changesets yet')),
 
                    category='warning')
 
            redirect(url('summary_home', repo_name=org_repo.repo_name))
 

	
 
        org_rev = request.GET.get('rev_end')
 
        # rev_start is not directly useful - its parent could however be used
 
        # as default for other and thus give a simple compare view
 
        #other_rev = request.POST.get('rev_start')
 
        branch = request.GET.get('branch')
 

	
 
        c.org_repos = []
 
        c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
 
        c.default_org_repo = org_repo.repo_name
 
        c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, rev=org_rev, branch=branch)
 

	
 
        c.other_repos = []
 
        other_repos_info = {}
 

	
 
        def add_other_repo(repo, branch_rev=None):
 
            if repo.repo_name in other_repos_info: # shouldn't happen
 
                return
 
            c.other_repos.append((repo.repo_name, repo.repo_name))
 
            other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
 
            other_repos_info[repo.repo_name] = {
 
                'user': dict(user_id=repo.user.user_id,
 
                             username=repo.user.username,
 
                             firstname=repo.user.firstname,
 
                             lastname=repo.user.lastname,
 
                             gravatar_link=h.gravatar_url(repo.user.email, 14)),
 
                'description': repo.description.split('\n', 1)[0],
 
                'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
 
            }
 

	
 
        # add org repo to other so we can open pull request against peer branches on itself
 
        add_other_repo(org_repo, branch_rev=org_rev)
 
        c.default_other_repo = org_repo.repo_name
 

	
 
        # gather forks and add to this list ... even though it is rare to
 
        # request forks to pull from their parent
 
        for fork in org_repo.forks:
 
            add_other_repo(fork)
 

	
 
        # add parents of this fork also, but only if it's not empty
 
        if org_repo.parent and org_repo.parent.scm_instance.revisions:
 
            add_other_repo(org_repo.parent)
 
            c.default_other_repo = org_repo.parent.repo_name
 

	
 
        c.default_other_repo_info = other_repos_info[c.default_other_repo]
 
        c.other_repos_info = json.dumps(other_repos_info)
 

	
 
        return render('/pullrequests/pullrequest.html')
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def create(self, repo_name):
 
        repo = RepoModel()._get_repo(repo_name)
 
        try:
 
            _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
 
        except formencode.Invalid, errors:
 
            log.error(traceback.format_exc())
 
            if errors.error_dict.get('revisions'):
 
                msg = 'Revisions: %s' % errors.error_dict['revisions']
 
            elif errors.error_dict.get('pullrequest_title'):
 
                msg = _('Pull request requires a title with min. 3 chars')
 
            else:
 
                msg = _('Error creating pull request')
 

	
 
            h.flash(msg, 'error')
 
            return redirect(url('pullrequest_home', repo_name=repo_name))
 

	
 
        org_repo = _form['org_repo']
 
        org_ref = 'rev:merge:%s' % _form['merge_rev']
 
        other_repo = _form['other_repo']
 
        other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
 
        revisions = [x for x in reversed(_form['revisions'])]
 
        reviewers = _form['review_members']
 

	
 
        title = _form['pullrequest_title']
 
        description = _form['pullrequest_desc']
 
        try:
 
            pull_request = PullRequestModel().create(
 
                self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
 
                other_ref, revisions, reviewers, title, description
 
            )
 
            Session().commit()
 
            h.flash(_('Successfully opened new pull request'),
 
                    category='success')
 
        except Exception:
 
            h.flash(_('Error occurred during sending pull request'),
 
                    category='error')
 
            log.error(traceback.format_exc())
 
            return redirect(url('pullrequest_home', repo_name=repo_name))
 

	
 
        return redirect(url('pullrequest_show', repo_name=other_repo,
 
                            pull_request_id=pull_request.pull_request_id))
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    @jsonify
 
    def update(self, repo_name, pull_request_id):
 
        pull_request = PullRequest.get_or_404(pull_request_id)
 
        if pull_request.is_closed():
 
            raise HTTPForbidden()
 
        #only owner or admin can update it
 
        owner = pull_request.author.user_id == c.rhodecode_user.user_id
 
        if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
 
            reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
 
                       request.POST.get('reviewers_ids', '').split(',')))
 

	
 
            PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
 
            Session().commit()
 
            return True
 
        raise HTTPForbidden()
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    @jsonify
 
    def delete(self, repo_name, pull_request_id):
 
        pull_request = PullRequest.get_or_404(pull_request_id)
 
        #only owner can delete it !
 
        if pull_request.author.user_id == c.rhodecode_user.user_id:
 
            PullRequestModel().delete(pull_request)
 
            Session().commit()
 
            h.flash(_('Successfully deleted pull request'),
 
                    category='success')
 
            return redirect(url('admin_settings_my_account', anchor='pullrequests'))
 
        raise HTTPForbidden()
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def show(self, repo_name, pull_request_id):
 
        repo_model = RepoModel()
 
        c.users_array = repo_model.get_users_js()
 
        c.users_groups_array = repo_model.get_users_groups_js()
 
        c.pull_request = PullRequest.get_or_404(pull_request_id)
 
        c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
 
        cc_model = ChangesetCommentsModel()
 
        cs_model = ChangesetStatusModel()
 
        _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
 
                                            pull_request=c.pull_request,
 
                                            with_revisions=True)
 

	
 
        cs_statuses = defaultdict(list)
 
        for st in _cs_statuses:
 
            cs_statuses[st.author.username] += [st]
 

	
 
        c.pull_request_reviewers = []
 
        c.pull_request_pending_reviewers = []
 
        for o in c.pull_request.reviewers:
 
            st = cs_statuses.get(o.user.username, None)
 
            if st:
 
                sorter = lambda k: k.version
 
                st = [(x, list(y)[0])
 
                      for x, y in (groupby(sorted(st, key=sorter), sorter))]
 
            else:
 
                c.pull_request_pending_reviewers.append(o.user)
 
            c.pull_request_reviewers.append([o.user, st])
 

	
 
        # pull_requests repo_name we opened it against
 
        # ie. other_repo must match
 
        if repo_name != c.pull_request.other_repo.repo_name:
 
            raise HTTPNotFound
 

	
 
        # load compare data into template context
 
        enable_comments = not c.pull_request.is_closed()
 
        self._load_compare_data(c.pull_request, enable_comments=enable_comments)
 

	
 
        # inline comments
 
        c.inline_cnt = 0
 
        c.inline_comments = cc_model.get_inline_comments(
 
                                c.rhodecode_db_repo.repo_id,
 
                                pull_request=pull_request_id)
 
        # count inline comments
 
        for __, lines in c.inline_comments:
 
            for comments in lines.values():
 
                c.inline_cnt += len(comments)
 
        # comments
 
        c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
 
                                           pull_request=pull_request_id)
 

	
 
        try:
 
            cur_status = c.statuses[c.pull_request.revisions[0]][0]
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            cur_status = 'undefined'
 
        if c.pull_request.is_closed() and 0:
 
            c.current_changeset_status = cur_status
 
        else:
 
            # changeset(pull-request) status calulation based on reviewers
 
            c.current_changeset_status = cs_model.calculate_status(
 
                                            c.pull_request_reviewers,
 
                                         )
 
        c.changeset_statuses = ChangesetStatus.STATUSES
 

	
 
        c.as_form = False
 
        c.ancestor = None # there is one - but right here we don't know which
 
        return render('/pullrequests/pullrequest_show.html')
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    @jsonify
 
    def comment(self, repo_name, pull_request_id):
 
        pull_request = PullRequest.get_or_404(pull_request_id)
 
        if pull_request.is_closed():
 
            raise HTTPForbidden()
 

	
 
        status = request.POST.get('changeset_status')
 
        change_status = request.POST.get('change_changeset_status')
 
        text = request.POST.get('text')
 
        close_pr = request.POST.get('save_close')
 

	
 
        allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
 
        if status and change_status and allowed_to_change_status:
 
            _def = (_('Status change -> %s')
 
                            % ChangesetStatus.get_status_lbl(status))
 
            if close_pr:
 
                _def = _('Closing with') + ' ' + _def
 
            text = text or _def
 
        comm = ChangesetCommentsModel().create(
 
            text=text,
 
            repo=c.rhodecode_db_repo.repo_id,
 
            user=c.rhodecode_user.user_id,
 
            pull_request=pull_request_id,
 
            f_path=request.POST.get('f_path'),
 
            line_no=request.POST.get('line'),
 
            status_change=(ChangesetStatus.get_status_lbl(status)
 
                           if status and change_status
 
                           and allowed_to_change_status else None),
 
            closing_pr=close_pr
 
        )
 

	
 
        action_logger(self.rhodecode_user,
 
                      'user_commented_pull_request:%s' % pull_request_id,
 
                      c.rhodecode_db_repo, self.ip_addr, self.sa)
 

	
 
        if allowed_to_change_status:
 
            # get status if set !
 
            if status and change_status:
 
                ChangesetStatusModel().set_status(
 
                    c.rhodecode_db_repo.repo_id,
 
                    status,
 
                    c.rhodecode_user.user_id,
 
                    comm,
 
                    pull_request=pull_request_id
 
                )
 

	
 
            if close_pr:
 
                if status in ['rejected', 'approved']:
 
                    PullRequestModel().close_pull_request(pull_request_id)
 
                    action_logger(self.rhodecode_user,
 
                              'user_closed_pull_request:%s' % pull_request_id,
 
                              c.rhodecode_db_repo, self.ip_addr, self.sa)
 
                else:
 
                    h.flash(_('Closing pull request on other statuses than '
 
                              'rejected or approved forbidden'),
 
                            category='warning')
 

	
 
        Session().commit()
 

	
 
        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            return redirect(h.url('pullrequest_show', repo_name=repo_name,
 
                                  pull_request_id=pull_request_id))
 

	
 
        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
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    @jsonify
 
    def delete_comment(self, repo_name, comment_id):
 
        co = ChangesetComment.get(comment_id)
 
        if co.pull_request.is_closed():
 
            #don't allow deleting comments on closed pull request
 
            raise HTTPForbidden()
 

	
 
        owner = 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/lib/diffs.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.lib.diffs
 
    ~~~~~~~~~~~~~~~~~~~
 

	
 
    Set of diffing helpers, previously part of vcs
 

	
 

	
 
    :created_on: Dec 4, 2011
 
    :author: marcink
 
    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :original copyright: 2007-2008 by Armin Ronacher
 
    :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 re
 
import difflib
 
import logging
 

	
 
from itertools import tee, imap
 

	
 
from pylons.i18n.translation import _
 

	
 
from rhodecode.lib.vcs.exceptions import VCSError
 
from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
 
from rhodecode.lib.vcs.backends.base import EmptyChangeset
 
from rhodecode.lib.helpers import escape
 
from rhodecode.lib.utils2 import safe_unicode, safe_str
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def wrap_to_table(str_):
 
    return '''<table class="code-difftable">
 
                <tr class="line no-comment">
 
                <td class="lineno new"></td>
 
                <td class="code no-comment"><pre>%s</pre></td>
 
                </tr>
 
              </table>''' % str_
 

	
 

	
 
def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
 
                ignore_whitespace=True, line_context=3,
 
                enable_comments=False):
 
    """
 
    returns a wrapped diff into a table, checks for cut_off_limit and presents
 
    proper message
 
    """
 

	
 
    if filenode_old is None:
 
        filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
 

	
 
    if filenode_old.is_binary or filenode_new.is_binary:
 
        diff = wrap_to_table(_('Binary file'))
 
        stats = (0, 0)
 
        size = 0
 

	
 
    elif cut_off_limit != -1 and (cut_off_limit is None or
 
    (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
 

	
 
        f_gitdiff = get_gitdiff(filenode_old, filenode_new,
 
                                ignore_whitespace=ignore_whitespace,
 
                                context=line_context)
 
        diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
 

	
 
        diff = diff_processor.as_html(enable_comments=enable_comments)
 
        stats = diff_processor.stat()
 
        size = len(diff or '')
 
    else:
 
        diff = wrap_to_table(_('Changeset was too big and was cut off, use '
 
                               'diff menu to display this diff'))
 
        stats = (0, 0)
 
        size = 0
 
    if not diff:
 
        submodules = filter(lambda o: isinstance(o, SubModuleNode),
 
                            [filenode_new, filenode_old])
 
        if submodules:
 
            diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
 
        else:
 
            diff = wrap_to_table(_('No changes detected'))
 

	
 
    cs1 = filenode_old.changeset.raw_id
 
    cs2 = filenode_new.changeset.raw_id
 

	
 
    return size, cs1, cs2, diff, stats
 

	
 

	
 
def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
 
    """
 
    Returns git style diff between given ``filenode_old`` and ``filenode_new``.
 

	
 
    :param ignore_whitespace: ignore whitespaces in diff
 
    """
 
    # make sure we pass in default context
 
    context = context or 3
 
    submodules = filter(lambda o: isinstance(o, SubModuleNode),
 
                        [filenode_new, filenode_old])
 
    if submodules:
 
        return ''
 

	
 
    for filenode in (filenode_old, filenode_new):
 
        if not isinstance(filenode, FileNode):
 
            raise VCSError("Given object should be FileNode object, not %s"
 
                % filenode.__class__)
 

	
 
    repo = filenode_new.changeset.repository
 
    old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
 
    new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
 

	
 
    vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
 
                                ignore_whitespace, context)
 
    return vcs_gitdiff
 

	
 
NEW_FILENODE = 1
 
DEL_FILENODE = 2
 
MOD_FILENODE = 3
 
RENAMED_FILENODE = 4
 
CHMOD_FILENODE = 5
 
BIN_FILENODE = 6
 

	
 

	
 
class DiffLimitExceeded(Exception):
 
    pass
 

	
 

	
 
class LimitedDiffContainer(object):
 

	
 
    def __init__(self, diff_limit, cur_diff_size, diff):
 
        self.diff = diff
 
        self.diff_limit = diff_limit
 
        self.cur_diff_size = cur_diff_size
 

	
 
    def __iter__(self):
 
        for l in self.diff:
 
            yield l
 

	
 

	
 
class DiffProcessor(object):
 
    """
 
    Give it a unified or git diff and it returns a list of the files that were
 
    mentioned in the diff together with a dict of meta information that
 
    can be used to render it in a HTML template.
 
    """
 
    _chunk_re = re.compile(r'^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
 
    _newline_marker = re.compile(r'^\\ No newline at end of file')
 
    _git_header_re = re.compile(r"""
 
        #^diff[ ]--git
 
            [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
 
        (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%\n
 
           ^rename[ ]from[ ](?P<rename_from>\S+)\n
 
           ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
 
        (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
 
           ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
 
        (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
 
        (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
 
        (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
 
            \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
 
        (?:^(?P<bin_patch>GIT[ ]binary[ ]patch)(?:\n|$))?
 
        (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
 
        (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
 
    """, re.VERBOSE | re.MULTILINE)
 
    _hg_header_re = re.compile(r"""
 
        #^diff[ ]--git
 
            [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
 
        (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
 
           ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
 
        (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%(?:\n|$))?
 
        (?:^rename[ ]from[ ](?P<rename_from>\S+)\n
 
           ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
 
        (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
 
        (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
 
        (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
 
            \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
 
        (?:^(?P<bin_patch>GIT[ ]binary[ ]patch)(?:\n|$))?
 
        (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
 
        (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
 
    """, re.VERBOSE | re.MULTILINE)
 

	
 
    #used for inline highlighter word split
 
    _token_re = re.compile(r'()(&gt;|&lt;|&amp;|\W+?)')
 

	
 
    def __init__(self, diff, vcs='hg', format='gitdiff', diff_limit=None):
 
        """
 
        :param diff:   a text in diff format
 
        :param vcs: type of version controll hg or git
 
        :param format: format of diff passed, `udiff` or `gitdiff`
 
        :param diff_limit: define the size of diff that is considered "big"
 
            based on that parameter cut off will be triggered, set to None
 
            to show full diff
 
        """
 
        if not isinstance(diff, basestring):
 
            raise Exception('Diff must be a basestring got %s instead' % type(diff))
 

	
 
        self._diff = diff
 
        self._format = format
 
        self.adds = 0
 
        self.removes = 0
 
        # calculate diff size
 
        self.diff_size = len(diff)
 
        self.diff_limit = diff_limit
 
        self.cur_diff_size = 0
 
        self.parsed = False
 
        self.parsed_diff = []
 
        self.vcs = vcs
 

	
 
        if format == 'gitdiff':
 
            self.differ = self._highlight_line_difflib
 
            self._parser = self._parse_gitdiff
 
        else:
 
            self.differ = self._highlight_line_udiff
 
            self._parser = self._parse_udiff
 

	
 
    def _copy_iterator(self):
 
        """
 
        make a fresh copy of generator, we should not iterate thru
 
        an original as it's needed for repeating operations on
 
        this instance of DiffProcessor
 
        """
 
        self.__udiff, iterator_copy = tee(self.__udiff)
 
        return iterator_copy
 

	
 
    def _escaper(self, string):
 
        """
 
        Escaper for diff escapes special chars and checks the diff limit
 

	
 
        :param string:
 
        :type string:
 
        """
 

	
 
        self.cur_diff_size += len(string)
 

	
 
        # escaper get's iterated on each .next() call and it checks if each
 
        # parsed line doesn't exceed the diff limit
 
        if self.diff_limit is not None and self.cur_diff_size > self.diff_limit:
 
            raise DiffLimitExceeded('Diff Limit Exceeded')
 

	
 
        return safe_unicode(string).replace('&', '&amp;')\
 
                .replace('<', '&lt;')\
 
                .replace('>', '&gt;')
 

	
 
    def _line_counter(self, l):
 
        """
 
        Checks each line and bumps total adds/removes for this diff
 

	
 
        :param l:
 
        """
 
        if l.startswith('+') and not l.startswith('+++'):
 
            self.adds += 1
 
        elif l.startswith('-') and not l.startswith('---'):
 
            self.removes += 1
 
        return safe_unicode(l)
 

	
 
    def _highlight_line_difflib(self, line, next_):
 
        """
 
        Highlight inline changes in both lines.
 
        """
 

	
 
        if line['action'] == 'del':
 
            old, new = line, next_
 
        else:
 
            old, new = next_, line
 

	
 
        oldwords = self._token_re.split(old['line'])
 
        newwords = self._token_re.split(new['line'])
 
        sequence = difflib.SequenceMatcher(None, oldwords, newwords)
 

	
 
        oldfragments, newfragments = [], []
 
        for tag, i1, i2, j1, j2 in sequence.get_opcodes():
 
            oldfrag = ''.join(oldwords[i1:i2])
 
            newfrag = ''.join(newwords[j1:j2])
 
            if tag != 'equal':
 
                if oldfrag:
 
                    oldfrag = '<del>%s</del>' % oldfrag
 
                if newfrag:
 
                    newfrag = '<ins>%s</ins>' % newfrag
 
            oldfragments.append(oldfrag)
 
            newfragments.append(newfrag)
 

	
 
        old['line'] = "".join(oldfragments)
 
        new['line'] = "".join(newfragments)
 

	
 
    def _highlight_line_udiff(self, line, next_):
 
        """
 
        Highlight inline changes in both lines.
 
        """
 
        start = 0
 
        limit = min(len(line['line']), len(next_['line']))
 
        while start < limit and line['line'][start] == next_['line'][start]:
 
            start += 1
 
        end = -1
 
        limit -= start
 
        while -end <= limit and line['line'][end] == next_['line'][end]:
 
            end -= 1
 
        end += 1
 
        if start or end:
 
            def do(l):
 
                last = end + len(l['line'])
 
                if l['action'] == 'add':
 
                    tag = 'ins'
 
                else:
 
                    tag = 'del'
 
                l['line'] = '%s<%s>%s</%s>%s' % (
 
                    l['line'][:start],
 
                    tag,
 
                    l['line'][start:last],
 
                    tag,
 
                    l['line'][last:]
 
                )
 
            do(line)
 
            do(next_)
 

	
 
    def _get_header(self, diff_chunk):
 
        """
 
        parses the diff header, and returns parts, and leftover diff
 
        parts consists of 14 elements::
 

	
 
            a_path, b_path, similarity_index, rename_from, rename_to,
 
            old_mode, new_mode, new_file_mode, deleted_file_mode,
 
            a_blob_id, b_blob_id, b_mode, a_file, b_file
 

	
 
        :param diff_chunk:
 
        :type diff_chunk:
 
        """
 

	
 
        if self.vcs == 'git':
 
            match = self._git_header_re.match(diff_chunk)
 
            diff = diff_chunk[match.end():]
 
            return match.groupdict(), imap(self._escaper, diff.splitlines(1))
 
        elif self.vcs == 'hg':
 
            match = self._hg_header_re.match(diff_chunk)
 
            diff = diff_chunk[match.end():]
 
            return match.groupdict(), imap(self._escaper, diff.splitlines(1))
 
        else:
 
            raise Exception('VCS type %s is not supported' % self.vcs)
 

	
 
    def _clean_line(self, line, command):
 
        if command in ['+', '-', ' ']:
 
            #only modify the line if it's actually a diff thing
 
            line = line[1:]
 
        return line
 

	
 
    def _parse_gitdiff(self, inline_diff=True):
 
        _files = []
 
        diff_container = lambda arg: arg
 

	
 
        ##split the diff in chunks of separate --git a/file b/file chunks
 
        for raw_diff in ('\n' + self._diff).split('\ndiff --git')[1:]:
 
            head, diff = self._get_header(raw_diff)
 

	
 
            op = None
 
            stats = None
 
            msgs = []
 
            stats = {
 
                'added': 0,
 
                'deleted': 0,
 
                'binary': False,
 
                'ops': {},
 
            }
 

	
 
            if head['deleted_file_mode']:
 
                op = 'D'
 
                stats = ['b', DEL_FILENODE]
 
                msgs.append('deleted file')
 
                stats['binary'] = True
 
                stats['ops'][DEL_FILENODE] = 'deleted file'
 

	
 
            elif head['new_file_mode']:
 
                op = 'A'
 
                stats = ['b', NEW_FILENODE]
 
                msgs.append('new file %s' % head['new_file_mode'])
 
            else:
 
                stats['binary'] = True
 
                stats['ops'][NEW_FILENODE] = 'new file %s' % head['new_file_mode']
 
            else:  # modify operation, can be cp, rename, chmod
 
                # CHMOD
 
                if head['new_mode'] and head['old_mode']:
 
                    op = 'M'
 
                    stats = ['b', CHMOD_FILENODE]
 
                    msgs.append('modified file chmod %s => %s'
 
                    stats['binary'] = True
 
                    stats['ops'][CHMOD_FILENODE] = ('modified file chmod %s => %s'
 
                                  % (head['old_mode'], head['new_mode']))
 
                # RENAME
 
                if (head['rename_from'] and head['rename_to']
 
                      and head['rename_from'] != head['rename_to']):
 
                    op = 'M'
 
                    stats = ['b', RENAMED_FILENODE] # might overwrite CHMOD_FILENODE
 
                    msgs.append('file renamed from %s to %s'
 
                    stats['binary'] = True
 
                    stats['ops'][RENAMED_FILENODE] = ('file renamed from %s to %s'
 
                                  % (head['rename_from'], head['rename_to']))
 
                if op is None: # fall back: detect missed old style add or remove
 

	
 
                # FALL BACK: detect missed old style add or remove
 
                if op is None:
 
                    if not head['a_file'] and head['b_file']:
 
                        op = 'A'
 
                        stats = ['b', NEW_FILENODE]
 
                        msgs.append('new file')
 
                        stats['binary'] = True
 
                        stats['ops'][NEW_FILENODE] = 'new file'
 

	
 
                    elif head['a_file'] and not head['b_file']:
 
                        op = 'D'
 
                        stats = ['b', DEL_FILENODE]
 
                        msgs.append('deleted file')
 
                        stats['binary'] = True
 
                        stats['ops'][DEL_FILENODE] = 'deleted file'
 

	
 
                # it's not ADD not DELETE
 
                if op is None:
 
                    op = 'M'
 
                    stats = ['b', MOD_FILENODE]
 
                    stats['binary'] = True
 
                    stats['ops'][MOD_FILENODE] = 'modified file'
 

	
 
            if head['a_file'] or head['b_file']: # a real diff
 
            # a real non-binary diff
 
            if head['a_file'] or head['b_file']:
 
                try:
 
                    chunks, stats = self._parse_lines(diff)
 
                    chunks, _stats = self._parse_lines(diff)
 
                    stats['binary'] = False
 
                    stats['added'] = _stats[0]
 
                    stats['deleted'] = _stats[1]
 
                    # explicit mark that it's a modified file
 
                    if op == 'M':
 
                        stats['ops'][MOD_FILENODE] = 'modified file'
 

	
 
                except DiffLimitExceeded:
 
                    diff_container = lambda _diff: LimitedDiffContainer(
 
                                                self.diff_limit,
 
                                                self.cur_diff_size,
 
                                                _diff)
 
                    diff_container = lambda _diff: \
 
                        LimitedDiffContainer(self.diff_limit,
 
                                            self.cur_diff_size, _diff)
 
                    break
 
            else: # GIT binary patch (or empty diff)
 
                # GIT Binary patch
 
                if head['bin_patch']:
 
                    stats['ops'][BIN_FILENODE] = 'binary diff not shown'
 
                chunks = []
 
                msgs.append('binary diff not shown') # or no diff because it was a rename or chmod or add/remove of empty file
 

	
 
            if msgs:
 
                chunks.insert(0, [{
 
                    'old_lineno': '',
 
                    'new_lineno': '',
 
                    'action':     'binary',
 
                'action':     'context',
 
                    'line':       msg,
 
                    } for msg in msgs])
 
                } for _op, msg in stats['ops'].iteritems()
 
                  if _op not in [MOD_FILENODE]])
 

	
 
            _files.append({
 
                'filename':         head['b_path'],
 
                'old_revision':     head['a_blob_id'],
 
                'new_revision':     head['b_blob_id'],
 
                'chunks':           chunks,
 
                'operation':        op,
 
                'stats':            stats,
 
            })
 

	
 
        sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
 

	
 
        if not inline_diff:
 
            return diff_container(sorted(_files, key=sorter))
 

	
 
        # highlight inline changes
 
        for diff_data in _files:
 
            for chunk in diff_data['chunks']:
 
                lineiter = iter(chunk)
 
                try:
 
                    while 1:
 
                        line = lineiter.next()
 
                        if line['action'] not in ['unmod', 'context']:
 
                            nextline = lineiter.next()
 
                            if nextline['action'] in ['unmod', 'context'] or \
 
                               nextline['action'] == line['action']:
 
                                continue
 
                            self.differ(line, nextline)
 
                except StopIteration:
 
                    pass
 

	
 
        return diff_container(sorted(_files, key=sorter))
 

	
 
    def _parse_udiff(self, inline_diff=True):
 
        raise NotImplementedError()
 

	
 
    def _parse_lines(self, diff):
 
        """
 
        Parse the diff an return data for the template.
 
        """
 

	
 
        lineiter = iter(diff)
 
        stats = [0, 0]
 

	
 
        try:
 
            chunks = []
 
            line = lineiter.next()
 

	
 
            while line:
 
                lines = []
 
                chunks.append(lines)
 

	
 
                match = self._chunk_re.match(line)
 

	
 
                if not match:
 
                    break
 

	
 
                gr = match.groups()
 
                (old_line, old_end,
 
                 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
 
                old_line -= 1
 
                new_line -= 1
 

	
 
                context = len(gr) == 5
 
                old_end += old_line
 
                new_end += new_line
 

	
 
                if context:
 
                    # skip context only if it's first line
 
                    if int(gr[0]) > 1:
 
                        lines.append({
 
                            'old_lineno': '...',
 
                            'new_lineno': '...',
 
                            'action':     'context',
 
                            'line':       line,
 
                        })
 

	
 
                line = lineiter.next()
 

	
 
                while old_line < old_end or new_line < new_end:
 
                    command = ' '
 
                    if line:
 
                        command = line[0]
 

	
 
                    affects_old = affects_new = False
 

	
 
                    # ignore those if we don't expect them
 
                    if command in '#@':
 
                        continue
 
                    elif command == '+':
 
                        affects_new = True
 
                        action = 'add'
 
                        stats[0] += 1
 
                    elif command == '-':
 
                        affects_old = True
 
                        action = 'del'
 
                        stats[1] += 1
 
                    else:
 
                        affects_old = affects_new = True
 
                        action = 'unmod'
 

	
 
                    if not self._newline_marker.match(line):
 
                        old_line += affects_old
 
                        new_line += affects_new
 
                        lines.append({
 
                            'old_lineno':   affects_old and old_line or '',
 
                            'new_lineno':   affects_new and new_line or '',
 
                            'action':       action,
 
                            'line':         self._clean_line(line, command)
 
                        })
 

	
 
                    line = lineiter.next()
 

	
 
                    if self._newline_marker.match(line):
 
                        # we need to append to lines, since this is not
 
                        # counted in the line specs of diff
 
                        lines.append({
 
                            'old_lineno':   '...',
 
                            'new_lineno':   '...',
 
                            'action':       'context',
 
                            'line':         self._clean_line(line, command)
 
                        })
 

	
 
        except StopIteration:
 
            pass
 
        return chunks, stats
 

	
 
    def _safe_id(self, idstring):
 
        """Make a string safe for including in an id attribute.
 

	
 
        The HTML spec says that id attributes 'must begin with
 
        a letter ([A-Za-z]) and may be followed by any number
 
        of letters, digits ([0-9]), hyphens ("-"), underscores
 
        ("_"), colons (":"), and periods (".")'. These regexps
 
        are slightly over-zealous, in that they remove colons
 
        and periods unnecessarily.
 

	
 
        Whitespace is transformed into underscores, and then
 
        anything which is not a hyphen or a character that
 
        matches \w (alphanumerics and underscore) is removed.
 

	
 
        """
 
        # Transform all whitespace to underscore
 
        idstring = re.sub(r'\s', "_", '%s' % idstring)
 
        # Remove everything that is not a hyphen or a member of \w
 
        idstring = re.sub(r'(?!-)\W', "", idstring).lower()
 
        return idstring
 

	
 
    def prepare(self, inline_diff=True):
 
        """
 
        Prepare the passed udiff for HTML rendering. It'l return a list
 
        of dicts with diff information
 
        """
 
        parsed = self._parser(inline_diff=inline_diff)
 
        self.parsed = True
 
        self.parsed_diff = parsed
 
        return parsed
 

	
 
    def as_raw(self, diff_lines=None):
 
        """
 
        Returns raw string diff
 
        """
 
        return self._diff
 
        #return u''.join(imap(self._line_counter, self._diff.splitlines(1)))
 

	
 
    def as_html(self, table_class='code-difftable', line_class='line',
 
                old_lineno_class='lineno old', new_lineno_class='lineno new',
 
                code_class='code', enable_comments=False, parsed_lines=None):
 
        """
 
        Return given diff as html table with customized css classes
 
        """
 
        def _link_to_if(condition, label, url):
 
            """
 
            Generates a link if condition is meet or just the label if not.
 
            """
 

	
 
            if condition:
 
                return '''<a href="%(url)s">%(label)s</a>''' % {
 
                    'url': url,
 
                    'label': label
 
                }
 
            else:
 
                return label
 
        if not self.parsed:
 
            self.prepare()
 

	
 
        diff_lines = self.parsed_diff
 
        if parsed_lines:
 
            diff_lines = parsed_lines
 

	
 
        _html_empty = True
 
        _html = []
 
        _html.append('''<table class="%(table_class)s">\n''' % {
 
            'table_class': table_class
 
        })
 

	
 
        for diff in diff_lines:
 
            for line in diff['chunks']:
 
                _html_empty = False
 
                for change in line:
 
                    _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
 
                        'lc': line_class,
 
                        'action': change['action']
 
                    })
 
                    anchor_old_id = ''
 
                    anchor_new_id = ''
 
                    anchor_old = "%(filename)s_o%(oldline_no)s" % {
 
                        'filename': self._safe_id(diff['filename']),
 
                        'oldline_no': change['old_lineno']
 
                    }
 
                    anchor_new = "%(filename)s_n%(oldline_no)s" % {
 
                        'filename': self._safe_id(diff['filename']),
 
                        'oldline_no': change['new_lineno']
 
                    }
 
                    cond_old = (change['old_lineno'] != '...' and
 
                                change['old_lineno'])
 
                    cond_new = (change['new_lineno'] != '...' and
 
                                change['new_lineno'])
 
                    if cond_old:
 
                        anchor_old_id = 'id="%s"' % anchor_old
 
                    if cond_new:
 
                        anchor_new_id = 'id="%s"' % anchor_new
 
                    ###########################################################
 
                    # OLD LINE NUMBER
 
                    ###########################################################
 
                    _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
 
                        'a_id': anchor_old_id,
 
                        'olc': old_lineno_class
 
                    })
 

	
 
                    _html.append('''%(link)s''' % {
 
                        'link': _link_to_if(True, change['old_lineno'],
 
                                            '#%s' % anchor_old)
 
                    })
 
                    _html.append('''</td>\n''')
 
                    ###########################################################
 
                    # NEW LINE NUMBER
 
                    ###########################################################
 

	
 
                    _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
 
                        'a_id': anchor_new_id,
 
                        'nlc': new_lineno_class
 
                    })
 

	
 
                    _html.append('''%(link)s''' % {
 
                        'link': _link_to_if(True, change['new_lineno'],
 
                                            '#%s' % anchor_new)
 
                    })
 
                    _html.append('''</td>\n''')
 
                    ###########################################################
 
                    # CODE
 
                    ###########################################################
 
                    comments = '' if enable_comments else 'no-comment'
 
                    _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
 
                        'cc': code_class,
 
                        'inc': comments
 
                    })
 
                    _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
 
                        'code': change['line']
 
                    })
 

	
 
                    _html.append('''\t</td>''')
 
                    _html.append('''\n</tr>\n''')
 
        _html.append('''</table>''')
 
        if _html_empty:
 
            return None
 
        return ''.join(_html)
 

	
 
    def stat(self):
 
        """
 
        Returns tuple of added, and removed lines for this instance
 
        """
 
        return self.adds, self.removes
rhodecode/lib/helpers.py
Show inline comments
 
"""Helper functions
 

	
 
Consists of functions to typically be used within templates, but also
 
available to Controllers. This module is available to both as 'h'.
 
"""
 
import random
 
import hashlib
 
import StringIO
 
import urllib
 
import math
 
import logging
 
import re
 
import urlparse
 
import textwrap
 

	
 
from datetime import datetime
 
from pygments.formatters.html import HtmlFormatter
 
from pygments import highlight as code_highlight
 
from pylons import url, request, config
 
from pylons.i18n.translation import _, ungettext
 
from hashlib import md5
 

	
 
from webhelpers.html import literal, HTML, escape
 
from webhelpers.html.tools import *
 
from webhelpers.html.builder import make_tag
 
from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
 
    end_form, file, form, hidden, image, javascript_link, link_to, \
 
    link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
 
    submit, text, password, textarea, title, ul, xml_declaration, radio
 
from webhelpers.html.tools import auto_link, button_to, highlight, \
 
    js_obfuscate, mail_to, strip_links, strip_tags, tag_re
 
from webhelpers.number import format_byte_size, format_bit_size
 
from webhelpers.pylonslib import Flash as _Flash
 
from webhelpers.pylonslib.secure_form import secure_form
 
from webhelpers.text import chop_at, collapse, convert_accented_entities, \
 
    convert_misc_entities, lchop, plural, rchop, remove_formatting, \
 
    replace_whitespace, urlify, truncate, wrap_paragraphs
 
from webhelpers.date import time_ago_in_words
 
from webhelpers.paginate import Page as _Page
 
from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
 
    convert_boolean_attrs, NotGiven, _make_safe_id_component
 

	
 
from rhodecode.lib.annotate import annotate_highlight
 
from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
 
from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
 
    get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict,\
 
    safe_int
 
from rhodecode.lib.markup_renderer import MarkupRenderer
 
from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
 
from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
 
from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
 
from rhodecode.model.changeset_status import ChangesetStatusModel
 
from rhodecode.model.db import URL_SEP, Permission
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
html_escape_table = {
 
    "&": "&amp;",
 
    '"': "&quot;",
 
    "'": "&apos;",
 
    ">": "&gt;",
 
    "<": "&lt;",
 
}
 

	
 

	
 
def html_escape(text):
 
    """Produce entities within text."""
 
    return "".join(html_escape_table.get(c, c) for c in text)
 

	
 

	
 
def shorter(text, size=20):
 
    postfix = '...'
 
    if len(text) > size:
 
        return text[:size - len(postfix)] + postfix
 
    return text
 

	
 

	
 
def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
 
    """
 
    Reset button
 
    """
 
    _set_input_attrs(attrs, type, name, value)
 
    _set_id_attr(attrs, id, name)
 
    convert_boolean_attrs(attrs, ["disabled"])
 
    return HTML.input(**attrs)
 

	
 
reset = _reset
 
safeid = _make_safe_id_component
 

	
 

	
 
def FID(raw_id, path):
 
    """
 
    Creates a uniqe ID for filenode based on it's hash of path and revision
 
    it's safe to use in urls
 

	
 
    :param raw_id:
 
    :param path:
 
    """
 

	
 
    return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
 

	
 

	
 
def get_token():
 
    """Return the current authentication token, creating one if one doesn't
 
    already exist.
 
    """
 
    token_key = "_authentication_token"
 
    from pylons import session
 
    if not token_key in session:
 
        try:
 
            token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
 
        except AttributeError:  # Python < 2.4
 
            token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
 
        session[token_key] = token
 
        if hasattr(session, 'save'):
 
            session.save()
 
    return session[token_key]
 

	
 

	
 
class _GetError(object):
 
    """Get error from form_errors, and represent it as span wrapped error
 
    message
 

	
 
    :param field_name: field to fetch errors for
 
    :param form_errors: form errors dict
 
    """
 

	
 
    def __call__(self, field_name, form_errors):
 
        tmpl = """<span class="error_msg">%s</span>"""
 
        if form_errors and field_name in form_errors:
 
            return literal(tmpl % form_errors.get(field_name))
 

	
 
get_error = _GetError()
 

	
 

	
 
class _ToolTip(object):
 

	
 
    def __call__(self, tooltip_title, trim_at=50):
 
        """
 
        Special function just to wrap our text into nice formatted
 
        autowrapped text
 

	
 
        :param tooltip_title:
 
        """
 
        tooltip_title = escape(tooltip_title)
 
        tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
 
        return tooltip_title
 
tooltip = _ToolTip()
 

	
 

	
 
class _FilesBreadCrumbs(object):
 

	
 
    def __call__(self, repo_name, rev, paths):
 
        if isinstance(paths, str):
 
            paths = safe_unicode(paths)
 
        url_l = [link_to(repo_name, url('files_home',
 
                                        repo_name=repo_name,
 
                                        revision=rev, f_path=''),
 
                         class_='ypjax-link')]
 
        paths_l = paths.split('/')
 
        for cnt, p in enumerate(paths_l):
 
            if p != '':
 
                url_l.append(link_to(p,
 
                                     url('files_home',
 
                                         repo_name=repo_name,
 
                                         revision=rev,
 
                                         f_path='/'.join(paths_l[:cnt + 1])
 
                                         ),
 
                                     class_='ypjax-link'
 
                                     )
 
                             )
 

	
 
        return literal('/'.join(url_l))
 

	
 
files_breadcrumbs = _FilesBreadCrumbs()
 

	
 

	
 
class CodeHtmlFormatter(HtmlFormatter):
 
    """
 
    My code Html Formatter for source codes
 
    """
 

	
 
    def wrap(self, source, outfile):
 
        return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
 

	
 
    def _wrap_code(self, source):
 
        for cnt, it in enumerate(source):
 
            i, t = it
 
            t = '<div id="L%s">%s</div>' % (cnt + 1, t)
 
            yield i, t
 

	
 
    def _wrap_tablelinenos(self, inner):
 
        dummyoutfile = StringIO.StringIO()
 
        lncount = 0
 
        for t, line in inner:
 
            if t:
 
                lncount += 1
 
            dummyoutfile.write(line)
 

	
 
        fl = self.linenostart
 
        mw = len(str(lncount + fl - 1))
 
        sp = self.linenospecial
 
        st = self.linenostep
 
        la = self.lineanchors
 
        aln = self.anchorlinenos
 
        nocls = self.noclasses
 
        if sp:
 
            lines = []
 

	
 
            for i in range(fl, fl + lncount):
 
                if i % st == 0:
 
                    if i % sp == 0:
 
                        if aln:
 
                            lines.append('<a href="#%s%d" class="special">%*d</a>' %
 
                                         (la, i, mw, i))
 
                        else:
 
                            lines.append('<span class="special">%*d</span>' % (mw, i))
 
                    else:
 
                        if aln:
 
                            lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
 
                        else:
 
                            lines.append('%*d' % (mw, i))
 
                else:
 
                    lines.append('')
 
            ls = '\n'.join(lines)
 
        else:
 
            lines = []
 
            for i in range(fl, fl + lncount):
 
                if i % st == 0:
 
                    if aln:
 
                        lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
 
                    else:
 
                        lines.append('%*d' % (mw, i))
 
                else:
 
                    lines.append('')
 
            ls = '\n'.join(lines)
 

	
 
        # in case you wonder about the seemingly redundant <div> here: since the
 
        # content in the other cell also is wrapped in a div, some browsers in
 
        # some configurations seem to mess up the formatting...
 
        if nocls:
 
            yield 0, ('<table class="%stable">' % self.cssclass +
 
                      '<tr><td><div class="linenodiv" '
 
                      'style="background-color: #f0f0f0; padding-right: 10px">'
 
                      '<pre style="line-height: 125%">' +
 
                      ls + '</pre></div></td><td id="hlcode" class="code">')
 
        else:
 
            yield 0, ('<table class="%stable">' % self.cssclass +
 
                      '<tr><td class="linenos"><div class="linenodiv"><pre>' +
 
                      ls + '</pre></div></td><td id="hlcode" class="code">')
 
        yield 0, dummyoutfile.getvalue()
 
        yield 0, '</td></tr></table>'
 

	
 

	
 
def pygmentize(filenode, **kwargs):
 
    """
 
    pygmentize function using pygments
 

	
 
    :param filenode:
 
    """
 
    lexer = get_custom_lexer(filenode.extension) or filenode.lexer
 
    return literal(code_highlight(filenode.content, lexer,
 
                                  CodeHtmlFormatter(**kwargs)))
 

	
 

	
 
def pygmentize_annotation(repo_name, filenode, **kwargs):
 
    """
 
    pygmentize function for annotation
 

	
 
    :param filenode:
 
    """
 

	
 
    color_dict = {}
 

	
 
    def gen_color(n=10000):
 
        """generator for getting n of evenly distributed colors using
 
        hsv color and golden ratio. It always return same order of colors
 

	
 
        :returns: RGB tuple
 
        """
 

	
 
        def hsv_to_rgb(h, s, v):
 
            if s == 0.0:
 
                return v, v, v
 
            i = int(h * 6.0)  # XXX assume int() truncates!
 
            f = (h * 6.0) - i
 
            p = v * (1.0 - s)
 
            q = v * (1.0 - s * f)
 
            t = v * (1.0 - s * (1.0 - f))
 
            i = i % 6
 
            if i == 0:
 
                return v, t, p
 
            if i == 1:
 
                return q, v, p
 
            if i == 2:
 
                return p, v, t
 
            if i == 3:
 
                return p, q, v
 
            if i == 4:
 
                return t, p, v
 
            if i == 5:
 
                return v, p, q
 

	
 
        golden_ratio = 0.618033988749895
 
        h = 0.22717784590367374
 

	
 
        for _ in xrange(n):
 
            h += golden_ratio
 
            h %= 1
 
            HSV_tuple = [h, 0.95, 0.95]
 
            RGB_tuple = hsv_to_rgb(*HSV_tuple)
 
            yield map(lambda x: str(int(x * 256)), RGB_tuple)
 

	
 
    cgenerator = gen_color()
 

	
 
    def get_color_string(cs):
 
        if cs in color_dict:
 
            col = color_dict[cs]
 
        else:
 
            col = color_dict[cs] = cgenerator.next()
 
        return "color: rgb(%s)! important;" % (', '.join(col))
 

	
 
    def url_func(repo_name):
 

	
 
        def _url_func(changeset):
 
            author = changeset.author
 
            date = changeset.date
 
            message = tooltip(changeset.message)
 

	
 
            tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
 
                            " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
 
                            "</b> %s<br/></div>")
 

	
 
            tooltip_html = tooltip_html % (author, date, message)
 
            lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
 
                                     short_id(changeset.raw_id))
 
            uri = link_to(
 
                    lnk_format,
 
                    url('changeset_home', repo_name=repo_name,
 
                        revision=changeset.raw_id),
 
                    style=get_color_string(changeset.raw_id),
 
                    class_='tooltip',
 
                    title=tooltip_html
 
                  )
 

	
 
            uri += '\n'
 
            return uri
 
        return _url_func
 

	
 
    return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
 

	
 

	
 
def is_following_repo(repo_name, user_id):
 
    from rhodecode.model.scm import ScmModel
 
    return ScmModel().is_following_repo(repo_name, user_id)
 

	
 
flash = _Flash()
 

	
 
#==============================================================================
 
# SCM FILTERS available via h.
 
#==============================================================================
 
from rhodecode.lib.vcs.utils import author_name, author_email
 
from rhodecode.lib.utils2 import credentials_filter, age as _age
 
from rhodecode.model.db import User, ChangesetStatus
 

	
 
age = lambda  x, y=False: _age(x, y)
 
capitalize = lambda x: x.capitalize()
 
email = author_email
 
short_id = lambda x: x[:12]
 
hide_credentials = lambda x: ''.join(credentials_filter(x))
 

	
 

	
 
def show_id(cs):
 
    """
 
    Configurable function that shows ID
 
    by default it's r123:fffeeefffeee
 

	
 
    :param cs: changeset instance
 
    """
 
    from rhodecode import CONFIG
 
    def_len = safe_int(CONFIG.get('show_sha_length', 12))
 
    show_rev = str2bool(CONFIG.get('show_revision_number', True))
 

	
 
    raw_id = cs.raw_id[:def_len]
 
    if show_rev:
 
        return 'r%s:%s' % (cs.revision, raw_id)
 
    else:
 
        return '%s' % (raw_id)
 

	
 

	
 
def fmt_date(date):
 
    if date:
 
        _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
 
        return date.strftime(_fmt).decode('utf8')
 

	
 
    return ""
 

	
 

	
 
def is_git(repository):
 
    if hasattr(repository, 'alias'):
 
        _type = repository.alias
 
    elif hasattr(repository, 'repo_type'):
 
        _type = repository.repo_type
 
    else:
 
        _type = repository
 
    return _type == 'git'
 

	
 

	
 
def is_hg(repository):
 
    if hasattr(repository, 'alias'):
 
        _type = repository.alias
 
    elif hasattr(repository, 'repo_type'):
 
        _type = repository.repo_type
 
    else:
 
        _type = repository
 
    return _type == 'hg'
 

	
 

	
 
def email_or_none(author):
 
    # extract email from the commit string
 
    _email = email(author)
 
    if _email != '':
 
        # check it against RhodeCode database, and use the MAIN email for this
 
        # user
 
        user = User.get_by_email(_email, case_insensitive=True, cache=True)
 
        if user is not None:
 
            return user.email
 
        return _email
 

	
 
    # See if it contains a username we can get an email from
 
    user = User.get_by_username(author_name(author), case_insensitive=True,
 
                                cache=True)
 
    if user is not None:
 
        return user.email
 

	
 
    # No valid email, not a valid user in the system, none!
 
    return None
 

	
 

	
 
def person(author, show_attr="username_and_name"):
 
    # attr to return from fetched user
 
    person_getter = lambda usr: getattr(usr, show_attr)
 

	
 
    # Valid email in the attribute passed, see if they're in the system
 
    _email = email(author)
 
    if _email != '':
 
        user = User.get_by_email(_email, case_insensitive=True, cache=True)
 
        if user is not None:
 
            return person_getter(user)
 

	
 
    # Maybe it's a username?
 
    _author = author_name(author)
 
    user = User.get_by_username(_author, case_insensitive=True,
 
                                cache=True)
 
    if user is not None:
 
        return person_getter(user)
 

	
 
    # Still nothing?  Just pass back the author name if any, else the email
 
    return _author or _email
 

	
 

	
 
def person_by_id(id_, show_attr="username_and_name"):
 
    # attr to return from fetched user
 
    person_getter = lambda usr: getattr(usr, show_attr)
 

	
 
    #maybe it's an ID ?
 
    if str(id_).isdigit() or isinstance(id_, int):
 
        id_ = int(id_)
 
        user = User.get(id_)
 
        if user is not None:
 
            return person_getter(user)
 
    return id_
 

	
 

	
 
def desc_stylize(value):
 
    """
 
    converts tags from value into html equivalent
 

	
 
    :param value:
 
    """
 
    value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
 
                   '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
 
    value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
 
                   '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
 
    value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
 
                   '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
 
    value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
 
                   '<div class="metatag" tag="lang">\\2</div>', value)
 
    value = re.sub(r'\[([a-z]+)\]',
 
                  '<div class="metatag" tag="\\1">\\1</div>', value)
 

	
 
    return value
 

	
 

	
 
def boolicon(value):
 
    """Returns boolean value of a value, represented as small html image of true/false
 
    icons
 

	
 
    :param value: value
 
    """
 

	
 
    if value:
 
        return HTML.tag('img', src=url("/images/icons/accept.png"),
 
                        alt=_('True'))
 
    else:
 
        return HTML.tag('img', src=url("/images/icons/cancel.png"),
 
                        alt=_('False'))
 

	
 

	
 
def action_parser(user_log, feed=False, parse_cs=False):
 
    """
 
    This helper will action_map the specified string action into translated
 
    fancy names with icons and links
 

	
 
    :param user_log: user log instance
 
    :param feed: use output for feeds (no html and fancy icons)
 
    :param parse_cs: parse Changesets into VCS instances
 
    """
 

	
 
    action = user_log.action
 
    action_params = ' '
 

	
 
    x = action.split(':')
 

	
 
    if len(x) > 1:
 
        action, action_params = x
 

	
 
    def get_cs_links():
 
        revs_limit = 3  # display this amount always
 
        revs_top_limit = 50  # show upto this amount of changesets hidden
 
        revs_ids = action_params.split(',')
 
        deleted = user_log.repository is None
 
        if deleted:
 
            return ','.join(revs_ids)
 

	
 
        repo_name = user_log.repository.repo_name
 

	
 
        def lnk(rev, repo_name):
 
            if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
 
                lazy_cs = True
 
                if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
 
                    lazy_cs = False
 
                    lbl = '?'
 
                    if rev.op == 'delete_branch':
 
                        lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
 
                        title = ''
 
                    elif rev.op == 'tag':
 
                        lbl = '%s' % _('Created tag: %s') % rev.ref_name
 
                        title = ''
 
                    _url = '#'
 

	
 
                else:
 
                    lbl = '%s' % (rev.short_id[:8])
 
                    _url = url('changeset_home', repo_name=repo_name,
 
                               revision=rev.raw_id)
 
                    title = tooltip(rev.message)
 
            else:
 
                ## changeset cannot be found/striped/removed etc.
 
                lbl = ('%s' % rev)[:12]
 
                _url = '#'
 
                title = _('Changeset not found')
 
            if parse_cs:
 
                return link_to(lbl, _url, title=title, class_='tooltip')
 
            return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
 
                           class_='lazy-cs' if lazy_cs else '')
 

	
 
        def _get_op(rev_txt):
 
            _op = None
 
            _name = rev_txt
 
            if len(rev_txt.split('=>')) == 2:
 
                _op, _name = rev_txt.split('=>')
 
            return _op, _name
 

	
 
        revs = []
 
        if len(filter(lambda v: v != '', revs_ids)) > 0:
 
            repo = None
 
            for rev in revs_ids[:revs_top_limit]:
 
                _op, _name = _get_op(rev)
 

	
 
                # we want parsed changesets, or new log store format is bad
 
                if parse_cs:
 
                    try:
 
                        if repo is None:
 
                            repo = user_log.repository.scm_instance
 
                        _rev = repo.get_changeset(rev)
 
                        revs.append(_rev)
 
                    except ChangesetDoesNotExistError:
 
                        log.error('cannot find revision %s in this repo' % rev)
 
                        revs.append(rev)
 
                        continue
 
                else:
 
                    _rev = AttributeDict({
 
                        'short_id': rev[:12],
 
                        'raw_id': rev,
 
                        'message': '',
 
                        'op': _op,
 
                        'ref_name': _name
 
                    })
 
                    revs.append(_rev)
 
        cs_links = []
 
        cs_links.append(" " + ', '.join(
 
            [lnk(rev, repo_name) for rev in revs[:revs_limit]]
 
            )
 
        )
 
        _op1, _name1 = _get_op(revs_ids[0])
 
        _op2, _name2 = _get_op(revs_ids[-1])
 

	
 
        _rev = '%s...%s' % (_name1, _name2)
 

	
 
        compare_view = (
 
            ' <div class="compare_view tooltip" title="%s">'
 
            '<a href="%s">%s</a> </div>' % (
 
                _('Show all combined changesets %s->%s') % (
 
                    revs_ids[0][:12], revs_ids[-1][:12]
 
                ),
 
                url('changeset_home', repo_name=repo_name,
 
                    revision=_rev
 
                ),
 
                _('compare view')
 
            )
 
        )
 

	
 
        # if we have exactly one more than normally displayed
 
        # just display it, takes less space than displaying
 
        # "and 1 more revisions"
 
        if len(revs_ids) == revs_limit + 1:
 
            rev = revs[revs_limit]
 
            cs_links.append(", " + lnk(rev, repo_name))
 

	
 
        # hidden-by-default ones
 
        if len(revs_ids) > revs_limit + 1:
 
            uniq_id = revs_ids[0]
 
            html_tmpl = (
 
                '<span> %s <a class="show_more" id="_%s" '
 
                'href="#more">%s</a> %s</span>'
 
            )
 
            if not feed:
 
                cs_links.append(html_tmpl % (
 
                      _('and'),
 
                      uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
 
                      _('revisions')
 
                    )
 
                )
 

	
 
            if not feed:
 
                html_tmpl = '<span id="%s" style="display:none">, %s </span>'
 
            else:
 
                html_tmpl = '<span id="%s"> %s </span>'
 

	
 
            morelinks = ', '.join(
 
              [lnk(rev, repo_name) for rev in revs[revs_limit:]]
 
            )
 

	
 
            if len(revs_ids) > revs_top_limit:
 
                morelinks += ', ...'
 

	
 
            cs_links.append(html_tmpl % (uniq_id, morelinks))
 
        if len(revs) > 1:
 
            cs_links.append(compare_view)
 
        return ''.join(cs_links)
 

	
 
    def get_fork_name():
 
        repo_name = action_params
 
        _url = url('summary_home', repo_name=repo_name)
 
        return _('fork name %s') % link_to(action_params, _url)
 

	
 
    def get_user_name():
 
        user_name = action_params
 
        return user_name
 

	
 
    def get_users_group():
 
        group_name = action_params
 
        return group_name
 

	
 
    def get_pull_request():
 
        pull_request_id = action_params
 
        deleted = user_log.repository is None
 
        if deleted:
 
            repo_name = user_log.repository_name
 
        else:
 
            repo_name = user_log.repository.repo_name
 
        return link_to(_('Pull request #%s') % pull_request_id,
 
                    url('pullrequest_show', repo_name=repo_name,
 
                    pull_request_id=pull_request_id))
 

	
 
    # action : translated str, callback(extractor), icon
 
    action_map = {
 
    'user_deleted_repo':           (_('[deleted] repository'),
 
                                    None, 'database_delete.png'),
 
    'user_created_repo':           (_('[created] repository'),
 
                                    None, 'database_add.png'),
 
    'user_created_fork':           (_('[created] repository as fork'),
 
                                    None, 'arrow_divide.png'),
 
    'user_forked_repo':            (_('[forked] repository'),
 
                                    get_fork_name, 'arrow_divide.png'),
 
    'user_updated_repo':           (_('[updated] repository'),
 
                                    None, 'database_edit.png'),
 
    'admin_deleted_repo':          (_('[delete] repository'),
 
                                    None, 'database_delete.png'),
 
    'admin_created_repo':          (_('[created] repository'),
 
                                    None, 'database_add.png'),
 
    'admin_forked_repo':           (_('[forked] repository'),
 
                                    None, 'arrow_divide.png'),
 
    'admin_updated_repo':          (_('[updated] repository'),
 
                                    None, 'database_edit.png'),
 
    'admin_created_user':          (_('[created] user'),
 
                                    get_user_name, 'user_add.png'),
 
    'admin_updated_user':          (_('[updated] user'),
 
                                    get_user_name, 'user_edit.png'),
 
    'admin_created_users_group':   (_('[created] user group'),
 
                                    get_users_group, 'group_add.png'),
 
    'admin_updated_users_group':   (_('[updated] user group'),
 
                                    get_users_group, 'group_edit.png'),
 
    'user_commented_revision':     (_('[commented] on revision in repository'),
 
                                    get_cs_links, 'comment_add.png'),
 
    'user_commented_pull_request': (_('[commented] on pull request for'),
 
                                    get_pull_request, 'comment_add.png'),
 
    'user_closed_pull_request':    (_('[closed] pull request for'),
 
                                    get_pull_request, 'tick.png'),
 
    'push':                        (_('[pushed] into'),
 
                                    get_cs_links, 'script_add.png'),
 
    'push_local':                  (_('[committed via RhodeCode] into repository'),
 
                                    get_cs_links, 'script_edit.png'),
 
    'push_remote':                 (_('[pulled from remote] into repository'),
 
                                    get_cs_links, 'connect.png'),
 
    'pull':                        (_('[pulled] from'),
 
                                    None, 'down_16.png'),
 
    'started_following_repo':      (_('[started following] repository'),
 
                                    None, 'heart_add.png'),
 
    'stopped_following_repo':      (_('[stopped following] repository'),
 
                                    None, 'heart_delete.png'),
 
    }
 

	
 
    action_str = action_map.get(action, action)
 
    if feed:
 
        action = action_str[0].replace('[', '').replace(']', '')
 
    else:
 
        action = action_str[0]\
 
            .replace('[', '<span class="journal_highlight">')\
 
            .replace(']', '</span>')
 

	
 
    action_params_func = lambda: ""
 

	
 
    if callable(action_str[1]):
 
        action_params_func = action_str[1]
 

	
 
    def action_parser_icon():
 
        action = user_log.action
 
        action_params = None
 
        x = action.split(':')
 

	
 
        if len(x) > 1:
 
            action, action_params = x
 

	
 
        tmpl = """<img src="%s%s" alt="%s"/>"""
 
        ico = action_map.get(action, ['', '', ''])[2]
 
        return literal(tmpl % ((url('/images/icons/')), ico, action))
 

	
 
    # returned callbacks we need to call to get
 
    return [lambda: literal(action), action_params_func, action_parser_icon]
 

	
 

	
 

	
 
#==============================================================================
 
# PERMS
 
#==============================================================================
 
from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
 
HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
 
HasReposGroupPermissionAny
 

	
 

	
 
#==============================================================================
 
# GRAVATAR URL
 
#==============================================================================
 

	
 
def gravatar_url(email_address, size=30):
 
    from pylons import url  # doh, we need to re-import url to mock it later
 
    _def = 'anonymous@rhodecode.org'
 
    use_gravatar = str2bool(config['app_conf'].get('use_gravatar'))
 
    email_address = email_address or _def
 
    if (not use_gravatar or not email_address or email_address == _def):
 
        f = lambda a, l: min(l, key=lambda x: abs(x - a))
 
        return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
 

	
 
    if use_gravatar and config['app_conf'].get('alternative_gravatar_url'):
 
        tmpl = config['app_conf'].get('alternative_gravatar_url', '')
 
        parsed_url = urlparse.urlparse(url.current(qualified=True))
 
        tmpl = tmpl.replace('{email}', email_address)\
 
                   .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
 
                   .replace('{netloc}', parsed_url.netloc)\
 
                   .replace('{scheme}', parsed_url.scheme)\
 
                   .replace('{size}', str(size))
 
        return tmpl
 

	
 
    ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
 
    default = 'identicon'
 
    baseurl_nossl = "http://www.gravatar.com/avatar/"
 
    baseurl_ssl = "https://secure.gravatar.com/avatar/"
 
    baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
 

	
 
    if isinstance(email_address, unicode):
 
        #hashlib crashes on unicode items
 
        email_address = safe_str(email_address)
 
    # construct the url
 
    gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
 
    gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
 

	
 
    return gravatar_url
 

	
 

	
 
class Page(_Page):
 
    """
 
    Custom pager to match rendering style with YUI paginator
 
    """
 

	
 
    def _get_pos(self, cur_page, max_page, items):
 
        edge = (items / 2) + 1
 
        if (cur_page <= edge):
 
            radius = max(items / 2, items - cur_page)
 
        elif (max_page - cur_page) < edge:
 
            radius = (items - 1) - (max_page - cur_page)
 
        else:
 
            radius = items / 2
 

	
 
        left = max(1, (cur_page - (radius)))
 
        right = min(max_page, cur_page + (radius))
 
        return left, cur_page, right
 

	
 
    def _range(self, regexp_match):
 
        """
 
        Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
 

	
 
        Arguments:
 

	
 
        regexp_match
 
            A "re" (regular expressions) match object containing the
 
            radius of linked pages around the current page in
 
            regexp_match.group(1) as a string
 

	
 
        This function is supposed to be called as a callable in
 
        re.sub.
 

	
 
        """
 
        radius = int(regexp_match.group(1))
 

	
 
        # Compute the first and last page number within the radius
 
        # e.g. '1 .. 5 6 [7] 8 9 .. 12'
 
        # -> leftmost_page  = 5
 
        # -> rightmost_page = 9
 
        leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
 
                                                            self.last_page,
 
                                                            (radius * 2) + 1)
 
        nav_items = []
 

	
 
        # Create a link to the first page (unless we are on the first page
 
        # or there would be no need to insert '..' spacers)
 
        if self.page != self.first_page and self.first_page < leftmost_page:
 
            nav_items.append(self._pagerlink(self.first_page, self.first_page))
 

	
 
        # Insert dots if there are pages between the first page
 
        # and the currently displayed page range
 
        if leftmost_page - self.first_page > 1:
 
            # Wrap in a SPAN tag if nolink_attr is set
 
            text = '..'
 
            if self.dotdot_attr:
 
                text = HTML.span(c=text, **self.dotdot_attr)
 
            nav_items.append(text)
 

	
 
        for thispage in xrange(leftmost_page, rightmost_page + 1):
 
            # Hilight the current page number and do not use a link
 
            if thispage == self.page:
 
                text = '%s' % (thispage,)
 
                # Wrap in a SPAN tag if nolink_attr is set
 
                if self.curpage_attr:
 
                    text = HTML.span(c=text, **self.curpage_attr)
 
                nav_items.append(text)
 
            # Otherwise create just a link to that page
 
            else:
 
                text = '%s' % (thispage,)
 
                nav_items.append(self._pagerlink(thispage, text))
 

	
 
        # Insert dots if there are pages between the displayed
 
        # page numbers and the end of the page range
 
        if self.last_page - rightmost_page > 1:
 
            text = '..'
 
            # Wrap in a SPAN tag if nolink_attr is set
 
            if self.dotdot_attr:
 
                text = HTML.span(c=text, **self.dotdot_attr)
 
            nav_items.append(text)
 

	
 
        # Create a link to the very last page (unless we are on the last
 
        # page or there would be no need to insert '..' spacers)
 
        if self.page != self.last_page and rightmost_page < self.last_page:
 
            nav_items.append(self._pagerlink(self.last_page, self.last_page))
 

	
 
        return self.separator.join(nav_items)
 

	
 
    def pager(self, format='~2~', page_param='page', partial_param='partial',
 
        show_if_single_page=False, separator=' ', onclick=None,
 
        symbol_first='<<', symbol_last='>>',
 
        symbol_previous='<', symbol_next='>',
 
        link_attr={'class': 'pager_link'},
 
        curpage_attr={'class': 'pager_curpage'},
 
        dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
 

	
 
        self.curpage_attr = curpage_attr
 
        self.separator = separator
 
        self.pager_kwargs = kwargs
 
        self.page_param = page_param
 
        self.partial_param = partial_param
 
        self.onclick = onclick
 
        self.link_attr = link_attr
 
        self.dotdot_attr = dotdot_attr
 

	
 
        # Don't show navigator if there is no more than one page
 
        if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
 
            return ''
 

	
 
        from string import Template
 
        # Replace ~...~ in token format by range of pages
 
        result = re.sub(r'~(\d+)~', self._range, format)
 

	
 
        # Interpolate '%' variables
 
        result = Template(result).safe_substitute({
 
            'first_page': self.first_page,
 
            'last_page': self.last_page,
 
            'page': self.page,
 
            'page_count': self.page_count,
 
            'items_per_page': self.items_per_page,
 
            'first_item': self.first_item,
 
            'last_item': self.last_item,
 
            'item_count': self.item_count,
 
            'link_first': self.page > self.first_page and \
 
                    self._pagerlink(self.first_page, symbol_first) or '',
 
            'link_last': self.page < self.last_page and \
 
                    self._pagerlink(self.last_page, symbol_last) or '',
 
            'link_previous': self.previous_page and \
 
                    self._pagerlink(self.previous_page, symbol_previous) \
 
                    or HTML.span(symbol_previous, class_="yui-pg-previous"),
 
            'link_next': self.next_page and \
 
                    self._pagerlink(self.next_page, symbol_next) \
 
                    or HTML.span(symbol_next, class_="yui-pg-next")
 
        })
 

	
 
        return literal(result)
 

	
 

	
 
#==============================================================================
 
# REPO PAGER, PAGER FOR REPOSITORY
 
#==============================================================================
 
class RepoPage(Page):
 

	
 
    def __init__(self, collection, page=1, items_per_page=20,
 
                 item_count=None, url=None, **kwargs):
 

	
 
        """Create a "RepoPage" instance. special pager for paging
 
        repository
 
        """
 
        self._url_generator = url
 

	
 
        # Safe the kwargs class-wide so they can be used in the pager() method
 
        self.kwargs = kwargs
 

	
 
        # Save a reference to the collection
 
        self.original_collection = collection
 

	
 
        self.collection = collection
 

	
 
        # The self.page is the number of the current page.
 
        # The first page has the number 1!
 
        try:
 
            self.page = int(page)  # make it int() if we get it as a string
 
        except (ValueError, TypeError):
 
            self.page = 1
 

	
 
        self.items_per_page = items_per_page
 

	
 
        # Unless the user tells us how many items the collections has
 
        # we calculate that ourselves.
 
        if item_count is not None:
 
            self.item_count = item_count
 
        else:
 
            self.item_count = len(self.collection)
 

	
 
        # Compute the number of the first and last available page
 
        if self.item_count > 0:
 
            self.first_page = 1
 
            self.page_count = int(math.ceil(float(self.item_count) /
 
                                            self.items_per_page))
 
            self.last_page = self.first_page + self.page_count - 1
 

	
 
            # Make sure that the requested page number is the range of
 
            # valid pages
 
            if self.page > self.last_page:
 
                self.page = self.last_page
 
            elif self.page < self.first_page:
 
                self.page = self.first_page
 

	
 
            # Note: the number of items on this page can be less than
 
            #       items_per_page if the last page is not full
 
            self.first_item = max(0, (self.item_count) - (self.page *
 
                                                          items_per_page))
 
            self.last_item = ((self.item_count - 1) - items_per_page *
 
                              (self.page - 1))
 

	
 
            self.items = list(self.collection[self.first_item:self.last_item + 1])
 

	
 
            # Links to previous and next page
 
            if self.page > self.first_page:
 
                self.previous_page = self.page - 1
 
            else:
 
                self.previous_page = None
 

	
 
            if self.page < self.last_page:
 
                self.next_page = self.page + 1
 
            else:
 
                self.next_page = None
 

	
 
        # No items available
 
        else:
 
            self.first_page = None
 
            self.page_count = 0
 
            self.last_page = None
 
            self.first_item = None
 
            self.last_item = None
 
            self.previous_page = None
 
            self.next_page = None
 
            self.items = []
 

	
 
        # This is a subclass of the 'list' type. Initialise the list now.
 
        list.__init__(self, reversed(self.items))
 

	
 

	
 
def changed_tooltip(nodes):
 
    """
 
    Generates a html string for changed nodes in changeset page.
 
    It limits the output to 30 entries
 

	
 
    :param nodes: LazyNodesGenerator
 
    """
 
    if nodes:
 
        pref = ': <br/> '
 
        suf = ''
 
        if len(nodes) > 30:
 
            suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
 
        return literal(pref + '<br/> '.join([safe_unicode(x.path)
 
                                             for x in nodes[:30]]) + suf)
 
    else:
 
        return ': ' + _('No Files')
 

	
 

	
 
def repo_link(groups_and_repos):
 
    """
 
    Makes a breadcrumbs link to repo within a group
 
    joins &raquo; on each group to create a fancy link
 

	
 
    ex::
 
        group >> subgroup >> repo
 

	
 
    :param groups_and_repos:
 
    :param last_url:
 
    """
 
    groups, just_name, repo_name = groups_and_repos
 
    last_url = url('summary_home', repo_name=repo_name)
 
    last_link = link_to(just_name, last_url)
 

	
 
    def make_link(group):
 
        return link_to(group.name,
 
                       url('repos_group_home', group_name=group.group_name))
 
    return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
 

	
 

	
 
def fancy_file_stats(stats):
 
    """
 
    Displays a fancy two colored bar for number of added/deleted
 
    lines of code on file
 

	
 
    :param stats: two element list of added/deleted lines of code
 
    """
 
    from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
 
        MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
 

	
 
    def cgen(l_type, a_v, d_v):
 
        mapping = {'tr': 'top-right-rounded-corner-mid',
 
                   'tl': 'top-left-rounded-corner-mid',
 
                   'br': 'bottom-right-rounded-corner-mid',
 
                   'bl': 'bottom-left-rounded-corner-mid'}
 
        map_getter = lambda x: mapping[x]
 

	
 
        if l_type == 'a' and d_v:
 
            #case when added and deleted are present
 
            return ' '.join(map(map_getter, ['tl', 'bl']))
 

	
 
        if l_type == 'a' and not d_v:
 
            return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
 

	
 
        if l_type == 'd' and a_v:
 
            return ' '.join(map(map_getter, ['tr', 'br']))
 

	
 
        if l_type == 'd' and not a_v:
 
            return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
 

	
 
    a, d = stats[0], stats[1]
 
    a, d = stats['added'], stats['deleted']
 
    width = 100
 

	
 
    if a == 'b':
 
    if stats['binary']:
 
        #binary mode
 
        b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
 
        b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
 
        lbl = ''
 
        bin_op = 1
 

	
 
        if BIN_FILENODE in stats['ops']:
 
            lbl = 'bin+'
 

	
 
        if NEW_FILENODE in stats['ops']:
 
            lbl += _('new file')
 
            bin_op = NEW_FILENODE
 
        elif MOD_FILENODE in stats['ops']:
 
            lbl += _('mod')
 
            bin_op = MOD_FILENODE
 
        elif DEL_FILENODE in stats['ops']:
 
            lbl += _('del')
 
            bin_op = DEL_FILENODE
 
        elif RENAMED_FILENODE in stats['ops']:
 
            lbl += _('rename')
 
            bin_op = RENAMED_FILENODE
 

	
 
        #chmod can go with other operations
 
        if CHMOD_FILENODE in stats['ops']:
 
            _org_lbl = _('chmod')
 
            lbl += _org_lbl if lbl.endswith('+') else '+%s' % _org_lbl
 

	
 
        #import ipdb;ipdb.set_trace()
 
        b_d = '<div class="bin bin%s %s" style="width:100%%">%s</div>' % (bin_op, cgen('a', a_v='', d_v=0), lbl)
 
        b_a = '<div class="bin bin1" style="width:0%%"></div>'
 
        return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
 

	
 
    t = stats[0] + stats[1]
 
    t = stats['added'] + stats['deleted']
 
    unit = float(width) / (t or 1)
 

	
 
    # needs > 9% of width to be visible or 0 to be hidden
 
    a_p = max(9, unit * a) if a > 0 else 0
 
    d_p = max(9, unit * d) if d > 0 else 0
 
    p_sum = a_p + d_p
 

	
 
    if p_sum > width:
 
        #adjust the percentage to be == 100% since we adjusted to 9
 
        if a_p > d_p:
 
            a_p = a_p - (p_sum - width)
 
        else:
 
            d_p = d_p - (p_sum - width)
 

	
 
    a_v = a if a > 0 else ''
 
    d_v = d if d > 0 else ''
 

	
 
    d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
 
        cgen('a', a_v, d_v), a_p, a_v
 
    )
 
    d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
 
        cgen('d', a_v, d_v), d_p, d_v
 
    )
 
    return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
 

	
 

	
 
def urlify_text(text_, safe=True):
 
    """
 
    Extrac urls from text and make html links out of them
 

	
 
    :param text_:
 
    """
 

	
 
    url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
 
                         '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
 

	
 
    def url_func(match_obj):
 
        url_full = match_obj.groups()[0]
 
        return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
 
    _newtext = url_pat.sub(url_func, text_)
 
    if safe:
 
        return literal(_newtext)
 
    return _newtext
 

	
 

	
 
def urlify_changesets(text_, repository):
 
    """
 
    Extract revision ids from changeset and make link from them
 

	
 
    :param text_:
 
    :param repository: repo name to build the URL with
 
    """
 
    from pylons import url  # doh, we need to re-import url to mock it later
 
    URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
 

	
 
    def url_func(match_obj):
 
        rev = match_obj.groups()[1]
 
        pref = match_obj.groups()[0]
 
        suf = match_obj.groups()[2]
 

	
 
        tmpl = (
 
        '%(pref)s<a class="%(cls)s" href="%(url)s">'
 
        '%(rev)s</a>%(suf)s'
 
        )
 
        return tmpl % {
 
         'pref': pref,
 
         'cls': 'revision-link',
 
         'url': url('changeset_home', repo_name=repository, revision=rev),
 
         'rev': rev,
 
         'suf': suf
 
        }
 

	
 
    newtext = URL_PAT.sub(url_func, text_)
 

	
 
    return newtext
 

	
 

	
 
def urlify_commit(text_, repository=None, link_=None):
 
    """
 
    Parses given text message and makes proper links.
 
    issues are linked to given issue-server, and rest is a changeset link
 
    if link_ is given, in other case it's a plain text
 

	
 
    :param text_:
 
    :param repository:
 
    :param link_: changeset link
 
    """
 
    import traceback
 
    from pylons import url  # doh, we need to re-import url to mock it later
 

	
 
    def escaper(string):
 
        return string.replace('<', '&lt;').replace('>', '&gt;')
 

	
 
    def linkify_others(t, l):
 
        urls = re.compile(r'(\<a.*?\<\/a\>)',)
 
        links = []
 
        for e in urls.split(t):
 
            if not urls.match(e):
 
                links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
 
            else:
 
                links.append(e)
 

	
 
        return ''.join(links)
 

	
 
    # urlify changesets - extrac revisions and make link out of them
 
    newtext = urlify_changesets(escaper(text_), repository)
 

	
 
    # extract http/https links and make them real urls
 
    newtext = urlify_text(newtext, safe=False)
 

	
 
    try:
 
        from rhodecode import CONFIG
 
        conf = CONFIG
 

	
 
        # allow multiple issue servers to be used
 
        valid_indices = [
 
            x.group(1)
 
            for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
 
            if x and 'issue_server_link%s' % x.group(1) in conf
 
            and 'issue_prefix%s' % x.group(1) in conf
 
        ]
 

	
 
        log.debug('found issue server suffixes `%s` during valuation of: %s'
 
                  % (','.join(valid_indices), newtext))
 

	
 
        for pattern_index in valid_indices:
 
            ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
 
            ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
 
            ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
 

	
 
            log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
 
                      % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
 
                         ISSUE_PREFIX))
 

	
 
            URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
 

	
 
            def url_func(match_obj):
 
                pref = ''
 
                if match_obj.group().startswith(' '):
 
                    pref = ' '
 

	
 
                issue_id = ''.join(match_obj.groups())
 
                tmpl = (
 
                '%(pref)s<a class="%(cls)s" href="%(url)s">'
 
                '%(issue-prefix)s%(id-repr)s'
 
                '</a>'
 
                )
 
                url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
 
                if repository:
 
                    url = url.replace('{repo}', repository)
 
                    repo_name = repository.split(URL_SEP)[-1]
 
                    url = url.replace('{repo_name}', repo_name)
 

	
 
                return tmpl % {
 
                     'pref': pref,
 
                     'cls': 'issue-tracker-link',
 
                     'url': url,
 
                     'id-repr': issue_id,
 
                     'issue-prefix': ISSUE_PREFIX,
 
                     'serv': ISSUE_SERVER_LNK,
 
                }
 
            newtext = URL_PAT.sub(url_func, newtext)
 
            log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
 

	
 
        # if we actually did something above
 
        if link_:
 
            # wrap not links into final link => link_
 
            newtext = linkify_others(newtext, link_)
 
    except Exception:
 
        log.error(traceback.format_exc())
 
        pass
 

	
 
    return literal(newtext)
 

	
 

	
 
def rst(source):
 
    return literal('<div class="rst-block">%s</div>' %
 
                   MarkupRenderer.rst(source))
 

	
 

	
 
def rst_w_mentions(source):
 
    """
 
    Wrapped rst renderer with @mention highlighting
 

	
 
    :param source:
 
    """
 
    return literal('<div class="rst-block">%s</div>' %
 
                   MarkupRenderer.rst_with_mentions(source))
 

	
 

	
 
def changeset_status(repo, revision):
 
    return ChangesetStatusModel().get_status(repo, revision)
 

	
 

	
 
def changeset_status_lbl(changeset_status):
 
    return dict(ChangesetStatus.STATUSES).get(changeset_status)
 

	
 

	
 
def get_permission_name(key):
 
    return dict(Permission.PERMS).get(key)
 

	
 

	
 
def journal_filter_help():
 
    return _(textwrap.dedent('''
 
        Example filter terms:
 
            repository:vcs
 
            username:marcin
 
            action:*push*
 
            ip:127.0.0.1
 
            date:20120101
 
            date:[20120101100000 TO 20120102]
 

	
 
        Generate wildcards using '*' character:
 
            "repositroy:vcs*" - search everything starting with 'vcs'
 
            "repository:*vcs*" - search for repository containing 'vcs'
 

	
 
        Optional AND / OR operators in queries
 
            "repository:vcs OR repository:test"
 
            "username:test AND repository:test*"
 
    '''))
 

	
 

	
 
def not_mapped_error(repo_name):
 
    flash(_('%s repository is not mapped to db perhaps'
 
            ' it was created or renamed from the filesystem'
 
            ' please run the application again'
 
            ' in order to rescan repositories') % repo_name, category='error')
 

	
 

	
 
def ip_range(ip_addr):
 
    from rhodecode.model.db import UserIpMap
 
    s, e = UserIpMap._get_ip_range(ip_addr)
 
    return '%s - %s' % (s, e)
rhodecode/public/css/style.css
Show inline comments
 
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td {
 
    border: 0;
 
    outline: 0;
 
    font-size: 100%;
 
    vertical-align: baseline;
 
    background: transparent;
 
    margin: 0;
 
    padding: 0;
 
}
 

	
 
body {
 
    line-height: 1;
 
    height: 100%;
 
    background: url("../images/background.png") repeat scroll 0 0 #B0B0B0;
 
    font-family: Lucida Grande, Verdana, Lucida Sans Regular,
 
        Lucida Sans Unicode, Arial, sans-serif; font-size : 12px;
 
    color: #000;
 
    margin: 0;
 
    padding: 0;
 
    font-size: 12px;
 
}
 

	
 
ol, ul {
 
    list-style: none;
 
}
 

	
 
blockquote, q {
 
    quotes: none;
 
}
 

	
 
blockquote:before, blockquote:after, q:before, q:after {
 
    content: none;
 
}
 

	
 
:focus {
 
    outline: 0;
 
}
 

	
 
del {
 
    text-decoration: line-through;
 
}
 

	
 
table {
 
    border-collapse: collapse;
 
    border-spacing: 0;
 
}
 

	
 
html {
 
    height: 100%;
 
}
 

	
 
a {
 
    color: #003367;
 
    text-decoration: none;
 
    cursor: pointer;
 
}
 

	
 
a:hover {
 
    color: #316293;
 
    text-decoration: underline;
 
}
 

	
 
h1, h2, h3, h4, h5, h6,
 
div.h1, div.h2, div.h3, div.h4, div.h5, div.h6 {
 
    color: #292929;
 
    font-weight: 700;
 
}
 

	
 
h1, div.h1 {
 
    font-size: 22px;
 
}
 

	
 
h2, div.h2 {
 
    font-size: 20px;
 
}
 

	
 
h3, div.h3 {
 
    font-size: 18px;
 
}
 

	
 
h4, div.h4 {
 
    font-size: 16px;
 
}
 

	
 
h5, div.h5 {
 
    font-size: 14px;
 
}
 

	
 
h6, div.h6 {
 
    font-size: 11px;
 
}
 

	
 
ul.circle {
 
    list-style-type: circle;
 
}
 

	
 
ul.disc {
 
    list-style-type: disc;
 
}
 

	
 
ul.square {
 
    list-style-type: square;
 
}
 

	
 
ol.lower-roman {
 
    list-style-type: lower-roman;
 
}
 

	
 
ol.upper-roman {
 
    list-style-type: upper-roman;
 
}
 

	
 
ol.lower-alpha {
 
    list-style-type: lower-alpha;
 
}
 

	
 
ol.upper-alpha {
 
    list-style-type: upper-alpha;
 
}
 

	
 
ol.decimal {
 
    list-style-type: decimal;
 
}
 

	
 
div.color {
 
    clear: both;
 
    overflow: hidden;
 
    position: absolute;
 
    background: #FFF;
 
    margin: 7px 0 0 60px;
 
    padding: 1px 1px 1px 0;
 
}
 

	
 
div.color a {
 
    width: 15px;
 
    height: 15px;
 
    display: block;
 
    float: left;
 
    margin: 0 0 0 1px;
 
    padding: 0;
 
}
 

	
 
div.options {
 
    clear: both;
 
    overflow: hidden;
 
    position: absolute;
 
    background: #FFF;
 
    margin: 7px 0 0 162px;
 
    padding: 0;
 
}
 

	
 
div.options a {
 
    height: 1%;
 
    display: block;
 
    text-decoration: none;
 
    margin: 0;
 
    padding: 3px 8px;
 
}
 

	
 
.top-left-rounded-corner {
 
    -webkit-border-top-left-radius: 8px;
 
    -khtml-border-radius-topleft: 8px;
 
    border-top-left-radius: 8px;
 
}
 

	
 
.top-right-rounded-corner {
 
    -webkit-border-top-right-radius: 8px;
 
    -khtml-border-radius-topright: 8px;
 
    border-top-right-radius: 8px;
 
}
 

	
 
.bottom-left-rounded-corner {
 
    -webkit-border-bottom-left-radius: 8px;
 
    -khtml-border-radius-bottomleft: 8px;
 
    border-bottom-left-radius: 8px;
 
}
 

	
 
.bottom-right-rounded-corner {
 
    -webkit-border-bottom-right-radius: 8px;
 
    -khtml-border-radius-bottomright: 8px;
 
    border-bottom-right-radius: 8px;
 
}
 

	
 
.top-left-rounded-corner-mid {
 
    -webkit-border-top-left-radius: 4px;
 
    -khtml-border-radius-topleft: 4px;
 
    border-top-left-radius: 4px;
 
}
 

	
 
.top-right-rounded-corner-mid {
 
    -webkit-border-top-right-radius: 4px;
 
    -khtml-border-radius-topright: 4px;
 
    border-top-right-radius: 4px;
 
}
 

	
 
.bottom-left-rounded-corner-mid {
 
    -webkit-border-bottom-left-radius: 4px;
 
    -khtml-border-radius-bottomleft: 4px;
 
    border-bottom-left-radius: 4px;
 
}
 

	
 
.bottom-right-rounded-corner-mid {
 
    -webkit-border-bottom-right-radius: 4px;
 
    -khtml-border-radius-bottomright: 4px;
 
    border-bottom-right-radius: 4px;
 
}
 

	
 
.help-block {
 
    color: #999999;
 
    display: block;
 
    margin-bottom: 0;
 
    margin-top: 5px;
 
}
 

	
 
.empty_data {
 
    color: #B9B9B9;
 
}
 

	
 
a.permalink {
 
    visibility: hidden;
 
    position: absolute;
 
    margin: 3px 4px;
 
}
 

	
 
a.permalink:hover {
 
    text-decoration: none;
 
}
 

	
 
h1:hover > a.permalink,
 
h2:hover > a.permalink,
 
h3:hover > a.permalink,
 
h4:hover > a.permalink,
 
h5:hover > a.permalink,
 
h6:hover > a.permalink,
 
div:hover > a.permalink {
 
    visibility: visible;
 
}
 

	
 
#header {
 
}
 
#header ul#logged-user {
 
    margin-bottom: 5px !important;
 
    -webkit-border-radius: 0px 0px 8px 8px;
 
    -khtml-border-radius: 0px 0px 8px 8px;
 
    border-radius: 0px 0px 8px 8px;
 
    height: 37px;
 
    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(to bottom, #003b76, #00376e);
 
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
 
    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
 
}
 

	
 
#header ul#logged-user li {
 
    list-style: none;
 
    float: left;
 
    margin: 8px 0 0;
 
    padding: 4px 12px;
 
    border-left: 1px solid #316293;
 
}
 

	
 
#header ul#logged-user li.first {
 
    border-left: none;
 
    margin: 4px;
 
}
 

	
 
#header ul#logged-user li.first div.gravatar {
 
    margin-top: -2px;
 
}
 

	
 
#header ul#logged-user li.first div.account {
 
    padding-top: 4px;
 
    float: left;
 
}
 

	
 
#header ul#logged-user li.last {
 
    border-right: none;
 
}
 

	
 
#header ul#logged-user li a {
 
    color: #fff;
 
    font-weight: 700;
 
    text-decoration: none;
 
}
 

	
 
#header ul#logged-user li a:hover {
 
    text-decoration: underline;
 
}
 

	
 
#header ul#logged-user li.highlight a {
 
    color: #fff;
 
}
 

	
 
#header ul#logged-user li.highlight a:hover {
 
    color: #FFF;
 
}
 
#header-dd {
 
    clear: both;
 
    position: fixed !important;
 
    background-color: #003B76;
 
    opacity: 0.01;
 
    cursor: pointer;
 
    min-height: 10px;
 
    width: 100% !important;
 
    -webkit-border-radius: 0px 0px 4px 4px;
 
    -khtml-border-radius: 0px 0px 4px 4px;
 
    border-radius: 0px 0px 4px 4px;
 
}
 

	
 
#header-dd:hover {
 
    opacity: 0.2;
 
    -webkit-transition: opacity 0.5s ease-in-out;
 
    -moz-transition: opacity 0.5s ease-in-out;
 
    transition: opacity 0.5s ease-in-out;
 
}
 

	
 
#header #header-inner {
 
    min-height: 44px;
 
    clear: both;
 
    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(to bottom, #003b76, #00376e);
 
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
 
    margin: 0;
 
    padding: 0;
 
    display: block;
 
    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
 
    -webkit-border-radius: 0px 0px 4px 4px;
 
    -khtml-border-radius: 0px 0px 4px 4px;
 
    border-radius: 0px 0px 4px 4px;
 
}
 
#header #header-inner.hover {
 
    width: 100% !important;
 
    -webkit-border-radius: 0px 0px 0px 0px;
 
    -khtml-border-radius: 0px 0px 0px 0px;
 
    border-radius: 0px 0px 0px 0px;
 
    position: fixed !important;
 
    z-index: 10000;
 
}
 

	
 
.ie7 #header #header-inner.hover,
 
.ie8 #header #header-inner.hover,
 
.ie9 #header #header-inner.hover
 
{
 
    z-index: auto !important;
 
}
 

	
 
.header-pos-fix, .anchor {
 
    margin-top: -46px;
 
    padding-top: 46px;
 
}
 

	
 
#header #header-inner #home a {
 
    height: 40px;
 
    width: 46px;
 
    display: block;
 
    background: url("../images/button_home.png");
 
    background-position: 0 0;
 
    margin: 0;
 
    padding: 0;
 
}
 

	
 
#header #header-inner #home a:hover {
 
    background-position: 0 -40px;
 
}
 

	
 
#header #header-inner #logo {
 
    float: left;
 
    position: absolute;
 
}
 

	
 
#header #header-inner #logo h1 {
 
    color: #FFF;
 
    font-size: 20px;
 
    margin: 12px 0 0 13px;
 
    padding: 0;
 
}
 

	
 
#header #header-inner #logo a {
 
    color: #fff;
 
    text-decoration: none;
 
}
 

	
 
#header #header-inner #logo a:hover {
 
    color: #bfe3ff;
 
}
 

	
 
#header #header-inner #quick {
 
    position: relative;
 
    float: right;
 
    list-style-type: none;
 
    list-style-position: outside;
 
    margin: 4px 8px 0 0;
 
    padding: 0;
 
    border-radius: 4px;
 
}
 

	
 
#header #header-inner #quick li span.short {
 
    padding: 9px 6px 8px 6px;
 
}
 

	
 
#header #header-inner #quick li span {
 
    display: inline;
 
    margin: 0;
 
}
 

	
 
#header #header-inner #quick li span.normal {
 
    border: none;
 
    padding: 10px 12px 8px;
 
}
 

	
 
#header #header-inner #quick li span.icon {
 
    border-left: none;
 
    padding-left: 10px;
 
}
 

	
 
#header #header-inner #quick li span.icon_short {
 
    top: 0;
 
    left: 0;
 
    border-left: none;
 
    border-right: 1px solid #2e5c89;
 
    padding: 8px 6px 4px;
 
}
 

	
 
#header #header-inner #quick li span.icon img, #header #header-inner #quick li span.icon_short img {
 
    vertical-align: middle;
 
    margin-bottom: 2px;
 
}
 

	
 
#header #header-inner #quick ul.repo_switcher {
 
    max-height: 275px;
 
    overflow-x: hidden;
 
    overflow-y: auto;
 
}
 

	
 
#header #header-inner #quick ul.repo_switcher li.qfilter_rs {
 
    padding: 2px 3px;
 
    padding-right: 17px;
 
}
 

	
 
#header #header-inner #quick ul.repo_switcher li.qfilter_rs  input {
 
    width: 100%;
 
    border-radius: 10px;
 
    padding: 2px 7px;
 
}
 

	
 
#header #header-inner #quick .repo_switcher_type {
 
    position: absolute;
 
    left: 0;
 
    top: 9px;
 
    margin: 0px 2px 0px 2px;
 
}
 

	
 
#header #header-inner #quick li ul li a.journal, #header #header-inner #quick li ul li a.journal:hover {
 
    background-image: url("../images/icons/book.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.private_repo, #header #header-inner #quick li ul li a.private_repo:hover {
 
    background-image: url("../images/icons/lock.png")
 
}
 

	
 
#header #header-inner #quick li ul li a.public_repo, #header #header-inner #quick li ul li a.public_repo:hover {
 
    background-image: url("../images/icons/lock_open.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.hg, #header #header-inner #quick li ul li a.hg:hover {
 
    background-image: url("../images/icons/hgicon.png");
 
    padding-left: 42px;
 
    background-position: 20px 9px;
 
}
 

	
 
#header #header-inner #quick li ul li a.git, #header #header-inner #quick li ul li a.git:hover {
 
    background-image: url("../images/icons/giticon.png");
 
    padding-left: 42px;
 
    background-position: 20px 9px;
 
}
 

	
 
#header #header-inner #quick li ul li a.repos, #header #header-inner #quick li ul li a.repos:hover {
 
    background-image: url("../images/icons/database_edit.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.repos_groups, #header #header-inner #quick li ul li a.repos_groups:hover {
 
    background-image: url("../images/icons/database_link.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.users, #header #header-inner #quick li ul li a.users:hover {
 
    background-image: url("../images/icons/user_edit.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.groups, #header #header-inner #quick li ul li a.groups:hover {
 
    background-image: url("../images/icons/group_edit.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.defaults, #header #header-inner #quick li ul li a.defaults:hover {
 
    background-image: url("../images/icons/wrench.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.settings, #header #header-inner #quick li ul li a.settings:hover {
 
    background-image: url("../images/icons/cog.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.permissions, #header #header-inner #quick li ul li a.permissions:hover {
 
    background-image: url("../images/icons/key.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.ldap, #header #header-inner #quick li ul li a.ldap:hover {
 
    background-image: url("../images/icons/server_key.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.fork, #header #header-inner #quick li ul li a.fork:hover {
 
    background-image: url("../images/icons/arrow_divide.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.locking_add, #header #header-inner #quick li ul li a.locking_add:hover {
 
    background-image: url("../images/icons/lock_add.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.locking_del, #header #header-inner #quick li ul li a.locking_del:hover {
 
    background-image: url("../images/icons/lock_delete.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.pull_request, #header #header-inner #quick li ul li a.pull_request:hover {
 
    background-image: url("../images/icons/arrow_join.png") ;
 
}
 

	
 
#header #header-inner #quick li ul li a.compare_request, #header #header-inner #quick li ul li a.compare_request:hover {
 
    background-image: url("../images/icons/arrow_inout.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.search, #header #header-inner #quick li ul li a.search:hover {
 
    background-image: url("../images/icons/search_16.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.delete, #header #header-inner #quick li ul li a.delete:hover {
 
    background-image: url("../images/icons/delete.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.branches, #header #header-inner #quick li ul li a.branches:hover {
 
    background-image: url("../images/icons/arrow_branch.png");
 
}
 

	
 
#header #header-inner #quick li ul li a.tags,
 
#header #header-inner #quick li ul li a.tags:hover {
 
    background: #FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
 
    width: 167px;
 
    margin: 0;
 
    padding: 12px 9px 7px 24px;
 
}
 

	
 
#header #header-inner #quick li ul li a.bookmarks,
 
#header #header-inner #quick li ul li a.bookmarks:hover {
 
    background: #FFF url("../images/icons/tag_green.png") no-repeat 4px 9px;
 
    width: 167px;
 
    margin: 0;
 
    padding: 12px 9px 7px 24px;
 
}
 

	
 
#header #header-inner #quick li ul li a.admin,
 
#header #header-inner #quick li ul li a.admin:hover {
 
    background: #FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
 
    width: 167px;
 
    margin: 0;
 
    padding: 12px 9px 7px 24px;
 
}
 

	
 
.groups_breadcrumbs a {
 
    color: #fff;
 
}
 

	
 
.groups_breadcrumbs a:hover {
 
    color: #bfe3ff;
 
    text-decoration: none;
 
}
 

	
 
td.quick_repo_menu {
 
    background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important;
 
    cursor: pointer;
 
    width: 8px;
 
    border: 1px solid transparent;
 
}
 

	
 
td.quick_repo_menu.active {
 
    background: url("../images/dt-arrow-dn.png") no-repeat scroll 5px 50% #FFFFFF !important;
 
    border: 1px solid #003367;
 
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
 
    cursor: pointer;
 
}
 

	
 
td.quick_repo_menu .menu_items {
 
    margin-top: 10px;
 
    margin-left: -6px;
 
    width: 150px;
 
    position: absolute;
 
    background-color: #FFF;
 
    background: none repeat scroll 0 0 #FFFFFF;
 
    border-color: #003367 #666666 #666666;
 
    border-right: 1px solid #666666;
 
    border-style: solid;
 
    border-width: 1px;
 
    box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
 
    border-top-style: none;
 
}
 

	
 
td.quick_repo_menu .menu_items li {
 
    padding: 0 !important;
 
}
 

	
 
td.quick_repo_menu .menu_items a {
 
    display: block;
 
    padding: 4px 12px 4px 8px;
 
}
 

	
 
td.quick_repo_menu .menu_items a:hover {
 
    background-color: #EEE;
 
    text-decoration: none;
 
}
 

	
 
td.quick_repo_menu .menu_items .icon img {
 
    margin-bottom: -2px;
 
}
 

	
 
td.quick_repo_menu .menu_items.hidden {
 
    display: none;
 
}
 

	
 
.yui-dt-first th {
 
    text-align: left;
 
}
 

	
 
/*
 
Copyright (c) 2011, Yahoo! Inc. All rights reserved.
 
Code licensed under the BSD License:
 
http://developer.yahoo.com/yui/license.html
 
version: 2.9.0
 
*/
 
.yui-skin-sam .yui-dt-mask {
 
    position: absolute;
 
    z-index: 9500;
 
}
 
.yui-dt-tmp {
 
    position: absolute;
 
    left: -9000px;
 
}
 
.yui-dt-scrollable .yui-dt-bd { overflow: auto }
 
.yui-dt-scrollable .yui-dt-hd {
 
    overflow: hidden;
 
    position: relative;
 
}
 
.yui-dt-scrollable .yui-dt-bd thead tr,
 
.yui-dt-scrollable .yui-dt-bd thead th {
 
    position: absolute;
 
    left: -1500px;
 
}
 
.yui-dt-scrollable tbody { -moz-outline: 0 }
 
.yui-skin-sam thead .yui-dt-sortable { cursor: pointer }
 
.yui-skin-sam thead .yui-dt-draggable { cursor: move }
 
.yui-dt-coltarget {
 
    position: absolute;
 
    z-index: 999;
 
}
 
.yui-dt-hd { zoom: 1 }
 
th.yui-dt-resizeable .yui-dt-resizerliner { position: relative }
 
.yui-dt-resizer {
 
    position: absolute;
 
    right: 0;
 
    bottom: 0;
 
    height: 100%;
 
    cursor: e-resize;
 
    cursor: col-resize;
 
    background-color: #CCC;
 
    opacity: 0;
 
    filter: alpha(opacity=0);
 
}
 
.yui-dt-resizerproxy {
 
    visibility: hidden;
 
    position: absolute;
 
    z-index: 9000;
 
    background-color: #CCC;
 
    opacity: 0;
 
    filter: alpha(opacity=0);
 
}
 
th.yui-dt-hidden .yui-dt-liner,
 
td.yui-dt-hidden .yui-dt-liner,
 
th.yui-dt-hidden .yui-dt-resizer { display: none }
 
.yui-dt-editor,
 
.yui-dt-editor-shim {
 
    position: absolute;
 
    z-index: 9000;
 
}
 
.yui-skin-sam .yui-dt table {
 
    margin: 0;
 
    padding: 0;
 
    font-family: arial;
 
    font-size: inherit;
 
    border-collapse: separate;
 
    *border-collapse: collapse;
 
    border-spacing: 0;
 
    border: 1px solid #7f7f7f;
 
}
 
.yui-skin-sam .yui-dt thead { border-spacing: 0 }
 
.yui-skin-sam .yui-dt caption {
 
    color: #000;
 
    font-size: 85%;
 
    font-weight: normal;
 
    font-style: italic;
 
    line-height: 1;
 
    padding: 1em 0;
 
    text-align: center;
 
}
 
.yui-skin-sam .yui-dt th { background: #d8d8da url(../images/sprite.png) repeat-x 0 0 }
 
.yui-skin-sam .yui-dt th,
 
.yui-skin-sam .yui-dt th a {
 
    font-weight: normal;
 
    text-decoration: none;
 
    color: #000;
 
    vertical-align: bottom;
 
}
 
.yui-skin-sam .yui-dt th {
 
    margin: 0;
 
    padding: 0;
 
    border: 0;
 
    border-right: 1px solid #cbcbcb;
 
}
 
.yui-skin-sam .yui-dt tr.yui-dt-first td { border-top: 1px solid #7f7f7f }
 
.yui-skin-sam .yui-dt th .yui-dt-liner { white-space: nowrap }
 
.yui-skin-sam .yui-dt-liner {
 
    margin: 0;
 
    padding: 0;
 
}
 
.yui-skin-sam .yui-dt-coltarget {
 
    width: 5px;
 
    background-color: red;
 
}
 
.yui-skin-sam .yui-dt td {
 
    margin: 0;
 
    padding: 0;
 
    border: 0;
 
    border-right: 1px solid #cbcbcb;
 
    text-align: left;
 
}
 
.yui-skin-sam .yui-dt-list td { border-right: 0 }
 
.yui-skin-sam .yui-dt-resizer { width: 6px }
 
.yui-skin-sam .yui-dt-mask {
 
    background-color: #000;
 
    opacity: .25;
 
    filter: alpha(opacity=25);
 
}
 
.yui-skin-sam .yui-dt-message { background-color: #FFF }
 
.yui-skin-sam .yui-dt-scrollable table { border: 0 }
 
.yui-skin-sam .yui-dt-scrollable .yui-dt-hd {
 
    border-left: 1px solid #7f7f7f;
 
    border-top: 1px solid #7f7f7f;
 
    border-right: 1px solid #7f7f7f;
 
}
 
.yui-skin-sam .yui-dt-scrollable .yui-dt-bd {
 
    border-left: 1px solid #7f7f7f;
 
    border-bottom: 1px solid #7f7f7f;
 
    border-right: 1px solid #7f7f7f;
 
    background-color: #FFF;
 
}
 
.yui-skin-sam .yui-dt-scrollable .yui-dt-data tr.yui-dt-last td { border-bottom: 1px solid #7f7f7f }
 
.yui-skin-sam th.yui-dt-asc,
 
.yui-skin-sam th.yui-dt-desc { background: url(../images/sprite.png) repeat-x 0 -100px }
 
.yui-skin-sam th.yui-dt-sortable .yui-dt-label { margin-right: 10px }
 
.yui-skin-sam th.yui-dt-asc .yui-dt-liner { background: url(../images/dt-arrow-up.png) no-repeat right }
 
.yui-skin-sam th.yui-dt-desc .yui-dt-liner { background: url(../images/dt-arrow-dn.png) no-repeat right }
 
tbody .yui-dt-editable { cursor: pointer }
 
.yui-dt-editor {
 
    text-align: left;
 
    background-color: #f2f2f2;
 
    border: 1px solid #808080;
 
    padding: 6px;
 
}
 
.yui-dt-editor label {
 
    padding-left: 4px;
 
    padding-right: 6px;
 
}
 
.yui-dt-editor .yui-dt-button {
 
    padding-top: 6px;
 
    text-align: right;
 
}
 
.yui-dt-editor .yui-dt-button button {
 
    background: url(../images/sprite.png) repeat-x 0 0;
 
    border: 1px solid #999;
 
    width: 4em;
 
    height: 1.8em;
 
    margin-left: 6px;
 
}
 
.yui-dt-editor .yui-dt-button button.yui-dt-default {
 
    background: url(../images/sprite.png) repeat-x 0 -1400px;
 
    background-color: #5584e0;
 
    border: 1px solid #304369;
 
    color: #FFF;
 
}
 
.yui-dt-editor .yui-dt-button button:hover {
 
    background: url(../images/sprite.png) repeat-x 0 -1300px;
 
    color: #000;
 
}
 
.yui-dt-editor .yui-dt-button button:active {
 
    background: url(../images/sprite.png) repeat-x 0 -1700px;
 
    color: #000;
 
}
 
.yui-skin-sam tr.yui-dt-even { background-color: #FFF }
 
.yui-skin-sam tr.yui-dt-odd { background-color: #edf5ff }
 
.yui-skin-sam tr.yui-dt-even td.yui-dt-asc,
 
.yui-skin-sam tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
 
.yui-skin-sam tr.yui-dt-odd td.yui-dt-asc,
 
.yui-skin-sam tr.yui-dt-odd td.yui-dt-desc { background-color: #dbeaff }
 
.yui-skin-sam .yui-dt-list tr.yui-dt-even { background-color: #FFF }
 
.yui-skin-sam .yui-dt-list tr.yui-dt-odd { background-color: #FFF }
 
.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-asc,
 
.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
 
.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-asc,
 
.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-desc { background-color: #edf5ff }
 
.yui-skin-sam th.yui-dt-highlighted,
 
.yui-skin-sam th.yui-dt-highlighted a { background-color: #b2d2ff }
 
.yui-skin-sam tr.yui-dt-highlighted,
 
.yui-skin-sam tr.yui-dt-highlighted td.yui-dt-asc,
 
.yui-skin-sam tr.yui-dt-highlighted td.yui-dt-desc,
 
.yui-skin-sam tr.yui-dt-even td.yui-dt-highlighted,
 
.yui-skin-sam tr.yui-dt-odd td.yui-dt-highlighted {
 
    cursor: pointer;
 
    background-color: #b2d2ff;
 
}
 
.yui-skin-sam .yui-dt-list th.yui-dt-highlighted,
 
.yui-skin-sam .yui-dt-list th.yui-dt-highlighted a { background-color: #b2d2ff }
 
.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted,
 
.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-asc,
 
.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-desc,
 
.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-highlighted,
 
.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-highlighted {
 
    cursor: pointer;
 
    background-color: #b2d2ff;
 
}
 
.yui-skin-sam th.yui-dt-selected,
 
.yui-skin-sam th.yui-dt-selected a { background-color: #446cd7 }
 
.yui-skin-sam tr.yui-dt-selected td,
 
.yui-skin-sam tr.yui-dt-selected td.yui-dt-asc,
 
.yui-skin-sam tr.yui-dt-selected td.yui-dt-desc {
 
    background-color: #426fd9;
 
    color: #FFF;
 
}
 
.yui-skin-sam tr.yui-dt-even td.yui-dt-selected,
 
.yui-skin-sam tr.yui-dt-odd td.yui-dt-selected {
 
    background-color: #446cd7;
 
    color: #FFF;
 
}
 
.yui-skin-sam .yui-dt-list th.yui-dt-selected,
 
.yui-skin-sam .yui-dt-list th.yui-dt-selected a { background-color: #446cd7 }
 
.yui-skin-sam .yui-dt-list tr.yui-dt-selected td,
 
.yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-asc,
 
.yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-desc {
 
    background-color: #426fd9;
 
    color: #FFF;
 
}
 
.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-selected,
 
.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-selected {
 
    background-color: #446cd7;
 
    color: #FFF;
 
}
 
.yui-skin-sam .yui-dt-paginator {
 
    display: block;
 
    margin: 6px 0;
 
    white-space: nowrap;
 
}
 
.yui-skin-sam .yui-dt-paginator .yui-dt-first,
 
.yui-skin-sam .yui-dt-paginator .yui-dt-last,
 
.yui-skin-sam .yui-dt-paginator .yui-dt-selected { padding: 2px 6px }
 
.yui-skin-sam .yui-dt-paginator a.yui-dt-first,
 
.yui-skin-sam .yui-dt-paginator a.yui-dt-last { text-decoration: none }
 
.yui-skin-sam .yui-dt-paginator .yui-dt-previous,
 
.yui-skin-sam .yui-dt-paginator .yui-dt-next { display: none }
 
.yui-skin-sam a.yui-dt-page {
 
    border: 1px solid #cbcbcb;
 
    padding: 2px 6px;
 
    text-decoration: none;
 
    background-color: #fff;
 
}
 
.yui-skin-sam .yui-dt-selected {
 
    border: 1px solid #fff;
 
    background-color: #fff;
 
}
 

	
 
#content #left {
 
    left: 0;
 
    width: 280px;
 
    position: absolute;
 
}
 

	
 
#content #right {
 
    margin: 0 60px 10px 290px;
 
}
 

	
 
#content div.box {
 
    clear: both;
 
    background: #fff;
 
    margin: 0 0 10px;
 
    padding: 0 0 10px;
 
    -webkit-border-radius: 4px 4px 4px 4px;
 
    -khtml-border-radius: 4px 4px 4px 4px;
 
    border-radius: 4px 4px 4px 4px;
 
    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
 
}
 

	
 
#content div.box-left {
 
    width: 49%;
 
    clear: none;
 
    float: left;
 
    margin: 0 0 10px;
 
}
 

	
 
#content div.box-right {
 
    width: 49%;
 
    clear: none;
 
    float: right;
 
    margin: 0 0 10px;
 
}
 

	
 
#content div.box div.title {
 
    clear: both;
 
    overflow: hidden;
 
    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(to bottom, #003b76, #00376e);
 
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
 
    margin: 0 0 20px;
 
    padding: 0;
 
    border-radius: 4px 4px 0 0;
 
}
 

	
 
#content div.box div.title h5 {
 
    float: left;
 
    border: none;
 
    color: #fff;
 
    margin: 0;
 
    padding: 11px 0 11px 10px;
 
}
 

	
 
#content div.box div.title .link-white {
 
    color: #FFFFFF;
 
}
 

	
 
#content div.box div.title .link-white.current {
 
    color: #BFE3FF;
 
}
 

	
 
#content div.box div.title ul.links li {
 
    list-style: none;
 
    float: left;
 
    margin: 0;
 
    padding: 0;
 
}
 

	
 
#content div.box div.title ul.links li a {
 
    border-left: 1px solid #316293;
 
    color: #FFFFFF;
 
    display: block;
 
    float: left;
 
    font-size: 13px;
 
    font-weight: 700;
 
    height: 1%;
 
    margin: 0;
 
    padding: 11px 22px 12px;
 
    text-decoration: none;
 
}
 

	
 
#content div.box h1, #content div.box h2, #content div.box h3, #content div.box h4, #content div.box h5, #content div.box h6,
 
#content div.box div.h1, #content div.box div.h2, #content div.box div.h3, #content div.box div.h4, #content div.box div.h5, #content div.box div.h6 {
 
    clear: both;
 
    overflow: hidden;
 
    border-bottom: 1px solid #DDD;
 
    margin: 10px 20px;
 
    padding: 0 0 15px;
 
}
 

	
 
#content div.box p {
 
    color: #5f5f5f;
 
    font-size: 12px;
 
    line-height: 150%;
 
    margin: 0 24px 10px;
 
    padding: 0;
 
}
 

	
 
#content div.box blockquote {
 
    border-left: 4px solid #DDD;
 
    color: #5f5f5f;
 
    font-size: 11px;
 
    line-height: 150%;
 
    margin: 0 34px;
 
    padding: 0 0 0 14px;
 
}
 

	
 
#content div.box blockquote p {
 
    margin: 10px 0;
 
    padding: 0;
 
}
 

	
 
#content div.box dl {
 
    margin: 10px 0px;
 
}
 

	
 
#content div.box dt {
 
    font-size: 12px;
 
    margin: 0;
 
}
 

	
 
#content div.box dd {
 
    font-size: 12px;
 
    margin: 0;
 
    padding: 8px 0 8px 15px;
 
}
 

	
 
#content div.box li {
 
    font-size: 12px;
 
    padding: 4px 0;
 
}
 

	
 
#content div.box ul.disc, #content div.box ul.circle {
 
    margin: 10px 24px 10px 38px;
 
}
 

	
 
#content div.box ul.square {
 
    margin: 10px 24px 10px 40px;
 
}
 

	
 
#content div.box img.left {
 
    border: none;
 
    float: left;
 
    margin: 10px 10px 10px 0;
 
}
 

	
 
#content div.box img.right {
 
    border: none;
 
    float: right;
 
    margin: 10px 0 10px 10px;
 
}
 

	
 
#content div.box div.messages {
 
    clear: both;
 
    overflow: hidden;
 
    margin: 0 20px;
 
    padding: 0;
 
}
 

	
 
#content div.box div.message {
 
    clear: both;
 
    overflow: hidden;
 
    margin: 0;
 
    padding: 5px 0;
 
    white-space: pre-wrap;
 
}
 
#content div.box div.expand {
 
    width: 110%;
 
    height: 14px;
 
    font-size: 10px;
 
    text-align: center;
 
    cursor: pointer;
 
    color: #666;
 

	
 
    background: -webkit-gradient(linear,0% 50%,100% 50%,color-stop(0%,rgba(255,255,255,0)),color-stop(100%,rgba(64,96,128,0.1)));
 
    background: -webkit-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
 
    background: -moz-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
 
    background: -o-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
 
    background: -ms-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
 
    background: linear-gradient(to bottom,rgba(255,255,255,0),rgba(64,96,128,0.1));
 

	
 
    display: none;
 
    overflow: hidden;
 
}
 
#content div.box div.expand .expandtext {
 
    background-color: #ffffff;
 
    padding: 2px;
 
    border-radius: 2px;
 
}
 

	
 
#content div.box div.message a {
 
    font-weight: 400 !important;
 
}
 

	
 
#content div.box div.message div.image {
 
    float: left;
 
    margin: 9px 0 0 5px;
 
    padding: 6px;
 
}
 

	
 
#content div.box div.message div.image img {
 
    vertical-align: middle;
 
    margin: 0;
 
}
 

	
 
#content div.box div.message div.text {
 
    float: left;
 
    margin: 0;
 
    padding: 9px 6px;
 
}
 

	
 
#content div.box div.message div.dismiss a {
 
    height: 16px;
 
    width: 16px;
 
    display: block;
 
    background: url("../images/icons/cross.png") no-repeat;
 
    margin: 15px 14px 0 0;
 
    padding: 0;
 
}
 

	
 
#content div.box div.message div.text h1, #content div.box div.message div.text h2, #content div.box div.message div.text h3, #content div.box div.message div.text h4, #content div.box div.message div.text h5, #content div.box div.message div.text h6 {
 
    border: none;
 
    margin: 0;
 
    padding: 0;
 
}
 

	
 
#content div.box div.message div.text span {
 
    height: 1%;
 
    display: block;
 
    margin: 0;
 
    padding: 5px 0 0;
 
}
 

	
 
#content div.box div.message-error {
 
    height: 1%;
 
    clear: both;
 
    overflow: hidden;
 
    background: #FBE3E4;
 
    border: 1px solid #FBC2C4;
 
    color: #860006;
 
}
 

	
 
#content div.box div.message-error h6 {
 
    color: #860006;
 
}
 

	
 
#content div.box div.message-warning {
 
    height: 1%;
 
    clear: both;
 
    overflow: hidden;
 
    background: #FFF6BF;
 
    border: 1px solid #FFD324;
 
    color: #5f5200;
 
}
 

	
 
#content div.box div.message-warning h6 {
 
    color: #5f5200;
 
}
 

	
 
#content div.box div.message-notice {
 
    height: 1%;
 
    clear: both;
 
    overflow: hidden;
 
    background: #8FBDE0;
 
    border: 1px solid #6BACDE;
 
    color: #003863;
 
}
 

	
 
#content div.box div.message-notice h6 {
 
    color: #003863;
 
}
 

	
 
#content div.box div.message-success {
 
    height: 1%;
 
    clear: both;
 
    overflow: hidden;
 
    background: #E6EFC2;
 
    border: 1px solid #C6D880;
 
    color: #4e6100;
 
}
 

	
 
#content div.box div.message-success h6 {
 
    color: #4e6100;
 
}
 

	
 
#content div.box div.form div.fields div.field {
 
    height: 1%;
 
    min-height: 12px;
 
    border-bottom: 1px solid #DDD;
 
    clear: both;
 
    margin: 0;
 
    padding: 10px 0;
 
}
 

	
 
#content div.box div.form div.fields div.field-first {
 
    padding: 0 0 10px;
 
}
 

	
 
#content div.box div.form div.fields div.field-noborder {
 
    border-bottom: 0 !important;
 
}
 

	
 
#content div.box div.form div.fields div.field span.error-message {
 
    height: 1%;
 
    display: inline-block;
 
    color: red;
 
    margin: 8px 0 0 4px;
 
    padding: 0;
 
}
 

	
 
#content div.box div.form div.fields div.field span.success {
 
    height: 1%;
 
    display: block;
 
    color: #316309;
 
    margin: 8px 0 0;
 
    padding: 0;
 
}
 

	
 
#content div.box div.form div.fields div.field div.label {
 
    left: 70px;
 
    width: 155px;
 
    position: absolute;
 
    margin: 0;
 
    padding: 5px 0 0 0px;
 
}
 

	
 
#content div.box div.form div.fields div.field div.label-summary {
 
    left: 30px;
 
    width: 155px;
 
    position: absolute;
 
    margin: 0;
 
    padding: 0px 0 0 0px;
 
}
 

	
 
#content div.box-left div.form div.fields div.field div.label,
 
#content div.box-right div.form div.fields div.field div.label,
 
#content div.box-left div.form div.fields div.field div.label,
 
#content div.box-left div.form div.fields div.field div.label-summary,
 
#content div.box-right div.form div.fields div.field div.label-summary,
 
#content div.box-left div.form div.fields div.field div.label-summary {
 
    clear: both;
 
    overflow: hidden;
 
    left: 0;
 
    width: auto;
 
    position: relative;
 
    margin: 0;
 
    padding: 0 0 8px;
 
}
 

	
 
#content div.box div.form div.fields div.field div.label-select {
 
    padding: 5px 0 0 5px;
 
}
 

	
 
#content div.box-left div.form div.fields div.field div.label-select,
 
#content div.box-right div.form div.fields div.field div.label-select {
 
    padding: 0 0 8px;
 
}
 

	
 
#content div.box-left div.form div.fields div.field div.label-textarea,
 
#content div.box-right div.form div.fields div.field div.label-textarea {
 
    padding: 0 0 8px !important;
 
}
 

	
 
#content div.box div.form div.fields div.field div.label label, div.label label {
 
    color: #393939;
 
    font-weight: 700;
 
}
 
#content div.box div.form div.fields div.field div.label label, div.label-summary label {
 
    color: #393939;
 
    font-weight: 700;
 
}
 
#content div.box div.form div.fields div.field div.input {
 
    margin: 0 0 0 200px;
 
}
 

	
 
#content div.box div.form div.fields div.field div.input.summary {
 
    margin: 0 0 0 110px;
 
}
 
#content div.box div.form div.fields div.field div.input.summary-short {
 
    margin: 0 0 0 110px;
 
}
 
#content div.box div.form div.fields div.field div.file {
 
    margin: 0 0 0 200px;
 
}
 

	
 
#content div.box-left div.form div.fields div.field div.input, #content div.box-right div.form div.fields div.field div.input {
 
    margin: 0 0 0 0px;
 
}
 

	
 
#content div.box div.form div.fields div.field div.input input,
 
.reviewer_ac input {
 
    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;
 
}
 

	
 
#content div.box div.form div.fields div.field div.input input#clone_url,
 
#content div.box div.form div.fields div.field div.input input#clone_url_id
 
{
 
    font-size: 16px;
 
    padding: 2px;
 
}
 

	
 
#content div.box div.form div.fields div.field div.file input {
 
    background: none repeat scroll 0 0 #FFFFFF;
 
    border-color: #B3B3B3 #EAEAEA #EAEAEA #B3B3B3;
 
    border-style: solid;
 
    border-width: 1px;
 
    color: #000000;
 
    font-size: 11px;
 
    margin: 0;
 
    padding: 7px 7px 6px;
 
}
 

	
 
input.disabled {
 
    background-color: #F5F5F5 !important;
 
}
 
#content div.box div.form div.fields div.field div.input input.small {
 
    width: 30%;
 
}
 

	
 
#content div.box div.form div.fields div.field div.input input.medium {
 
    width: 55%;
 
}
 

	
 
#content div.box div.form div.fields div.field div.input input.large {
 
    width: 85%;
 
}
 

	
 
#content div.box div.form div.fields div.field div.input input.date {
 
    width: 177px;
 
}
 

	
 
#content div.box div.form div.fields div.field div.input input.button {
 
    background: #D4D0C8;
 
    border-top: 1px solid #FFF;
 
    border-left: 1px solid #FFF;
 
    border-right: 1px solid #404040;
 
    border-bottom: 1px solid #404040;
 
    color: #000;
 
    margin: 0;
 
    padding: 4px 8px;
 
}
 

	
 
#content div.box div.form div.fields div.field div.textarea {
 
    border-top: 1px solid #b3b3b3;
 
    border-left: 1px solid #b3b3b3;
 
    border-right: 1px solid #eaeaea;
 
    border-bottom: 1px solid #eaeaea;
 
    margin: 0 0 0 200px;
 
    padding: 10px;
 
}
 

	
 
#content div.box div.form div.fields div.field div.textarea-editor {
 
    border: 1px solid #ddd;
 
    padding: 0;
 
}
 

	
 
#content div.box div.form div.fields div.field div.textarea textarea {
 
    width: 100%;
 
    height: 220px;
 
    overflow: hidden;
 
    background: #FFF;
 
    color: #000;
 
    font-size: 11px;
 
    outline: none;
 
    border-width: 0;
 
    margin: 0;
 
    padding: 0;
 
}
 

	
 
#content div.box-left div.form div.fields div.field div.textarea textarea, #content div.box-right div.form div.fields div.field div.textarea textarea {
 
    width: 100%;
 
    height: 100px;
 
}
 

	
 
#content div.box div.form div.fields div.field div.textarea table {
 
    width: 100%;
 
    border: none;
 
    margin: 0;
 
    padding: 0;
 
}
 

	
 
#content div.box div.form div.fields div.field div.textarea table td {
 
    background: #DDD;
 
    border: none;
 
    padding: 0;
 
}
 

	
 
#content div.box div.form div.fields div.field div.textarea table td table {
 
    width: auto;
 
    border: none;
 
    margin: 0;
 
    padding: 0;
 
}
 

	
 
#content div.box div.form div.fields div.field div.textarea table td table td {
 
    font-size: 11px;
 
    padding: 5px 5px 5px 0;
 
}
 

	
 
#content div.box div.form div.fields div.field input[type=text]:focus,
 
#content div.box div.form div.fields div.field input[type=password]:focus,
 
#content div.box div.form div.fields div.field input[type=file]:focus,
 
#content div.box div.form div.fields div.field textarea:focus,
 
#content div.box div.form div.fields div.field select:focus,
 
.reviewer_ac input:focus {
 
    background: #f6f6f6;
 
    border-color: #666;
 
}
 

	
 
.reviewer_ac {
 
    padding: 10px
 
}
 

	
 
div.form div.fields div.field div.button {
 
    margin: 0;
 
    padding: 0 0 0 8px;
 
}
 
#content div.box table.noborder {
 
    border: 1px solid transparent;
 
}
 

	
 
#content div.box table {
 
    width: 100%;
 
    border-collapse: separate;
 
    margin: 0;
 
    padding: 0;
 
    border: 1px solid #eee;
 
    -webkit-border-radius: 4px;
 
    border-radius: 4px;
 
}
 

	
 
#content div.box table th {
 
    background: #eee;
 
    border-bottom: 1px solid #ddd;
 
    padding: 5px 0px 5px 5px;
 
    text-align: left;
 
}
 

	
 
#content div.box table th.left {
 
    text-align: left;
 
}
 

	
 
#content div.box table th.right {
 
    text-align: right;
 
}
 

	
 
#content div.box table th.center {
 
    text-align: center;
 
}
 

	
 
#content div.box table th.selected {
 
    vertical-align: middle;
 
    padding: 0;
 
}
 

	
 
#content div.box table td {
 
    background: #fff;
 
    border-bottom: 1px solid #cdcdcd;
 
    vertical-align: middle;
 
    padding: 5px;
 
}
 

	
 
#content div.box table tr.selected td {
 
    background: #FFC;
 
}
 

	
 
#content div.box table td.selected {
 
    width: 3%;
 
    text-align: center;
 
    vertical-align: middle;
 
    padding: 0;
 
}
 

	
 
#content div.box table td.action {
 
    width: 45%;
 
    text-align: left;
 
}
 

	
 
#content div.box table td.date {
 
    width: 33%;
 
    text-align: center;
 
}
 

	
 
#content div.box div.action {
 
    float: right;
 
    background: #FFF;
 
    text-align: right;
 
    margin: 10px 0 0;
 
    padding: 0;
 
}
 

	
 
#content div.box div.action select {
 
    font-size: 11px;
 
    margin: 0;
 
}
 

	
 
#content div.box div.action .ui-selectmenu {
 
    margin: 0;
 
    padding: 0;
 
}
 

	
 
#content div.box div.pagination {
 
    height: 1%;
 
    clear: both;
 
    overflow: hidden;
 
    margin: 10px 0 0;
 
    padding: 0;
 
}
 

	
 
#content div.box div.pagination ul.pager {
 
    float: right;
 
    text-align: right;
 
    margin: 0;
 
    padding: 0;
 
}
 

	
 
#content div.box div.pagination ul.pager li {
 
    height: 1%;
 
    float: left;
 
    list-style: none;
 
    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 0 0 4px;
 
    padding: 0;
 
}
 

	
 
#content div.box div.pagination ul.pager li.separator {
 
    padding: 6px;
 
}
 

	
 
#content div.box div.pagination ul.pager li.current {
 
    background: #b4b4b4 url("../images/pager_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;
 
    padding: 6px;
 
}
 

	
 
#content div.box div.pagination ul.pager li a {
 
    height: 1%;
 
    display: block;
 
    float: left;
 
    color: #515151;
 
    text-decoration: none;
 
    margin: 0;
 
    padding: 6px;
 
}
 

	
 
#content div.box div.pagination ul.pager li a:hover,
 
#content div.box div.pagination ul.pager li a:active {
 
    background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
 
    border-top: 1px solid #ccc;
 
    border-left: 1px solid #bebebe;
 
    border-right: 1px solid #b1b1b1;
 
    border-bottom: 1px solid #afafaf;
 
    margin: -1px;
 
}
 

	
 
#content div.box div.pagination-right {
 
    float: right;
 
}
 

	
 
#content div.box div.pagination-wh {
 
    height: 1%;
 
    overflow: hidden;
 
    text-align: right;
 
    margin: 10px 0 0;
 
    padding: 0;
 
}
 

	
 
#content div.box div.pagination-wh > :first-child {
 
    border-radius: 4px 0px 0px 4px;
 
}
 

	
 
#content div.box div.pagination-wh > :last-child {
 
    border-radius: 0px 4px 4px 0px;
 
    border-right: 1px solid #cfcfcf;
 
}
 

	
 
#content div.box div.pagination-wh a,
 
#content div.box div.pagination-wh span.pager_dotdot,
 
#content div.box div.pagination-wh span.yui-pg-previous,
 
#content div.box div.pagination-wh span.yui-pg-last,
 
#content div.box div.pagination-wh span.yui-pg-next,
 
#content div.box div.pagination-wh span.yui-pg-first {
 
    height: 1%;
 
    float: left;
 
    background: #ebebeb url("../images/pager.png") repeat-x;
 
    border-top: 1px solid #dedede;
 
    border-left: 1px solid #cfcfcf;
 
    border-bottom: 1px solid #c4c4c4;
 
    color: #4A4A4A;
 
    font-weight: 700;
 
    padding: 6px;
 
}
 

	
 
#content div.box div.pagination-wh span.pager_curpage {
 
    height: 1%;
 
    float: left;
 
    background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
 
    border-top: 1px solid #ccc;
 
    border-left: 1px solid #bebebe;
 
    border-bottom: 1px solid #afafaf;
 
    color: #515151;
 
    font-weight: 700;
 
    padding: 6px;
 
}
 

	
 
#content div.box div.pagination-wh a:hover, #content div.box div.pagination-wh a:active {
 
    background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
 
    border-top: 1px solid #ccc;
 
    border-left: 1px solid #bebebe;
 
    border-bottom: 1px solid #afafaf;
 
    text-decoration: none;
 
}
 

	
 
#content div.box div.traffic div.legend {
 
    clear: both;
 
    overflow: hidden;
 
    border-bottom: 1px solid #ddd;
 
    margin: 0 0 10px;
 
    padding: 0 0 10px;
 
}
 

	
 
#content div.box div.traffic div.legend h6 {
 
    float: left;
 
    border: none;
 
    margin: 0;
 
    padding: 0;
 
}
 

	
 
#content div.box div.traffic div.legend li {
 
    list-style: none;
 
    float: left;
 
    font-size: 11px;
 
    margin: 0;
 
    padding: 0 8px 0 4px;
 
}
 

	
 
#content div.box div.traffic div.legend li.visits {
 
    border-left: 12px solid #edc240;
 
}
 

	
 
#content div.box div.traffic div.legend li.pageviews {
 
    border-left: 12px solid #afd8f8;
 
}
 

	
 
#content div.box div.traffic table {
 
    width: auto;
 
}
 

	
 
#content div.box div.traffic table td {
 
    background: transparent;
 
    border: none;
 
    padding: 2px 3px 3px;
 
}
 

	
 
#content div.box div.traffic table td.legendLabel {
 
    padding: 0 3px 2px;
 
}
 

	
 
#content div.box #summary {
 
    margin-right: 200px;
 
}
 

	
 
#summary-menu-stats {
 
    float: left;
 
    width: 180px;
 
    position: absolute;
 
    top: 0;
 
    right: 0;
 
}
 

	
 
#summary-menu-stats ul {
 
    margin: 0 10px;
 
    display: block;
 
    background-color: #f9f9f9;
 
    border: 1px solid #d1d1d1;
 
    border-radius: 4px;
 
}
 

	
 
#content #summary-menu-stats li {
 
    border-top: 1px solid #d1d1d1;
 
    padding: 0;
 
}
 

	
 
#content #summary-menu-stats li:hover {
 
    background: #f0f0f0;
 
}
 

	
 
#content #summary-menu-stats li:first-child {
 
    border-top: none;
 
}
 

	
 
#summary-menu-stats a.followers { background-image: url('../images/icons/heart.png')}
 
#summary-menu-stats a.forks { background-image: url('../images/icons/arrow_divide.png')}
 
#summary-menu-stats a.settings { background-image: url('../images/icons/cog_edit.png')}
 
#summary-menu-stats a.feed { background-image: url('../images/icons/rss_16.png')}
 
#summary-menu-stats a.repo-size { background-image: url('../images/icons/server.png')}
 

	
 
#summary-menu-stats a {
 
    display: block;
 
    padding: 12px 30px;
 
    background-repeat: no-repeat;
 
    background-position: 10px 50%;
 
    padding-right: 10px;
 
}
 

	
 
#repo_size_2.loaded {
 
    margin-left: 30px;
 
    display: block;
 
    padding-right: 10px;
 
    padding-bottom: 7px;
 
}
 

	
 
#summary-menu-stats a:hover {
 
    text-decoration: none;
 
}
 

	
 
#summary-menu-stats a span {
 
    background-color: #DEDEDE;
 
    color: 888 !important;
 
    border-radius: 4px;
 
    padding: 2px 4px;
 
    font-size: 10px;
 
}
 

	
 
#summary .metatag {
 
    display: inline-block;
 
    padding: 3px 5px;
 
    margin-bottom: 3px;
 
    margin-right: 1px;
 
    border-radius: 5px;
 
}
 

	
 
#content div.box #summary p {
 
    margin-bottom: -5px;
 
    width: 600px;
 
    white-space: pre-wrap;
 
}
 

	
 
#content div.box #summary p:last-child {
 
    margin-bottom: 9px;
 
}
 

	
 
#content div.box #summary p:first-of-type {
 
    margin-top: 9px;
 
}
 

	
 
.metatag {
 
    display: inline-block;
 
    margin-right: 1px;
 
    -webkit-border-radius: 4px 4px 4px 4px;
 
    -khtml-border-radius: 4px 4px 4px 4px;
 
    border-radius: 4px 4px 4px 4px;
 

	
 
    border: solid 1px #9CF;
 
    padding: 2px 3px 2px 3px !important;
 
    background-color: #DEF;
 
}
 

	
 
.metatag[tag="dead"] {
 
    background-color: #E44;
 
}
 

	
 
.metatag[tag="stale"] {
 
    background-color: #EA4;
 
}
 

	
 
.metatag[tag="featured"] {
 
    background-color: #AEA;
 
}
 

	
 
.metatag[tag="requires"] {
 
    background-color: #9CF;
 
}
 

	
 
.metatag[tag="recommends"] {
 
    background-color: #BDF;
 
}
 

	
 
.metatag[tag="lang"] {
 
    background-color: #FAF474;
 
}
 

	
 
.metatag[tag="license"] {
 
    border: solid 1px #9CF;
 
    background-color: #DEF;
 
    target-new: tab !important;
 
}
 
.metatag[tag="see"] {
 
    border: solid 1px #CBD;
 
    background-color: #EDF;
 
}
 

	
 
a.metatag[tag="license"]:hover {
 
    background-color: #003367;
 
    color: #FFF;
 
    text-decoration: none;
 
}
 

	
 
#summary .desc {
 
    white-space: pre;
 
    width: 100%;
 
}
 

	
 
#summary .repo_name {
 
    font-size: 1.6em;
 
    font-weight: bold;
 
    vertical-align: baseline;
 
    clear: right
 
}
 

	
 
#footer {
 
    clear: both;
 
    overflow: hidden;
 
    text-align: right;
 
    margin: 0;
 
    padding: 0 10px 4px;
 
    margin: -10px 0 0;
 
}
 

	
 
#footer div#footer-inner {
 
    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(to bottom, #003b76, #00376e);
 
    filter: progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
 
    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
 
    -webkit-border-radius: 4px 4px 4px 4px;
 
    -khtml-border-radius: 4px 4px 4px 4px;
 
    border-radius: 4px 4px 4px 4px;
 
}
 

	
 
#footer div#footer-inner p {
 
    padding: 15px 25px 15px 0;
 
    color: #FFF;
 
    font-weight: 700;
 
}
 

	
 
#footer div#footer-inner .footer-link {
 
    float: left;
 
    padding-left: 10px;
 
}
 

	
 
#footer div#footer-inner .footer-link a, #footer div#footer-inner .footer-link-right a {
 
    color: #FFF;
 
}
 

	
 
#login 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(to bottom, #003b76, #00376e);
 
    filter: progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
 
    margin: 0 auto;
 
    padding: 0;
 
}
 

	
 
#login div.inner {
 
    background: #FFF url("../images/login.png") no-repeat top left;
 
    border-top: none;
 
    border-bottom: none;
 
    margin: 0 auto;
 
    padding: 20px;
 
}
 

	
 
#login div.form div.fields div.field div.label {
 
    width: 173px;
 
    float: left;
 
    text-align: right;
 
    margin: 2px 10px 0 0;
 
    padding: 5px 0 0 5px;
 
}
 

	
 
#login div.form div.fields div.field div.input input {
 
    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;
 
}
 

	
 
#login div.form div.fields div.buttons {
 
    clear: both;
 
    overflow: hidden;
 
    border-top: 1px solid #DDD;
 
    text-align: right;
 
    margin: 0;
 
    padding: 10px 0 0;
 
}
 

	
 
#login div.form div.links {
 
    clear: both;
 
    overflow: hidden;
 
    margin: 10px 0 0;
 
    padding: 0 0 2px;
 
}
 

	
 
.user-menu {
 
    margin: 0px !important;
 
    float: left;
 
}
 

	
 
.user-menu .container {
 
    padding: 0px 4px 0px 4px;
 
    margin: 0px 0px 0px 0px;
 
}
 

	
 
.user-menu .gravatar {
 
    margin: 0px 0px 0px 0px;
 
    cursor: pointer;
 
}
 
.user-menu .gravatar.enabled {
 
    background-color: #FDF784 !important;
 
}
 
.user-menu .gravatar:hover {
 
    background-color: #FDF784 !important;
 
}
 
#quick_login {
 
    min-height: 110px;
 
    padding: 4px;
 
    position: absolute;
 
    right: 0;
 
    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(to bottom, #003b76, #00376e);
 
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
 

	
 
    z-index: 999;
 
    -webkit-border-radius: 0px 0px 4px 4px;
 
    -khtml-border-radius: 0px 0px 4px 4px;
 
    border-radius: 0px 0px 4px 4px;
 
    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
 

	
 
    overflow: hidden;
 
}
 
#quick_login h4 {
 
    color: #fff;
 
    padding: 5px 0px 5px 14px;
 
}
 

	
 
#quick_login .password_forgoten {
 
    padding-right: 0px;
 
    padding-top: 0px;
 
    text-align: left;
 
}
 

	
 
#quick_login .password_forgoten a {
 
    font-size: 10px;
 
    color: #fff;
 
    padding: 0px !important;
 
    line-height: 20px !important;
 
}
 

	
 
#quick_login .register {
 
    padding-right: 10px;
 
    padding-top: 5px;
 
    text-align: left;
 
}
 

	
 
#quick_login .register a {
 
    font-size: 10px;
 
    color: #fff;
 
    padding: 0px !important;
 
    line-height: 20px !important;
 
}
 

	
 
#quick_login .submit {
 
    margin: -20px 0 0 0px;
 
    position: absolute;
 
    right: 15px;
 
}
 

	
 
#quick_login .links_left {
 
    float: left;
 
    margin-right: 130px;
 
    width: 170px;
 
}
 
#quick_login .links_right {
 

	
 
    position: absolute;
 
    right: 0;
 
}
 
#quick_login .full_name {
 
    color: #FFFFFF;
 
    font-weight: bold;
 
    padding: 3px 3px 3px 6px;
 
}
 
#quick_login .big_gravatar {
 
    padding: 4px 0px 0px 6px;
 
}
 
#quick_login .notifications {
 
    padding: 2px 0px 0px 6px;
 
    color: #FFFFFF;
 
    font-weight: bold;
 
    line-height: 10px !important;
 
}
 
#quick_login .notifications a,
 
#quick_login .unread a {
 
    color: #FFFFFF;
 
    display: block;
 
    padding: 0px !important;
 
}
 
#quick_login .notifications a:hover,
 
#quick_login .unread a:hover {
 
    background-color: inherit !important;
 
}
 
#quick_login .email, #quick_login .unread {
 
    color: #FFFFFF;
 
    padding: 3px 3px 3px 6px;
 
}
 
#quick_login .links .logout {
 
}
 

	
 
#quick_login div.form div.fields {
 
    padding-top: 2px;
 
    padding-left: 10px;
 
}
 

	
 
#quick_login div.form div.fields div.field {
 
    padding: 5px;
 
}
 

	
 
#quick_login div.form div.fields div.field div.label label {
 
    color: #fff;
 
    padding-bottom: 3px;
 
}
 

	
 
#quick_login div.form div.fields div.field div.input input {
 
    width: 236px;
 
    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: 5px 7px 4px;
 
}
 

	
 
#quick_login div.form div.fields div.buttons {
 
    clear: both;
 
    overflow: hidden;
 
    text-align: right;
 
    margin: 0;
 
    padding: 5px 14px 0px 5px;
 
}
 

	
 
#quick_login div.form div.links {
 
    clear: both;
 
    overflow: hidden;
 
    margin: 10px 0 0;
 
    padding: 0 0 2px;
 
}
 

	
 
#quick_login ol.links {
 
    display: block;
 
    font-weight: bold;
 
    list-style: none outside none;
 
    text-align: right;
 
}
 
#quick_login ol.links li {
 
    line-height: 27px;
 
    margin: 0;
 
    padding: 0;
 
    color: #fff;
 
    display: block;
 
    float: none !important;
 
}
 

	
 
#quick_login ol.links li a {
 
    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(to bottom, #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_user.deleted {
 
    color: #747474;
 
    font-size: 14px;
 
    font-weight: normal;
 
    height: 30px;
 
    font-style: italic;
 
}
 

	
 

	
 
#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;
 
}
 

	
 
.file_history {
 
    padding-top: 10px;
 
    font-size: 16px;
 
}
 
.file_author {
 
    float: left;
 
}
 

	
 
.file_author .item {
 
    float: left;
 
    padding: 5px;
 
    color: #888;
 
}
 

	
 
.tablerow0 {
 
    background-color: #F8F8F8;
 
}
 

	
 
.tablerow1 {
 
    background-color: #FFFFFF;
 
}
 

	
 
.changeset_id {
 
    color: #666666;
 
    margin-right: -3px;
 
}
 

	
 
.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 .message {
 
    white-space: pre-wrap;
 
}
 
#changeset_content .container .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;
 
}
 
/*new binary*/
 
.cs_files .changes .bin1 {
 
/*new binary
 
NEW_FILENODE = 1
 
DEL_FILENODE = 2
 
MOD_FILENODE = 3
 
RENAMED_FILENODE = 4
 
CHMOD_FILENODE = 5
 
BIN_FILENODE = 6
 
*/
 
.cs_files .changes .bin {
 
    background-color: #BBFFBB;
 
    float: left;
 
    text-align: center;
 
    font-size: 9px;
 
    padding: 2px 0px 2px 0px;
 
}
 
.cs_files .changes .bin.bin1 {
 
    background-color: #BBFFBB;
 
}
 

	
 
/*deleted binary*/
 
.cs_files .changes .bin2 {
 
.cs_files .changes .bin.bin2 {
 
    background-color: #FF8888;
 
    float: left;
 
    text-align: center;
 
    font-size: 9px;
 
    padding: 2px 0px 2px 0px;
 
}
 

	
 
/*mod binary*/
 
.cs_files .changes .bin3 {
 
.cs_files .changes .bin.bin3 {
 
    background-color: #DDDDDD;
 
    float: left;
 
    text-align: center;
 
    font-size: 9px;
 
    padding: 2px 0px 2px 0px;
 
}
 

	
 
/*rename file*/
 
.cs_files .changes .bin4 {
 
.cs_files .changes .bin.bin4 {
 
    background-color: #6D99FF;
 
}
 

	
 
/*rename file*/
 
.cs_files .changes .bin.bin4 {
 
    background-color: #6D99FF;
 
    float: left;
 
    text-align: center;
 
    font-size: 9px;
 
    padding: 2px 0px 2px 0px;
 
}
 

	
 
}
 

	
 
/*chmod file*/
 
.cs_files .changes .bin.bin5 {
 
    background-color: #6D99FF;
 
}
 

	
 
.cs_files .cs_added, .cs_files .cs_A {
 
    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, .cs_files .cs_M {
 
    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, .cs_files .cs_D {
 
    background: url("../images/icons/page_white_delete.png") no-repeat
 
        scroll 3px;
 
    height: 16px;
 
    padding-left: 20px;
 
    margin-top: 7px;
 
    text-align: left;
 
}
 

	
 
.table {
 
    position: relative;
 
}
 

	
 
#graph {
 
    position: relative;
 
    overflow: hidden;
 
}
 

	
 
#graph_nodes {
 
    position: absolute;
 
}
 

	
 
#graph_content,
 
#graph .info_box,
 
#graph .container_header {
 
    margin-left: 100px;
 
}
 

	
 
#graph_content {
 
    position: relative;
 
}
 

	
 
#graph .container_header {
 
    padding: 10px;
 
    height: 25px;
 
}
 

	
 
#graph_content #rev_range_container {
 
    float: left;
 
    margin: 0px 0px 0px 3px;
 
}
 

	
 
#graph_content #rev_range_clear {
 
    float: left;
 
    margin: 0px 0px 0px 3px;
 
}
 

	
 
#graph_content #changesets {
 
    table-layout: fixed;
 
    border-collapse: collapse;
 
    border-left: none;
 
    border-right: none;
 
    border-color: #cdcdcd;
 
}
 

	
 
#graph_content #changesets td {
 
    overflow: hidden;
 
    text-overflow: ellipsis;
 
    white-space: nowrap;
 
    height: 31px;
 
    border-color: #cdcdcd;
 
    text-align: left;
 
}
 

	
 
#graph_content .container .checkbox {
 
    width: 12px;
 
    font-size: 0.85em;
 
}
 

	
 
#graph_content .container .status {
 
    width: 14px;
 
    font-size: 0.85em;
 
}
 

	
 
#graph_content .container .author {
 
   width: 105px;
 
}
 

	
 
#graph_content .container .hash {
 
    width: 100px;
 
    font-size: 0.85em;
 
}
 

	
 
#graph_content #changesets .container .date {
 
    width: 76px;
 
    color: #666;
 
    font-size: 10px;
 
}
 

	
 
#graph_content #changesets .container .right {
 
    width: 120px;
 
    padding-right: 0px;
 
    overflow: visible;
 
    position: relative;
 
}
 

	
 
#graph_content .container .mid {
 
    padding: 0;
 
}
 

	
 
#graph_content .log-container {
 
    position: relative;
 
}
 

	
 
#graph_content .container .changeset_range {
 
    float: left;
 
    margin: 6px 3px;
 
}
 

	
 
#graph_content .container .author img {
 
    vertical-align: middle;
 
}
 

	
 
#graph_content .container .author .user {
 
    color: #444444;
 
}
 

	
 
#graph_content .container .mid .message {
 
    white-space: pre-wrap;
 
    padding: 0;
 
    overflow: hidden;
 
    height: 1.1em;
 
}
 

	
 
#graph_content .container .extra-container {
 
    display: block;
 
    position: absolute;
 
    top: -15px;
 
    right: 0;
 
    padding-left: 5px;
 
    background: #FFFFFF;
 
    height: 41px;
 
}
 

	
 
#graph_content .comments-container,
 
#shortlog_data .comments-container,
 
#graph_content .logtags {
 
    display: block;
 
    float: left;
 
    overflow: hidden;
 
    padding: 0;
 
    margin: 0;
 
}
 

	
 
#graph_content .comments-container {
 
    margin: 0.8em 0;
 
    margin-right: 0.5em;
 
}
 

	
 
#graph_content  .tagcontainer {
 
    width: 80px;
 
    position: relative;
 
    float: right;
 
    height: 100%;
 
    top: 7px;
 
    margin-left: 0.5em;
 
}
 

	
 
#graph_content .logtags {
 
    min-width: 80px;
 
    height: 1.1em;
 
    position: absolute;
 
    left: 0px;
 
    width: auto;
 
    top: 0px;
 
}
 

	
 
#graph_content .logtags.tags {
 
    top: 14px;
 
}
 

	
 
#graph_content .logtags:hover {
 
    overflow: visible;
 
    position: absolute;
 
    width: auto;
 
    right: 0;
 
    left: initial;
 
}
 

	
 
#graph_content .logtags .booktag,
 
#graph_content .logtags .tagtag {
 
    float: left;
 
    line-height: 1em;
 
    margin-bottom: 1px;
 
    margin-right: 1px;
 
    padding: 1px 3px;
 
    font-size: 10px;
 
}
 

	
 
#graph_content .container .mid .message a:hover {
 
    text-decoration: none;
 
}
 

	
 
.revision-link {
 
    color: #3F6F9F;
 
    font-weight: bold !important;
 
}
 

	
 
.issue-tracker-link {
 
    color: #3F6F9F;
 
    font-weight: bold !important;
 
}
 

	
 
.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;
 
}
 
.changeset-status-container .changeset-status-lbl {
 
    color: rgb(136, 136, 136);
 
    float: left;
 
    padding: 3px 4px 0px 0px
 
}
 
.code-header .changeset-status-container .changeset-status-lbl {
 
    float: left;
 
    padding: 0px 4px 0px 0px;
 
}
 
.changeset-status-container .changeset-status-ico {
 
    float: left;
 
}
 
.code-header .changeset-status-container .changeset-status-ico, .container .changeset-status-ico {
 
    float: left;
 
}
 

	
 
#graph_content .comments-cnt {
 
    color: rgb(136, 136, 136);
 
    padding: 5px 0;
 
}
 

	
 
#shortlog_data .comments-cnt {
 
    color: rgb(136, 136, 136);
 
    padding: 3px 0;
 
}
 

	
 
#graph_content .comments-cnt a,
 
#shortlog_data .comments-cnt a {
 
    background-image: url('../images/icons/comments.png');
 
    background-repeat: no-repeat;
 
    background-position: 100% 50%;
 
    padding: 5px 0;
 
    padding-right: 20px;
 
}
 

	
 
.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;
 
    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;
 
    color: #ffffff;
 
    text-transform: uppercase;
 
    white-space: nowrap;
 
    -webkit-border-radius: 3px;
 
    border-radius: 3px;
 
    margin-right: 2px;
 
}
 

	
 
.right .parent {
 
    color: #666666;
 
    clear: both;
 
}
 
.right .logtags {
 
    line-height: 2.2em;
 
}
 
.branchtag, .logtags .tagtag, .logtags .booktag {
 
    margin: 0px 2px;
 
}
 

	
 
.branchtag,
 
.tagtag,
 
.booktag,
 
.spantag {
 
    padding: 1px 3px 1px 3px;
 
    font-size: 10px;
 
    color: #336699;
 
    white-space: nowrap;
 
    -webkit-border-radius: 4px;
 
    border-radius: 4px;
 
    border: 1px solid #d9e8f8;
 
    line-height: 1.5em;
 
}
 

	
 
#graph_content .branchtag,
 
#graph_content .tagtag,
 
#graph_content .booktag {
 
    margin: 1.1em 0;
 
    margin-right: 0.5em;
 
}
 

	
 
.branchtag,
 
.tagtag,
 
.booktag {
 
    float: left;
 
}
 

	
 
.right .logtags .branchtag,
 
.right .logtags .tagtag,
 
.right .logtags .booktag,
 
.right .merge {
 
    float: right;
 
    line-height: 1em;
 
    margin: 1px 1px !important;
 
    display: block;
 
}
 

	
 
.booktag {
 
    border-color: #46A546;
 
    color: #46A546;
 
}
 

	
 
.tagtag {
 
    border-color: #62cffc;
 
    color: #62cffc;
 
}
 

	
 
.logtags .branchtag a:hover,
 
.logtags .branchtag a,
 
.branchtag a,
 
.branchtag a:hover {
 
    text-decoration: none;
 
    color: inherit;
 
}
 
.logtags .tagtag {
 
    padding: 1px 3px 1px 3px;
 
    background-color: #62cffc;
 
    font-size: 10px;
 
    color: #ffffff;
 
    white-space: nowrap;
 
    -webkit-border-radius: 3px;
 
    border-radius: 3px;
 
}
 

	
 
.tagtag a,
 
.tagtag a:hover,
 
.logtags .tagtag a,
 
.logtags .tagtag a:hover {
 
    text-decoration: none;
 
    color: inherit;
 
}
 
.logbooks .booktag, .logbooks .booktag, .logtags .booktag, .logtags .booktag {
 
    padding: 1px 3px 1px 3px;
 
    background-color: #46A546;
 
    font-size: 10px;
 
    color: #ffffff;
 
    white-space: nowrap;
 
    -webkit-border-radius: 3px;
 
    border-radius: 3px;
 
}
 
.logbooks .booktag, .logbooks .booktag a, .right .logtags .booktag, .logtags .booktag a {
 
    color: #ffffff;
 
}
 

	
 
.logbooks .booktag, .logbooks .booktag a:hover,
 
.logtags .booktag, .logtags .booktag a:hover,
 
.booktag a,
 
.booktag a:hover {
 
    text-decoration: none;
 
    color: inherit;
 
}
 
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;
 
    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;
 
}
 

	
 
#tip-box {
 
    position: absolute;
 

	
 
    background-color: #FFF;
 
    border: 2px solid #003367;
 
    font: 100% sans-serif;
 
    width: auto;
 
    opacity: 1;
 
    padding: 8px;
 

	
 
    white-space: pre-wrap;
 
    -webkit-border-radius: 8px 8px 8px 8px;
 
    -khtml-border-radius: 8px 8px 8px 8px;
 
    border-radius: 8px 8px 8px 8px;
 
    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
 
    -webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
 
}
 

	
 
.hl-tip-box {
 
    visibility: hidden;
 
    position: absolute;
 
    color: #666;
 
    background-color: #FFF;
 
    border: 2px solid #003367;
 
    font: 100% sans-serif;
 
    width: auto;
 
    opacity: 1;
 
    padding: 8px;
 
    white-space: pre-wrap;
 
    -webkit-border-radius: 8px 8px 8px 8px;
 
    -khtml-border-radius: 8px 8px 8px 8px;
 
    border-radius: 8px 8px 8px 8px;
 
    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
 
}
 

	
 

	
 
.mentions-container {
 
    width: 90% !important;
 
}
 
.mentions-container .yui-ac-content {
 
    width: 100% !important;
 
}
 

	
 
.ac {
 
    vertical-align: top;
 
}
 

	
 
.ac .yui-ac {
 
    position: inherit;
 
    font-size: 100%;
 
}
 

	
 
.ac .perm_ac {
 
    width: 20em;
 
}
 

	
 
.ac .yui-ac-input {
 
    width: 100%;
 
}
 

	
 
.ac .yui-ac-container {
 
    position: absolute;
 
    top: 1.6em;
 
    width: auto;
 
}
 

	
 
.ac .yui-ac-content {
 
    position: absolute;
 
    border: 1px solid gray;
 
    background: #fff;
 
    z-index: 9050;
 
}
 

	
 
.ac .yui-ac-shadow {
 
    position: absolute;
 
    width: 100%;
 
    background: #000;
 
    opacity: .10;
 
    filter: alpha(opacity = 10);
 
    z-index: 9049;
 
    margin: .3em;
 
}
 

	
 
.ac .yui-ac-content ul {
 
    width: 100%;
 
    margin: 0;
 
    padding: 0;
 
    z-index: 9050;
 
}
 

	
 
.ac .yui-ac-content li {
 
    cursor: default;
 
    white-space: nowrap;
 
    margin: 0;
 
    padding: 2px 5px;
 
    height: 18px;
 
    z-index: 9050;
 
    display: block;
 
    width: auto !important;
 
}
 

	
 
.ac .yui-ac-content li .ac-container-wrap {
 
    width: auto;
 
}
 

	
 
.ac .yui-ac-content li.yui-ac-prehighlight {
 
    background: #B3D4FF;
 
    z-index: 9050;
 
}
 

	
 
.ac .yui-ac-content li.yui-ac-highlight {
 
    background: #556CB5;
 
    color: #FFF;
 
    z-index: 9050;
 
}
 
.ac .yui-ac-bd {
 
    z-index: 9050;
 
}
 

	
 
.reposize {
 
    background: url("../images/icons/server.png") no-repeat scroll 3px;
 
    height: 16px;
 
    width: 20px;
 
    cursor: pointer;
 
    display: block;
 
    float: right;
 
    margin-top: 2px;
 
}
 

	
 
#repo_size {
 
    display: block;
 
    margin-top: 4px;
 
    color: #666;
 
    float: right;
 
}
 

	
 
.locking_locked {
 
    background: #FFF url("../images/icons/block_16.png") no-repeat scroll 3px;
 
    height: 16px;
 
    width: 20px;
 
    cursor: pointer;
 
    display: block;
 
    float: right;
 
    margin-top: 2px;
 
}
 

	
 
.locking_unlocked {
 
    background: #FFF url("../images/icons/accept.png") no-repeat scroll 3px;
 
    height: 16px;
 
    width: 20px;
 
    cursor: pointer;
 
    display: block;
 
    float: right;
 
    margin-top: 2px;
 
}
 

	
 
.currently_following {
 
    padding-left: 10px;
 
    padding-bottom: 5px;
 
}
 

	
 
.add_icon {
 
    background: url("../images/icons/add.png") no-repeat scroll 3px;
 
    padding-left: 20px;
 
    padding-top: 0px;
 
    text-align: left;
 
}
 

	
 
.accept_icon {
 
    background: url("../images/icons/accept.png") no-repeat scroll 3px;
 
    padding-left: 20px;
 
    padding-top: 0px;
 
    text-align: left;
 
}
 

	
 
.edit_icon {
 
    background: url("../images/icons/application_form_edit.png") no-repeat scroll 3px;
 
    padding-left: 20px;
 
    padding-top: 0px;
 
    text-align: left;
 
}
 

	
 
.delete_icon {
 
    background: url("../images/icons/delete.png") no-repeat scroll 3px;
 
    padding-left: 20px;
 
    padding-top: 0px;
 
    text-align: left;
 
}
 

	
 
.refresh_icon {
 
    background: url("../images/icons/arrow_refresh.png") no-repeat scroll
 
        3px;
 
    padding-left: 20px;
 
    padding-top: 0px;
 
    text-align: left;
 
}
 

	
 
.pull_icon {
 
    background: url("../images/icons/connect.png") no-repeat scroll 3px;
 
    padding-left: 20px;
 
    padding-top: 0px;
 
    text-align: left;
 
}
 

	
 
.rss_icon {
 
    background: url("../images/icons/rss_16.png") no-repeat scroll 3px;
 
    padding-left: 20px;
 
    padding-top: 4px;
 
    text-align: left;
 
    font-size: 8px
 
}
 

	
 
.atom_icon {
 
    background: url("../images/icons/rss_16.png") no-repeat scroll 3px;
 
    padding-left: 20px;
 
    padding-top: 4px;
 
    text-align: left;
 
    font-size: 8px
 
}
 

	
 
.archive_icon {
 
    background: url("../images/icons/compress.png") no-repeat scroll 3px;
 
    padding-left: 20px;
 
    text-align: left;
 
    padding-top: 1px;
 
}
 

	
 
.start_following_icon {
 
    background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
 
    padding-left: 20px;
 
    text-align: left;
 
    padding-top: 0px;
 
}
 

	
 
.stop_following_icon {
 
    background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
 
    padding-left: 20px;
 
    text-align: left;
 
    padding-top: 0px;
 
}
 

	
 
.action_button {
 
    border: 0;
 
    display: inline;
 
}
 

	
 
.action_button:hover {
 
    border: 0;
 
    text-decoration: underline;
 
    cursor: pointer;
 
}
 

	
 
#switch_repos {
 
    position: absolute;
 
    height: 25px;
 
    z-index: 1;
 
}
 

	
 
#switch_repos select {
 
    min-width: 150px;
 
    max-height: 250px;
 
    z-index: 1;
 
}
 

	
 
.breadcrumbs {
 
    border: medium none;
 
    color: #FFF;
 
    float: left;
 
    font-weight: 700;
 
    font-size: 14px;
 
    margin: 0;
 
    padding: 11px 0 11px 10px;
 
}
 

	
 
.breadcrumbs .hash {
 
    text-transform: none;
 
    color: #fff;
 
}
 

	
 
.breadcrumbs a {
 
    color: #FFF;
 
}
 

	
 
.flash_msg {
 
}
 

	
 
.flash_msg ul {
 
}
 

	
 
.error_red {
 
    color: red;
 
}
 

	
 
.error_msg {
 
    background-color: #c43c35;
 
    background-repeat: repeat-x;
 
    background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35) );
 
    background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
 
    background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
 
    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35) );
 
    background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
 
    background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
 
    background-image: linear-gradient(to bottom, #ee5f5b, #c43c35);
 
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#c43c35', GradientType=0 );
 
    border-color: #c43c35 #c43c35 #882a25;
 
}
 

	
 
.error_msg a {
 
    text-decoration: underline;
 
}
 

	
 
.warning_msg {
 
    color: #404040 !important;
 
    background-color: #eedc94;
 
    background-repeat: repeat-x;
 
    background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94) );
 
    background-image: -moz-linear-gradient(top, #fceec1, #eedc94);
 
    background-image: -ms-linear-gradient(top, #fceec1, #eedc94);
 
    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94) );
 
    background-image: -webkit-linear-gradient(top, #fceec1, #eedc94);
 
    background-image: -o-linear-gradient(top, #fceec1, #eedc94);
 
    background-image: linear-gradient(to bottom, #fceec1, #eedc94);
 
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0 );
 
    border-color: #eedc94 #eedc94 #e4c652;
 
}
 

	
 
.warning_msg a {
 
    text-decoration: underline;
 
}
 

	
 
.success_msg {
 
    background-color: #57a957;
 
    background-repeat: repeat-x !important;
 
    background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957) );
 
    background-image: -moz-linear-gradient(top, #62c462, #57a957);
 
    background-image: -ms-linear-gradient(top, #62c462, #57a957);
 
    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957) );
 
    background-image: -webkit-linear-gradient(top, #62c462, #57a957);
 
    background-image: -o-linear-gradient(top, #62c462, #57a957);
 
    background-image: linear-gradient(to bottom, #62c462, #57a957);
 
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0 );
 
    border-color: #57a957 #57a957 #3d773d;
 
}
 

	
 
.success_msg a {
 
    text-decoration: underline;
 
    color: #FFF !important;
 
}
 

	
 
.notice_msg {
 
    background-color: #339bb9;
 
    background-repeat: repeat-x;
 
    background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9) );
 
    background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
 
    background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
 
    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9) );
 
    background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
 
    background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
 
    background-image: linear-gradient(to bottom, #5bc0de, #339bb9);
 
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0 );
 
    border-color: #339bb9 #339bb9 #22697d;
 
}
 

	
 
.notice_msg a {
 
    text-decoration: underline;
 
}
 

	
 
.success_msg, .error_msg, .notice_msg, .warning_msg {
 
    font-size: 12px;
 
    font-weight: 700;
 
    min-height: 14px;
 
    line-height: 14px;
 
    margin-bottom: 10px;
 
    margin-top: 0;
 
    display: block;
 
    overflow: auto;
 
    padding: 6px 10px 6px 10px;
 
    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
 
    position: relative;
 
    color: #FFF;
 
    border-width: 1px;
 
    border-style: solid;
 
    -webkit-border-radius: 4px;
 
    border-radius: 4px;
 
    -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
 
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
 
}
 

	
 
#msg_close {
 
    background: transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
 
    cursor: pointer;
 
    height: 16px;
 
    position: absolute;
 
    right: 5px;
 
    top: 5px;
 
    width: 16px;
 
}
 
div#legend_data {
 
    padding-left: 10px;
 
}
 
div#legend_container table {
 
    border: none !important;
 
}
 
div#legend_container table, div#legend_choices table {
 
    width: auto !important;
 
}
 

	
 
table#permissions_manage {
 
    width: 0 !important;
 
}
 

	
 
table#permissions_manage span.private_repo_msg {
 
    font-size: 0.8em;
 
    opacity: 0.6;
 
}
 

	
 
table#permissions_manage td.private_repo_msg {
 
    font-size: 0.8em;
 
}
 

	
 
table#permissions_manage tr#add_perm_input td {
 
    vertical-align: middle;
 
}
 

	
 
div.gravatar {
 
    background-color: #FFF;
 
    float: left;
 
    margin-right: 0.7em;
 
    padding: 1px 1px 1px 1px;
 
    line-height: 0;
 
    -webkit-border-radius: 3px;
 
    -khtml-border-radius: 3px;
 
    border-radius: 3px;
 
}
 

	
 
div.gravatar img {
 
    -webkit-border-radius: 2px;
 
    -khtml-border-radius: 2px;
 
    border-radius: 2px;
 
}
 

	
 
#header, #content, #footer {
 
    min-width: 978px;
 
}
 

	
 
#content {
 
    clear: both;
 
    padding: 10px 10px 14px 10px;
 
}
 

	
 
#content.hover {
 
    padding: 55px 10px 14px 10px !important;
 
}
 

	
 
#content div.box div.title div.search {
 
    border-left: 1px solid #316293;
 
}
 

	
 
#content div.box div.title div.search div.input input {
 
    border: 1px solid #316293;
 
}
 

	
 
.ui-btn {
 
    color: #515151;
 
    background-color: #DADADA;
 
    background-repeat: repeat-x;
 
    background-image: -khtml-gradient(linear, left top, left bottom, from(#F4F4F4),to(#DADADA) );
 
    background-image: -moz-linear-gradient(top, #F4F4F4, #DADADA);
 
    background-image: -ms-linear-gradient(top, #F4F4F4, #DADADA);
 
    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #F4F4F4),color-stop(100%, #DADADA) );
 
    background-image: -webkit-linear-gradient(top, #F4F4F4, #DADADA) );
 
    background-image: -o-linear-gradient(top, #F4F4F4, #DADADA) );
 
    background-image: linear-gradient(to bottom, #F4F4F4, #DADADA);
 
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#F4F4F4', endColorstr='#DADADA', GradientType=0);
 

	
 
    border-top: 1px solid #DDD;
 
    border-left: 1px solid #c6c6c6;
 
    border-right: 1px solid #DDD;
 
    border-bottom: 1px solid #c6c6c6;
 
    color: #515151;
 
    outline: none;
 
    margin: 0px 3px 3px 0px;
 
    -webkit-border-radius: 4px 4px 4px 4px !important;
 
    -khtml-border-radius: 4px 4px 4px 4px !important;
 
    border-radius: 4px 4px 4px 4px !important;
 
    cursor: pointer !important;
 
    padding: 3px 3px 3px 3px;
 
    background-position: 0 -15px;
 

	
 
}
 

	
 
.ui-btn.disabled {
 
    color: #999;
 
}
 

	
 
.ui-btn.xsmall {
 
    padding: 1px 2px 1px 1px;
 
}
 

	
 
.ui-btn.large {
 
    padding: 6px 12px;
 
}
 

	
 
.ui-btn.clone {
 
    padding: 5px 2px 6px 1px;
 
    margin: 0px 0px 3px -4px;
 
    -webkit-border-radius: 0px 4px 4px 0px !important;
 
    -khtml-border-radius: 0px 4px 4px 0px !important;
 
    border-radius: 0px 4px 4px 0px !important;
 
    width: 100px;
 
    text-align: center;
 
    display: inline-block;
 
    position: relative;
 
    top: -2px;
 
}
 
.ui-btn:focus {
 
    outline: none;
 
}
 
.ui-btn:hover {
 
    background-position: 0 -15px;
 
    text-decoration: none;
 
    color: #515151;
 
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 0 3px #FFFFFF !important;
 
}
 

	
 
.ui-btn.disabled:hover {
 
    background-position: 0;
 
    color: #999;
 
    text-decoration: none;
 
    box-shadow: none !important;
 
}
 

	
 
.ui-btn.red {
 
    color: #fff;
 
    background-color: #c43c35;
 
    background-repeat: repeat-x;
 
    background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));
 
    background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
 
    background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
 
    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));
 
    background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
 
    background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
 
    background-image: linear-gradient(to bottom, #ee5f5b, #c43c35);
 
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);
 
    border-color: #c43c35 #c43c35 #882a25;
 
    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
 
}
 

	
 

	
 
.ui-btn.blue {
 
    color: #fff;
 
    background-color: #339bb9;
 
    background-repeat: repeat-x;
 
    background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));
 
    background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
 
    background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
 
    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));
 
    background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
 
    background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
 
    background-image: linear-gradient(to bottom, #5bc0de, #339bb9);
 
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);
 
    border-color: #339bb9 #339bb9 #22697d;
 
    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
 
}
 

	
 
.ui-btn.green {
 
    background-color: #57a957;
 
    background-repeat: repeat-x;
 
    background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));
 
    background-image: -moz-linear-gradient(top, #62c462, #57a957);
 
    background-image: -ms-linear-gradient(top, #62c462, #57a957);
 
    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));
 
    background-image: -webkit-linear-gradient(top, #62c462, #57a957);
 
    background-image: -o-linear-gradient(top, #62c462, #57a957);
 
    background-image: linear-gradient(to bottom, #62c462, #57a957);
 
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);
 
    border-color: #57a957 #57a957 #3d773d;
 
    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
 
}
 

	
 
.ui-btn.blue.hidden {
 
    display: none;
 
}
 

	
 
.ui-btn.active {
 
    font-weight: bold;
 
}
 

	
 
ins, div.options a:hover {
 
    text-decoration: none;
 
}
 

	
 
img,
 
#header #header-inner #quick li a:hover span.normal,
 
#content div.box div.form div.fields div.field div.textarea table td table td a,
 
#clone_url,
 
#clone_url_id
 
{
 
    border: none;
 
}
 

	
 
img.icon, .right .merge img {
 
    vertical-align: bottom;
 
}
 

	
 
#header ul#logged-user, #content div.box div.title ul.links,
 
#content div.box div.message div.dismiss,
 
#content div.box div.traffic div.legend ul {
 
    float: right;
 
    margin: 0;
 
    padding: 0;
 
}
 

	
 
#header #header-inner #home, #header #header-inner #logo,
 
#content div.box ul.left, #content div.box ol.left,
 
div#commit_history,
 
div#legend_data, div#legend_container, div#legend_choices {
 
    float: left;
 
}
 

	
 
#header #header-inner #quick li #quick_login,
 
#header #header-inner #quick li:hover ul ul,
 
#header #header-inner #quick li:hover ul ul ul,
 
#header #header-inner #quick li:hover ul ul ul ul,
 
#content #left #menu ul.closed, #content #left #menu li ul.collapsed, .yui-tt-shadow {
 
    display: none;
 
}
 

	
 
#header #header-inner #quick li:hover #quick_login,
 
#header #header-inner #quick li:hover ul, #header #header-inner #quick li li:hover ul, #header #header-inner #quick li li li:hover ul, #header #header-inner #quick li li li li:hover ul, #content #left #menu ul.opened, #content #left #menu li ul.expanded {
 
    display: block;
 
}
 

	
 
#content div.graph {
 
    padding: 0 10px 10px;
 
}
 

	
 
#content div.box div.title ul.links li a:hover,
 
#content div.box div.title ul.links li.ui-tabs-selected a {
 

	
 
    background: #6388ad; /* Old browsers */
 
    background: -moz-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* FF3.6+ */
 
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,0.1)), color-stop(100%,rgba(255,255,255,0))); /* Chrome,Safari4+ */
 
    background: -webkit-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* Chrome10+,Safari5.1+ */
 
    background: -o-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* Opera 11.10+ */
 
    background: -ms-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* IE10+ */
 
    background: linear-gradient(to bottom, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* W3C */
 
    /*filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#88bfe8', endColorstr='#70b0e0',GradientType=0 ); /* IE6-9 */*/
 
}
 

	
 
#content div.box ol.lower-roman, #content div.box ol.upper-roman, #content div.box ol.lower-alpha, #content div.box ol.upper-alpha, #content div.box ol.decimal {
 
    margin: 10px 24px 10px 44px;
 
}
 

	
 
#content div.box div.form, #content div.box div.table, #content div.box div.traffic {
 
    position: relative;
 
    clear: both;
 
    margin: 0;
 
    padding: 0 20px 10px;
 
}
 

	
 
#content div.box div.form div.fields, #login div.form, #login div.form div.fields, #register div.form, #register div.form div.fields {
 
    clear: both;
 
    overflow: hidden;
 
    margin: 0;
 
    padding: 0;
 
}
 

	
 
#content div.box div.form div.fields div.field div.label span, #login div.form div.fields div.field div.label span, #register div.form div.fields div.field div.label span {
 
    height: 1%;
 
    display: block;
 
    color: #363636;
 
    margin: 0;
 
    padding: 2px 0 0;
 
}
 

	
 
#content div.box div.form div.fields div.field div.input input.error, #login div.form div.fields div.field div.input input.error, #register div.form div.fields div.field div.input input.error {
 
    background: #FBE3E4;
 
    border-top: 1px solid #e1b2b3;
 
    border-left: 1px solid #e1b2b3;
 
    border-right: 1px solid #FBC2C4;
 
    border-bottom: 1px solid #FBC2C4;
 
}
 

	
 
#content div.box div.form div.fields div.field div.input input.success, #login div.form div.fields div.field div.input input.success, #register div.form div.fields div.field div.input input.success {
 
    background: #E6EFC2;
 
    border-top: 1px solid #cebb98;
 
    border-left: 1px solid #cebb98;
 
    border-right: 1px solid #c6d880;
 
    border-bottom: 1px solid #c6d880;
 
}
 

	
 
#content div.box-left div.form div.fields div.field div.textarea, #content div.box-right div.form div.fields div.field div.textarea, #content div.box div.form div.fields div.field div.select select, #content div.box table th.selected input, #content div.box table td.selected input {
 
    margin: 0;
 
}
 

	
 
#content div.box-left div.form div.fields div.field div.select, #content div.box-left div.form div.fields div.field div.checkboxes, #content div.box-left div.form div.fields div.field div.radios, #content div.box-right div.form div.fields div.field div.select, #content div.box-right div.form div.fields div.field div.checkboxes, #content div.box-right div.form div.fields div.field div.radios {
 
    margin: 0 0 0 0px !important;
 
    padding: 0;
 
}
 

	
 
#content div.box div.form div.fields div.field div.select, #content div.box div.form div.fields div.field div.checkboxes, #content div.box div.form div.fields div.field div.radios {
 
    margin: 0 0 0 200px;
 
    padding: 0;
 
}
 

	
 
#content div.box div.form div.fields div.field div.select a:hover, #content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover, #content div.box div.action a:hover {
 
    color: #000;
 
    text-decoration: none;
 
}
 

	
 
#content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus, #content div.box div.action a.ui-selectmenu-focus {
 
    border: 1px solid #666;
 
}
 

	
 
#content div.box div.form div.fields div.field div.checkboxes div.checkbox, #content div.box div.form div.fields div.field div.radios div.radio {
 
    clear: both;
 
    overflow: hidden;
 
    margin: 0;
 
    padding: 8px 0 2px;
 
}
 

	
 
#content div.box div.form div.fields div.field div.checkboxes div.checkbox input, #content div.box div.form div.fields div.field div.radios div.radio input {
 
    float: left;
 
    margin: 0;
 
}
 

	
 
#content div.box div.form div.fields div.field div.checkboxes div.checkbox label, #content div.box div.form div.fields div.field div.radios div.radio label {
 
    height: 1%;
 
    display: block;
 
    float: left;
 
    margin: 2px 0 0 4px;
 
}
 

	
 
div.form div.fields div.field div.button input,
 
#content div.box div.form div.fields div.buttons input
 
div.form div.fields div.buttons input,
 
#content div.box div.action div.button input {
 
    font-size: 11px;
 
    font-weight: 700;
 
    margin: 0;
 
}
 

	
 
input.ui-button {
 
    background: #e5e3e3 url("../images/button.png") repeat-x;
 
    border-top: 1px solid #DDD;
 
    border-left: 1px solid #c6c6c6;
 
    border-right: 1px solid #DDD;
 
    border-bottom: 1px solid #c6c6c6;
 
    color: #515151 !important;
 
    outline: none;
 
    margin: 0;
 
    padding: 6px 12px;
 
    -webkit-border-radius: 4px 4px 4px 4px;
 
    -khtml-border-radius: 4px 4px 4px 4px;
 
    border-radius: 4px 4px 4px 4px;
 
    box-shadow: 0 1px 0 #ececec;
 
    cursor: pointer;
 
}
 

	
 
input.ui-button: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;
 
}
 

	
 
div.form div.fields div.field div.highlight, #content div.box div.form div.fields div.buttons div.highlight {
 
    display: inline;
 
}
 

	
 
#content div.box div.form div.fields div.buttons, div.form div.fields div.buttons {
 
    margin: 10px 0 0 200px;
 
    padding: 0;
 
}
 

	
 
#content div.box-left div.form div.fields div.buttons, #content div.box-right div.form div.fields div.buttons, div.box-left div.form div.fields div.buttons, div.box-right div.form div.fields div.buttons {
 
    margin: 10px 0 0;
 
}
 

	
 
#content div.box table td.user, #content div.box table td.address {
 
    width: 10%;
 
    text-align: center;
 
}
 

	
 
#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.input input.large {
 
    width: 250px;
 
}
 

	
 
#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 .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;
 
    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 {
 
    min-height: 85px;
 
    padding-left: 20px;
 
}
 

	
 
.group_members .group_member {
 
    height: 30px;
 
    padding: 0px 0px 0px 0px;
 
}
 

	
 
.reviewers_member {
 
    height: 15px;
 
    padding: 0px 0px 0px 10px;
 
}
 

	
 
.emails_wrap {
 
    padding: 0px 20px;
 
}
 

	
 
.emails_wrap .email_entry {
 
    height: 30px;
 
    padding: 0px 0px 0px 10px;
 
}
 
.emails_wrap .email_entry .email {
 
    float: left
 
}
 
.emails_wrap .email_entry .email_action {
 
    float: left
 
}
 

	
 
.ips_wrap {
 
    padding: 0px 20px;
 
}
 

	
 
.ips_wrap .ip_entry {
 
    height: 30px;
 
    padding: 0px 0px 0px 10px;
 
}
 
.ips_wrap .ip_entry .ip {
 
    float: left
 
}
 
.ips_wrap .ip_entry .ip_action {
 
    float: left
 
}
 

	
 

	
 
/*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;
 
    border-radius: 3px;
 
}
 

	
 
div.readme .readme_box table {
 
    display: table;
 
    border-collapse: separate;
 
    border-spacing: 2px;
 
    border-color: gray;
 
    width: auto !important;
 
}
 

	
 

	
 
/** 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;
 
    border-radius: 3px;
 
}
 

	
 

	
 
/** comment main **/
 
.comments {
 
    padding: 10px 20px;
 
}
 

	
 
.comments .comment {
 
    border: 1px solid #ddd;
 
    margin-top: 10px;
 
    -webkit-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 {
 
    min-height: 80px;
 
    clear: both
 
}
 

	
 

	
 
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-inline-form .comment-block-ta,
 
.comment-form .comment-block-ta {
 
    border: 1px solid #ccc;
 
    border-radius: 3px;
 
    box-sizing: border-box;
 
}
 

	
 
.comment-form-submit {
 
    margin-top: 5px;
 
    margin-left: 525px;
 
}
 

	
 
.file-comments {
 
    display: none;
 
}
 

	
 
.comment-form .comment {
 
    margin-left: 10px;
 
}
 

	
 
.comment-form .comment-help {
 
    padding: 5px 5px 5px 5px;
 
    color: #666;
 
}
 
.comment-form .comment-help .preview-btn,
 
.comment-form .comment-help .edit-btn {
 
    float: right;
 
    margin: -6px 0px 0px 0px;
 
}
 

	
 
.comment-form .preview-box.unloaded,
 
.comment-inline-form .preview-box.unloaded {
 
    height: 50px;
 
    text-align: center;
 
    padding: 20px;
 
    background-color: #fafafa;
 
}
 

	
 
.comment-form .comment-button {
 
    padding-top: 5px;
 
}
 

	
 
.add-another-button {
 
    margin-left: 10px;
 
    margin-top: 10px;
 
    margin-bottom: 10px;
 
}
 

	
 
.comment .buttons {
 
    float: right;
 
    margin: -1px 0px 0px 0px;
 
}
 

	
 

	
 
.show-inline-comments {
 
    position: relative;
 
    top: 1px
 
}
 

	
 
/** comment inline form **/
 
.comment-inline-form {
 
    margin: 4px;
 
}
 
.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,
 
.comment-form .clearfix {
 
    background: #EEE;
 
    -webkit-border-radius: 4px;
 
    border-radius: 4px;
 
    padding: 5px;
 
    margin: 0px;
 
}
 

	
 
div.comment-inline-form {
 
    padding: 4px 0px 6px 0px;
 
}
 

	
 
.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: 5px 5px 5px 5px;
 
    color: #666;
 
}
 

	
 
.comment-inline-form .comment-help .preview-btn,
 
.comment-inline-form .comment-help .edit-btn {
 
    float: right;
 
    margin: -6px 0px 0px 0px;
 
}
 

	
 
.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;
 
    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;
 
    height: 20px;
 
}
 

	
 
.inline-comments .comment .meta img {
 
    vertical-align: middle;
 
}
 

	
 
.inline-comments .comment .meta .user {
 
    font-weight: bold;
 
    float: left;
 
    padding: 3px;
 
}
 

	
 
.inline-comments .comment .meta .date {
 
    float: left;
 
    padding: 3px;
 
}
 

	
 
.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;
 
}
 

	
 
#context-pages .pull-request span,
 
.menu_link_notifications {
 
    padding: 4px 4px !important;
 
    text-align: center;
 
    color: #888 !important;
 
    background-color: #DEDEDE !important;
 
    border-radius: 4px !important;
 
    -webkit-border-radius: 4px !important;
 
}
 

	
 
#context-pages .forks span,
 
.menu_link_notifications {
 
    padding: 4px 4px !important;
 
    text-align: center;
 
    color: #888 !important;
 
    background-color: #DEDEDE !important;
 
    border-radius: 4px !important;
 
    -webkit-border-radius: 4px !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-list .container .notification-header .desc {
 
    font-weight: bold;
 
    font-size: 17px;
 
}
 
.notification-table {
 
    border: 1px solid #ccc;
 
    -webkit-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-header .read-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
 
}
 

	
 
/****
 
PULL REQUESTS
 
*****/
 
.pullrequests_section_head {
 
    padding: 10px 10px 10px 0px;
 
    font-size: 16px;
 
    font-weight: bold;
 
}
 

	
 
h3.closed,
 
#pullrequests_container li.closed a
 
 {
 
    color: #555;
 
    background: #eee;
 
}
 

	
 
div.pr-title {
 
    font-size: 1.6em;
 
}
 

	
 
div.pr {
 
    border-bottom: 1px solid #DDD;
 
    margin: 0px 20px;
 
    padding: 10px 4px;
 
}
 
div.pr-closed {
 
    background-color: rgba(245,245,245,0.5);
 
}
 

	
 
span.pr-closed-tag {
 
    margin-bottom: 1px;
 
    margin-right: 1px;
 
    padding: 1px 3px;
 
    font-size: 10px;
 
    padding: 1px 3px 1px 3px;
 
    font-size: 10px;
 
    color: #336699;
 
    white-space: nowrap;
 
    -webkit-border-radius: 4px;
 
    border-radius: 4px;
 
    border: 1px solid #d9e8f8;
 
    line-height: 1.5em;
 
}
 

	
 
/****
 
  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;
 
    border-radius: 3px;
 
}
 

	
 
#perms .perm_tag.admin {
 
    background-color: #B94A48;
 
    color: #ffffff;
 
}
 

	
 
#perms .perm_tag.write {
 
    background-color: #DB7525;
 
    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
 
******************************************************************************/
 
.diff-collapse {
 
    text-align: center;
 
    margin-bottom: -15px;
 
}
 
.diff-collapse-button {
 
    cursor: pointer;
 
    color: #666;
 
    font-size: 16px;
 
}
 
.diff-container {
 

	
 
}
 

	
 
.diff-container.hidden {
 
    display: none;
 
    overflow: hidden;
 
}
 

	
 

	
 
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;
 
    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.banner {
 
    border-bottom: 1px solid #CCCCCC;
 
    background: #EEEEEE;
 
    height: 14px;
 
    margin: 0px 95px 0px 95px;
 
    padding: 3px 3px 11px 3px;
 
}
 

	
 
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;
 
}
 
div.diffblock .code-header div {
 
    margin-left: 4px;
 
    font-weight: bold;
 
    font-size: 14px;
 
}
 

	
 
div.diffblock .parents {
 
    float: left;
 
    height: 26px;
 
    width: 100px;
 
    font-size: 10px;
 
    font-weight: 400;
 
    vertical-align: middle;
 
    padding: 0px 2px 2px 2px;
 
    background-color: #eeeeee;
 
    border-bottom: 1px solid #CCCCCC;
 
}
 

	
 
div.diffblock .children {
 
    float: right;
 
    height: 26px;
 
    width: 100px;
 
    font-size: 10px;
 
    font-weight: 400;
 
    vertical-align: middle;
 
    text-align: right;
 
    padding: 0px 2px 2px 2px;
 
    background-color: #eeeeee;
 
    border-bottom: 1px solid #CCCCCC;
 
}
 

	
 
div.diffblock .code-body {
 
    background: #FFFFFF;
 
}
 
div.diffblock pre.raw {
 
    background: #FFFFFF;
 
    color: #000000;
 
}
 
table.code-difftable {
 
    border-collapse: collapse;
 
    width: 99%;
 
    border-radius: 0px !important;
 
}
 
table.code-difftable td {
 
    padding: 0 !important;
 
    background: none !important;
 
    border: 0 !important;
 
    vertical-align: baseline !important
 
}
 
table.code-difftable .context {
 
    background: none repeat scroll 0 0 #DDE7EF;
 
}
 
table.code-difftable .add {
 
    background: none repeat scroll 0 0 #DDFFDD;
 
}
 
table.code-difftable .add ins {
 
    background: none repeat scroll 0 0 #AAFFAA;
 
    text-decoration: none;
 
}
 
table.code-difftable .del {
 
    background: none repeat scroll 0 0 #FFDDDD;
 
}
 
table.code-difftable .del del {
 
    background: none repeat scroll 0 0 #FFAAAA;
 
    text-decoration: none;
 
}
 

	
 
/** LINE NUMBERS **/
 
table.code-difftable .lineno {
 

	
 
    padding-left: 2px;
 
    padding-right: 2px;
 
    text-align: right;
 
    width: 32px;
 
    -moz-user-select: none;
 
    -webkit-user-select: none;
 
    border-right: 1px solid #CCC !important;
 
    border-left: 0px solid #CCC !important;
 
    border-top: 0px solid #CCC !important;
 
    border-bottom: none !important;
 
    vertical-align: middle !important;
 

	
 
}
 
table.code-difftable .lineno.new {
 
}
 
table.code-difftable .lineno.old {
 
}
 
table.code-difftable .lineno a {
 
    color: #747474 !important;
 
    font: 11px "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace !important;
 
    letter-spacing: -1px;
 
    text-align: right;
 
    padding-right: 2px;
 
    cursor: pointer;
 
    display: block;
 
    width: 32px;
 
}
 

	
 
table.code-difftable .lineno-inline {
 
    background: none repeat scroll 0 0 #FFF !important;
 
    padding-left: 2px;
 
    padding-right: 2px;
 
    text-align: right;
 
    width: 30px;
 
    -moz-user-select: none;
 
    -webkit-user-select: none;
 
}
 

	
 
/** CODE **/
 
table.code-difftable .code {
 
    display: block;
 
    width: 100%;
 
}
 
table.code-difftable .code td {
 
    margin: 0;
 
    padding: 0;
 
}
 
table.code-difftable .code pre {
 
    margin: 0;
 
    padding: 0;
 
    height: 17px;
 
    line-height: 17px;
 
}
 

	
 

	
 
.diffblock.margined.comm .line .code:hover {
 
    background-color: #FFFFCC !important;
 
    cursor: pointer !important;
 
    background-image: url("../images/icons/comment_add.png") !important;
 
    background-repeat: no-repeat !important;
 
    background-position: right !important;
 
    background-position: 0% 50% !important;
 
}
 
.diffblock.margined.comm .line .code.no-comment:hover {
 
    background-image: none !important;
 
    cursor: auto !important;
 
    background-color: inherit !important;
 
}
 

	
 
div.comment:target>.comment-wrapp {
 
    border: solid 2px #ee0 !important;
 
}
 

	
 
.lineno:target a {
 
    border: solid 2px #ee0 !important;
 
    margin: -2px;
 
}
rhodecode/templates/changeset/diff_block.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
##usage:
 
## <%namespace name="diff_block" file="/changeset/diff_block.html"/>
 
## ${diff_block.diff_block(change)}
 
##
 
<%def name="diff_block(change)">
 
<div class="diff-collapse">
 
    <span target="${'diff-container-%s' % (id(change))}" class="diff-collapse-button">&uarr; ${_('Collapse diff')} &uarr;</span>
 
</div>
 
<div class="diff-container" id="${'diff-container-%s' % (id(change))}">
 
%for FID,(cs1, cs2, change, path, diff, stats) in change.iteritems():
 
    ##%if op !='removed':
 
    <div id="${FID}_target" style="clear:both;margin-top:25px"></div>
 
    <div id="${FID}" class="diffblock  margined comm">
 
        <div class="code-header">
 
            <div class="changeset_header">
 
                <div class="changeset_file">
 
                    ${h.link_to_if(change!='D',h.safe_unicode(path),h.url('files_home',repo_name=c.repo_name,
 
                    revision=cs2,f_path=h.safe_unicode(path)))}
 
                </div>
 
                <div class="diff-actions">
 
                  <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full diff for this file'))}"><img class="icon" src="${h.url('/images/icons/page_white_go.png')}"/></a>
 
                  <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='raw')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
 
                  <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}"><img class="icon" src="${h.url('/images/icons/page_save.png')}"/></a>
 
                  ${c.ignorews_url(request.GET, h.FID(cs2,path))}
 
                  ${c.context_url(request.GET, h.FID(cs2,path))}
 
                </div>
 
                <span style="float:right;margin-top:-3px">
 
                  <label>
 
                  ${_('Show inline comments')}
 
                  ${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=h.FID(cs2,path))}
 
                  </label>
 
                </span>
 
            </div>
 
        </div>
 
        <div class="code-body">
 
            <div class="full_f_path" path="${h.safe_unicode(path)}"></div>
 
            ${diff|n}
 
        </div>
 
    </div>
 
    ##%endif
 
%endfor
 
</div>
 
</%def>
 

	
 
<%def name="diff_block_simple(change)">
 

	
 
  %for op,filenode_path,diff in change:
 
    <div id="${h.FID('',filenode_path)}_target" style="clear:both;margin-top:25px"></div>
 
    <div id="${h.FID('',filenode_path)}" class="diffblock  margined comm">
 
      <div class="code-header">
 
          <div class="changeset_header">
 
              <div class="changeset_file">
 
                  ${h.safe_unicode(filenode_path)} |
 
                  <a class="spantag" href="${h.url('files_home', repo_name=c.other_repo.repo_name, f_path=filenode_path, revision=c.org_ref)}" title="${_('Show file at latest version in this repo')}">${c.org_ref_type}@${h.short_id(c.org_ref) if c.org_ref_type=='rev' else c.org_ref}</a> -&gt;
 
                  <a class="spantag" href="${h.url('files_home', repo_name=c.repo_name, f_path=filenode_path, revision=c.other_ref)}" title="${_('Show file at initial version in this repo')}">${c.other_ref_type}@${h.short_id(c.other_ref) if c.other_ref_type=='rev' else c.other_ref}</a>
 
              </div>
 
          </div>
 
      </div>
 
        <div class="code-body">
 
            <div class="full_f_path" path="${h.safe_unicode(filenode_path)}"></div>
 
            ${diff|n}
 
        </div>
 
    </div>
 
  %endfor
 
</%def>
rhodecode/tests/fixtures/hg_diff_binary_and_normal.diff
Show inline comments
 
diff --git a/img/baseline-10px.png b/img/baseline-10px.png
 
new file mode 100644
 
index 0000000000000000000000000000000000000000..16095dcbf5c9ea41caeb1e3e41d647d425222ed1
 
GIT binary patch
 
literal 152
 
zc%17D@N?(olHy`uVBq!ia0vp^j6j^i!3HGVb)pi0lw^r(L`iUdT1k0gQ7VIDN`6wR
 
zf@f}GdTLN=VoGJ<$y6JlA}dc9$B>F!Nx%O8w`Ue+77%bXFxq5j_~-xsZV_1~1zCBH
 
y)y@U((_~Lrb!=|_@`K?vV_&A58+!u-Gs6x+MGjBnI|qTLFnGH9xvX<aXaWHBd@WW0
 

	
 
diff --git a/img/baseline-20px.png b/img/baseline-20px.png
 
deleted file mode 100644
 
Binary file img/baseline-20px.png has changed
 
index f76dd238ade08917e6712764a16a22005a50573d..0000000000000000000000000000000000000000
 
GIT binary patch
 
literal 0
 
Hc$@<O00001
 

	
 
diff --git a/index.html b/index.html
 
--- a/index.html
 
+++ b/index.html
 
@@ -2,7 +2,7 @@
 
 <html lang="en">
 
   <head>
 
     <meta charset="utf-8">
 
-    <title>Baseline</title>
 
+    <title>Twitter Baseline</title>
 
 
 
     <!-- // Less.js at the ready! -->
 
     <link rel="stylesheet/less" type="text/css" media="all" href="less/baseline.less" />
 
@@ -11,6 +11,7 @@
 
     <!-- // jQuery! -->
 
     <script type="text/javascript" src="http://code.jquery.com/jquery-1.5.2.min.js"></script>
 
     <script type="text/javascript" src="http://tablesorter.com/jquery.tablesorter.min.js"></script>
 
+    <script type="text/javascript" src="js/jquery/hashgrid.js"></script>
 
     <script type="text/javascript">
 
       $(document).ready(function(){
 
         // Active state in top nav
 
@@ -36,7 +37,7 @@
 
 <!--
 
 		<style>
 
 		  body {
 
-		    background: url(img/baseline-20px.png) repeat 0 0, url(img/grid-940px.png) repeat-y top center;
 
+		    background: url(img/baseline-10px.png) repeat 0 0, url(img/grid-940px.png) repeat-y top center;
 
 		    background-color: #fff;
 
 		  }
 
 		</style>
 
diff --git a/js/global.js b/js/global.js
 
deleted file mode 100644
 
--- a/js/global.js
 
+++ /dev/null
 
@@ -1,75 +0,0 @@
 
-$(document).ready(function(){
 
-  // Get Heights
 
-  windowHeight = $(window).height();
 
-  documentHeight = $(document).height();
 
-  sidebarHeight = windowHeight - 40;
 
-  containerHeight = windowHeight - 40;
 
-  
 
-  // Get Widths
 
-  windowWidth = $(window).width();
 
-  containerWidth = windowWidth - 200;
 
-  
 
-  if (windowHeight < containerHeight) {
 
-  
 
-    // Set Dimensions for default state (before resize)
 
-    $('div#sidebar').css({
 
-      height: sidebarHeight
 
-    });
 
-    $('div#container').css({
 
-      width: containerWidth,
 
-      height: containerHeight
 
-    });
 
-    
 
-  } else {
 
-  
 
-    // During resize, set widths
 
-    $(window).resize(function() {
 
-      console.log('Window Height: ' + $(window).height() + ', Sidebar Height:' + ($(window).height() - 40));
 
-  
 
-  	  // Get Heights
 
-  	  windowHeight = $(window).height();
 
-  	  sidebarHeight = windowHeight - 40;
 
-  	  containerHeight = windowHeight - 40;
 
-  	  
 
-      // Get Widths
 
-  	  windowWidth = $(window).width();
 
-  	  containerWidth = windowWidth - 200;
 
-  
 
-  	  // Set Dimensions for default state (before resize)
 
-  	  $('div#sidebar').css({
 
-  	    height: sidebarHeight
 
-  	  });
 
-  	  $('div#container').css({
 
-  	    width: containerWidth,
 
-  	    height: containerHeight
 
-  	  });
 
-    });
 
-    // console.log('omgz window is less than container so... fuck.');
 
-    $('div#sidebar').css({
 
-      height: documentHeight - 40
 
-    });
 
-    
 
-  }
 
-  
 
-  
 
-  
 
-/*
 
-  // Toggle Calendars
 
-  $('div#sidebar ul li a').click(function() {
 
-    if ($(this).is('#toggleMonthView')) {
 
-      console.log('toggle month');
 
-      $(this).addClass('active');
 
-      $('#toggleListView').removeClass('active');
 
-      $('table#monthView').show();
 
-      $('table#listView').hide();
 
-    } else {
 
-      console.log('toggle list');
 
-      $(this).addClass('active');
 
-      $('#toggleMonthView').removeClass('active');
 
-      $('table#listView').show();
 
-      $('table#monthView').hide();
 
-    }
 
-    return false;
 
-  });    
 
-*/
 
-});
 
diff --git a/js/jquery/hashgrid.js b/js/jquery/hashgrid.js
 
new file mode 100755
 
--- /dev/null
 
+++ b/js/jquery/hashgrid.js
 
@@ -0,0 +1,340 @@
 
+/**
 
+ * hashgrid (jQuery version)
 
+ * http://github.com/dotjay/hashgrid
 
+ * Version 5, 3 Nov 2010
 
+ * Written by Jon Gibbins, dotjay.co.uk, accessibility.co.uk
 
+ * Contibutors:
 
+ * Sean Coates, seancoates.com
 
+ * Phil Dokas, jetless.org
 
+ *
 
+ * // Using a basic #grid setup
 
+ * var grid = new hashgrid();
 
+ *
 
+ * // Using #grid with a custom id (e.g. #mygrid)
 
+ * var grid = new hashgrid("mygrid");
 
+ *
 
+ * // Using #grid with additional options
 
+ * var grid = new hashgrid({
 
+ *     id: 'mygrid',            // id for the grid container
 
+ *     modifierKey: 'alt',      // optional 'ctrl', 'alt' or 'shift'
 
+ *     showGridKey: 's',        // key to show the grid
 
+ *     holdGridKey: 'enter',    // key to hold the grid in place
 
+ *     foregroundKey: 'f',      // key to toggle foreground/background
 
+ *     jumpGridsKey: 'd',       // key to cycle through the grid classes
 
+ *     numberOfGrids: 2,        // number of grid classes used
 
+ *     classPrefix: 'class',    // prefix for the grid classes
 
+ *     cookiePrefix: 'mygrid'   // prefix for the cookie name
 
+ * });
 
+ */
 
+if (typeof jQuery == "undefined") {
 
+	alert("Hashgrid: jQuery not loaded. Make sure it's linked to your pages.");
 
+}
 
+
 
+
 
+/**
 
+ * hashgrid overlay
 
+ */
 
+var hashgrid = function(set) {
 
+
 
+	var options = {
 
+		id: 'grid',             // id for the grid container
 
+		modifierKey: null,      // optional 'ctrl', 'alt' or 'shift'
 
+		showGridKey: 'g',       // key to show the grid
 
+		holdGridKey: 'h',       // key to hold the grid in place
 
+		foregroundKey: 'f',     // key to toggle foreground/background
 
+		jumpGridsKey: 'j',      // key to cycle through the grid classes
 
+		numberOfGrids: 1,       // number of grid classes used
 
+		classPrefix: 'grid-',   // prefix for the grid classes
 
+		cookiePrefix: 'hashgrid'// prefix for the cookie name
 
+	};
 
+	var overlayOn = false,
 
+		sticky = false,
 
+		overlayZState = 'B',
 
+		overlayZBackground = -1,
 
+		overlayZForeground = 9999,
 
+		classNumber = 1;
 
+
 
+	// Apply options
 
+	if (typeof set == 'object') {
 
+		var k;
 
+		for (k in set) options[k] = set[k];
 
+	}
 
+	else if (typeof set == 'string') {
 
+		options.id = set;
 
+	}
 
+
 
+	// Remove any conflicting overlay
 
+	if ($('#' + options.id).length > 0) {
 
+		$('#' + options.id).remove();
 
+	}
 
+
 
+	// Create overlay, hidden before adding to DOM
 
+	var overlayEl = $('<div></div>');
 
+	overlayEl
 
+		.attr('id', options.id)
 
+		.css({
 
+			display: 'none',
 
+			'pointer-events': 'none'
 
+		});
 
+	$("body").prepend(overlayEl);
 
+	var overlay = $('#' + options.id);
 
+
 
+	// Unless a custom z-index is set, ensure the overlay will be behind everything
 
+	if (overlay.css('z-index') == 'auto') overlay.css('z-index', overlayZBackground);
 
+
 
+	// Override the default overlay height with the actual page height
 
+	var pageHeight = parseFloat($(document).height());
 
+	overlay.height(pageHeight);
 
+
 
+	// Add the first grid line so that we can measure it
 
+	overlay.append('<div id="' + options.id + '-horiz" class="horiz first-line">');
 
+
 
+	// Position off-screen and display to calculate height
 
+	var top = overlay.css("top");
 
+	overlay.css({
 
+		top: "-999px",
 
+		display: "block"
 
+	});
 
+
 
+	// Calculate the number of grid lines needed
 
+	var line = $('#' + options.id + '-horiz'),
 
+		lineHeight = line.outerHeight();
 
+
 
+	// Hide and reset top
 
+	overlay.css({
 
+		display: "none",
 
+		top: top
 
+	});
 
+
 
+	// Break on zero line height
 
+	if (lineHeight <= 0) return true;
 
+
 
+	// Add the remaining grid lines
 
+	var i, numGridLines = Math.floor(pageHeight / lineHeight);
 
+	for (i = numGridLines - 1; i >= 1; i--) {
 
+		overlay.append('<div class="horiz"></div>');
 
+	}
 
+
 
+	// vertical grid
 
+	overlay.append($('<div class="vert-container"></div>'));
 
+	var overlayVert = overlay.children('.vert-container');
 
+	var gridWidth = overlay.width();
 
+	overlayVert.css({width: gridWidth, position: 'absolute', top: 0});
 
+	overlayVert.append('<div class="vert first-line">&nbsp;</div>');
 
+
 
+	// 30 is an arbitrarily large number...
 
+	// can't calculate the margin width properly
 
+	for (i = 0; i < 30; i++) {
 
+		overlayVert.append('<div class="vert">&nbsp;</div>');
 
+	}
 
+
 
+	overlayVert.children()
 
+		.height(pageHeight)
 
+		.css({display: 'inline-block'});
 
+
 
+	// Check for saved state
 
+	var overlayCookie = readCookie(options.cookiePrefix + options.id);
 
+	if (typeof overlayCookie == 'string') {
 
+		var state = overlayCookie.split(',');
 
+		state[2] = Number(state[2]);
 
+		if ((typeof state[2] == 'number') && !isNaN(state[2])) {
 
+			classNumber = state[2].toFixed(0);
 
+			overlay.addClass(options.classPrefix + classNumber);
 
+		}
 
+		if (state[1] == 'F') {
 
+			overlayZState = 'F';
 
+			overlay.css('z-index', overlayZForeground);
 
+		}
 
+		if (state[0] == '1') {
 
+			overlayOn = true;
 
+			sticky = true;
 
+			showOverlay();
 
+		}
 
+	}
 
+	else {
 
+		overlay.addClass(options.classPrefix + classNumber);
 
+	}
 
+
 
+	// Keyboard controls
 
+	$(document).bind('keydown', keydownHandler);
 
+	$(document).bind('keyup', keyupHandler);
 
+
 
+	/**
 
+	 * Helpers
 
+	 */
 
+
 
+	function getModifier(e) {
 
+		if (options.modifierKey == null) return true; // Bypass by default
 
+		var m = true;
 
+		switch(options.modifierKey) {
 
+			case 'ctrl':
 
+				m = (e.ctrlKey ? e.ctrlKey : false);
 
+				break;
 
+
 
+			case 'alt':
 
+				m = (e.altKey ? e.altKey : false);
 
+				break;
 
+
 
+			case 'shift':
 
+				m = (e.shiftKey ? e.shiftKey : false);
 
+				break;
 
+		}
 
+		return m;
 
+	}
 
+
 
+	function getKey(e) {
 
+		var k = false, c = (e.keyCode ? e.keyCode : e.which);
 
+		// Handle keywords
 
+		if (c == 13) k = 'enter';
 
+		// Handle letters
 
+		else k = String.fromCharCode(c).toLowerCase();
 
+		return k;
 
+	}
 
+
 
+	function saveState() {
 
+		createCookie(options.cookiePrefix + options.id, (sticky ? '1' : '0') + ',' + overlayZState + ',' + classNumber, 1);
 
+	}
 
+
 
+	function showOverlay() {
 
+		overlay.show();
 
+		overlayVert.css({width: overlay.width()});
 
+		// hide any vertical blocks that aren't at the top of the viewport
 
+		overlayVert.children('.vert').each(function () {
 
+			$(this).css('display','inline-block');
 
+			if ($(this).offset().top > 0) {
 
+				$(this).hide();
 
+			}
 
+		});
 
+	}
 
+
 
+	/**
 
+	 * Event handlers
 
+	 */
 
+
 
+	function keydownHandler(e) {
 
+		var source = e.target.tagName.toLowerCase();
 
+		if ((source == 'input') || (source == 'textarea') || (source == 'select')) return true;
 
+		var m = getModifier(e);
 
+		if (!m) return true;
 
+		var k = getKey(e);
 
+		if (!k) return true;
 
+		switch(k) {
 
+			case options.showGridKey:
 
+				if (!overlayOn) {
 
+					showOverlay();
 
+					overlayOn = true;
 
+				}
 
+				else if (sticky) {
 
+					overlay.hide();
 
+					overlayOn = false;
 
+					sticky = false;
 
+					saveState();
 
+				}
 
+				break;
 
+			case options.holdGridKey:
 
+				if (overlayOn && !sticky) {
 
+					// Turn sticky overlay on
 
+					sticky = true;
 
+					saveState();
 
+				}
 
+				break;
 
+			case options.foregroundKey:
 
+				if (overlayOn) {
 
+					// Toggle sticky overlay z-index
 
+					if (overlay.css('z-index') == overlayZForeground) {
 
+						overlay.css('z-index', overlayZBackground);
 
+						overlayZState = 'B';
 
+					}
 
+					else {
 
+						overlay.css('z-index', overlayZForeground);
 
+						overlayZState = 'F';
 
+					}
 
+					saveState();
 
+				}
 
+				break;
 
+			case options.jumpGridsKey:
 
+				if (overlayOn && (options.numberOfGrids > 1)) {
 
+					// Cycle through the available grids
 
+					overlay.removeClass(options.classPrefix + classNumber);
 
+					classNumber++;
 
+					if (classNumber > options.numberOfGrids) classNumber = 1;
 
+					overlay.addClass(options.classPrefix + classNumber);
 
+					showOverlay();
 
+					if (/webkit/.test( navigator.userAgent.toLowerCase() )) {
 
+						forceRepaint();
 
+					}
 
+					saveState();
 
+				}
 
+				break;
 
+		}
 
+	}
 
+
 
+	function keyupHandler(e) {
 
+		var m = getModifier(e);
 
+		if (!m) return true;
 
+		var k = getKey(e);
 
+		if (!k) return true;
 
+		if ((k == options.showGridKey) && !sticky) {
 
+			overlay.hide();
 
+			overlayOn = false;
 
+		}
 
+	}
 
+
 
+	/**
 
+	 * Cookie functions
 
+	 *
 
+	 * By Peter-Paul Koch:
 
+	 * http://www.quirksmode.org/js/cookies.html
 
+	 */
 
+	function createCookie(name,value,days) {
 
+		if (days) {
 
+			var date = new Date();
 
+			date.setTime(date.getTime()+(days*24*60*60*1000));
 
+			var expires = "; expires="+date.toGMTString();
 
+		}
 
+		else var expires = "";
 
+		document.cookie = name+"="+value+expires+"; path=/";
 
+	}
 
+
 
+	function readCookie(name) {
 
+		var nameEQ = name + "=";
 
+		var ca = document.cookie.split(';');
 
+		for(var i=0;i < ca.length;i++) {
 
+			var c = ca[i];
 
+			while (c.charAt(0)==' ') c = c.substring(1,c.length);
 
+			if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
 
+		}
 
+		return null;
 
+	}
 
+
 
+	function eraseCookie(name) {
 
+		createCookie(name,"",-1);
 
+	}
 
+
 
+	/**
 
+	 * Forces a repaint (because WebKit has issues)
 
+	 * http://www.sitepoint.com/forums/showthread.php?p=4538763
 
+	 * http://www.phpied.com/the-new-game-show-will-it-reflow/
 
+	 */
 
+	function forceRepaint() {
 
+		var ss = document.styleSheets[0];
 
+		try {
 
+			ss.addRule('.xxxxxx', 'position: relative');
 
+			ss.removeRule(ss.rules.length - 1);
 
+		} catch(e){}
 
+	}
 
+
 
+}
 
+
 
+
 
+/**
 
+ * You can call hashgrid from your own code, but it's loaded here as
 
+ * an example for your convenience.
 
+ */
 
+$(document).ready(function() {
 
+
 
+	var grid = new hashgrid({
 
+		numberOfGrids: 2
 
+	});
 
+
 
+});
 
diff --git a/less/docs.less b/less/docs.less
 
--- a/less/docs.less
 
+++ b/less/docs.less
 
@@ -1,3 +1,10 @@
 
+body {
 
+  #gradient > .vertical-three-colors(#eee, #fff, 0.15, #fff);
 
+  background-attachment: fixed;
 
+  background-position: 0 40px;
 
+  position: relative;
 
+}
 
+
 
 // Give us some love
 
 header,
 
 section,
 
@@ -77,3 +84,30 @@
 
 section {
 
   margin-bottom: 40px;
 
 }
 
+
 
+// Hashgrid grid
 
+/**
 
+ * Grid
 
+ */
 
+#grid {
 
+  width: 980px;
 
+  position: absolute;
 
+  top: 0;
 
+  left: 50%;
 
+  margin-left: -490px;
 
+}
 
+#grid div.vert {
 
+  width: 39px;
 
+  border: solid darkturquoise;
 
+  border-width: 0 1px;
 
+  margin-right: 19px;
 
+}
 
+#grid div.vert.first-line {
 
+  margin-left: 19px;
 
+}
 
+#grid div.horiz {
 
+  height: 19px;
 
+  border-bottom: 1px dotted rgba(255,0,0,.25);
 
+  margin: 0;
 
+  padding: 0;
 
+}
 
diff --git a/less/scaffolding.less b/less/scaffolding.less
 
--- a/less/scaffolding.less
 
+++ b/less/scaffolding.less
 
@@ -7,7 +7,7 @@
 
 -------------------------------------------------- */
 
 
 
 div.row {
 
-  .clearfix;
 
+  .clearfix();
 
   div.span1   { .columns(1); }
 
   div.span2   { .columns(2); }
 
   div.span3   { .columns(3); }
 
@@ -34,8 +34,6 @@
 
   background-color: #fff;
 
 }
 
 body {
 
-  #gradient > .vertical-three-colors(#eee, #fff, 0.25, #fff);
 
-  background-attachment: fixed;
 
   margin: 0;
 
   .sans-serif(normal,14px,20px);
 
   color: @gray;
 
diff --git a/readme.markdown b/readme.markdown
 
--- a/readme.markdown
 
+++ b/readme.markdown
 
@@ -1,13 +1,4 @@
 
 TODOS
 
 
 
-* Write "Using Twitter BP" section
 
-** Two ways to use: LESS.js or compiled
 
-** Not meant to be 100% bulletproof, but is 90% bulletproof (stats?)
 
-** Advanced framework for fast prototyping, internal app development, bootstraping new websites
 
-** Can be easily modified to provide more legacy support
 
-
 
-* Add grid examples back in
 
 * Cross browser checks? Show this anywhere?
 
-* Add layouts section back in
 
-
 
-* Point JS libraries to public library links instead of within the repo
 
\ No newline at end of file
 
+* Add layouts section back in
 
\ No newline at end of file
rhodecode/tests/fixtures/hg_diff_chmod_and_mod_single_binary_file.diff
Show inline comments
 
new file 100644
 
diff --git a/gravatar.png b/gravatar.png
 
old mode 100644
 
new mode 100755
 
index 54d2d129e3372c45a4c68a742e269864222e99ae..0c1d65410bd51fdff935443e01931c9fc3ad7def
 
GIT binary patch
 
literal 740
 
zc%17D@N?(olHy`uVBq!ia0vp^dx3Z#2NRHduQ>B0kYX$ja(7}_cTVOdki%Kv5n0T@
 
zz%2~Ij105pNB{-dOFVsD*>AH+2@7*F&3eGWz`&H|>EaktaqI0JMZd!V5(hqpU(%ki
 
zz<{N_Dc~^g1GeA{8S#b#jBU<`Ij0FINT{SO2`=WI*Kku%Kte?~QvUtk`~FuatbIH`
 
zU*0#<bY2fr$(Coz4(2b|?k<qNz`M&4#A?WcvPzmkEJg29#pjiUe<s|iw%c!Ml>foD
 
zHLr#RtfPb(q61ZL^D3dLy27sS9LG1ks-ApS8e$A1NIi%J(b=2_we?fTsn6Zh<#UVP
 
z|9tiF%$|eaMRt7@#I6)<9go)1-`(@)=Q(p!@3vNKDKmRt(o@-7dHLx1lNY~Ad}g~_
 
za{b)1``zCC$!DbBm~WE-irmOC$k!;|zwzb_^B2daEtWo!yMN=;86Y!nbQtDu5Wn34
 
zbmZh?b(0@$%sb%=43(hD^~>9*yZ+6yNtdtp|J$sq{^jrc-CO$Jn%~HOWByJ0bK9TY
 
zg@4_kydS*R^JDe{4Z08FKRNbQ`g%Ud?r+k+d3UblwmiT6sL83<_jKRAJ-Pr-V6<tp
 
z?epIfY2R5iFZ#Ccl<T?EX78;p`&F5H??E-!-5(q%k>Y5MY!uto=R2=&d?)y?yZLYL
 
zJ^SCEe$Bh{bKS3dDW9<fAX0p_*<5X%lMalj*{B8|p0JPMtzFP9%Uvy?WXj;_>gTe~
 
RDWNH`I3+QqIFSoR0|1y<SJMCh
 
\ No newline at end of file
rhodecode/tests/fixtures/hg_diff_del_single_binary_file.diff
Show inline comments
 
diff --git a/US Warszawa.jpg b/US Warszawa.jpg
 
deleted file mode 100755
 
Binary file US Warszawa.jpg has changed
 
index f76dd238ade08917e6712764a16a22005a50573d..0000000000000000000000000000000000000000
 
GIT binary patch
 
literal 0
 
Hc$@<O00001
rhodecode/tests/fixtures/hg_diff_mod_file_and_rename.diff
Show inline comments
 
new file 100644
 
diff --git a/README b/README.rst
 
rename from README
 
rename to README.rst
 
--- a/README
 
+++ b/README.rst
 
@@ -1,1 +1,4 @@
 
 readme2
 
+line 1
 
+ line2
 
+
 
\ No newline at end of file
rhodecode/tests/fixtures/hg_diff_mod_single_file_and_rename_and_chmod.diff
Show inline comments
 
new file 100644
 
diff --git a/README.rst b/README
 
old mode 100755
 
new mode 100644
 
rename from README.rst
 
rename to README
 
--- a/README.rst
 
+++ b/README
 
@@ -1,4 +1,7 @@
 
 readme2
 
 line 1
 
  line2
 

	
 
+line 1
 
+ line2
 
+
 
\ No newline at end of file
rhodecode/tests/fixtures/hg_diff_rename_and_chmod_file.diff
Show inline comments
 
new file 100644
 
diff --git a/README.rst b/README
 
old mode 100644
 
new mode 100755
 
rename from README.rst
 
rename to README
 
\ No newline at end of file
rhodecode/tests/models/test_diff_parsers.py
Show inline comments
 
from __future__ import with_statement
 
import os
 
import unittest
 
from rhodecode.tests import *
 
from rhodecode.lib.diffs import DiffProcessor, NEW_FILENODE, DEL_FILENODE, \
 
    MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE
 
    MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
 

	
 
dn = os.path.dirname
 
FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'fixtures')
 

	
 
DIFF_FIXTURES = {
 
    'hg_diff_add_single_binary_file.diff': [
 
        (u'US Warszawa.jpg', 'A', ['b', NEW_FILENODE]),
 
        ('US Warszawa.jpg', 'A',
 
         {'added': 0,
 
          'deleted': 0,
 
          'binary': True,
 
          'ops': {NEW_FILENODE: 'new file 100755',
 
                  BIN_FILENODE: 'binary diff not shown'}}),
 
    ],
 
    'hg_diff_mod_single_binary_file.diff': [
 
        (u'US Warszawa.jpg', 'M', ['b', MOD_FILENODE]),
 
        ('US Warszawa.jpg', 'M',
 
         {'added': 0,
 
          'deleted': 0,
 
          'binary': True,
 
          'ops': {MOD_FILENODE: 'modified file',
 
                  BIN_FILENODE: 'binary diff not shown'}}),
 
    ],
 

	
 
    'hg_diff_mod_single_file_and_rename_and_chmod.diff': [
 
        ('README', 'M',
 
         {'added': 3,
 
          'deleted': 0,
 
          'binary': False,
 
          'ops': {MOD_FILENODE: 'modified file',
 
                  RENAMED_FILENODE: 'file renamed from README.rst to README',
 
                  CHMOD_FILENODE: 'modified file chmod 100755 => 100644'}}),
 
    ],
 
    'hg_diff_rename_and_chmod_file.diff': [
 
        ('README', 'M',
 
         {'added': 3,
 
          'deleted': 0,
 
          'binary': False,
 
          'ops': {MOD_FILENODE: 'modified file',
 
                  BIN_FILENODE: 'binary diff not shown'}}),
 
    ],
 
    'hg_diff_del_single_binary_file.diff': [
 
        (u'US Warszawa.jpg', 'D', ['b', DEL_FILENODE]),
 
        ('US Warszawa.jpg', 'D',
 
         {'added': 0,
 
          'deleted': 0,
 
          'binary': True,
 
          'ops': {DEL_FILENODE: 'deleted file',
 
                  BIN_FILENODE: 'binary diff not shown'}}),
 
    ],
 
    'hg_diff_chmod_and_mod_single_binary_file.diff': [
 
        ('gravatar.png', 'M',
 
         {'added': 0,
 
          'deleted': 0,
 
          'binary': True,
 
          'ops': {CHMOD_FILENODE: 'modified file chmod 100644 => 100755',
 
                  BIN_FILENODE: 'binary diff not shown'}}),
 
    ],
 
    'hg_diff_chmod.diff': [
 
        ('file', 'M',
 
         {'added': 0,
 
          'deleted': 0,
 
          'binary': True,
 
          'ops': {CHMOD_FILENODE: 'modified file chmod 100755 => 100644'}}),
 
    ],
 
    'hg_diff_rename_file.diff': [
 
        ('file_renamed', 'M',
 
         {'added': 0,
 
          'deleted': 0,
 
          'binary': True,
 
          'ops': {RENAMED_FILENODE: 'file renamed from file to file_renamed'}}),
 
    ],
 
    'hg_diff_rename_and_chmod_file.diff': [
 
        ('README', 'M',
 
         {'added': 0,
 
          'deleted': 0,
 
          'binary': True,
 
          'ops': {CHMOD_FILENODE: 'modified file chmod 100644 => 100755',
 
                  RENAMED_FILENODE: 'file renamed from README.rst to README'}}),
 
    ],
 
    'hg_diff_binary_and_normal.diff': [
 
        (u'img/baseline-10px.png', 'A', ['b', NEW_FILENODE]),
 
        (u'js/jquery/hashgrid.js', 'A', [340, 0]),
 
        (u'index.html',            'M', [3, 2]),
 
        (u'less/docs.less',        'M', [34, 0]),
 
        (u'less/scaffolding.less', 'M', [1, 3]),
 
        (u'readme.markdown',       'M', [1, 10]),
 
        (u'img/baseline-20px.png', 'D', ['b', DEL_FILENODE]),
 
        (u'js/global.js',          'D', [0, 75])
 
    ],
 
    'hg_diff_chmod.diff': [
 
        (u'file', 'M', ['b', CHMOD_FILENODE]),
 
    ],
 
    'hg_diff_rename_file.diff': [
 
        (u'file_renamed', 'M', ['b', RENAMED_FILENODE]),
 
        ('img/baseline-10px.png', 'A',
 
         {'added': 0,
 
          'deleted': 0,
 
          'binary': True,
 
          'ops': {NEW_FILENODE: 'new file 100644',
 
                  BIN_FILENODE: 'binary diff not shown'}}),
 
        ('js/jquery/hashgrid.js', 'A',
 
         {'added': 340,
 
          'deleted': 0,
 
          'binary': False,
 
          'ops': {NEW_FILENODE: 'new file 100755'}}),
 
        ('index.html', 'M',
 
         {'added': 3,
 
          'deleted': 2,
 
          'binary': False,
 
          'ops': {MOD_FILENODE: 'modified file'}}),
 
        ('less/docs.less', 'M',
 
         {'added': 34,
 
          'deleted': 0,
 
          'binary': False,
 
          'ops': {MOD_FILENODE: 'modified file'}}),
 
        ('less/scaffolding.less', 'M',
 
         {'added': 1,
 
          'deleted': 3,
 
          'binary': False,
 
          'ops': {MOD_FILENODE: 'modified file'}}),
 
        ('readme.markdown', 'M',
 
         {'added': 1,
 
          'deleted': 10,
 
          'binary': False,
 
          'ops': {MOD_FILENODE: 'modified file'}}),
 
        ('img/baseline-20px.png', 'D',
 
         {'added': 0,
 
          'deleted': 0,
 
          'binary': True,
 
          'ops': {DEL_FILENODE: 'deleted file',
 
                  BIN_FILENODE: 'binary diff not shown'}}),
 
        ('js/global.js', 'D',
 
         {'added': 0,
 
          'deleted': 75,
 
          'binary': False,
 
          'ops': {DEL_FILENODE: 'deleted file'}})
 
    ],
 
    'git_diff_chmod.diff': [
 
        (u'work-horus.xls', 'M', ['b', CHMOD_FILENODE]),
 
        ('work-horus.xls', 'M',
 
         {'added': 0,
 
          'deleted': 0,
 
          'binary': True,
 
          'ops': {CHMOD_FILENODE: 'modified file chmod 100644 => 100755'}})
 
    ],
 
    'git_diff_rename_file.diff': [
 
        (u'file.xls', 'M', ['b', RENAMED_FILENODE]),
 
        ('file.xls', 'M',
 
         {'added': 0,
 
          'deleted': 0,
 
          'binary': True,
 
          'ops': {RENAMED_FILENODE: 'file renamed from work-horus.xls to file.xls'}})
 
    ],
 
    'git_diff_mod_single_binary_file.diff': [
 
        ('US Warszawa.jpg', 'M', ['b', MOD_FILENODE])
 

	
 
        ('US Warszawa.jpg', 'M',
 
         {'added': 0,
 
          'deleted': 0,
 
          'binary': True,
 
          'ops': {MOD_FILENODE: 'modified file',
 
                  BIN_FILENODE: 'binary diff not shown'}})
 
    ],
 
    'git_diff_binary_and_normal.diff': [
 
        (u'img/baseline-10px.png', 'A', ['b', NEW_FILENODE]),
 
        (u'js/jquery/hashgrid.js', 'A', [340, 0]),
 
        (u'index.html',            'M', [3, 2]),
 
        (u'less/docs.less',        'M', [34, 0]),
 
        (u'less/scaffolding.less', 'M', [1, 3]),
 
        (u'readme.markdown',       'M', [1, 10]),
 
        (u'img/baseline-20px.png', 'D', ['b', DEL_FILENODE]),
 
        (u'js/global.js',          'D', [0, 75])
 
        ('img/baseline-10px.png', 'A',
 
         {'added': 0,
 
          'deleted': 0,
 
          'binary': True,
 
          'ops': {NEW_FILENODE: 'new file 100644',
 
                  BIN_FILENODE: 'binary diff not shown'}}),
 
        ('js/jquery/hashgrid.js', 'A',
 
         {'added': 340,
 
          'deleted': 0,
 
          'binary': False,
 
          'ops': {NEW_FILENODE: 'new file 100755'}}),
 
        ('index.html', 'M',
 
         {'added': 3,
 
          'deleted': 2,
 
          'binary': False,
 
          'ops': {MOD_FILENODE: 'modified file'}}),
 
        ('less/docs.less', 'M',
 
         {'added': 34,
 
          'deleted': 0,
 
          'binary': False,
 
          'ops': {MOD_FILENODE: 'modified file'}}),
 
        ('less/scaffolding.less', 'M',
 
         {'added': 1,
 
          'deleted': 3,
 
          'binary': False,
 
          'ops': {MOD_FILENODE: 'modified file'}}),
 
        ('readme.markdown', 'M',
 
         {'added': 1,
 
          'deleted': 10,
 
          'binary': False,
 
          'ops': {MOD_FILENODE: 'modified file'}}),
 
        ('img/baseline-20px.png', 'D',
 
         {'added': 0,
 
          'deleted': 0,
 
          'binary': True,
 
          'ops': {DEL_FILENODE: 'deleted file',
 
                  BIN_FILENODE: 'binary diff not shown'}}),
 
        ('js/global.js', 'D',
 
         {'added': 0,
 
          'deleted': 75,
 
          'binary': False,
 
          'ops': {DEL_FILENODE: 'deleted file'}}),
 
    ],
 
    'diff_with_diff_data.diff': [
 
        (u'vcs/backends/base.py', 'M', [18, 2]),
 
        (u'vcs/backends/git/repository.py', 'M', [46, 15]),
 
        (u'vcs/backends/hg.py', 'M', [22, 3]),
 
        (u'vcs/tests/test_git.py', 'M', [5, 5]),
 
        (u'vcs/tests/test_repository.py', 'M', [174, 2])
 
        ('vcs/backends/base.py', 'M',
 
         {'added': 18,
 
          'deleted': 2,
 
          'binary': False,
 
          'ops': {MOD_FILENODE: 'modified file'}}),
 
        ('vcs/backends/git/repository.py', 'M',
 
         {'added': 46,
 
          'deleted': 15,
 
          'binary': False,
 
          'ops': {MOD_FILENODE: 'modified file'}}),
 
        ('vcs/backends/hg.py', 'M',
 
         {'added': 22,
 
          'deleted': 3,
 
          'binary': False,
 
          'ops': {MOD_FILENODE: 'modified file'}}),
 
        ('vcs/tests/test_git.py', 'M',
 
         {'added': 5,
 
          'deleted': 5,
 
          'binary': False,
 
          'ops': {MOD_FILENODE: 'modified file'}}),
 
        ('vcs/tests/test_repository.py', 'M',
 
         {'added': 174,
 
          'deleted': 2,
 
          'binary': False,
 
          'ops': {MOD_FILENODE: 'modified file'}}),
 
    ],
 
#    'large_diff.diff': [
 
#
 
#         ('.hgignore', 'A', {'deleted': 0, 'binary': False, 'added': 3, 'ops': {1: 'new file 100644'}}),
 
#         ('MANIFEST.in', 'A', {'deleted': 0, 'binary': False, 'added': 3, 'ops': {1: 'new file 100644'}}),
 
#         ('README.txt', 'A', {'deleted': 0, 'binary': False, 'added': 19, 'ops': {1: 'new file 100644'}}),
 
#         ('development.ini', 'A', {'deleted': 0, 'binary': False, 'added': 116, 'ops': {1: 'new file 100644'}}),
 
#         ('docs/index.txt', 'A', {'deleted': 0, 'binary': False, 'added': 19, 'ops': {1: 'new file 100644'}}),
 
#         ('ez_setup.py', 'A', {'deleted': 0, 'binary': False, 'added': 276, 'ops': {1: 'new file 100644'}}),
 
#         ('hgapp.py', 'A', {'deleted': 0, 'binary': False, 'added': 26, 'ops': {1: 'new file 100644'}}),
 
#         ('hgwebdir.config', 'A', {'deleted': 0, 'binary': False, 'added': 21, 'ops': {1: 'new file 100644'}}),
 
#         ('pylons_app.egg-info/PKG-INFO', 'A', {'deleted': 0, 'binary': False, 'added': 10, 'ops': {1: 'new file 100644'}}),
 
#         ('pylons_app.egg-info/SOURCES.txt', 'A', {'deleted': 0, 'binary': False, 'added': 33, 'ops': {1: 'new file 100644'}}),
 
#         ('pylons_app.egg-info/dependency_links.txt', 'A', {'deleted': 0, 'binary': False, 'added': 1, 'ops': {1: 'new file 100644'}}),
 
#         #TODO:
 
#    ],
 

	
 

	
 
}
 

	
 

	
 
def _diff_checker(fixture):
 
    with open(os.path.join(FIXTURES, fixture)) as f:
 
class DiffLibTest(unittest.TestCase):
 

	
 
    @parameterized.expand([(x,) for x in DIFF_FIXTURES])
 
    def test_diff(self, diff_fixture):
 

	
 
        with open(os.path.join(FIXTURES, diff_fixture)) as f:
 
        diff = f.read()
 

	
 
    diff_proc = DiffProcessor(diff)
 
    diff_proc_d = diff_proc.prepare()
 
    data = [(x['filename'], x['operation'], x['stats']) for x in diff_proc_d]
 
    expected_data = DIFF_FIXTURES[fixture]
 

	
 
    assert expected_data == data
 

	
 

	
 
def test_parse_diff():
 
    for fixture in DIFF_FIXTURES:
 
        yield _diff_checker, fixture
 
        expected_data = DIFF_FIXTURES[diff_fixture]
 
        self.assertListEqual(expected_data, data)
0 comments (0 inline, 0 general)