]> source.dussan.org Git - redmine.git/commitdiff
Revision graph code cleanup.
authorEtienne Massip <etienne.massip@gmail.com>
Sat, 4 Feb 2012 14:02:31 +0000 (14:02 +0000)
committerEtienne Massip <etienne.massip@gmail.com>
Sat, 4 Feb 2012 14:02:31 +0000 (14:02 +0000)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@8773 e93f8b46-1217-0410-a6f0-8f06a7374b81

app/helpers/repositories_helper.rb
app/views/repositories/_revision_graph.html.erb
app/views/repositories/_revisions.html.erb
public/javascripts/revision_graph.js

index 24807139eeaae450e4a74231094c5e5dcf6ffe1d..3c12262a517cff713bddd63631c63de0e2dea87d 100644 (file)
@@ -256,59 +256,64 @@ module RepositoriesHelper
                      '<br />'.html_safe + l(:text_scm_path_encoding_note))
   end
 
-  def index_commits(commits, heads, href_proc = nil)
+  def index_commits(commits, heads)
     return nil if commits.nil? or commits.first.parents.nil?
-    map  = {}
-    commit_hashes = []
+
     refs_map = {}
-    href_proc ||= Proc.new {|x|x}
-    heads.each{|r| refs_map[r.scmid] ||= []; refs_map[r.scmid] << r}
-    commits.reverse.each_with_index do |c, i|
-      h = {}
-      h[:parents] = c.parents.collect do |p|
-        [p.scmid, 0, 0]
-      end
-      h[:rdmid] = i
-      h[:space] = 0
-      h[:refs]  = refs_map[c.scmid].join(" ") if refs_map.include? c.scmid
-      h[:scmid] = c.scmid
-      h[:href]  = href_proc.call(c.scmid)
-      commit_hashes << h
-      map[c.scmid] = h
+    heads.each do |head|
+      refs_map[head.scmid] ||= []
+      refs_map[head.scmid] << head
     end
-    heads.sort! do |a,b|
-      a.to_s <=> b.to_s
+
+    commits_by_scmid = {}
+    commits.reverse.each_with_index do |commit, commit_index|
+
+      commits_by_scmid[commit.scmid] = {
+        :parent_scmids => commit.parents.collect { |parent| parent.scmid },
+        :rdmid => commit_index,
+        :space => 0,
+        :refs  => refs_map.include?(commit.scmid) ? refs_map[commit.scmid].join(" ") : nil,
+        :scmid => commit.scmid,
+        :href  => block_given? ? yield(commit.scmid) : commit.scmid
+      }
     end
-    j = 0
-    heads.each do |h|
-      if map.include? h.scmid then
-        j = mark_chain(j += 1, map[h.scmid], map)
+
+    heads.sort! { |head1, head2| head1.to_s <=> head2.to_s }
+
+    mark_index = 0
+    heads.each do |head|
+      if commits_by_scmid.include? head.scmid
+        mark_index = mark_chain(mark_index += 1, commits_by_scmid[head.scmid], commits_by_scmid)
       end
     end
     # when no head matched anything use first commit
-    if j == 0 then
-       mark_chain(j += 1, map.values.first, map)
+    if mark_index == 0
+       mark_chain(mark_index += 1, commits_by_scmid.values.first, commits_by_scmid)
     end
-    map
+    commits_by_scmid
   end
 
-  def mark_chain(mark, commit, map)
-    stack = [[mark, commit]]
-    markmax = mark
+  def mark_chain(mark_index, commit, commits_by_scmid)
+
+    stack = [[mark_index, commit]]
+    mark_max_index = mark_index
+
     until stack.empty?
-      current = stack.pop
-      m, commit = current
-      commit[:space] = m  if commit[:space] == 0
-      m1 = m - 1
-      commit[:parents].each_with_index do |p, i|
-        psha = p[0]
-        if map.include? psha  and  map[psha][:space] == 0 then
-          stack << [m1 += 1, map[psha]] if i == 0
-          stack = [[m1 += 1, map[psha]]] + stack if i > 0
+      mark_index, commit = stack.pop
+      commit[:space] = mark_index if commit[:space] == 0
+
+      mark_index -=1
+      commit[:parent_scmids].each_with_index do |parent_scmid, parent_index|
+
+        parent_commit = commits_by_scmid[parent_scmid]
+
+        if parent_commit and parent_commit[:space] == 0
+
+          stack.unshift [mark_index += 1, parent_commit]
         end
       end
-      markmax = m1 if markmax < m1
+      mark_max_index = mark_index if mark_max_index < mark_index
     end
