Changeset - 0c7dc3402efa
[Not reviewed]
beta
0 1 1
Marcin Kuzminski - 13 years ago 2012-06-04 01:21:15
marcin@python-works.com
Unified DAG generation for hg and git
- also fixes issue #470
2 files changed with 131 insertions and 14 deletions:
0 comments (0 inline, 0 general)
rhodecode/controllers/changelog.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.controllers.changelog
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    changelog controller for rhodecode
 

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

	
 
import logging
 
import traceback
 

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

	
 
import rhodecode.lib.helpers as h
 
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 
from rhodecode.lib.base import BaseRepoController, render
 
from rhodecode.lib.helpers import RepoPage
 
from rhodecode.lib.compat import json
 

	
 
from rhodecode.lib.graphmod import _colored, _dagwalker
 
from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class ChangelogController(BaseRepoController):
 

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

	
 
    def index(self):
 
        limit = 100
 
        default = 20
 
        if request.params.get('size'):
 
            try:
 
                int_size = int(request.params.get('size'))
 
            except ValueError:
 
                int_size = default
 
            int_size = int_size if int_size <= limit else limit
 
            c.size = int_size
 
            session['changelog_size'] = c.size
 
            session.save()
 
        else:
 
            c.size = int(session.get('changelog_size', default))
 

	
 
        p = int(request.params.get('page', 1))
 
        branch_name = request.params.get('branch', None)
 
        try:
 
            if branch_name:
 
                collection = [z for z in
 
                              c.rhodecode_repo.get_changesets(start=0,
 
                                                    branch_name=branch_name)]
 
                c.total_cs = len(collection)
 
            else:
 
                collection = c.rhodecode_repo
 
                c.total_cs = len(c.rhodecode_repo)
 

	
 
            c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
 
                                    items_per_page=c.size, branch=branch_name)
 
            collection = list(c.pagination)
 
            page_revisions = [x.raw_id for x in collection]
 
            c.comments = c.rhodecode_db_repo.comments(page_revisions)
 

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

	
 
        self._graph(c.rhodecode_repo, collection, c.total_cs, c.size, p)
 

	
 
        c.branch_name = branch_name
 
        c.branch_filters = [('', _('All Branches'))] + \
 
            [(k, k) for k in c.rhodecode_repo.branches.keys()]
 

	
 
        return render('changelog/changelog.html')
 

	
 
    def changelog_details(self, cs):
 
        if request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            c.cs = c.rhodecode_repo.get_changeset(cs)
 
            return render('changelog/changelog_details.html')
 

	
 
    def _graph(self, repo, collection, repo_size, size, p):
 
        """
 
        Generates a DAG graph for mercurial
 

	
 
        :param repo: repo instance
 
        :param size: number of commits to show
 
        :param p: page number
 
        """
 
        if not collection:
 
            c.jsdata = json.dumps([])
 
            return
 

	
 
        data = []
 
        revs = [x.revision for x in collection]
 

	
 
        if repo.alias == 'git':
 
            for _ in revs:
 
                vtx = [0, 1]
 
                edges = [[0, 0, 1]]
 
                data.append(['', vtx, edges])
 

	
 
        elif repo.alias == 'hg':
 
            dag = graphmod.dagwalker(repo._repo, revs)
 
            c.dag = graphmod.colored(dag, repo._repo)
 
            for (id, type, ctx, vtx, edges) in c.dag:
 
                if type != graphmod.CHANGESET:
 
                    continue
 
        dag = _dagwalker(repo, revs, repo.alias)
 
        dag = _colored(dag)
 
        for (id, type, ctx, vtx, edges) in dag:
 
                data.append(['', vtx, edges])
 

	
 
        c.jsdata = json.dumps(data)
rhodecode/lib/graphmod.py
Show inline comments
 
new file 100644
 
"""
 
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 grandparent(parentrev_func, lowestrev, roots, head):
 
    """
 
    Return all ancestors of head in roots which revision is
 
    greater or equal to lowestrev.
 
    """
 
    pending = set([head])
 
    seen = set()
 
    kept = set()
 
    llowestrev = max(nullrev, lowestrev)
 
    while pending:
 
        r = pending.pop()
 
        if r >= llowestrev and r not in seen:
 
            if r in roots:
 
                kept.add(r)
 
            else:
 
                pending.update([p for p in parentrev_func(r)])
 
            seen.add(r)
 
    return sorted(kept)
 

	
 

	
 
def _dagwalker(repo, revs, alias):
 
    if not revs:
 
        return
 

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

	
 
    lowestrev = min(revs)
 
    gpcache = {}
 

	
 
    knownrevs = set(revs)
 
    for rev in revs:
 
        ctx = repo[rev]
 
        parents = sorted(set([p.revision for p in ctx.parents
 
                              if p.revision in knownrevs]))
 
        mpars = [p.revision for p in ctx.parents if
 
                 p.revision != nullrev and p.revision not in parents]
 

	
 
        for mpar in mpars:
 
            gp = gpcache.get(mpar)
 
            if gp is None:
 
                gp = gpcache[mpar] = grandparent(cl, lowestrev, revs, mpar)
 
            if not gp:
 
                parents.append(mpar)
 
            else:
 
                parents.extend(g for g in gp if g not in parents)
 

	
 
        yield (ctx.revision, 'C', ctx, parents)
 

	
 

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

	
 
    For each DAG node this function emits tuples::
 

	
 
      (id, type, data, (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.
 
    """
 
    seen = []
 
    colors = {}
 
    newcolor = 1
 

	
 
    getconf = lambda rev: {}
 

	
 
    for (cur, type, data, parents) in dag:
 

	
 
        # Compute seen and next
 
        if cur not in seen:
 
            seen.append(cur)  # new head
 
            colors[cur] = newcolor
 
            newcolor += 1
 

	
 
        col = seen.index(cur)
 
        color = colors.pop(cur)
 
        next = seen[:]
 

	
 
        # Add parents to next
 
        addparents = [p for p in parents if p not in next]
 
        next[col:col + 1] = addparents
 

	
 
        # Set colors for the parents
 
        for i, p in enumerate(addparents):
 
            if not i:
 
                colors[p] = color
 
            else:
 
                colors[p] = newcolor
 
                newcolor += 1
 

	
 
        # Add edges to the graph
 
        edges = []
 
        for ecol, eid in enumerate(seen):
 
            if eid in next:
 
                bconf = getconf(eid)
 
                edges.append((
 
                    ecol, next.index(eid), colors[eid],
 
                    bconf.get('width', -1),
 
                    bconf.get('color', '')))
 
            elif eid == cur:
 
                for p in parents:
 
                    bconf = getconf(p)
 
                    edges.append((
 
                        ecol, next.index(p), color,
 
                        bconf.get('width', -1),
 
                        bconf.get('color', '')))
 

	
 
        # Yield and move on
 
        yield (cur, type, data, (col, color), edges)
 
        seen = next
0 comments (0 inline, 0 general)