Ejemplo n.º 1
0
    def install_git_hooks(self, repo, force_create=False):
        """
        Creates a kallithea hook inside a git repository

        :param repo: Instance of VCS repo
        :param force_create: Create even if same name hook exists
        """

        loc = os.path.join(repo.path, 'hooks')
        if not repo.bare:
            loc = os.path.join(repo.path, '.git', 'hooks')
        if not os.path.isdir(loc):
            os.makedirs(loc)

        tmpl_post = b"#!%s\n" % safe_bytes(self._get_git_hook_interpreter())
        tmpl_post += pkg_resources.resource_string(
            'kallithea', os.path.join('config', 'post_receive_tmpl.py')
        )
        tmpl_pre = b"#!%s\n" % safe_bytes(self._get_git_hook_interpreter())
        tmpl_pre += pkg_resources.resource_string(
            'kallithea', os.path.join('config', 'pre_receive_tmpl.py')
        )

        for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
            _hook_file = os.path.join(loc, '%s-receive' % h_type)
            has_hook = False
            log.debug('Installing git hook in repo %s', repo)
            if os.path.exists(_hook_file):
                # let's take a look at this hook, maybe it's kallithea ?
                log.debug('hook exists, checking if it is from kallithea')
                with open(_hook_file, 'rb') as f:
                    data = f.read()
                    matches = re.search(br'^KALLITHEA_HOOK_VER\s*=\s*(.*)$', data, flags=re.MULTILINE)
                    if matches:
                        try:
                            ver = matches.groups()[0]
                            log.debug('Found Kallithea hook - it has KALLITHEA_HOOK_VER %r', ver)
                            has_hook = True
                        except Exception:
                            log.error(traceback.format_exc())
            else:
                # there is no hook in this dir, so we want to create one
                has_hook = True

            if has_hook or force_create:
                log.debug('writing %s hook file !', h_type)
                try:
                    with open(_hook_file, 'wb') as f:
                        tmpl = tmpl.replace(b'_TMPL_', safe_bytes(kallithea.__version__))
                        f.write(tmpl)
                    os.chmod(_hook_file, 0o755)
                except IOError as e:
                    log.error('error writing %s: %s', _hook_file, e)
            else:
                log.debug('skipping writing hook file')
Ejemplo n.º 2
0
def get_crypt_password(password):
    """
    Cryptographic function used for bcrypt password hashing.

    :param password: password to hash
    """
    return ascii_str(bcrypt.hashpw(safe_bytes(password), bcrypt.gensalt(10)))
Ejemplo n.º 3
0
    def _make_app(self, parsed_request):
        """
        Make an hgweb wsgi application.
        """
        repo_name = parsed_request.repo_name
        repo_path = os.path.join(self.basepath, repo_name)
        baseui = make_ui(repo_path=repo_path)
        hgweb_app = mercurial.hgweb.hgweb(safe_bytes(repo_path),
                                          name=safe_bytes(repo_name),
                                          baseui=baseui)

        def wrapper_app(environ, start_response):
            environ['REPO_NAME'] = repo_name  # used by mercurial.hgweb.hgweb
            return hgweb_app(environ, start_response)

        return wrapper_app
Ejemplo n.º 4
0
    def __call__(self, environ, start_response):
        # Extract path_info as get_path_info does, but do it explicitly because
        # we also have to do the reverse operation when patching it back in
        path_info = safe_str(environ['PATH_INFO'].encode('latin1'))
        if path_info.startswith('/'):  # it must
            path_info = '/' + fix_repo_id_name(path_info[1:])
            environ['PATH_INFO'] = safe_bytes(path_info).decode('latin1')

        return self.application(environ, start_response)