-    markmax
+    mark_max_index
   end
 end
index 6dcaa3f2d66e91739fa41ad5ef85b7306968b393..5b0ea73de812750fceaec0ebba621d358948e4e0 100644 (file)
@@ -1,13 +1,12 @@
-<%= javascript_include_tag "raphael.js" %>
-<script type="text/javascript" charset="utf-8">
-  var chunk = {commits:<%= commits.values.to_json.html_safe %>}
-</script>
-<%= javascript_include_tag "revision_graph.js" %>
+<%= javascript_include_tag 'raphael.js' %>
+<%= javascript_include_tag 'revision_graph.js' %>
 
-<script type="text/javascript">
-  Event.observe(window,"load", function(){
-    branchGraph(document.getElementById("holder"));
-  })
+<script type="text/javascript" charset="utf-8">
+  Event.observe(window, 'load', function(){
+    branchGraph(
+        document.getElementById("holder"),
+        <%= commits.to_json.html_safe %>);
+  });
 </script>
 
 <div id="holder" class="graph"></div>
index d9dfaf2b9819d03ae7aa7d982235d51e33a9173c..77744dae1f58cfcafd2624187daf46ddecd61689 100644 (file)
 <% if show_revision_graph %>
   <% if line_num == 1 %>
     <td class="revision_graph" rowspan="<%= revisions.size %>">
-      <% href_base = Proc.new {|x| url_for(:controller => 'repositories',
-                                           :action => 'revision',
-                                           :id => project,
-                                           :repository_id => @repository.identifier_param,
-                                           :rev => x) } %>
       <%= render :partial => 'revision_graph',
                  :locals => {
                     :commits => index_commits(
-                                         revisions,
-                                         @repository.branches,
-                                         href_base
-                                            )
+                                 revisions,
+                                 @repository.branches) do |scmid|
+                                     url_for(
+                                       :controller => 'repositories',
+                                       :action => 'revision',
+                                       :id => project,
+                                       :repository_id => @repository.identifier_param,
+                                       :rev => scmid)
+                                end
                     } %>
     </td>
   <% end %>
index 26b59d5300b9403481cca7a1763a42e0678793e8..dfaf7df4d98c63f264713db1bd11c5562c4d9b97 100644 (file)
-var commits = chunk.commits,
-    comms = {},
-    pixelsX = [],
-    pixelsY = [],
-    mmax = Math.max,
-    max_rdmid = 0,
-    max_space = 0,
-    parents = {};
-for (var i = 0, ii = commits.length; i < ii; i++) {
-    for (var j = 0, jj = commits[i].parents.length; j < jj; j++) {
-        parents[commits[i].parents[j][0]] = true;
-    }
-    max_rdmid = Math.max(max_rdmid, commits[i].rdmid);
-    max_space = Math.max(max_space, commits[i].space);
-}
 
