Changeset - 42bc65838fd1
[Not reviewed]
default
0 4 0
Mads Kiilerich - 12 years ago 2013-12-10 19:30:37
madski@unity3d.com
graph: cleanup of changelog
4 files changed with 44 insertions and 66 deletions:
0 comments (0 inline, 0 general)
kallithea/controllers/changelog.py
Show inline comments
 
@@ -17,49 +17,49 @@ kallithea.controllers.changelog
 

	
 
changelog controller for Kallithea
 

	
 
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: Apr 21, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import logging
 
import traceback
 

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

	
 
import kallithea.lib.helpers as h
 
from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 
from kallithea.lib.base import BaseRepoController, render
 
from kallithea.lib.helpers import RepoPage
 
from kallithea.lib.compat import json
 
from kallithea.lib.graphmod import _colored, _dagwalker
 
from kallithea.lib.graphmod import graph_data
 
from kallithea.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError,\
 
    ChangesetError, NodeDoesNotExistError, EmptyRepositoryError
 
from kallithea.lib.utils2 import safe_int, safe_str
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def _load_changelog_summary():
 
    p = safe_int(request.GET.get('page'), 1)
 
    size = safe_int(request.GET.get('size'), 10)
 

	
 
    def url_generator(**kw):
 
        return url('changelog_summary_home',
 
                   repo_name=c.db_repo.repo_name, size=size, **kw)
 

	
 
    collection = c.db_repo_scm_instance
 

	
 
    c.repo_changesets = RepoPage(collection, page=p,
 
                                 items_per_page=size,
 
                                 url=url_generator)
 
    page_revisions = [x.raw_id for x in list(c.repo_changesets)]
 
    c.comments = c.db_repo.get_comments(page_revisions)
 
    c.statuses = c.db_repo.statuses(page_revisions)
 
@@ -77,72 +77,48 @@ class ChangelogController(BaseRepoContro
 
        Safe way to get changeset if error occur it redirects to changeset with
 
        proper message. If partial is set then don't do redirect raise Exception
 
        instead
 

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

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

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

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

	
 
        :param repo:
 
        :param revs_int:
 
        :param repo_size:
 
        :param size:
 
        :param p:
 
        """
 
        if not revs_int:
 
            c.jsdata = json.dumps([])
 
            return
 

	
 
        data = []
 
        revs = revs_int
 

	
 
        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)
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def index(self, repo_name, revision=None, f_path=None):
 
        limit = 100
 
        default = 20
 
        if request.GET.get('size'):
 
            c.size = max(min(safe_int(request.GET.get('size')), limit), 1)
 
            session['changelog_size'] = c.size
 
            session.save()
 
        else:
 
            c.size = int(session.get('changelog_size', default))
 
        # min size must be 1
 
        c.size = max(c.size, 1)
 
        p = safe_int(request.GET.get('page', 1), 1)
 
        branch_name = request.GET.get('branch', None)
 
        if (branch_name and
 
            branch_name not in c.db_repo_scm_instance.branches and
 
            branch_name not in c.db_repo_scm_instance.closed_branches and
 
            not revision):
 
            return redirect(url('changelog_file_home', repo_name=c.repo_name,
 
                                    revision=branch_name, f_path=f_path or ''))
 

	
 
        if revision == 'tip':
 
@@ -171,51 +147,51 @@ class ChangelogController(BaseRepoContro
 
                                                        branch_name=branch_name)
 
            c.total_cs = len(collection)
 

	
 
            c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
 
                                    items_per_page=c.size, branch=branch_name,)
 

	
 
            page_revisions = [x.raw_id for x in c.pagination]
 
            c.comments = c.db_repo.get_comments(page_revisions)
 
            c.statuses = c.db_repo.statuses(page_revisions)
 
        except (EmptyRepositoryError), e:
 
            h.flash(safe_str(e), category='warning')
 
            return redirect(url('summary_home', repo_name=c.repo_name))
 
        except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
 
            log.error(traceback.format_exc())
 
            h.flash(safe_str(e), category='error')
 
            return redirect(url('changelog_home', repo_name=c.repo_name))
 

	
 
        c.branch_name = branch_name
 
        c.branch_filters = [('', _('All Branches'))] + \
 
            [(k, k) for k in c.db_repo_scm_instance.branches.keys()]
 
        if c.db_repo_scm_instance.closed_branches:
 
            prefix = _('(closed)') + ' '
 
            c.branch_filters += [('-', '-')] + \
 
                [(k, prefix + k) for k in c.db_repo_scm_instance.closed_branches.keys()]
 
        _revs = []
 
        revs = []
 
        if not f_path:
 
            _revs = [x.revision for x in c.pagination]
 
        self._graph(c.db_repo_scm_instance, _revs, c.total_cs, c.size, p)
 
            revs = [x.revision for x in c.pagination]
 
        c.jsdata = json.dumps(graph_data(c.db_repo_scm_instance, revs))
 

	
 
        c.revision = revision # requested revision ref
 
        c.first_revision = c.pagination[0] # pagination is never empty here!
 
        return render('changelog/changelog.html')
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def changelog_details(self, cs):
 
        if request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            c.cs = c.db_repo_scm_instance.get_changeset(cs)
 
            return render('changelog/changelog_details.html')
 
        raise HTTPNotFound()
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def changelog_summary(self, repo_name):
 
        if request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            _load_changelog_summary()
 

	
 
            return render('changelog/changelog_summary_data.html')
 
        raise HTTPNotFound()
kallithea/lib/graphmod.py
Show inline comments
 
@@ -18,48 +18,63 @@ It allows to have a shared codebase for 
 
"""
 

	
 
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 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, repo.alias)
 
    return [(vtx, edges) for (_id, _type, ctx, vtx, edges) in _colored(dag)]
 

	
 
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]
 

	
kallithea/public/js/graph.js
Show inline comments
 