Ejemplo n.º 5
0
def __get_lockkey(func, *fargs, **fkwargs):
    params = list(fargs)
    params.extend(['%s-%s' % ar for ar in fkwargs.items()])

    func_name = str(func.__name__) if hasattr(func, '__name__') else str(func)

    lockkey = 'task_%s.lock' % \
        md5(safe_bytes(func_name + '-' + '-'.join(str(x) for x in params))).hexdigest()
    return lockkey
Ejemplo n.º 6
0
def FID(raw_id, path):
    """
    Creates a unique ID for filenode based on it's hash of path and revision
    it's safe to use in urls

    :param raw_id:
    :param path:
    """

    return 'C-%s-%s' % (short_id(raw_id), hashlib.md5(safe_bytes(path)).hexdigest()[:12])
Ejemplo n.º 7
0
def repo_size(ui, repo, hooktype=None, **kwargs):
    """Show size of Mercurial repository.

    Called as Mercurial hook changegroup.repo_size after push.
    """
    size_hg_f, size_root_f, size_total_f = _get_scm_size(
        '.hg', safe_str(repo.root))

    last_cs = repo[len(repo) - 1]

    msg = ('Repository size .hg: %s Checkout: %s Total: %s\n'
           'Last revision is now r%s:%s\n') % (size_hg_f, size_root_f,
                                               size_total_f, last_cs.rev(),
                                               ascii_str(last_cs.hex())[:12])
    ui.status(safe_bytes(msg))
Ejemplo n.º 8
0
    def _request(self,
                 url,
                 body=None,
                 headers=None,
                 method=None,
                 noformat=False,
                 empty_response_ok=False):
        _headers = {
            "Content-type": "application/json",
            "Accept": "application/json"
        }
        if self.user and self.passwd:
            authstring = ascii_str(
                base64.b64encode(safe_bytes("%s:%s" %
                                            (self.user, self.passwd))))
            _headers["Authorization"] = "Basic %s" % authstring
        if headers:
            _headers.update(headers)
        log.debug("Sent to crowd at %s:\nHeaders: %s\nBody:\n%s", url,
                  _headers, body)
        req = urllib.request.Request(url, body, _headers)
        if method:
            req.get_method = lambda: method

        global msg
        msg = ""
        try:
            rdoc = self.opener.open(req)
            msg = "".join(rdoc.readlines())
            if not msg and empty_response_ok:
                rval = {}
                rval["status"] = True
                rval["error"] = "Response body was empty"
            elif not noformat:
                rval = ext_json.loads(msg)
                rval["status"] = True
            else:
                rval = "".join(rdoc.readlines())
        except Exception as e:
            if not noformat:
                rval = {
                    "status": False,
                    "body": body,
                    "error": str(e) + "\n" + msg
                }
            else:
                rval = None
        return rval
Ejemplo n.º 9
0
def make_ui(repo_path=None):
    """
    Create an Mercurial 'ui' object based on database Ui settings, possibly
    augmenting with content from a hgrc file.
    """
    baseui = mercurial.ui.ui()

    # clean the baseui object
    baseui._ocfg = mercurial.config.config()
    baseui._ucfg = mercurial.config.config()
    baseui._tcfg = mercurial.config.config()

    sa = meta.Session()
    for ui_ in sa.query(Ui).order_by(Ui.ui_section, Ui.ui_key):
        if ui_.ui_active:
            log.debug('config from db: [%s] %s=%r', ui_.ui_section, ui_.ui_key,
                      ui_.ui_value)
            baseui.setconfig(
                ascii_bytes(ui_.ui_section), ascii_bytes(ui_.ui_key),
                b'' if ui_.ui_value is None else safe_bytes(ui_.ui_value))

    # force set push_ssl requirement to False, Kallithea handles that
    baseui.setconfig(b'web', b'push_ssl', False)
    baseui.setconfig(b'web', b'allow_push', b'*')
    # prevent interactive questions for ssh password / passphrase
    ssh = baseui.config(b'ui', b'ssh', default=b'ssh')
    baseui.setconfig(b'ui', b'ssh',
                     b'%s -oBatchMode=yes -oIdentitiesOnly=yes' % ssh)
    # push / pull hooks
    baseui.setconfig(b'hooks', b'changegroup.kallithea_log_push_action',
                     b'python:kallithea.lib.hooks.log_push_action')
    baseui.setconfig(b'hooks', b'outgoing.kallithea_log_pull_action',
                     b'python:kallithea.lib.hooks.log_pull_action')

    if repo_path is not None:
        # Note: MercurialRepository / mercurial.localrepo.instance will do this too, so it will always be possible to override db settings or what is hardcoded above
        baseui.readconfig(repo_path)

    assert baseui.plain(
    )  # set by hgcompat.monkey_do (invoked from import of vcs.backends.hg) to minimize potential impact of loading config files
    return baseui