-for (i = 0; i < ii; i++) {
-    if (commits[i].scmid in parents) {
-        commits[i].isParent = true;
+function branchGraph(holder, commits_hash) {
+
+    var LEFT_PADDING = 3,
+        TOP_PADDING = 10,
+        XSTEP = YSTEP = 20;
+
+    var commits_by_scmid = $H(commits_hash),
+        commits = commits_by_scmid.values();
+
+    // init max dimensions
+    var max_rdmid = max_space = 0;
+    commits.each(function(commit) {
+
+        max_rdmid = Math.max(max_rdmid, commit.rdmid);
+        max_space = Math.max(max_space, commit.space);
+    });
+
+    var graph_height = max_rdmid * YSTEP + YSTEP,
+        graph_width = max_space * XSTEP + XSTEP;
+
+    // init colors
+    var colors = ['#000'];
+    for (var k = 0; k < max_space; k++) {
+        colors.push(Raphael.getColor());
     }
-    comms[commits[i].scmid] = commits[i];
-}
-var colors = ["#000"];
-for (var k = 0; k < max_space; k++) {
-    colors.push(Raphael.getColor());
-}
-
-function branchGraph(holder) {
-    var xstep = 20, ystep = 20;
-    var ch, cw;
-    cw = max_space * xstep + xstep;
-    ch = max_rdmid * ystep + ystep;
-    var r = Raphael("holder", cw, ch),
-        top = r.set();
-    var cuday = 0, cumonth = "";
-
-    for (i = 0; i < ii; i++) {
-        var x, y;
-        y = 10 + ystep *(max_rdmid - commits[i].rdmid);
-        x = 3 + xstep * commits[i].space;
-        var stroke = "none";
-        r.circle(x, y, 3).attr({fill: colors[commits[i].space], stroke: stroke});
-        if (commits[i].refs != null && commits[i].refs != "") {
-            var longrefs  = commits[i].refs
-            var shortrefs = commits[i].refs;
-            if (shortrefs.length > 15) {
-              shortrefs = shortrefs.substr(0,13) + "...";
-              }
-            var t = r.text(x+5,y+5,shortrefs).attr({font: "12px Fontin-Sans, Arial", fill: "#666",
-            title: longrefs, cursor: "pointer", rotation: "0"});
-
-            var textbox = t.getBBox();
-            t.translate(textbox.width / 2, textbox.height / -3);
-         }
-        for (var j = 0, jj = commits[i].parents.length; j < jj; j++) {
-            var c = comms[commits[i].parents[j][0]];
-            var p,arrow;
-            if (c) {
-                var cy, cx;
-                cy = 10 + ystep * (max_rdmid - c.rdmid),
-                cx = 3 + xstep * c.space;
-
-                if (c.space == commits[i].space) {
-                    p = r.path("M" + x + "," + y + "L" + cx + "," + cy);
+
+    // create graph
+    var graph = Raphael(holder, graph_width, graph_height),
+        top = graph.set();
+
+    var parent_commit;
+    var x, y, parent_x, parent_y;
+    var path, longrefs, shortrefs, label, labelBBox;
+
+    commits.each(function(commit) {
+
+        y = TOP_PADDING + YSTEP *(max_rdmid - commit.rdmid);
+        x = LEFT_PADDING + XSTEP * commit.space;
+
+        graph.circle(x, y, 3).attr({fill: colors[commit.space], stroke: 'none'});
+
+        // title
+        if (commit.refs != null && commit.refs != '') {
+            longrefs  = commit.refs;
+            shortrefs = longrefs.length > 15 ? longrefs.substr(0, 13) + '...' : longrefs;
+
+            label = graph.text(x + 5, y + 5, shortrefs)
+                .attr({
+                    font: '12px Fontin-Sans, Arial',
+                    fill: '#666',
+                    title: longrefs,
+                    cursor: 'pointer',
+                    rotation: '0'});
+
+            labelBBox = label.getBBox();
+            label.translate(labelBBox.width / 2, -labelBBox.height / 3);
+        }
+
+        // paths to parents
+        commit.parent_scmids.each(function(parent_scmid) {
+            parent_commit = commits_by_scmid.get(parent_scmid);
+
+            if (parent_commit) {
+                parent_y = TOP_PADDING + YSTEP * (max_rdmid - parent_commit.rdmid);
+                parent_x = LEFT_PADDING + XSTEP * parent_commit.space;
+
+                if (parent_commit.space == commit.space) {
+                    // vertical path
+                    path = graph.path([
+                        'M', x, y,
+                        'V', parent_y]);
                 } else {
-                    p = r.path(["M", x, y, "C",x,y,x, y+(cy-y)/2,x+(cx-x)/2, y+(cy-y)/2,
-                                "C", x+(cx-x)/2,y+(cy-y)/2, cx, cy-(cy-y)/2, cx, cy]);
+                    // path to a commit in a different branch (Bezier curve)
+                    path = graph.path([
+                        'M', x, y,
+                        'C', x, y, x, y + (parent_y - y) / 2, x + (parent_x - x) / 2, y + (parent_y - y) / 2,
+                        'C', x + (parent_x - x) / 2, y + (parent_y - y) / 2, parent_x, parent_y-(parent_y-y)/2, parent_x, parent_y]);
                 }
             } else {
-              p = r.path("M" + x + "," + y + "L" + x + "," + ch);
-             }
-            p.attr({stroke: colors[commits[i].space], "stroke-width": 1.5});
-         }
-        (function (c, x, y) {
-            top.push(r.circle(x, y, 10).attr({fill: "#000", opacity: 0,
-                                              cursor: "pointer", href: commits[i].href})
-              .hover(function () {}, function () {})
-              );
-        }(commits[i], x, y));
-     }
-    top.toFront();
-    var hw = holder.offsetWidth,
-        hh = holder.offsetHeight,
-        drag,
-        dragger = function (e) {
-            if (drag) {
-                e = e || window.event;
-                holder.scrollLeft = drag.sl - (e.clientX - drag.x);
-                holder.scrollTop = drag.st - (e.clientY - drag.y);
+                // vertical path ending at the bottom of the graph
+                path = graph.path([
+                    'M', x, y,
+                    'V', graph_height]);
             }
-        };
-    holder.onmousedown = function (e) {
-        e = e || window.event;
-        drag = {x: e.clientX, y: e.clientY, st: holder.scrollTop, sl: holder.scrollLeft};
-        document.onmousemove = dragger;
-    };
-    document.onmouseup = function () {
-        drag = false;
-        document.onmousemove = null;
-    };
-    holder.scrollLeft = cw;
-};
+            path.attr({stroke: colors[commit.space], "stroke-width": 1.5});
+        });
 
-Raphael.fn.popupit = function (x, y, set, dir, size) {
-    dir = dir == null ? 2 : dir;
-    size = size || 5;
-    x = Math.round(x);
-    y = Math.round(y);
-    var bb = set.getBBox(),
-        w = Math.round(bb.width / 2),
-        h = Math.round(bb.height / 2),
-        dx = [0, w + size * 2, 0, -w - size * 2],
-        dy = [-h * 2 - size * 3, -h - size, 0, -h - size],
-        p = ["M", x - dx[dir], y - dy[dir], "l", -size, (dir == 2) * -size, -mmax(w - size, 0),
-             0, "a", size, size, 0, 0, 1, -size, -size,
-            "l", 0, -mmax(h - size, 0), (dir == 3) * -size, -size, (dir == 3) * size, -size, 0,
-            -mmax(h - size, 0), "a", size, size, 0, 0, 1, size, -size,
-            "l", mmax(w - size, 0), 0, size, !dir * -size, size, !dir * size, mmax(w - size, 0),
-            0, "a", size, size, 0, 0, 1, size, size,
-            "l", 0, mmax(h - size, 0), (dir == 1) * size, size, (dir == 1) * -size, size, 0,
-            mmax(h - size, 0), "a", size, size, 0, 0, 1, -size, size,
-            "l", -mmax(w - size, 0), 0, "z"].join(","),
-        xy = [{x: x, y: y + size * 2 + h},
-              {x: x - size * 2 - w, y: y},
-              {x: x, y: y - size * 2 - h},
-              {x: x + size * 2 + w, y: y}]
-              [dir];
-    set.translate(xy.x - w - bb.x, xy.y - h - bb.y);
-    return this.set(this.path(p).attr({fill: "#234", stroke: "none"})
-                     .insertBefore(set.node ? set : set[0]), set);
-};
+        top.push(graph.circle(x, y, 10)
+            .attr({
+                fill: '#000',
+                opacity: 0,
+                cursor: 'pointer',
+                href: commit.href})
+            .hover(function () {}, function () {}));
+    });
 
-Raphael.fn.popup = function (x, y, text, dir, size) {
-    dir = dir == null ? 2 : dir > 3 ? 3 : dir;
-    size = size || 5;
-    text = text || "$9.99";
-    var res = this.set(),
-        d = 3;
-    res.push(this.path().attr({fill: "#000", stroke: "#000"}));
-    res.push(this.text(x, y, text).attr(this.g.txtattr).attr({fill: "#fff", "font-family": "Helvetica, Arial"}));
-    res.update = function (X, Y, withAnimation) {
-        X = X || x;
-        Y = Y || y;
-        var bb = this[1].getBBox(),
-            w = bb.width / 2,
-            h = bb.height / 2,
-            dx = [0, w + size * 2, 0, -w - size * 2],
-            dy = [-h * 2 - size * 3, -h - size, 0, -h - size],
-            p = ["M", X - dx[dir], Y - dy[dir], "l", -size, (dir == 2) * -size,
-                 -mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, -size, -size,
-                "l", 0, -mmax(h - size, 0), (dir == 3) * -size, -size, (dir == 3) * size, -size,
-                 0, -mmax(h - size, 0), "a", size, size, 0, 0, 1, size, -size,
-                "l", mmax(w - size, 0), 0, size, !dir * -size, size, !dir * size, mmax(w - size, 0),
-                 0, "a", size, size, 0, 0, 1, size, size,
-                "l", 0, mmax(h - size, 0), (dir == 1) * size, size, (dir == 1) * -size, size, 0,
-                mmax(h - size, 0), "a", size, size, 0, 0, 1, -size, size,
-                "l", -mmax(w - size, 0), 0, "z"].join(","),
-            xy = [{x: X, y: Y + size * 2 + h},
-                  {x: X - size * 2 - w, y: Y},
-                  {x: X, y: Y - size * 2 - h},
-                  {x: X + size * 2 + w, y: Y}]
-                  [dir];
-        xy.path = p;
-        if (withAnimation) {
-            this.animate(xy, 500, ">");
-        } else {
-            this.attr(xy);
-         }
-        return this;
-     };
-    return res.update(x, y);
+    top.toFront();
 };