// branch_renderer.js - Rendering of branch DAGs on the client side
 
//
 
// Copyright 2010 Marcin Kuzminski <marcin AT python-works DOT com>
 
// Copyright 2008 Jesper Noehr <jesper AT noehr DOT org>
 
// Copyright 2008 Dirkjan Ochtman <dirkjan AT ochtman DOT nl>
 
// Copyright 2006 Alexander Schremmer <alex AT alexanderweb DOT de>
 
//
 
// derived from code written by Scott James Remnant <scott@ubuntu.com>
 
// Copyright 2005 Canonical Ltd.
 
//
 
// This software may be used and distributed according to the terms
 
// of the GNU General Public License, incorporated herein by reference.
 

	
 
var colors = [
 
	[ 1.0, 0.0, 0.0 ],
 
	[ 1.0, 1.0, 0.0 ],
 
	[ 0.0, 1.0, 0.0 ],
 
	[ 0.0, 1.0, 1.0 ],
 
	[ 0.0, 0.0, 1.0 ],
 
	[ 1.0, 0.0, 1.0 ],
 
	[ 1.0, 1.0, 0.0 ],
 
	[ 0.0, 0.0, 0.0 ]
 
];
 

	
 
function BranchRenderer() {
 
	
 
	this.canvas = document.getElementById("graph_canvas");
 
function BranchRenderer(canvas_id, content_id) {
 

	
 
	this.canvas = document.getElementById(canvas_id);
 
	var t = document.getElementById(content_id);
 
	this.canvas.setAttribute('height',t.clientHeight);
 
	
 
	if (!document.createElement("canvas").getContext)
 
		this.canvas = window.G_vmlCanvasManager.initElement(this.canvas);
 
	this.ctx = this.canvas.getContext('2d');
 
	this.ctx.strokeStyle = 'rgb(0, 0, 0)';
 
	this.ctx.fillStyle = 'rgb(0, 0, 0)';
 
	this.cur = [0, 0];
 
	this.line_width = 2.0;
 
	this.dot_radius = 3.5;
 
	
 
	this.setColor = function(color, bg, fg) {
 
		color %= colors.length;
 
		var red = (colors[color][0] * fg) || bg;
 
		var green = (colors[color][1] * fg) || bg;
 
		var blue = (colors[color][2] * fg) || bg;
 
		red = Math.round(red * 255);
 
		green = Math.round(green * 255);
 
		blue = Math.round(blue * 255);
 
		var s = 'rgb(' + red + ', ' + green + ', ' + blue + ')';
 
		this.ctx.strokeStyle = s;
 
		this.ctx.fillStyle = s;
 
	}
 

	
 
	this.render = function(data,canvasWidth,lineCount) {
 
	this.render = function(data,canvasWidth) {
 
		var idx = 1;
 
		var rela = document.getElementById('graph');
 
		var rela = this.canvas;
 

	
 
		this.canvas.setAttribute('width',canvasWidth);
 

	
 
		if (lineCount == 0)
 
			lineCount = 1;
 
		var lineCount = 1;
 
		for (var i=0;i<data.length;i++) {
 
			var in_l = data[i][1];
 
			for (var j in in_l) {
 
				var m = in_l[j][0];
 
				if (m > lineCount)
 
					lineCount = m;
 
			}
 
		}
 

	
 
		var edge_pad = this.dot_radius + 2;
 
		var box_size = Math.min(18, Math.floor((canvasWidth - edge_pad*2)/(lineCount)));
 
		var base_x = canvasWidth - edge_pad;
 

	
 
		for (var i in data) {
 

	
 
			var row = document.getElementById("chg_"+idx);
 
			if (row == null)
 
				continue;
 
			var	next = document.getElementById("chg_"+(idx+1));
 
			var extra = 0;
 
			
 
			cur = data[i];
 
			node = cur[1];
 
			in_l = cur[2];
 
			node = cur[0];
 
			in_l = cur[1];
 

	
 
			var rowY = row.offsetTop + row.offsetHeight/2 - rela.offsetTop;
 
			var nextY = (next == null) ? rowY + row.offsetHeight/2 : next.offsetTop + next.offsetHeight/2 - rela.offsetTop;
 

	
 
			for (var j in in_l) {
 
				
 
				line = in_l[j];
 
				start = line[0];
 
				end = line[1];
 
				color = line[2];
 

	
 
				this.setColor(color, 0.0, 0.65);
 

	
 
				
 
				x = base_x - box_size * start;
 
				
 
				this.ctx.lineWidth=this.line_width;
 
				this.ctx.beginPath();
 
				this.ctx.moveTo(x, rowY);
 

	
 
				if (start == end)
 
				{
 
					this.ctx.lineTo(x,nextY+extra,3);
 
				}
kallithea/templates/changelog/changelog.html
Show inline comments
 
@@ -16,49 +16,49 @@
 
    <% size = c.size if c.size <= c.total_cs else c.total_cs %>
 
    ${_('Changelog')}
 
    %if c.changelog_for_path:
 
     - /${c.changelog_for_path}
 
    %endif
 
    %if c.revision:
 
    @ ${h.short_id(c.first_revision.raw_id)}
 
    %endif
 
    - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
 
</%def>
 

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

	
 
<%def name="main()">
 
${self.repo_context_bar('changelog', c.first_revision.raw_id if c.first_revision else None)}
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}
 
    </div>
 
    <div class="table">
 
        % if c.pagination:
 
            <div id="graph">
 
            <div>
 
                <div style="overflow:auto; display:${'none' if c.changelog_for_path else ''}">
 
                    <div class="container_header">
 
                       <div style="float:right; margin: 0px 0px 0px 4px">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
 
                       <div class="info_box" style="text-align: right; float: right">
 
                            <a href="#" class="btn btn-mini" id="rev_range_container" style="display:none"></a>
 
                            <a href="#" class="btn btn-mini" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
 
                            %if c.revision:
 
                                <a class="btn btn-mini" href="${h.url('changelog_home', repo_name=c.repo_name)}">
 
                                    ${_('Go to tip of repository')}
 
                                </a>
 
                            %endif
 
                            %if c.db_repo.fork:
 
                                <a id="compare_fork"
 
                                   title="${_('Compare fork with %s' % c.db_repo.fork.repo_name)}"
 
                                   href="${h.url('compare_url',repo_name=c.db_repo.fork.repo_name,org_ref_type=c.db_repo.landing_rev[0],org_ref_name=c.db_repo.landing_rev[1],other_repo=c.repo_name,other_ref_type='branch' if request.GET.get('branch') else c.db_repo.landing_rev[0],other_ref_name=request.GET.get('branch') or c.db_repo.landing_rev[1], merge=1)}"
 
                                   class="btn btn-mini"><i class="icon-loop"></i> ${_('Compare fork with parent repo (%s)' % c.db_repo.fork.repo_name)}</a>
 
                            %endif
 
                            ## text and href of open_new_pr is controlled from javascript
 
                            <a id="open_new_pr" class="btn btn-mini"></a>
 
                       </div>
 

	
 
                       ${h.form(h.url.current(),method='get')}
 
                       <div style="float:left">
 
                           ${h.submit('set',_('Show'),class_="btn btn-mini")}
 
@@ -241,76 +241,52 @@ ${self.repo_context_bar('changelog', c.f
 
                    checkbox_checker();
 
                    YUE.preventDefault(e);
 
                });
 

	
 
                var msgs = YUQ('.message');
 
                // get first element height
 
                var el = YUQ('#graph_content .container')[0];
 
                var row_h = el.clientHeight;
 
                for(var i=0;i<msgs.length;i++){
 
                    var m = msgs[i];
 

	
 
                    var h = m.clientHeight;
 
                    var pad = YUD.getStyle(m,'padding');
 
                    if(h > row_h){
 
                        var offset = row_h - (h+12);
 
                        YUD.setStyle(m.nextElementSibling,'display','block');
 
                        YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
 
                    };
 
                }
 

	
 
                $('.expand_commit').on('click',function(e){
 
                    $(this).children('i').hide();
 
                    var cid = $(this).attr('commit_id');
 
                    $('#C-'+cid).css({'height': 'auto', 'margin': '4px 0px 4px 0px'})
 
                    //redraw the graph, line_count and jsdata are global vars
 
                    set_canvas(100);
 

	
 
                    var r = new BranchRenderer();
 
                    r.render(jsdata,100,line_count);
 
                    //redraw the graph, r and jsdata are bound outside function
 
                    r.render(jsdata,100);
 
                });
 

	
 
                // change branch filter
 
                YUE.on(YUD.get('branch_filter'),'change',function(e){
 
                    var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
 
                    if(selected_branch != ''){
 
                        window.location = pyroutes.url('changelog_home', {'repo_name': '${c.repo_name}',
 
                                                                          'branch':selected_branch});
 
                    }else{
 
                        window.location = pyroutes.url('changelog_home', {'repo_name': '${c.repo_name}'});
 
                    }
 
                });
 

	
 
                function set_canvas(width) {
 
                    var c = document.getElementById('graph_nodes');
 
                    var t = document.getElementById('graph_content');
 
                    canvas = document.getElementById('graph_canvas');
 
                    var div_h = t.clientHeight;
 
                    canvas.setAttribute('height',div_h);
 
                    canvas.setAttribute('width',width);
 
                };
 
                var heads = 1;
 
                var line_count = 0;
 
                var jsdata = ${c.jsdata|n};
 

	
 
                for (var i=0;i<jsdata.length;i++) {
 
                    var in_l = jsdata[i][2];
 
                    for (var j in in_l) {
 
                        var m = in_l[j][1];
 
                        if (m > line_count)
 
                            line_count = m;
 
                    }
 
                }
 
                set_canvas(100);
 

	
 
                var r = new BranchRenderer();
 
                r.render(jsdata,100,line_count);
 

	
 
                var r = new BranchRenderer('graph_canvas', 'graph_content');
 
                r.render(jsdata,100);
 
            });
 

	
 
        </script>
 
        %else:
 
            ${_('There are no changes yet')}
 
        %endif
 
    </div>
 
</div>
 
</%def>
0 comments (0 inline, 0 general)