Ejemplo n.º 10
0
def gravatar_url(email_address, size=30, default=''):
    # doh, we need to re-import those to mock it later
    from kallithea.config.routing import url
    from kallithea.model.db import User
    from tg import tmpl_context as c
    if not c.visual.use_gravatar:
        return ""

    _def = '*****@*****.**'  # default gravatar
    email_address = email_address or _def

    if email_address == _def:
        return default

    parsed_url = urllib.parse.urlparse(url.current(qualified=True))
    url = (c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL) \
               .replace('{email}', email_address) \
               .replace('{md5email}', hashlib.md5(safe_bytes(email_address).lower()).hexdigest()) \
               .replace('{netloc}', parsed_url.netloc) \
               .replace('{scheme}', parsed_url.scheme) \
               .replace('{size}', str(size))
    return url
Ejemplo n.º 11
0
def check_password(password, hashed):
    """
    Checks password match the hashed value using bcrypt.
    Remains backwards compatible and accept plain sha256 hashes which used to
    be used on Windows.

    :param password: password
    :param hashed: password in hashed form
    """
    # sha256 hashes will always be 64 hex chars
    # bcrypt hashes will always contain $ (and be shorter)
    if len(hashed) == 64 and all(x in string.hexdigits for x in hashed):
        return hashlib.sha256(password).hexdigest() == hashed
    try:
        return bcrypt.checkpw(safe_bytes(password), ascii_bytes(hashed))
    except ValueError as e:
        # bcrypt will throw ValueError 'Invalid hashed_password salt' on all password errors
        log.error('error from bcrypt checking password: %s', e)
        return False
    log.error('check_password failed - no method found for hash length %s',
              len(hashed))
    return False
