Changeset - d42d7b2a3b2f
[Not reviewed]
default
0 4 0
Mads Kiilerich - 11 years ago 2015-01-21 17:35:11
madski@unity3d.com
pullrequests: reintroduce 'update' functionality as separate submit button in the big form
4 files changed with 42 insertions and 34 deletions:
0 comments (0 inline, 0 general)
kallithea/controllers/pullrequests.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# 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/>.
 
"""
 
kallithea.controllers.pullrequests
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
pull requests controller for Kallithea for initializing pull requests
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: May 7, 2012
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import logging
 
import traceback
 
import formencode
 
import re
 

	
 
from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest
 

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

	
 
from kallithea.lib.compat import json
 
from kallithea.lib.base import BaseRepoController, render
 
from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
 
    NotAnonymous
 
from kallithea.lib.helpers import Page
 
from kallithea.lib import helpers as h
 
from kallithea.lib import diffs
 
from kallithea.lib.utils import action_logger, jsonify
 
from kallithea.lib.vcs.utils import safe_str
 
from kallithea.lib.vcs.exceptions import EmptyRepositoryError
 
from kallithea.lib.diffs import LimitedDiffContainer
 
from kallithea.model.db import  PullRequest, ChangesetStatus, ChangesetComment,\
 
    PullRequestReviewers
 
from kallithea.model.pull_request import PullRequestModel
 
from kallithea.model.meta import Session
 
from kallithea.model.repo import RepoModel
 
from kallithea.model.comment import ChangesetCommentsModel
 
from kallithea.model.changeset_status import ChangesetStatusModel
 
from kallithea.model.forms import PullRequestForm, PullRequestPostForm
 
from kallithea.lib.utils2 import safe_int
 
from kallithea.controllers.changeset import _ignorews_url,\
 
    _context_url, get_line_ctx, get_ignore_ws
 
from kallithea.controllers.compare import CompareController
 
from kallithea.lib.graphmod import graph_data
 

	
 
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.user_groups_array = repo_model.get_user_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)
 
            # a revset not restricting to merge() would be better
 
            # (especially because it would get the branch point)
 
            # ... but is currently too expensive
 
            # including branches of children could be nice too
 
            peerbranches = set()
 
            for i in repo._repo.revs(
 
                "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)), -rev)",
 
                branch_rev, branch_rev):
 
                abranch = repo.get_changeset(i).branch
 
                if abranch not in peerbranches:
 
                    n = 'branch:%s:%s' % (abranch, repo.get_changeset(abranch).raw_id)
 
                    peers.append((n, abranch))
 
                    peerbranches.add(abranch)
 

	
 
        selected = None
 
        tiprev = repo.tags.get('tip')
 
        tipbranch = None
 

	
 
        branches = []
 
        for abranch, branchrev in repo.branches.iteritems():
 
            n = 'branch:%s:%s' % (abranch, branchrev)
 
            desc = abranch
 
            if branchrev == tiprev:
 
                tipbranch = abranch
 
                desc = '%s (current tip)' % desc
 
            branches.append((n, desc))
 
            if rev == branchrev:
 
                selected = n
 
            if branch == abranch:
 
                if not rev:
 
                    selected = n
 
                branch = None
 
        if branch:  # branch not in list - it is probably closed
 
            branchrev = repo.closed_branches.get(branch)
 
            if branchrev:
 
                n = 'branch:%s:%s' % (branch, branchrev)
 
                branches.append((n, _('%s (closed)') % branch))
 
                selected = n
 
                branch = None
 
            if branch:
 
                log.error('branch %r not found in %s', branch, repo)
 

	
 
        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():
 
            if tag == 'tip':
 
                continue
 
            n = 'tag:%s:%s' % (tag, tagrev)
 
            tags.append((n, tag))
 
            if rev == tagrev:
 
                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]
 

	
 
        # prio 4: tip revision
 
        if not selected:
 
            if h.is_hg(repo):
 
                if tipbranch:
 
                    selected = 'branch:%s:%s' % (tipbranch, tiprev)
 
                else:
 
                    selected = 'tag:null:' + repo.EMPTY_CHANGESET
 
                    tags.append((selected, 'null'))
 
            else:
 
                if 'master' in repo.branches:
 
                    selected = 'branch:master:%s' % repo.branches['master']
 
                else:
 
                    k, v = repo.branches.items()[0]
 
                    selected = 'branch:%s:%s' % (k, v)
 

	
 
        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):
 
        if pull_request.is_closed():
 
            return False
 

	
 
        owner = self.authuser.user_id == pull_request.user_id
 
        reviewer = self.authuser.user_id in [x.user_id for x in
 
                                                   pull_request.reviewers]
 
        return self.authuser.admin or owner or reviewer
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def show_all(self, repo_name):
 
        c.from_ = request.GET.get('from_') or ''
 
        c.closed = request.GET.get('closed') or ''
 
        c.pull_requests = PullRequestModel().get_all(repo_name, from_=c.from_, closed=c.closed)
 
        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()
 
    def show_my(self): # my_account_my_pullrequests
 
        c.show_closed = request.GET.get('pr_show_closed')
 
        return render('/pullrequests/pullrequest_show_my.html')
 

	
 
    @NotAnonymous()
 
    def show_my_data(self):
 
        c.show_closed = request.GET.get('pr_show_closed')
 

	
 
        def _filter(pr):
 
            s = sorted(pr, key=lambda o: o.created_on, reverse=True)
 
            if not c.show_closed:
 
                s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
 
            return s
 

	
 
        c.my_pull_requests = _filter(PullRequest.query()\
 
                                .filter(PullRequest.user_id ==
 
                                        self.authuser.user_id)\
 
                                .all())
 

	
 
        c.participate_in_pull_requests = _filter(PullRequest.query()\
 
                                .join(PullRequestReviewers)\
 
                                .filter(PullRequestReviewers.user_id ==
 
                                        self.authuser.user_id)\
 
                                                 )
 

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

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def index(self):
 
        org_repo = c.db_repo
 
        org_scm_instance = org_repo.scm_instance
 
        try:
 
            org_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.cs_repos = [(org_repo.repo_name, org_repo.repo_name)]
 
        c.default_cs_repo = org_repo.repo_name
 
        c.cs_refs, c.default_cs_ref = self._get_repo_refs(org_scm_instance, rev=org_rev, branch=branch)
 

	
 
        # add org repo to other so we can open pull request against peer branches on itself
 
        c.a_repos = [(org_repo.repo_name, '%s (self)' % org_repo.repo_name)]
 

	
 
        # add parent of this fork also and select it
 
        if org_repo.parent:
 
            c.a_repos.append((org_repo.parent.repo_name, '%s (parent)' % org_repo.parent.repo_name))
 
            c.a_repo = org_repo.parent
 
            c.a_refs, c.default_a_ref = self._get_repo_refs(org_repo.parent.scm_instance)
 
        else:
 
            c.a_repo = org_repo
 
            c.a_refs, c.default_a_ref = self._get_repo_refs(org_scm_instance) # without rev and branch
 

	
 
        # 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:
 
            c.a_repos.append((fork.repo_name, fork.repo_name))
 

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

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    @jsonify
 
    def repo_info(self, repo_name):
 
        repo = RepoModel()._get_repo(repo_name)
 
        refs, selected_ref = self._get_repo_refs(repo.scm_instance)
 
        return {
 
            'description': repo.description.split('\n', 1)[0],
 
            'selected_ref': selected_ref,
 
            'refs': refs,
 
            '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)),
 
            }
 

	
 
    @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())
 
            log.error(str(errors))
 
            msg = _('Error creating pull request: %s') % errors.msg
 
            h.flash(msg, 'error')
 
            raise HTTPBadRequest
 

	
 
        # heads up: org and other might seem backward here ...
 
        org_repo_name = _form['org_repo']
 
        org_ref = _form['org_ref'] # will have merge_rev as rev but symbolic name
 
        org_repo = RepoModel()._get_repo(org_repo_name)
 
        (org_ref_type,
 
         org_ref_name,
 
         org_rev) = org_ref.split(':')
 

	
 
        other_repo_name = _form['other_repo']
 
        other_ref = _form['other_ref'] # will have symbolic name and head revision
 
        other_repo = RepoModel()._get_repo(other_repo_name)
 
        (other_ref_type,
 
         other_ref_name,
 
         other_rev) = other_ref.split(':')
 

	
 
        cs_ranges, _cs_ranges_not, ancestor_rev = \
 
            CompareController._get_changesets(org_repo.scm_instance.alias,
 
                                              other_repo.scm_instance, other_rev, # org and other "swapped"
 
                                              org_repo.scm_instance, org_rev,
 
                                              )
 
        revisions = [cs.raw_id for cs in cs_ranges]
 

	
 
        # hack: ancestor_rev is not an other_rev but we want to show the
 
        # requested destination and have the exact ancestor
 
        other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, ancestor_rev)
 

	
 
        reviewers = _form['review_members']
 

	
 
        title = _form['pullrequest_title']
 
        if not title:
 
            title = '%s#%s to %s#%s' % (org_repo_name, h.short_ref(org_ref_type, org_ref_name),
 
                                        other_repo_name, h.short_ref(other_ref_type, other_ref_name))
 
        description = _form['pullrequest_desc'].strip() or _('No description')
 
        try:
 
            pull_request = PullRequestModel().create(
 
                self.authuser.user_id, org_repo_name, org_ref, other_repo_name,
 
                other_ref, revisions, reviewers, title, description
 
            )
 
            Session().commit()
 
            h.flash(_('Successfully opened new pull request'),
 
                    category='success')
 
        except Exception:
 
            h.flash(_('Error occurred while creating pull request'),
 
                    category='error')
 
            log.error(traceback.format_exc())
 
            return redirect(url('pullrequest_home', repo_name=repo_name))
 

	
 
        return redirect(pull_request.url())
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def copy_update(self, repo_name, pull_request_id):
 
        old_pull_request = PullRequest.get_or_404(pull_request_id)
 
        assert old_pull_request.other_repo.repo_name == repo_name
 
        if old_pull_request.is_closed():
 
            raise HTTPForbidden()
 

	
 
    def create_update(self, old_pull_request, updaterev, title, description, reviewers_ids):
 
        org_repo = RepoModel()._get_repo(old_pull_request.org_repo.repo_name)
 
        org_ref_type, org_ref_name, org_rev = old_pull_request.org_ref.split(':')
 
        updaterev = request.POST.get('updaterev')
 
        if updaterev:
 
            new_org_rev = self._get_ref_rev(org_repo, 'rev', updaterev)
 
        else:
 
            # assert org_ref_type == 'branch', org_ref_type # TODO: what if not?
 
            new_org_rev = self._get_ref_rev(org_repo, org_ref_type, org_ref_name)
 

	
 
        other_repo = RepoModel()._get_repo(old_pull_request.other_repo.repo_name)
 
        other_ref_type, other_ref_name, other_rev = old_pull_request.other_ref.split(':') # other_rev is ancestor
 
        #assert other_ref_type == 'branch', other_ref_type # TODO: what if not?
 
        new_other_rev = self._get_ref_rev(other_repo, other_ref_type, other_ref_name)
 

	
 
        cs_ranges, _cs_ranges_not, ancestor_rev = CompareController._get_changesets(org_repo.scm_instance.alias,
 
            other_repo.scm_instance, new_other_rev, # org and other "swapped"
 
            org_repo.scm_instance, new_org_rev)
 

	
 
        old_revisions = set(old_pull_request.revisions)
 
        revisions = [cs.raw_id for cs in cs_ranges]
 
        new_revisions = [r for r in revisions if r not in old_revisions]
 
        lost = old_revisions.difference(revisions)
 

	
 
        infos = ['This is an update of %s "%s".' %
 
                 (h.canonical_url('pullrequest_show', repo_name=old_pull_request.other_repo.repo_name,
 
                      pull_request_id=pull_request_id),
 
                      pull_request_id=old_pull_request.pull_request_id),
 
                  old_pull_request.title)]
 

	
 
        if lost:
 
            infos.append(_('Missing changesets since the previous version:'))
 
            for r in old_pull_request.revisions:
 
                if r in lost:
 
                    rev_desc = org_repo.get_changeset(r).message.split('\n')[0]
 
                    infos.append('  %s "%s"' % (h.short_id(r), rev_desc))
 

	
 
        if new_revisions:
 
            infos.append(_('New changesets on %s %s since the previous version:') % (org_ref_type, org_ref_name))
 
            for r in reversed(revisions):
 
                if r in new_revisions:
 
                    rev_desc = org_repo.get_changeset(r).message.split('\n')[0]
 
                    infos.append('  %s %s' % (h.short_id(r), h.shorter(rev_desc, 80)))
 

	
 
            if ancestor_rev == other_rev:
 
                infos.append(_("Ancestor didn't change - show diff since previous version:"))
 
                infos.append(h.canonical_url('compare_url',
 
                                 repo_name=org_repo.repo_name, # other_repo is always same as repo_name
 
                                 org_ref_type='rev', org_ref_name=h.short_id(org_rev), # use old org_rev as base
 
                                 other_ref_type='rev', other_ref_name=h.short_id(new_org_rev),
 
                                 )) # note: linear diff, merge or not doesn't matter
 
            else:
 
                infos.append(_('This pull request is based on another %s revision and there is no simple diff.') % other_ref_name)
 
        else:
 
           infos.append(_('No changes found on %s %s since previous version.') % (org_ref_type, org_ref_name))
 
           # TODO: fail?
 

	
 
        # hack: ancestor_rev is not an other_ref but we want to show the
 
        # requested destination and have the exact ancestor
 
        new_other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, ancestor_rev)
 
        new_org_ref = '%s:%s:%s' % (org_ref_type, org_ref_name, new_org_rev)
 

	
 
        reviewers_ids = [r.user_id for r in old_pull_request.reviewers]
 

	
 
        try:
 
            old_title, old_v = re.match(r'(.*)\(v(\d+)\)\s*$', old_pull_request.title).groups()
 
            title, old_v = re.match(r'(.*)\(v(\d+)\)\s*$', title).groups()
 
            v = int(old_v) + 1
 
        except (AttributeError, ValueError):
 
            old_title = old_pull_request.title
 
            v = 2
 
        title = '%s (v%s)' % (old_title.strip(), v)
 
        title = '%s (v%s)' % (title.strip(), v)
 

	
 
        # using a mail-like separator, insert new update info at the top of the list
 
        descriptions = old_pull_request.description.replace('\r\n', '\n').split('\n-- \n', 1)
 
        descriptions = description.replace('\r\n', '\n').split('\n-- \n', 1)
 
        description = descriptions[0].strip() + '\n\n-- \n' + '\n'.join(infos)
 
        if len(descriptions) > 1:
 
            description += '\n\n' + descriptions[1].strip()
 

	
 
        try:
 
            pull_request = PullRequestModel().create(
 
                self.authuser.user_id,
 
                old_pull_request.org_repo.repo_name, new_org_ref,
 
                old_pull_request.other_repo.repo_name, new_other_ref,
 
                revisions, reviewers_ids, title, description
 
            )
 
        except Exception:
 
            h.flash(_('Error occurred while creating pull request'),
 
                    category='error')
 
            log.error(traceback.format_exc())
 
            return redirect(old_pull_request.url())
 

	
 
        ChangesetCommentsModel().create(
 
            text=_('Closed, replaced by %s .') % h.canonical_url('pullrequest_show',
 
                                                   repo_name=old_pull_request.other_repo.repo_name,
 
                                                   pull_request_id=pull_request.pull_request_id),
 
            repo=old_pull_request.other_repo.repo_id,
 
            user=c.authuser.user_id,
 
            pull_request=pull_request_id,
 
            pull_request=old_pull_request.pull_request_id,
 
            closing_pr=True)
 
        PullRequestModel().close_pull_request(pull_request_id)
 
        PullRequestModel().close_pull_request(old_pull_request.pull_request_id)
 

	
 
        Session().commit()
 
        h.flash(_('Pull request update created'),
 
                category='success')
 

	
 
        return redirect(pull_request.url())
 

	
 
    # pullrequest_post for PR editing
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def post(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.authuser.user_id
 
        repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
 
        if not (h.HasPermissionAny('hg.admin') or repo_admin or owner):
 
            raise HTTPForbidden()
 

	
 
        _form = PullRequestPostForm()().to_python(request.POST)
 
        reviewers_ids = [int(s) for s in _form['review_members']]
 

	
 
        if _form['updaterev']:
 
            return self.create_update(pull_request,
 
                                      _form['updaterev'],
 
                                      _form['pullrequest_title'],
 
                                      _form['pullrequest_desc'],
 
                                      reviewers_ids)
 

	
 
        old_description = pull_request.description
 
        pull_request.title = _form['pullrequest_title']
 
        pull_request.description = _form['pullrequest_desc'].strip() or _('No description')
 
        PullRequestModel().mention_from_description(pull_request, old_description)
 

	
 
        reviewers_ids = [int(s) for s in _form['review_members']]
 
        PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
 

	
 
        Session().commit()
 
        h.flash(_('Pull request updated'), category='success')
 

	
 
        return redirect(pull_request.url())
 

	
 
    @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.authuser.user_id:
 
            PullRequestModel().delete(pull_request)
 
            Session().commit()
 
            h.flash(_('Successfully deleted pull request'),
 
                    category='success')
 
            return redirect(url('my_pullrequests'))
 
        raise HTTPForbidden()
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def show(self, repo_name, pull_request_id, extra=None):
 
        repo_model = RepoModel()
 
        c.users_array = repo_model.get_users_js()
 
        c.user_groups_array = repo_model.get_user_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()
 

	
 
        # 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
 
        c.cs_repo = c.pull_request.org_repo
 
        (c.cs_ref_type,
 
         c.cs_ref_name,
 
         c.cs_rev) = c.pull_request.org_ref.split(':')
 

	
 
        c.a_repo = c.pull_request.other_repo
 
        (c.a_ref_type,
 
         c.a_ref_name,
 
         c.a_rev) = c.pull_request.other_ref.split(':') # other_rev is ancestor
 

	
 
        org_scm_instance = c.cs_repo.scm_instance # property with expensive cache invalidation check!!!
 
        c.cs_repo = c.cs_repo
 
        c.cs_ranges = [org_scm_instance.get_changeset(x) for x in c.pull_request.revisions]
 
        c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ...
 
        revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
 
        c.jsdata = json.dumps(graph_data(org_scm_instance, revs))
 

	
 
        arevs = []
 
        c.cs_branch_name = c.cs_ref_name
 
        other_scm_instance = c.a_repo.scm_instance
 
        c.update_msg = ""
 
        c.update_msg_other = ""
 
        if org_scm_instance.alias == 'hg' and c.a_ref_name != 'ancestor':
 
            if c.cs_ref_type != 'branch':
 
                c.cs_branch_name = org_scm_instance.get_changeset(c.cs_ref_name).branch # use ref_type ?
 
            c.a_branch_name = c.a_ref_name
 
            if c.a_ref_type != 'branch':
 
                try:
 
                    c.a_branch_name = other_scm_instance.get_changeset(c.a_ref_name).branch # use ref_type ?
 
                except EmptyRepositoryError:
 
                    c.a_branch_name = 'null' # not a branch name ... but close enough
 
            # candidates: descendants of old head that are on the right branch
 
            #             and not are the old head itself ...
 
            #             and nothing at all if old head is a descendent of target ref name
 
            if other_scm_instance._repo.revs('present(%s)::&%s', c.cs_ranges[-1].raw_id, c.a_branch_name):
 
                c.update_msg = _('This pull request has already been merged to %s.') % c.a_branch_name
 
            else: # look for children of PR head on source branch in org repo
 
                arevs = org_scm_instance._repo.revs('%s:: & branch(%s) - %s',
 
                                                    revs[0], c.cs_branch_name, revs[0])
 
                if arevs:
 
                    arevs = sorted(arevs, reverse=True)
 
                    if c.pull_request.is_closed():
 
                        c.update_msg = _('This pull request has been closed and can not be updated with descendent changes on %s:') % c.cs_branch_name
 
                    else:
 
                        c.update_msg = _('This pull request can be updated with descendent changes on %s:') % c.cs_branch_name
 
                else:
 
                    c.update_msg = _('No changesets found for updating this pull request.')
 

	
 
            brevs = org_scm_instance._repo.revs('%s - %d - %ld', c.cs_branch_name, revs[0], arevs)
 
            if brevs:
 
                c.update_msg_other = _('Note: Branch %s has another head: %s.') % (c.cs_branch_name,
 
                    h.short_id(org_scm_instance.get_changeset((max(brevs))).raw_id))
 

	
 
        elif org_scm_instance.alias == 'git':
 
            c.update_msg = _("Git pull requests don't support updates yet.")
 

	
 
        c.available = [org_scm_instance.get_changeset(r) for r in arevs]
 

	
 
        raw_ids = [x.raw_id for x in c.cs_ranges]
 
        c.cs_comments = c.cs_repo.get_comments(raw_ids)
 
        c.statuses = c.cs_repo.statuses(raw_ids)
 

	
 
        ignore_whitespace = request.GET.get('ignorews') == '1'
 
        line_context = request.GET.get('context', 3)
 
        c.ignorews_url = _ignorews_url
 
        c.context_url = _context_url
 
        c.fulldiff = request.GET.get('fulldiff')
 
        diff_limit = self.cut_off_limit if not c.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'
 
                  % (c.a_rev, c.cs_rev, org_scm_instance.path))
 
        txtdiff = org_scm_instance.get_diff(rev1=safe_str(c.a_rev), rev2=safe_str(c.cs_rev),
 
                                      ignore_whitespace=ignore_whitespace,
 
                                      context=line_context)
 

	
 
        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']
 
            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=True,
 
                                              parsed_lines=[f])
 
            c.changes[fid] = [f['operation'], f['filename'], htmldiff]
 

	
 
        # inline comments
 
        c.inline_cnt = 0
 
        c.inline_comments = cc_model.get_inline_comments(
 
                                c.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.db_repo.repo_id,
 
                                           pull_request=pull_request_id)
 

	
 
        # (badly named) pull-request status calculation based on reviewer votes
 
        (c.pull_request_reviewers,
 
         c.pull_request_pending_reviewers,
 
         c.current_voting_result,
 
         ) = cs_model.calculate_pull_request_result(c.pull_request)
 
        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)
 

	
 
        status = 0
 
        close_pr = False
 
        allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
 
        if allowed_to_change_status:
 
            status = request.POST.get('changeset_status')
 
            close_pr = request.POST.get('save_close')
 
        text = request.POST.get('text', '').strip() or _('No comments.')
 
        if close_pr:
 
            text = _('Closing.') + '\n' + text
 

	
 
        comm = ChangesetCommentsModel().create(
 
            text=text,
 
            repo=c.db_repo.repo_id,
 
            user=c.authuser.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 allowed_to_change_status else None),
 
            closing_pr=close_pr
 
        )
 

	
 
        action_logger(self.authuser,
 
                      'user_commented_pull_request:%s' % pull_request_id,
 
                      c.db_repo, self.ip_addr, self.sa)
 

	
 
        if allowed_to_change_status:
 
            # get status if set !
 
            if status:
 
                ChangesetStatusModel().set_status(
 
                    c.db_repo.repo_id,
 
                    status,
 
                    c.authuser.user_id,
 
                    comm,
 
                    pull_request=pull_request_id
 
                )
 

	
 
            if close_pr:
 
                PullRequestModel().close_pull_request(pull_request_id)
 
                action_logger(self.authuser,
 
                              'user_closed_pull_request:%s' % pull_request_id,
 
                              c.db_repo, self.ip_addr, self.sa)
 

	
 
        Session().commit()
 

	
 
        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            return redirect(pull_request.url())
 

	
 
        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.authuser.user_id
 
        repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
 
        if h.HasPermissionAny('hg.admin') or repo_admin or owner:
 
            ChangesetCommentsModel().delete(comment=co)
 
            Session().commit()
 
            return True
 
        else:
 
            raise HTTPForbidden()
kallithea/model/forms.py
Show inline comments
 
@@ -124,401 +124,402 @@ def UserForm(edit=False, old_data={}):
 

	
 
def UserGroupForm(edit=False, old_data={}, available_members=[]):
 
    class _UserGroupForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 

	
 
        users_group_name = All(
 
            v.UnicodeString(strip=True, min=1, not_empty=True),
 
            v.ValidUserGroup(edit, old_data)
 
        )
 
        user_group_description = v.UnicodeString(strip=True, min=1,
 
                                                 not_empty=False)
 

	
 
        users_group_active = v.StringBoolean(if_missing=False)
 

	
 
        if edit:
 
            users_group_members = v.OneOf(
 
                available_members, hideList=False, testValueList=True,
 
                if_missing=None, not_empty=False
 
            )
 

	
 
    return _UserGroupForm
 

	
 

	
 
def RepoGroupForm(edit=False, old_data={}, available_groups=[],
 
                   can_create_in_root=False):
 
    class _RepoGroupForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 

	
 
        group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
 
                         v.SlugifyName(),
 
                         v.ValidRegex(msg=_('Name must not contain only digits'))(r'(?!^\d+$)^.+$'))
 
        group_description = v.UnicodeString(strip=True, min=1,
 
                                            not_empty=False)
 
        group_copy_permissions = v.StringBoolean(if_missing=False)
 

	
 
        if edit:
 
            #FIXME: do a special check that we cannot move a group to one of
 
            #its children
 
            pass
 
        group_parent_id = All(v.CanCreateGroup(can_create_in_root),
 
                              v.OneOf(available_groups, hideList=False,
 
                                      testValueList=True,
 
                                      if_missing=None, not_empty=True))
 
        enable_locking = v.StringBoolean(if_missing=False)
 
        chained_validators = [v.ValidRepoGroup(edit, old_data)]
 

	
 
    return _RepoGroupForm
 

	
 

	
 
def RegisterForm(edit=False, old_data={}):
 
    class _RegisterForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        username = All(
 
            v.ValidUsername(edit, old_data),
 
            v.UnicodeString(strip=True, min=1, not_empty=True)
 
        )
 
        password = All(
 
            v.ValidPassword(),
 
            v.UnicodeString(strip=False, min=6, not_empty=True)
 
        )
 
        password_confirmation = All(
 
            v.ValidPassword(),
 
            v.UnicodeString(strip=False, min=6, not_empty=True)
 
        )
 
        active = v.StringBoolean(if_missing=False)
 
        firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
 
        lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
 
        email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
 

	
 
        chained_validators = [v.ValidPasswordsMatch()]
 

	
 
    return _RegisterForm
 

	
 

	
 
def PasswordResetForm():
 
    class _PasswordResetForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
 
    return _PasswordResetForm
 

	
 

	
 
def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
 
             repo_groups=[], landing_revs=[]):
 
    class _RepoForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
 
                        v.SlugifyName())
 
        repo_group = All(v.CanWriteGroup(old_data),
 
                         v.OneOf(repo_groups, hideList=True))
 
        repo_type = v.OneOf(supported_backends, required=False,
 
                            if_missing=old_data.get('repo_type'))
 
        repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
 
        repo_private = v.StringBoolean(if_missing=False)
 
        repo_landing_rev = v.OneOf(landing_revs, hideList=True)
 
        repo_copy_permissions = v.StringBoolean(if_missing=False)
 
        clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
 

	
 
        repo_enable_statistics = v.StringBoolean(if_missing=False)
 
        repo_enable_downloads = v.StringBoolean(if_missing=False)
 
        repo_enable_locking = v.StringBoolean(if_missing=False)
 

	
 
        if edit:
 
            #this is repo owner
 
            user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
 
            clone_uri_change = v.UnicodeString(not_empty=False, if_missing=v.Missing)
 

	
 
        chained_validators = [v.ValidCloneUri(),
 
                              v.ValidRepoName(edit, old_data)]
 
    return _RepoForm
 

	
 

	
 
def RepoPermsForm():
 
    class _RepoPermsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        chained_validators = [v.ValidPerms(type_='repo')]
 
    return _RepoPermsForm
 

	
 

	
 
def RepoGroupPermsForm(valid_recursive_choices):
 
    class _RepoGroupPermsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        recursive = v.OneOf(valid_recursive_choices)
 
        chained_validators = [v.ValidPerms(type_='repo_group')]
 
    return _RepoGroupPermsForm
 

	
 

	
 
def UserGroupPermsForm():
 
    class _UserPermsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        chained_validators = [v.ValidPerms(type_='user_group')]
 
    return _UserPermsForm
 

	
 

	
 
def RepoFieldForm():
 
    class _RepoFieldForm(formencode.Schema):
 
        filter_extra_fields = True
 
        allow_extra_fields = True
 

	
 
        new_field_key = All(v.FieldKey(),
 
                            v.UnicodeString(strip=True, min=3, not_empty=True))
 
        new_field_value = v.UnicodeString(not_empty=False, if_missing='')
 
        new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
 
                                 if_missing='str')
 
        new_field_label = v.UnicodeString(not_empty=False)
 
        new_field_desc = v.UnicodeString(not_empty=False)
 

	
 
    return _RepoFieldForm
 

	
 

	
 
def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
 
                 repo_groups=[], landing_revs=[]):
 
    class _RepoForkForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
 
                        v.SlugifyName())
 
        repo_group = All(v.CanWriteGroup(),
 
                         v.OneOf(repo_groups, hideList=True))
 
        repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
 
        description = v.UnicodeString(strip=True, min=1, not_empty=True)
 
        private = v.StringBoolean(if_missing=False)
 
        copy_permissions = v.StringBoolean(if_missing=False)
 
        update_after_clone = v.StringBoolean(if_missing=False)
 
        fork_parent_id = v.UnicodeString()
 
        chained_validators = [v.ValidForkName(edit, old_data)]
 
        landing_rev = v.OneOf(landing_revs, hideList=True)
 

	
 
    return _RepoForkForm
 

	
 

	
 
def ApplicationSettingsForm():
 
    class _ApplicationSettingsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        title = v.UnicodeString(strip=True, not_empty=False)
 
        realm = v.UnicodeString(strip=True, min=1, not_empty=True)
 
        ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
 
        captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
 
        captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
 

	
 
    return _ApplicationSettingsForm
 

	
 

	
 
def ApplicationVisualisationForm():
 
    class _ApplicationVisualisationForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        show_public_icon = v.StringBoolean(if_missing=False)
 
        show_private_icon = v.StringBoolean(if_missing=False)
 
        stylify_metatags = v.StringBoolean(if_missing=False)
 

	
 
        repository_fields = v.StringBoolean(if_missing=False)
 
        lightweight_journal = v.StringBoolean(if_missing=False)
 
        dashboard_items = v.Int(min=5, not_empty=True)
 
        admin_grid_items = v.Int(min=5, not_empty=True)
 
        show_version = v.StringBoolean(if_missing=False)
 
        use_gravatar = v.StringBoolean(if_missing=False)
 
        gravatar_url = v.UnicodeString(min=3)
 
        clone_uri_tmpl = v.UnicodeString(min=3)
 

	
 
    return _ApplicationVisualisationForm
 

	
 

	
 
def ApplicationUiSettingsForm():
 
    class _ApplicationUiSettingsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        web_push_ssl = v.StringBoolean(if_missing=False)
 
        paths_root_path = All(
 
            v.ValidPath(),
 
            v.UnicodeString(strip=True, min=1, not_empty=True)
 
        )
 
        hooks_changegroup_update = v.StringBoolean(if_missing=False)
 
        hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
 
        hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
 
        hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
 

	
 
        extensions_largefiles = v.StringBoolean(if_missing=False)
 
        extensions_hgsubversion = v.StringBoolean(if_missing=False)
 
        extensions_hggit = v.StringBoolean(if_missing=False)
 

	
 
    return _ApplicationUiSettingsForm
 

	
 

	
 
def DefaultPermissionsForm(repo_perms_choices, group_perms_choices,
 
                           user_group_perms_choices, create_choices,
 
                           create_on_write_choices, repo_group_create_choices,
 
                           user_group_create_choices, fork_choices,
 
                           register_choices, extern_activate_choices):
 
    class _DefaultPermissionsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        overwrite_default_repo = v.StringBoolean(if_missing=False)
 
        overwrite_default_group = v.StringBoolean(if_missing=False)
 
        overwrite_default_user_group = v.StringBoolean(if_missing=False)
 
        anonymous = v.StringBoolean(if_missing=False)
 
        default_repo_perm = v.OneOf(repo_perms_choices)
 
        default_group_perm = v.OneOf(group_perms_choices)
 
        default_user_group_perm = v.OneOf(user_group_perms_choices)
 

	
 
        default_repo_create = v.OneOf(create_choices)
 
        create_on_write = v.OneOf(create_on_write_choices)
 
        default_user_group_create = v.OneOf(user_group_create_choices)
 
        #default_repo_group_create = v.OneOf(repo_group_create_choices) #not impl. yet
 
        default_fork = v.OneOf(fork_choices)
 

	
 
        default_register = v.OneOf(register_choices)
 
        default_extern_activate = v.OneOf(extern_activate_choices)
 
    return _DefaultPermissionsForm
 

	
 

	
 
def CustomDefaultPermissionsForm():
 
    class _CustomDefaultPermissionsForm(formencode.Schema):
 
        filter_extra_fields = True
 
        allow_extra_fields = True
 
        inherit_default_permissions = v.StringBoolean(if_missing=False)
 

	
 
        create_repo_perm = v.StringBoolean(if_missing=False)
 
        create_user_group_perm = v.StringBoolean(if_missing=False)
 
        #create_repo_group_perm Impl. later
 

	
 
        fork_repo_perm = v.StringBoolean(if_missing=False)
 

	
 
    return _CustomDefaultPermissionsForm
 

	
 

	
 
def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
 
    class _DefaultsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        default_repo_type = v.OneOf(supported_backends)
 
        default_repo_private = v.StringBoolean(if_missing=False)
 
        default_repo_enable_statistics = v.StringBoolean(if_missing=False)
 
        default_repo_enable_downloads = v.StringBoolean(if_missing=False)
 
        default_repo_enable_locking = v.StringBoolean(if_missing=False)
 

	
 
    return _DefaultsForm
 

	
 

	
 
def AuthSettingsForm(current_active_modules):
 
    class _AuthSettingsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        auth_plugins = All(v.ValidAuthPlugins(),
 
                           v.UniqueListFromString()(not_empty=True))
 

	
 
        def __init__(self, *args, **kwargs):
 
            # The auth plugins tell us what form validators they use
 
            if current_active_modules:
 
                import kallithea.lib.auth_modules
 
                from kallithea.lib.auth_modules import LazyFormencode
 
                for module in current_active_modules:
 
                    plugin = kallithea.lib.auth_modules.loadplugin(module)
 
                    plugin_name = plugin.name
 
                    for sv in plugin.plugin_settings():
 
                        newk = "auth_%s_%s" % (plugin_name, sv["name"])
 
                        # can be a LazyFormencode object from plugin settings
 
                        validator = sv["validator"]
 
                        if isinstance(validator, LazyFormencode):
 
                            validator = validator()
 
                        #init all lazy validators from formencode.All
 
                        if isinstance(validator, All):
 
                            init_validators = []
 
                            for validator in validator.validators:
 
                                if isinstance(validator, LazyFormencode):
 
                                    validator = validator()
 
                                init_validators.append(validator)
 
                            validator.validators = init_validators
 

	
 
                        self.add_field(newk, validator)
 
            formencode.Schema.__init__(self, *args, **kwargs)
 

	
 
    return _AuthSettingsForm
 

	
 

	
 
def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
 
                     tls_kind_choices):
 
    class _LdapSettingsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        #pre_validators = [LdapLibValidator]
 
        ldap_active = v.StringBoolean(if_missing=False)
 
        ldap_host = v.UnicodeString(strip=True,)
 
        ldap_port = v.Number(strip=True,)
 
        ldap_tls_kind = v.OneOf(tls_kind_choices)
 
        ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
 
        ldap_dn_user = v.UnicodeString(strip=True,)
 
        ldap_dn_pass = v.UnicodeString(strip=True,)
 
        ldap_base_dn = v.UnicodeString(strip=True,)
 
        ldap_filter = v.UnicodeString(strip=True,)
 
        ldap_search_scope = v.OneOf(search_scope_choices)
 
        ldap_attr_login = v.AttrLoginValidator()(not_empty=True)
 
        ldap_attr_firstname = v.UnicodeString(strip=True,)
 
        ldap_attr_lastname = v.UnicodeString(strip=True,)
 
        ldap_attr_email = v.UnicodeString(strip=True,)
 

	
 
    return _LdapSettingsForm
 

	
 

	
 
def UserExtraEmailForm():
 
    class _UserExtraEmailForm(formencode.Schema):
 
        email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
 
    return _UserExtraEmailForm
 

	
 

	
 
def UserExtraIpForm():
 
    class _UserExtraIpForm(formencode.Schema):
 
        ip = v.ValidIp()(not_empty=True)
 
    return _UserExtraIpForm
 

	
 

	
 
def PullRequestForm(repo_id):
 
    class _PullRequestForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 

	
 
        org_repo = v.UnicodeString(strip=True, required=True)
 
        org_ref = v.UnicodeString(strip=True, required=True)
 
        other_repo = v.UnicodeString(strip=True, required=True)
 
        other_ref = v.UnicodeString(strip=True, required=True)
 
        review_members = v.Set()
 

	
 
        pullrequest_title = v.UnicodeString(strip=True, required=True)
 
        pullrequest_desc = v.UnicodeString(strip=True, required=False)
 

	
 
    return _PullRequestForm
 

	
 

	
 
def PullRequestPostForm():
 
    class _PullRequestPostForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 

	
 
        pullrequest_title = v.UnicodeString(strip=True, required=True)
 
        pullrequest_desc = v.UnicodeString(strip=True, required=False)
 
        review_members = v.Set()
 
        updaterev = v.UnicodeString(strip=True, required=False, if_missing=None)
 

	
 
    return _PullRequestPostForm
 

	
 

	
 
def GistForm(lifetime_options):
 
    class _GistForm(formencode.Schema):
 

	
 
        filename = All(v.BasePath()(),
 
                       v.UnicodeString(strip=True, required=False))
 
        description = v.UnicodeString(required=False, if_missing=u'')
 
        lifetime = v.OneOf(lifetime_options)
 
        mimetype = v.UnicodeString(required=False, if_missing=None)
 
        content = v.UnicodeString(required=True, not_empty=True)
 
        public = v.UnicodeString(required=False, if_missing=u'')
 
        private = v.UnicodeString(required=False, if_missing=u'')
 

	
 
    return _GistForm
kallithea/public/css/style.css
Show inline comments
 
@@ -3109,768 +3109,769 @@ table.code-browser i[class^='icon-'] {
 

	
 
.hl-tip-box {
 
    z-index: 1;
 
    position: absolute;
 
    color: #666;
 
    background-color: #FFF;
 
    border: 2px solid #577632;
 
    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;
 
}
 

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

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

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

	
 
.flash_msg .alert-error {
 
    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;
 
}
 

	
 
.flash_msg .alert-error a {
 
    text-decoration: underline;
 
}
 

	
 
.flash_msg .alert-warning {
 
    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;
 
}
 

	
 
.flash_msg .alert-warning a {
 
    text-decoration: underline;
 
}
 

	
 
.flash_msg .alert-success {
 
    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;
 
}
 

	
 
.flash_msg .alert-success a {
 
    text-decoration: underline;
 
    color: #FFF !important;
 
}
 

	
 
.flash_msg .alert-info {
 
    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;
 
}
 

	
 
.flash_msg .alert-info a {
 
    text-decoration: underline;
 
}
 

	
 
.flash_msg .alert-error,
 
.flash_msg .alert-warning,
 
.flash_msg .alert-success,
 
.flash_msg .alert-info {
 
    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);
 
}
 

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

	
 
#content div.box div.title div.search div.input input {
 
    border: 1px solid #576622;
 
}
 

	
 
.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;
 
    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;
 
    display: inline-block;
 
}
 

	
 
ul.nav-stacked {
 
    margin: 20px;
 
    color: #393939;
 
    font-weight: 700;
 
}
 

	
 
ul.nav-stacked a {
 
    color: inherit;
 
}
 

	
 
/* make .btn inputs and buttons and divs look the same */
 
button.btn,
 
input.btn {
 
    font-family: inherit;
 
    font-size: inherit;
 
    line-height: inherit;
 
}
 

	
 
.btn::-moz-focus-inner {
 
    border: 0;
 
    padding: 0;
 
}
 

	
 
.btn.badge {
 
    cursor: default !important;
 
}
 

	
 
input[disabled].btn,
 
.btn.disabled {
 
    color: #999;
 
}
 

	
 
.btn.btn-danger.disabled {
 
    color: #eee;
 
    background-color: #c77;
 
    border-color: #b66
 
}
 

	
 
.btn.btn-small {
 
    padding: 2px 6px;
 
}
 

	
 
.btn.btn-mini {
 
    padding: 0px 4px;
 
}
 

	
 
.btn:focus {
 
    outline: none;
 
}
 
.btn:hover {
 
    text-decoration: none;
 
    color: #515151;
 
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 0 3px #FFFFFF !important;
 
}
 
.btn.badge:hover {
 
    box-shadow: none !important;
 
}
 
.btn.disabled:hover {
 
    background-position: 0;
 
    color: #999;
 
    text-decoration: none;
 
    box-shadow: none !important;
 
}
 

	
 
.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);
 
}
 

	
 

	
 
.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);
 
}
 

	
 
.btn.green {
 
    color: #fff;
 
    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);
 
}
 

	
 
.btn.yellow {
 
    color: #fff;
 
    background-color: #faa732;
 
    background-repeat: repeat-x;
 
    background-image: -khtml-gradient(linear, left top, left bottom, from(#fbb450), to(#f89406));
 
    background-image: -moz-linear-gradient(top, #fbb450, #f89406);
 
    background-image: -ms-linear-gradient(top, #fbb450, #f89406);
 
    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fbb450), color-stop(100%, #f89406));
 
    background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
 
    background-image: -o-linear-gradient(top, #fbb450, #f89406);
 
    background-image: linear-gradient(to bottom, #fbb450, #f89406);
 
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);
 
    border-color: #f89406 #f89406 #ad6704;
 
    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
 
}
 

	
 
label.disabled {
 
    color: #aaa;
 
}
 

	
 
.btn.blue.hidden {
 
    display: none;
 
}
 

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

	
 
.repo-switcher .select2-choice {
 
    padding: 0px 8px 1px !important;
 
    display: block;
 
    height: 100%;
 
}
 

	
 
.repo-switcher .select2-container,
 
.repo-switcher .select2-choice,
 
.repo-switcher .select2-choice span {
 
    background: transparent !important;
 
    border: 0 !important;
 
    box-shadow: none !important;
 
    color: #FFFFFF !important;
 
}
 

	
 
.repo-switcher .select2-arrow {
 
    display: none !important;
 
}
 

	
 
.repo-switcher .select2-chosen:after {
 
    content: ' \25BE';
 
}
 

	
 
.repo-switcher-dropdown.select2-drop.select2-drop-active {
 
    xborder-color: black;
 
    -webkit-box-shadow: none;
 
    -moz-box-shadow: none;
 
    box-shadow: none;
 
    color: #fff;
 
    background-color: #576622;
 
}
 

	
 
.repo-switcher-dropdown.select2-drop.select2-drop-active .select2-results .select2-highlighted {
 
    background-color: #6388ad;
 
}
 

	
 
#content div.graph {
 
    padding: 0 10px 10px;
 
}
 

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

	
 
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.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;
kallithea/templates/pullrequests/pullrequest_show.html
Show inline comments
 
<%inherit file="/base/base.html"/>
 

	
 
<%def name="title()">
 
    ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
 
    %if c.site_name:
 
        &middot; ${c.site_name}
 
    %endif
 
</%def>
 

	
 
<%def name="breadcrumbs_links()">
 
    ${_('Pull request #%s from %s#%s') % (c.pull_request.pull_request_id, c.pull_request.org_repo.repo_name, c.cs_branch_name)}
 
</%def>
 

	
 
<%def name="page_nav()">
 
    ${self.menu('repositories')}
 
</%def>
 

	
 
<%def name="main()">
 
<% editable = not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or c.pull_request.author.user_id == c.authuser.user_id) %>
 
${self.repo_context_bar('showpullrequest')}
 
<div class="box">
 
  <!-- box / title -->
 
  <div class="title">
 
    ${self.breadcrumbs()}
 
  </div>
 

	
 
  ${h.form(url('pullrequest_post', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id), method='post', id='pull_request_form')}
 
    <div class="form pr-box" style="float: left">
 
      <div class="pr-details-title ${'closed' if c.pull_request.is_closed() else ''}">
 
          ${_('Title')}: ${c.pull_request.title}
 
          %if c.pull_request.is_closed():
 
              (${_('Closed')})
 
          %endif
 
      </div>
 
      <div id="pr-summary" class="fields">
 

	
 
        <div class="field pr-not-edit" style="min-height:37px">
 
          <div class="label-summary">
 
            <label>${_('Description')}:</label>
 
            %if editable:
 
            <div style="margin: 5px">
 
              <a class="btn btn-small" onclick="YUD.setStyle('pr-edit-form','display','');YUD.setStyle(YUD.getElementsByClassName('pr-not-edit'),'display','none')">${_("Edit")}</a>
 
            </div>
 
            %endif
 
          </div>
 
          <div class="input">
 
            <div style="white-space:pre-wrap; line-height: 14px">${h.urlify_commit(c.pull_request.description, c.pull_request.org_repo.repo_name)}</div>
 
          </div>
 
        </div>
 

	
 
        %if editable:
 
        <div id="pr-edit-form" style="display:none">
 
          <div class="field">
 
              <div class="label-summary">
 
                  <label for="pullrequest_title">${_('Title')}:</label>
 
              </div>
 
              <div class="input">
 
                  ${h.text('pullrequest_title',class_="large",value=c.pull_request.title,placeholder=_('Summarize the changes'))}
 
              </div>
 
          </div>
 

	
 
          <div class="field">
 
              <div class="label-summary label-textarea">
 
                  <label for="pullrequest_desc">${_('Description')}:</label>
 
              </div>
 
              <div class="textarea text-area editor">
 
                  ${h.textarea('pullrequest_desc',size=30,content=c.pull_request.description,placeholder=_('Write a short description on this pull request'))}
 
              </div>
 
          </div>
 
        </div>
 
        %endif
 

	
 
        <div class="field">
 
          <div class="label-summary">
 
              <label>${_('Reviewer voting result')}:</label>
 
          </div>
 
          <div class="input">
 
            <div class="changeset-status-container" style="float:none;clear:both">
 
            %if c.current_voting_result:
 
              <div class="changeset-status-ico" style="padding:0px 4px 0px 0px">
 
                  <i class="icon-circle changeset-status-${c.current_voting_result}" title="${_('Pull request status calculated from votes')}"></i></div>
 
              <div class="changeset-status-lbl tooltip" title="${_('Pull request status calculated from votes')}">
 
                %if c.pull_request.is_closed():
 
                    ${_('Closed')},
 
                %endif
 
                ${h.changeset_status_lbl(c.current_voting_result)}
 
              </div>
 

	
 
            %endif
 
            </div>
 
          </div>
 
        </div>
 
        <div class="field">
 
          <div class="label-summary">
 
              <label>${_('Still not reviewed by')}:</label>
 
          </div>
 
          <div class="input">
 
            % if len(c.pull_request_pending_reviewers) > 0:
 
                <div class="tooltip" title="${h.tooltip(', '.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div>
 
            % elif len(c.pull_request_reviewers) > 0:
 
                <div>${_('Pull request was reviewed by all reviewers')}</div>
 
            %else:
 
                <div>${_('There are no reviewers')}</div>
 
            %endif
 
          </div>
 
        </div>
 
        <div class="field">
 
          <div class="label-summary">
 
              <label>${_('Origin')}:</label>
 
          </div>
 
          <div class="input">
 
            <div>
 
              ${h.link_to_ref(c.pull_request.org_repo.repo_name, c.cs_ref_type, c.cs_ref_name, c.cs_rev)}
 
              %if c.cs_ref_type != 'branch':
 
                ${_('on')} ${h.link_to_ref(c.pull_request.org_repo.repo_name, 'branch', c.cs_branch_name)}
 
              %endif
 
            </div>
 
          </div>
 
        </div>
 
        <div class="field">
 
          <div class="label-summary">
 
              <label>${_('Target')}:</label>
 
          </div>
 
          <div class="input">
 
              <div>
 
              ${h.link_to_ref(c.pull_request.other_repo.repo_name, c.a_ref_type, c.a_ref_name)}
 
              ## we don't know other rev - c.a_rev is ancestor and not necessarily on other_name_branch branch
 
              </div>
 
          </div>
 
        </div>
 
        <div class="field">
 
          <div class="label-summary">
 
              <label>${_('Pull changes')}:</label>
 
          </div>
 
          <div class="input">
 
              <div>
 
               ## TODO: use cs_ranges[-1] or org_ref_parts[1] in both cases?
 
               %if h.is_hg(c.pull_request.org_repo):
 
                 <span style="font-family: monospace">hg pull ${c.pull_request.org_repo.clone_url()} -r ${h.short_id(c.cs_ranges[-1].raw_id)}</span>
 
               %elif h.is_git(c.pull_request.org_repo):
 
                 <span style="font-family: monospace">git pull ${c.pull_request.org_repo.clone_url()} ${c.pull_request.org_ref_parts[1]}</span>
 
               %endif
 
              </div>
 
          </div>
 
        </div>
 
        <div class="field">
 
          <div class="label-summary">
 
              <label>${_('Created on')}:</label>
 
          </div>
 
          <div class="input">
 
              <div>${h.fmt_date(c.pull_request.created_on)}</div>
 
          </div>
 
        </div>
 
        <div class="field">
 
          <div class="label-summary">
 
              <label>${_('Created by')}:</label>
 
          </div>
 
          <div class="input">
 
              <div class="author">
 
                  <div class="gravatar">
 
                      <img alt="gravatar" src="${h.gravatar_url(c.pull_request.author.email,20)}"/>
 
                  </div>
 
                  <span>${c.pull_request.author.username_and_name}</span><br/>
 
                  <span><a href="mailto:${c.pull_request.author.email}">${c.pull_request.author.email}</a></span><br/>
 
              </div>
 
          </div>
 
        </div>
 

	
 
        <div class="field">
 
            <div class="label-summary">
 
              <label>${_('Update')}:</label>
 
            </div>
 
            <div class="input">
 
              <div class="msg-div">${c.update_msg}</div>
 
              %if c.available:
 
              <div style="max-height:200px; overflow-y:auto; overflow-x:hidden; margin-bottom: 10px">
 
              <div id="updaterevs" style="max-height:200px; overflow-y:auto; overflow-x:hidden; margin-bottom: 10px">
 
                <table class="noborder">
 
                  %for cnt, cs in enumerate(c.available):
 
                    <tr>
 
                    <td>${h.radio(name='updaterev', value=cs.raw_id)}</td>
 
                      %if cs.revision == c.cs_ranges[-1].revision:
 
                        <td>
 
                          %if editable:
 
                            ${h.radio(name='updaterev', value='')}
 
                          %endif
 
                        </td>
 
                        <td colspan="2">${_("Current revision - no change")}</td>
 
                      %else:
 
                        <td>
 
                          %if editable:
 
                            ${h.radio(name='updaterev', value=cs.raw_id)}
 
                          %endif
 
                        </td>
 
                    <td>${h.link_to(h.show_id(cs),h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id))}</td>
 
                    <td><div class="message" style="white-space:normal; height:1.1em; max-width: 500px; padding:0">${h.urlify_commit(cs.message, c.repo_name)}</div></td>
 
                      %endif
 
                    </tr>
 
                  %endfor
 
                </table>
 
              </div>
 
              %endif
 
              <div class="msg-div">${c.update_msg_other}</div>
 
            </div>
 
            %if editable and c.available:
 
              <div class="buttons">
 
                ${h.submit('copy_update',_('Create Pull Request Update'),class_="btn btn-small")}
 
              </div>
 
            %endif
 
        </div>
 
      </div>
 
    </div>
 
    ## REVIEWERS
 
    <div style="float:left; border-left:1px dashed #eee">
 
        <div class="pr-details-title">${_('Pull Request Reviewers')}</div>
 
        <div id="reviewers" style="padding:0px 0px 5px 10px">
 
          ## members goes here !
 
          <div>
 
            <ul id="review_members" class="group_members">
 
            %for member,status in c.pull_request_reviewers:
 
              <li id="reviewer_${member.user_id}">
 
                <div class="reviewers_member">
 
                    <div class="reviewer_status tooltip" title="${h.tooltip(h.changeset_status_lbl(status.status if status else 'not_reviewed'))}">
 
                      <i class="icon-circle changeset-status-${status.status if status else 'not_reviewed'}"></i>
 
                    </div>
 
                  <div class="reviewer_gravatar gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
 
                  <div style="float:left;">${member.full_name} (${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div>
 
                  <input type="hidden" value="${member.user_id}" name="review_members" />
 
                  %if editable:
 
                  <div class="reviewer_member_remove action_button" onclick="removeReviewMember(${member.user_id})" title="${_('Remove reviewer')}">
 
                      <i class="icon-minus-circled" style="color: #FF4444;"></i>
 
                  </div>
 
                  %endif
 
                </div>
 
              </li>
 
            %endfor
 
            </ul>
 
          </div>
 
          %if editable:
 
          <div class='ac'>
 
            <div class="reviewer_ac">
 
               ${h.text('user', class_='yui-ac-input',placeholder=_('Type name of reviewer to add'))}
 
               <div id="reviewers_container"></div>
 
            </div>
 
          </div>
 
          %endif
 
        </div>
 
    </div>
 
    <div class="form" style="clear:both">
 
      <div class="fields">
 
        %if editable:
 
          <div class="buttons">
 
            ${h.submit('pr-form-save',_('Save'),class_="btn btn-small")}
 
            ${h.reset('pr-form-reset',_('Cancel'),class_="btn btn-small")}
 
            ${h.submit('pr-form-save',_('Save Changes'),class_="btn btn-small")}
 
            ${h.submit('pr-form-clone',_('Save as New Pull Request'),class_="btn btn-small",disabled='disabled')}
 
            ${h.reset('pr-form-reset',_('Cancel Changes'),class_="btn btn-small")}
 
          </div>
 
        %endif
 
      </div>
 
    </div>
 
  ${h.end_form()}
 
</div>
 

	
 
<div class="box">
 
    <div class="title">
 
      <div class="breadcrumbs">${_('Pull Request Content')}</div>
 
    </div>
 
    <div class="table">
 
          <div id="changeset_compare_view_content">
 
              <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
 
                ${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt} <span class="firstlink"></span>
 
              </div>
 
              ##CS
 
              <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
 
                ${ungettext('Showing %s commit','Showing %s commits', len(c.cs_ranges)) % len(c.cs_ranges)}
 
              </div>
 
              <%include file="/compare/compare_cs.html" />
 

	
 
              <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
 
              ${_('Common ancestor')}:
 
              ${h.link_to(h.short_id(c.a_rev),h.url('changeset_home',repo_name=c.a_repo.repo_name,revision=c.a_rev))}
 
              </div>
 

	
 
              ## FILES
 
              <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
 

	
 
              % if c.limited_diff:
 
                  ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)}
 
              % else:
 
                  ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.files)) % (len(c.files),c.lines_added,c.lines_deleted)}:
 
              %endif
 

	
 
              </div>
 
              <div class="cs_files">
 
                %if not c.files:
 
                   <span class="empty_data">${_('No files')}</span>
 
                %endif
 
                %for fid, change, f, stat in c.files:
 
                    <div class="cs_${change}">
 
                      <div class="node">
 
                          <i class="icon-diff-${change}"></i>
 
                          ${h.link_to(h.safe_unicode(f),'#' + fid)}
 
                      </div>
 
                      <div class="changes">${h.fancy_file_stats(stat)}</div>
 
                    </div>
 
                %endfor
 
              </div>
 
              % if c.limited_diff:
 
                <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff anyway')}</a></h5>
 
              % endif
 
          </div>
 
    </div>
 
    <script>
 
    var _USERS_AC_DATA = ${c.users_array|n};
 
    var _GROUPS_AC_DATA = ${c.user_groups_array|n};
 
    // TODO: switch this to pyroutes
 
    AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
 
    AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
 

	
 
    pyroutes.register('pullrequest_comment', "${url('pullrequest_comment',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']);
 
    pyroutes.register('pullrequest_comment_delete', "${url('pullrequest_comment_delete',repo_name='%(repo_name)s',comment_id='%(comment_id)s')}", ['repo_name', 'comment_id']);
 

	
 
    </script>
 

	
 
    ## diff block
 
    <%namespace name="diff_block" file="/changeset/diff_block.html"/>
 
    ${diff_block.diff_block_js()}
 
    %for fid, change, f, stat in c.files:
 
      ${diff_block.diff_block_simple([c.changes[fid]])}
 
    %endfor
 
    % if c.limited_diff:
 
      <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff anyway')}</a></h4>
 
    % endif
 

	
 

	
 
    ## template for inline comment form
 
    <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
 
    ${comment.comment_inline_form()}
 

	
 
    ## render comments and inlines
 
    ${comment.generate_comments()}
 

	
 
    ## main comment form and it status
 
    ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
 
                              pull_request_id=c.pull_request.pull_request_id),
 
                       c.current_voting_result,
 
                       is_pr=True, change_status=c.allowed_to_change_status)}
 

	
 
    <script type="text/javascript">
 
      $(document).ready(function(){
 
          PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
 

	
 
          $('.add-bubble').click(function(e){
 
              var tr = e.currentTarget;
 
              injectInlineForm(tr.parentNode.parentNode);
 
          });
 

	
 
          // inject comments into they proper positions
 
          var file_comments = $('.inline-comment-placeholder').toArray();
 
          renderInlineComments(file_comments);
 

	
 
          linkInlineComments(document.getElementsByClassName('firstlink'), document.getElementsByClassName("inline-comment"));
 

	
 
          $('#updaterevs input').change(function(e){
 
              var update = !!e.target.value;
 
              $('#pr-form-save').prop('disabled',update);
 
              $('#pr-form-clone').prop('disabled',!update);
 
          });
 
          var $org_review_members = $('#review_members').clone();
 
          $('#pr-form-reset').click(function(e){
 
              $('#pr-edit-form').hide();
 
              $('.pr-not-edit').show();
 
              $('#pr-form-save').prop('disabled',false);
 
              $('#pr-form-clone').prop('disabled',true);
 
              $('#review_members').html($org_review_members);
 
          });
 

	
 
          // hack: re-navigate to target after JS is done ... if a target is set and setting href thus won't reload
 
          if (window.location.hash != "") {
 
              window.location.href = window.location.href;
 
          }
 
      });
 
    </script>
 

	
 
</div>
 

	
 
</%def>
0 comments (0 inline, 0 general)