Files @ 19418a4c6c61
Branch filter:

Location: kallithea/kallithea/lib/graphmod.py

mads
wsgi: make WSGI wrapper follow the size of the result and log when it finished

The WSGI protocol allow for explicit writes and for iterator yields, and we
thus have to do accounting in both places.
# -*- 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/>.
"""
Modified mercurial DAG graph functions that re-uses VCS structure

It allows to have a shared codebase for DAG generation for hg and git repos
"""

nullrev = -1


def _first_known_ancestors(parentrev_func, minrev, knownrevs, head):
    """
    Return the apparent parents of the head revision in a filtered DAG.
    This is like calling plain parentrev_func, except that if the parent has
    been filtered (isn't in knownrevs), recurse on its parents.
    For ancestors that are unknown in knownrevs, the revision number is negated.
    (This means that a Mercurial revision can have more than 2 "parents".)

    parentrev_func defines the full DAG.
    knownrevs contains the subset we care about. minrev is lower bound on knownrevs.
    """
    pending = set([head])
    seen = set()
    ancestors = set()
    while pending:
        r = pending.pop()
        if r < minrev:
            if r > nullrev: # ignore -1 returned from parentrev_func
                ancestors.add(-head) # fake unique unknown parent for this rev
        elif r in knownrevs:
            ancestors.add(r)
        elif r not in seen:
            pending.update(parentrev_func(r))
            seen.add(r)
    return ancestors


def graph_data(repo, revs):
    """Return a DAG with colored edge information for revs

    For each DAG node this function emits tuples::

      ((col, color), [(col, nextcol, color)])

    with the following new elements:

      - Tuple (col, color) with column and color index for the current node
      - A list of tuples indicating the edges between the current node and its
        parents.
    """
    dag = _dagwalker(repo, revs)
    return list(_colored(repo, dag))


def _dagwalker(repo, revs):
    """Iterate over revs, yielding revs (highest first) and parents to show in the graph."""
    if not revs:
        return

    if repo.alias == 'hg':
        parentrev_func = repo._repo.changelog.parentrevs
    elif repo.alias == 'git':
        def parentrev_func(rev):
            return [x.revision for x in repo[rev].parents]

    minrev = revs[-1] # assuming sorted with highest revision numbers first
    knownrevs = set(revs)
    acache = {}
    for rev in revs:
        parents = set(parentrev_func(rev)) - set([nullrev])
        dagparents = parents & knownrevs
        # Calculate indirect parents
        for p in parents - dagparents:
            ancestors = acache.get(p)
            if ancestors is None:
                ancestors = acache[p] = _first_known_ancestors(parentrev_func, minrev, knownrevs, p)
            dagparents.update(ancestors)

        yield (rev, sorted(dagparents))


def _colored(repo, dag):
    """annotates a DAG with colored edge information

    For each DAG node this function emits tuples::

      ((col, color), [(col, nextcol, color)])

    with the following new elements:

      - Tuple (col, color) with column and color index for the current node
      - A list of tuples indicating the edges between the current node and its
        parents.
    """
    branch_cache = {}

    def branch(rev):
        """Return branch for rev, using cache for efficiency.
        For Mercurial, always return the named branch name (which may be 'default').
        For Git, return a branch name for branch heads, otherwise None."""
        if rev not in branch_cache:
            branch_cache[rev] = repo[rev].branch
        return branch_cache[rev]

    row = []  # the ancestor revision that each column is tracking
    colors = {}  # color number for revisions - set by descendants
    obs = {}  # obsolete flag for revisions - set by descendants
    newcolor = 1  # the next available color

    for (rev, dagparents) in dag:

        # Compute row and nextrow
        if rev not in row:
            row.append(rev)  # new head
            colors[rev] = newcolor
            obs[rev] = int(repo[rev].obsolete)
            newcolor += 1

        col = row.index(rev)
        addparents = [p for p in dagparents if p not in row]

        # Add unknown parents to nextrow
        tmprow = row[:]
        tmprow[col:col + 1] = reversed(addparents) # highest revs first (to the right), dead ends last (to the left)
        # Filter old fake parents but keep new ones
        nextrow = []
        for r in tmprow:
            if r >= 0 or r in dagparents:
                nextrow.append(r)
            else:
                colors.pop(r)
                obs.pop(r)

        # Let color and obs for this rev be "inherited" by the first "parent"
        color = colors.pop(rev)
        if addparents:
            b = branch(rev)
            searching = True
            for p in reversed(addparents):
                obs[p] = int(repo[p].obsolete) if p >= 0 else 0
                if searching and branch(abs(p)) in [b, None]:
                    # This is the first parent on the same branch - inherit the color
                    colors[p] = color
                    searching = False # make sure we don't give the color away twice
                else:
                    colors[p] = newcolor
                    newcolor += 1

        # Add edges to the graph
        edges = []
        for ecol, ep in enumerate(row):
            if ep in nextrow:
                edges.append((ecol, nextrow.index(ep), colors[ep], obs[ep]))
            elif ep == rev:
                for p in dagparents:
                    edges.append((ecol, nextrow.index(p), colors[p], obs[p]))

        # Yield and move on
        closing = int(repo[rev].closesbranch)
        obsolete = int(repo[rev].obsolete)
        bumped = int(repo[rev].bumped)
        divergent = int(repo[rev].divergent)
        extinct = int(repo[rev].extinct)
        unstable = int(repo[rev].unstable)
        yield ((col, color), edges, closing, obsolete, bumped, divergent, extinct, unstable)
        row = nextrow