Ejemplo n.º 12
0
    def _get_changesets(alias, org_repo, org_rev, other_repo, other_rev):
        """
        Returns lists of changesets that can be merged from org_repo@org_rev
        to other_repo@other_rev
        ... and the other way
        ... and the ancestors that would be used for merge

        :param org_repo: repo object, that is most likely the original repo we forked from
        :param org_rev: the revision we want our compare to be made
        :param other_repo: repo object, most likely the fork of org_repo. It has
            all changesets that we need to obtain
        :param other_rev: revision we want out compare to be made on other_repo
        """
        ancestors = None
        if org_rev == other_rev:
            org_changesets = []
            other_changesets = []

        elif alias == 'hg':
            # case two independent repos
            if org_repo != other_repo:
                hgrepo = mercurial.unionrepo.makeunionrepository(
                    other_repo.baseui, safe_bytes(other_repo.path),
                    safe_bytes(org_repo.path))
                # all ancestors of other_rev will be in other_repo and
                # rev numbers from hgrepo can be used in other_repo - org_rev ancestors cannot

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

            ancestors = [
                ascii_str(hgrepo[ancestor].hex()) for ancestor in hgrepo.revs(
                    b"id(%s) & ::id(%s)", ascii_bytes(other_rev),
                    ascii_bytes(org_rev))
            ]
            if ancestors:
                log.debug("shortcut found: %s is already an ancestor of %s",
                          other_rev, org_rev)
            else:
                log.debug("no shortcut found: %s is not an ancestor of %s",
                          other_rev, org_rev)
                ancestors = [
                    ascii_str(hgrepo[ancestor].hex())
                    for ancestor in hgrepo.revs(b"heads(::id(%s) & ::id(%s))",
                                                ascii_bytes(org_rev),
                                                ascii_bytes(other_rev))
                ]  # FIXME: expensive!

            other_changesets = [
                other_repo.get_changeset(rev) for rev in hgrepo.revs(
                    b"ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
                    ascii_bytes(other_rev), ascii_bytes(org_rev),
                    ascii_bytes(org_rev))
            ]
            org_changesets = [
                org_repo.get_changeset(ascii_str(hgrepo[rev].hex()))
                for rev in hgrepo.revs(
                    b"ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
                    ascii_bytes(org_rev), ascii_bytes(other_rev),
                    ascii_bytes(other_rev))
            ]

        elif alias == 'git':
            if org_repo != other_repo:
                from dulwich.repo import Repo
                from dulwich.client import SubprocessGitClient

                gitrepo = Repo(org_repo.path)
                SubprocessGitClient(thin_packs=False).fetch(
                    other_repo.path, gitrepo)

                gitrepo_remote = Repo(other_repo.path)
                SubprocessGitClient(thin_packs=False).fetch(
                    org_repo.path, gitrepo_remote)

                revs = [
                    ascii_str(x.commit.id) for x in gitrepo_remote.get_walker(
                        include=[ascii_bytes(other_rev)],
                        exclude=[ascii_bytes(org_rev)])
                ]
                other_changesets = [
                    other_repo.get_changeset(rev) for rev in reversed(revs)
                ]
                if other_changesets:
                    ancestors = [other_changesets[0].parents[0].raw_id]
                else:
                    # no changesets from other repo, ancestor is the other_rev
                    ancestors = [other_rev]

                gitrepo.close()
                gitrepo_remote.close()

            else:
                so = org_repo.run_git_command([
                    'log', '--reverse', '--pretty=format:%H', '-s',
                    '%s..%s' % (org_rev, other_rev)
                ])
                other_changesets = [
                    org_repo.get_changeset(cs)
                    for cs in re.findall(r'[0-9a-fA-F]{40}', so)
                ]
                so = org_repo.run_git_command(
                    ['merge-base', org_rev, other_rev])
                ancestors = [re.findall(r'[0-9a-fA-F]{40}', so)[0]]
            org_changesets = []

        else:
            raise Exception('Bad alias only git and hg is allowed')

        return other_changesets, org_changesets, ancestors
Ejemplo n.º 13
0
def rejectpush(ui, **kwargs):
    """Mercurial hook to be installed as pretxnopen and prepushkey for read-only repos"""
    ex = get_hook_environment()
    ui.warn(safe_bytes("Push access to %r denied\n" % ex.repository))
    return 1
Ejemplo n.º 14
0
def handle_git_post_receive(repo_path, git_stdin_lines):
    """Called from Git post-receive hook"""
    try:
        baseui, repo = _hook_environment(repo_path)
    except HookEnvironmentError as e:
        sys.stderr.write(
            "Skipping Kallithea Git post-recieve hook %r.\nGit was apparently not invoked by Kallithea: %s\n"
            % (sys.argv[0], e))
        return 0

    # the post push hook should never use the cached instance
    scm_repo = repo.scm_instance_no_cache()

    rev_data = []
    for l in git_stdin_lines:
        old_rev, new_rev, ref = l.strip().split(' ')
        _ref_data = ref.split('/')
        if _ref_data[1] in ['tags', 'heads']:
            rev_data.append({
                'old_rev': old_rev,
                'new_rev': new_rev,
                'ref': ref,
                'type': _ref_data[1],
                'name': '/'.join(_ref_data[2:])
            })

    git_revs = []
    for push_ref in rev_data:
        _type = push_ref['type']
        if _type == 'heads':
            if push_ref['old_rev'] == EmptyChangeset().raw_id:
                # update the symbolic ref if we push new repo
                if scm_repo.is_empty():
                    scm_repo._repo.refs.set_symbolic_ref(
                        b'HEAD',
                        b'refs/heads/%s' % safe_bytes(push_ref['name']))

                # build exclude list without the ref
                cmd = ['for-each-ref', '--format=%(refname)', 'refs/heads/*']
                stdout = scm_repo.run_git_command(cmd)
                ref = push_ref['ref']
                heads = [head for head in stdout.splitlines() if head != ref]
                # now list the git revs while excluding from the list
                cmd = [
                    'log', push_ref['new_rev'], '--reverse',
                    '--pretty=format:%H'
                ]
                cmd.append('--not')
                cmd.extend(heads)  # empty list is ok
                stdout = scm_repo.run_git_command(cmd)
                git_revs += stdout.splitlines()

            elif push_ref['new_rev'] == EmptyChangeset().raw_id:
                # delete branch case
                git_revs += ['delete_branch=>%s' % push_ref['name']]
            else:
                cmd = [
                    'log',
                    '%(old_rev)s..%(new_rev)s' % push_ref, '--reverse',
                    '--pretty=format:%H'
                ]
                stdout = scm_repo.run_git_command(cmd)
                git_revs += stdout.splitlines()

        elif _type == 'tags':
            git_revs += ['tag=>%s' % push_ref['name']]

    process_pushed_raw_ids(git_revs)

    return 0
Ejemplo n.º 15
0
    def show(self, repo_name, pull_request_id, extra=None):
        c.pull_request = PullRequest.get_or_404(pull_request_id)
        c.allowed_to_change_status = self._is_allowed_to_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(':')  # a_rev is ancestor

        org_scm_instance = c.cs_repo.scm_instance  # property with expensive cache invalidation check!!!
        c.cs_ranges = []
        for x in c.pull_request.revisions:
            try:
                c.cs_ranges.append(org_scm_instance.get_changeset(x))
            except ChangesetDoesNotExistError:
                c.cs_ranges = []
                h.flash(
                    _('Revision %s not found in %s') %
                    (x, c.cs_repo.repo_name), 'error')
                break
        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 = graph_data(org_scm_instance, revs)

        c.is_range = False
        try:
            if c.a_ref_type == 'rev':  # this looks like a free range where target is ancestor
                cs_a = org_scm_instance.get_changeset(c.a_rev)
                root_parents = c.cs_ranges[0].parents
                c.is_range = cs_a in root_parents
                #c.merge_root = len(root_parents) > 1 # a range starting with a merge might deserve a warning
        except ChangesetDoesNotExistError:  # probably because c.a_rev not found
            pass
        except IndexError:  # probably because c.cs_ranges is empty, probably because revisions are missing
            pass

        avail_revs = set()
        avail_show = []
        c.cs_branch_name = c.cs_ref_name
        c.a_branch_name = None
        other_scm_instance = c.a_repo.scm_instance
        c.update_msg = ""
        c.update_msg_other = ""
        try:
            if not c.cs_ranges:
                c.update_msg = _(
                    'Error: changesets not found when displaying pull request from %s.'
                ) % c.cs_rev
            elif 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 descendant of target ref name
                if not c.is_range and 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
                elif c.pull_request.is_closed():
                    c.update_msg = _(
                        'This pull request has been closed and can not be updated.'
                    )
                else:  # look for descendants of PR head on source branch in org repo
                    avail_revs = org_scm_instance._repo.revs(
                        '%s:: & branch(%s)', revs[0], c.cs_branch_name)
                    if len(avail_revs) > 1:  # more than just revs[0]
                        # also show changesets that not are descendants but would be merged in
                        targethead = other_scm_instance.get_changeset(
                            c.a_branch_name).raw_id
                        if org_scm_instance.path != other_scm_instance.path:
                            # Note: org_scm_instance.path must come first so all
                            # valid revision numbers are 100% org_scm compatible
                            # - both for avail_revs and for revset results
                            hgrepo = mercurial.unionrepo.makeunionrepository(
                                org_scm_instance.baseui,
                                safe_bytes(org_scm_instance.path),
                                safe_bytes(other_scm_instance.path))
                        else:
                            hgrepo = org_scm_instance._repo
                        show = set(
                            hgrepo.revs('::%ld & !::parents(%s) & !::%s',
                                        avail_revs, revs[0], targethead))
                        if show:
                            c.update_msg = _(
                                'The following additional changes are available on %s:'
                            ) % c.cs_branch_name
                        else:
                            c.update_msg = _(
                                'No additional changesets found for iterating on this pull request.'
                            )
                    else:
                        show = set()
                        avail_revs = set()  # drop revs[0]
                        c.update_msg = _(
                            'No additional changesets found for iterating on this pull request.'
                        )

                    # TODO: handle branch heads that not are tip-most
                    brevs = org_scm_instance._repo.revs(
                        '%s - %ld - %s', c.cs_branch_name, avail_revs, revs[0])
                    if brevs:
                        # also show changesets that are on branch but neither ancestors nor descendants
                        show.update(
                            org_scm_instance._repo.revs(
                                '::%ld - ::%ld - ::%s', brevs, avail_revs,
                                c.a_branch_name))
                        show.add(
                            revs[0]
                        )  # make sure graph shows this so we can see how they relate
                        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))

                    avail_show = sorted(show, reverse=True)

            elif org_scm_instance.alias == 'git':
                c.cs_repo.scm_instance.get_changeset(
                    c.cs_rev
                )  # check it exists - raise ChangesetDoesNotExistError if not
                c.update_msg = _(
                    "Git pull requests don't support iterating yet.")
        except ChangesetDoesNotExistError:
            c.update_msg = _(
                'Error: some changesets not found when displaying pull request from %s.'
            ) % c.cs_rev

        c.avail_revs = avail_revs
        c.avail_cs = [org_scm_instance.get_changeset(r) for r in avail_show]
        c.avail_jsdata = graph_data(org_scm_instance, avail_show)

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

        ignore_whitespace = request.GET.get('ignorews') == '1'
        line_context = safe_int(request.GET.get('context'), 3)
        c.ignorews_url = _ignorews_url
        c.context_url = _context_url
        fulldiff = request.GET.get('fulldiff')
        diff_limit = None if fulldiff else self.cut_off_limit

        # 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)
        try:
            raw_diff = diffs.get_diff(org_scm_instance,
                                      rev1=c.a_rev,
                                      rev2=c.cs_rev,
                                      ignore_whitespace=ignore_whitespace,
                                      context=line_context)
        except ChangesetDoesNotExistError:
            raw_diff = safe_bytes(
                _("The diff can't be shown - the PR revisions could not be found."
                  ))
        diff_processor = diffs.DiffProcessor(raw_diff, diff_limit=diff_limit)
        c.limited_diff = diff_processor.limited_diff
        c.file_diff_data = []
        c.lines_added = 0
        c.lines_deleted = 0

        for f in diff_processor.parsed:
            st = f['stats']
            c.lines_added += st['added']
            c.lines_deleted += st['deleted']
            filename = f['filename']
            fid = h.FID('', filename)
            html_diff = diffs.as_html(enable_comments=True, parsed_lines=[f])
            c.file_diff_data.append(
                (fid, None, f['operation'], f['old_filename'], filename,
                 html_diff, st))

        # 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.is_ajax_preview = False
        c.ancestors = None  # [c.a_rev] ... but that is shown in an other way
        return render('/pullrequests/pullrequest_show.html')