Commit d8a40d8c933da8e89013e989940f8b60d0f2e247

Authored by Sato Hiroyuki
1 parent c9b1df12

Move graph module from lib or vendor directory to app directory.

Because not autoloading lib directory at development mode.
app/assets/javascripts/branch-graph.js 0 → 100644
... ... @@ -0,0 +1,385 @@
  1 +!function(){
  2 +
  3 + var BranchGraph = function(element, options){
  4 + this.element = element;
  5 + this.options = options;
  6 +
  7 + this.preparedCommits = {};
  8 + this.mtime = 0;
  9 + this.mspace = 0;
  10 + this.parents = {};
  11 + this.colors = ["#000"];
  12 +
  13 + this.load();
  14 + };
  15 +
  16 + BranchGraph.prototype.load = function(){
  17 + $.ajax({
  18 + url: this.options.url,
  19 + method: 'get',
  20 + dataType: 'json',
  21 + success: $.proxy(function(data){
  22 + $('.loading', this.element).hide();
  23 + this.prepareData(data.days, data.commits);
  24 + this.buildGraph();
  25 + }, this)
  26 + });
  27 + };
  28 +
  29 + BranchGraph.prototype.prepareData = function(days, commits){
  30 + this.days = days;
  31 + this.dayCount = days.length;
  32 + this.commits = commits;
  33 + this.commitCount = commits.length;
  34 +
  35 + this.collectParents();
  36 +
  37 + this.mtime += 4;
  38 + this.mspace += 10;
  39 + for (var i = 0; i < this.commitCount; i++) {
  40 + if (this.commits[i].id in this.parents) {
  41 + this.commits[i].isParent = true;
  42 + }
  43 + this.preparedCommits[this.commits[i].id] = this.commits[i];
  44 + }
  45 + this.collectColors();
  46 + };
  47 +
  48 + BranchGraph.prototype.collectParents = function(){
  49 + for (var i = 0; i < this.commitCount; i++) {
  50 + for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) {
  51 + this.parents[this.commits[i].parents[j][0]] = true;
  52 + }
  53 + this.mtime = Math.max(this.mtime, this.commits[i].time);
  54 + this.mspace = Math.max(this.mspace, this.commits[i].space);
  55 + }
  56 + };
  57 +
  58 + BranchGraph.prototype.collectColors = function(){
  59 + for (var k = 0; k < this.mspace; k++) {
  60 + this.colors.push(Raphael.getColor(.8));
  61 + // Skipping a few colors in the spectrum to get more contrast between colors
  62 + Raphael.getColor();Raphael.getColor();
  63 + }
  64 + };
  65 +
  66 + BranchGraph.prototype.buildGraph = function(){
  67 + var graphWidth = $(this.element).width()
  68 + , ch = this.mspace * 20 + 100
  69 + , cw = Math.max(graphWidth, this.mtime * 20 + 260)
  70 + , r = Raphael(this.element.get(0), cw, ch)
  71 + , top = r.set()
  72 + , cuday = 0
  73 + , cumonth = ""
  74 + , offsetX = 20
  75 + , offsetY = 60
  76 + , barWidth = Math.max(graphWidth, this.dayCount * 20 + 320)
  77 + , scrollLeft = cw;
  78 +
  79 + this.raphael = r;
  80 +
  81 + r.rect(0, 0, barWidth, 20).attr({fill: "#222"});
  82 + r.rect(0, 20, barWidth, 20).attr({fill: "#444"});
  83 +
  84 + for (mm = 0; mm < this.dayCount; mm++) {
  85 + if(this.days[mm] != null){
  86 + if(cuday != this.days[mm][0]){
  87 + // Dates
  88 + r.text(offsetX + mm * 20, 31, this.days[mm][0]).attr({
  89 + font: "12px Monaco, monospace",
  90 + fill: "#DDD"
  91 + });
  92 + cuday = this.days[mm][0];
  93 + }
  94 + if(cumonth != this.days[mm][1]){
  95 + // Months
  96 + r.text(offsetX + mm * 20, 11, this.days[mm][1]).attr({
  97 + font: "12px Monaco, monospace",
  98 + fill: "#EEE"
  99 + });
  100 + cumonth = this.days[mm][1];
  101 + }
  102 + }
  103 + }
  104 +
  105 + for (i = 0; i < this.commitCount; i++) {
  106 + var x = offsetX + 20 * this.commits[i].time
  107 + , y = offsetY + 10 * this.commits[i].space
  108 + , c
  109 + , ps;
  110 +
  111 + // Draw dot
  112 + r.circle(x, y, 3).attr({
  113 + fill: this.colors[this.commits[i].space],
  114 + stroke: "none"
  115 + });
  116 +
  117 + // Draw lines
  118 + for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) {
  119 + c = this.preparedCommits[this.commits[i].parents[j][0]];
  120 + ps = this.commits[i].parent_spaces[j];
  121 + if (c) {
  122 + var cx = offsetX + 20 * c.time
  123 + , cy = offsetY + 10 * c.space
  124 + , psy = offsetY + 10 * ps;
  125 + if (c.space == this.commits[i].space && c.space == ps) {
  126 + r.path([
  127 + "M", x, y,
  128 + "L", cx, cy
  129 + ]).attr({
  130 + stroke: this.colors[c.space],
  131 + "stroke-width": 2
  132 + });
  133 +
  134 + } else if (c.space < this.commits[i].space) {
  135 + r.path([
  136 + "M", x - 5, y,
  137 + "l-5-2,0,4,5,-2",
  138 + "L", x - 10, y,
  139 + "L", x - 15, psy,
  140 + "L", cx + 5, psy,
  141 + "L", cx, cy])
  142 + .attr({
  143 + stroke: this.colors[this.commits[i].space],
  144 + "stroke-width": 2
  145 + });
  146 + } else {
  147 + r.path([
  148 + "M", x - 3, y + 6,
  149 + "l-4,3,4,2,0,-5",
  150 + "L", x - 5, y + 10,
  151 + "L", x - 10, psy,
  152 + "L", cx + 5, psy,
  153 + "L", cx, cy])
  154 + .attr({
  155 + stroke: this.colors[c.space],
  156 + "stroke-width": 2
  157 + });
  158 + }
  159 + }
  160 + }
  161 +
  162 + if (this.commits[i].refs) {
  163 + this.appendLabel(x, y, this.commits[i].refs);
  164 + }
  165 +
  166 + // mark commit and displayed in the center
  167 + if (this.commits[i].id == this.options.commit_id) {
  168 + r.path([
  169 + 'M', x, y - 5,
  170 + 'L', x + 4, y - 15,
  171 + 'L', x - 4, y - 15,
  172 + 'Z'
  173 + ]).attr({
  174 + "fill": "#000",
  175 + "fill-opacity": .7,
  176 + "stroke": "none"
  177 + });
  178 + scrollLeft = x - graphWidth / 2;
  179 + }
  180 +
  181 + this.appendAnchor(top, this.commits[i], x, y);
  182 + }
  183 + top.toFront();
  184 + this.element.scrollLeft(scrollLeft);
  185 + this.bindEvents();
  186 + };
  187 +
  188 + BranchGraph.prototype.bindEvents = function(){
  189 + var drag = {}
  190 + , element = this.element;
  191 +
  192 + var dragger = function(event){
  193 + element.scrollLeft(drag.sl - (event.clientX - drag.x));
  194 + element.scrollTop(drag.st - (event.clientY - drag.y));
  195 + };
  196 +
  197 + element.on({
  198 + mousedown: function (event) {
  199 + drag = {
  200 + x: event.clientX,
  201 + y: event.clientY,
  202 + st: element.scrollTop(),
  203 + sl: element.scrollLeft()
  204 + };
  205 + $(window).on('mousemove', dragger);
  206 + }
  207 + });
  208 + $(window).on({
  209 + mouseup: function(){
  210 + //bars.animate({opacity: 0}, 300);
  211 + $(window).off('mousemove', dragger);
  212 + },
  213 + keydown: function(event){
  214 + if(event.keyCode == 37){
  215 + // left
  216 + element.scrollLeft( element.scrollLeft() - 50);
  217 + }
  218 + if(event.keyCode == 38){
  219 + // top
  220 + element.scrollTop( element.scrollTop() - 50);
  221 + }
  222 + if(event.keyCode == 39){
  223 + // right
  224 + element.scrollLeft( element.scrollLeft() + 50);
  225 + }
  226 + if(event.keyCode == 40){
  227 + // bottom
  228 + element.scrollTop( element.scrollTop() + 50);
  229 + }
  230 + }
  231 + });
  232 + };
  233 +
  234 + BranchGraph.prototype.appendLabel = function(x, y, refs){
  235 + var r = this.raphael
  236 + , shortrefs = refs
  237 + , text, textbox, rect;
  238 +
  239 + if (shortrefs.length > 17){
  240 + // Truncate if longer than 15 chars
  241 + shortrefs = shortrefs.substr(0,15) + "…";
  242 + }
  243 +
  244 + text = r.text(x+5, y+8 + 10, shortrefs).attr({
  245 + font: "10px Monaco, monospace",
  246 + fill: "#FFF",
  247 + title: refs
  248 + });
  249 +
  250 + textbox = text.getBBox();
  251 + text.transform([
  252 + 't', textbox.height/-4, textbox.width/2 + 5,
  253 + 'r90'
  254 + ]);
  255 +
  256 + // Create rectangle based on the size of the textbox
  257 + rect = r.rect(x, y, textbox.width + 15, textbox.height + 5, 4).attr({
  258 + "fill": "#000",
  259 + "fill-opacity": .7,
  260 + "stroke": "none"
  261 + });
  262 +
  263 + triangle = r.path([
  264 + 'M', x, y + 5,
  265 + 'L', x + 4, y + 15,
  266 + 'L', x - 4, y + 15,
  267 + 'Z'
  268 + ]).attr({
  269 + "fill": "#000",
  270 + "fill-opacity": .7,
  271 + "stroke": "none"
  272 + });
  273 +
  274 + // Rotate and reposition rectangle over text
  275 + rect.transform([
  276 + 'r', 90, x, y,
  277 + 't', 15, -9
  278 + ]);
  279 +
  280 + // Set text to front
  281 + text.toFront();
  282 + };
  283 +
  284 + BranchGraph.prototype.appendAnchor = function(top, commit, x, y) {
  285 + var r = this.raphael
  286 + , options = this.options
  287 + , anchor;
  288 + anchor = r.circle(x, y, 10).attr({
  289 + fill: "#000",
  290 + opacity: 0,
  291 + cursor: "pointer"
  292 + })
  293 + .click(function(){
  294 + window.open(options.commit_url.replace('%s', commit.id), '_blank');
  295 + })
  296 + .hover(function(){
  297 + this.tooltip = r.commitTooltip(x, y + 5, commit);
  298 + top.push(this.tooltip.insertBefore(this));
  299 + }, function(){
  300 + this.tooltip && this.tooltip.remove() && delete this.tooltip;
  301 + });
  302 + top.push(anchor);
  303 + };
  304 +
  305 + this.BranchGraph = BranchGraph;
  306 +
  307 +}(this);
  308 +Raphael.fn.commitTooltip = function(x, y, commit){
  309 + var nameText, idText, messageText
  310 + , boxWidth = 300
  311 + , boxHeight = 200;
  312 +
  313 + nameText = this.text(x, y + 10, commit.author.name);
  314 + idText = this.text(x, y + 35, commit.id);
  315 + messageText = this.text(x, y + 50, commit.message);
  316 +
  317 + textSet = this.set(nameText, idText, messageText).attr({
  318 + "text-anchor": "start",
  319 + "font": "12px Monaco, monospace"
  320 + });
  321 +
  322 + nameText.attr({
  323 + "font": "14px Arial",
  324 + "font-weight": "bold"
  325 + });
  326 +
  327 + idText.attr({
  328 + "fill": "#AAA"
  329 + });
  330 +
  331 + textWrap(messageText, boxWidth - 50);
  332 +
  333 + var rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({
  334 + "fill": "#FFF",
  335 + "stroke": "#000",
  336 + "stroke-linecap": "round",
  337 + "stroke-width": 2
  338 + });
  339 + var tooltip = this.set(rect, textSet);
  340 +
  341 + rect.attr({
  342 + "height" : tooltip.getBBox().height + 10,
  343 + "width" : tooltip.getBBox().width + 10
  344 + });
  345 +
  346 + tooltip.transform([
  347 + 't', 20, 20
  348 + ]);
  349 +
  350 + return tooltip;
  351 +};
  352 +
  353 +function textWrap(t, width) {
  354 + var content = t.attr("text");
  355 + var abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  356 + t.attr({
  357 + "text" : abc
  358 + });
  359 + var letterWidth = t.getBBox().width / abc.length;
  360 +
  361 + t.attr({
  362 + "text" : content
  363 + });
  364 +
  365 + var words = content.split(" ");
  366 + var x = 0, s = [];
  367 + for ( var i = 0; i < words.length; i++) {
  368 +
  369 + var l = words[i].length;
  370 + if (x + (l * letterWidth) > width) {
  371 + s.push("\n");
  372 + x = 0;
  373 + }
  374 + x += l * letterWidth;
  375 + s.push(words[i] + " ");
  376 + }
  377 + t.attr({
  378 + "text" : s.join("")
  379 + });
  380 + var b = t.getBBox()
  381 + , h = Math.abs(b.y2) - Math.abs(b.y) + 1;
  382 + t.attr({
  383 + "y": b.y + h
  384 + });
  385 +}
... ...
app/controllers/graph_controller.rb
... ... @@ -20,7 +20,7 @@ class GraphController &lt; ProjectResourceController
20 20 respond_to do |format|
21 21 format.html
22 22 format.json do
23   - graph = Gitlab::Graph::JsonBuilder.new(project, @ref, @commit)
  23 + graph = Graph::JsonBuilder.new(project, @ref, @commit)
24 24 render :json => graph.to_json
25 25 end
26 26 end
... ...
app/controllers/projects_controller.rb
1   -require Rails.root.join('lib', 'gitlab', 'graph', 'json_builder')
2   -
3 1 class ProjectsController < ProjectResourceController
4 2 skip_before_filter :project, only: [:new, :create]
5 3 skip_before_filter :repository, only: [:new, :create]
... ...
app/models/graph/commit.rb 0 → 100644
... ... @@ -0,0 +1,50 @@
  1 +require "grit"
  2 +
  3 +module Graph
  4 + class Commit
  5 + include ActionView::Helpers::TagHelper
  6 +
  7 + attr_accessor :time, :space, :refs, :parent_spaces
  8 +
  9 + def initialize(commit)
  10 + @_commit = commit
  11 + @time = -1
  12 + @space = 0
  13 + @parent_spaces = []
  14 + end
  15 +
  16 + def method_missing(m, *args, &block)
  17 + @_commit.send(m, *args, &block)
  18 + end
  19 +
  20 + def to_graph_hash
  21 + h = {}
  22 + h[:parents] = self.parents.collect do |p|
  23 + [p.id,0,0]
  24 + end
  25 + h[:author] = {
  26 + name: author.name,
  27 + email: author.email
  28 + }
  29 + h[:time] = time
  30 + h[:space] = space
  31 + h[:parent_spaces] = parent_spaces
  32 + h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil?
  33 + h[:id] = sha
  34 + h[:date] = date
  35 + h[:message] = message
  36 + h
  37 + end
  38 +
  39 + def add_refs(ref_cache, repo)
  40 + if ref_cache.empty?
  41 + repo.refs.each do |ref|
  42 + ref_cache[ref.commit.id] ||= []
  43 + ref_cache[ref.commit.id] << ref
  44 + end
  45 + end
  46 + @refs = ref_cache[@_commit.id] if ref_cache.include?(@_commit.id)
  47 + @refs ||= []
  48 + end
  49 + end
  50 +end
... ...
app/models/graph/json_builder.rb 0 → 100644
... ... @@ -0,0 +1,266 @@
  1 +require "grit"
  2 +
  3 +module Graph
  4 + class JsonBuilder
  5 + attr_accessor :days, :commits, :ref_cache, :repo
  6 +
  7 + def self.max_count
  8 + @max_count ||= 650
  9 + end
  10 +
  11 + def initialize project, ref, commit
  12 + @project = project
  13 + @ref = ref
  14 + @commit = commit
  15 + @repo = project.repo
  16 + @ref_cache = {}
  17 +
  18 + @commits = collect_commits
  19 + @days = index_commits
  20 + end
  21 +
  22 + def to_json(*args)
  23 + {
  24 + days: @days.compact.map { |d| [d.day, d.strftime("%b")] },
  25 + commits: @commits.map(&:to_graph_hash)
  26 + }.to_json(*args)
  27 + end
  28 +
  29 + protected
  30 +
  31 + # Get commits from repository
  32 + #
  33 + def collect_commits
  34 +
  35 + @commits = Grit::Commit.find_all(repo, nil, {topo_order: true, max_count: self.class.max_count, skip: to_commit}).dup
  36 +
  37 + # Decorate with app/models/commit.rb
  38 + @commits.map! { |commit| Commit.new(commit) }
  39 +
  40 + # Decorate with lib/gitlab/graph/commit.rb
  41 + @commits.map! { |commit| Graph::Commit.new(commit) }
  42 +
  43 + # add refs to each commit
  44 + @commits.each { |commit| commit.add_refs(ref_cache, repo) }
  45 +
  46 + @commits
  47 + end
  48 +
  49 + # Method is adding time and space on the
  50 + # list of commits. As well as returns date list
  51 + # corelated with time set on commits.
  52 + #
  53 + # @param [Array<Graph::Commit>] commits to index
  54 + #
  55 + # @return [Array<TimeDate>] list of commit dates corelated with time on commits
  56 + def index_commits
  57 + days, times = [], []
  58 + map = {}
  59 +
  60 + commits.reverse.each_with_index do |c,i|
  61 + c.time = i
  62 + days[i] = c.committed_date
  63 + map[c.id] = c
  64 + times[i] = c
  65 + end
  66 +
  67 + @_reserved = {}
  68 + days.each_index do |i|
  69 + @_reserved[i] = []
  70 + end
  71 +
  72 + commits_sort_by_ref.each do |commit|
  73 + if map.include? commit.id then
  74 + place_chain(map[commit.id], map)
  75 + end
  76 + end
  77 +
  78 + # find parent spaces for not overlap lines
  79 + times.each do |c|
  80 + c.parent_spaces.concat(find_free_parent_spaces(c, map, times))
  81 + end
  82 +
  83 + days
  84 + end
  85 +
  86 + # Skip count that the target commit is displayed in center.
  87 + def to_commit
  88 + commits = Grit::Commit.find_all(repo, nil, {topo_order: true})
  89 + commit_index = commits.index do |c|
  90 + c.id == @commit.id
  91 + end
  92 +
  93 + if commit_index && (self.class.max_count / 2 < commit_index) then
  94 + # get max index that commit is displayed in the center.
  95 + commit_index - self.class.max_count / 2
  96 + else
  97 + 0
  98 + end
  99 + end
  100 +
  101 + def commits_sort_by_ref
  102 + commits.sort do |a,b|
  103 + if include_ref?(a)
  104 + -1
  105 + elsif include_ref?(b)
  106 + 1
  107 + else
  108 + b.committed_date <=> a.committed_date
  109 + end
  110 + end
  111 + end
  112 +
  113 + def include_ref?(commit)
  114 + heads = commit.refs.select do |ref|
  115 + ref.is_a?(Grit::Head) or ref.is_a?(Grit::Remote) or ref.is_a?(Grit::Tag)
  116 + end
  117 +
  118 + heads.map! do |head|
  119 + head.name
  120 + end
  121 +
  122 + heads.include?(@ref)
  123 + end
  124 +
  125 + def find_free_parent_spaces(commit, map, times)
  126 + spaces = []
  127 +
  128 + commit.parents.each do |p|
  129 + if map.include?(p.id) then
  130 + parent = map[p.id]
  131 +
  132 + range = if commit.time < parent.time then
  133 + commit.time..parent.time
  134 + else
  135 + parent.time..commit.time
  136 + end
  137 +
  138 + space = if commit.space >= parent.space then
  139 + find_free_parent_space(range, parent.space, 1, commit.space, times)
  140 + else
  141 + find_free_parent_space(range, parent.space, -1, parent.space, times)
  142 + end
  143 +
  144 + mark_reserved(range, space)
  145 + spaces << space
  146 + end
  147 + end
  148 +
  149 + spaces
  150 + end
  151 +
  152 + def find_free_parent_space(range, space_base, space_step, space_default, times)
  153 + if is_overlap?(range, times, space_default) then
  154 + find_free_space(range, space_base, space_step)
  155 + else
  156 + space_default
  157 + end
  158 + end
  159 +
  160 + def is_overlap?(range, times, overlap_space)
  161 + range.each do |i|
  162 + if i != range.first &&
  163 + i != range.last &&
  164 + times[i].space == overlap_space then
  165 +
  166 + return true;
  167 + end
  168 + end
  169 +
  170 + false
  171 + end
  172 +
  173 + # Add space mark on commit and its parents
  174 + #
  175 + # @param [Graph::Commit] the commit object.
  176 + # @param [Hash<String,Graph::Commit>] map of commits
  177 + def place_chain(commit, map, parent_time = nil)
  178 + leaves = take_left_leaves(commit, map)
  179 + if leaves.empty?
  180 + return
  181 + end
  182 + # and mark it as reserved
  183 + min_time = leaves.last.time
  184 + max_space = 1
  185 + parents = leaves.last.parents.collect
  186 + parents.each do |p|
  187 + if map.include? p.id
  188 + parent = map[p.id]
  189 + if parent.time < min_time
  190 + min_time = parent.time
  191 + end
  192 + if max_space < parent.space then
  193 + max_space = parent.space
  194 + end
  195 + end
  196 + end
  197 + if parent_time.nil?
  198 + max_time = leaves.first.time
  199 + else
  200 + max_time = parent_time - 1
  201 + end
  202 +
  203 + time_range = leaves.last.time..leaves.first.time
  204 + space = find_free_space(time_range, max_space, 2)
  205 + leaves.each{|l| l.space = space}
  206 +
  207 + mark_reserved(min_time..max_time, space)
  208 +
  209 + # Visit branching chains
  210 + leaves.each do |l|
  211 + parents = l.parents.collect.select{|p| map.include? p.id and map[p.id].space.zero?}
  212 + for p in parents
  213 + place_chain(map[p.id], map, l.time)
  214 + end
  215 + end
  216 + end
  217 +
  218 + def mark_reserved(time_range, space)
  219 + for day in time_range
  220 + @_reserved[day].push(space)
  221 + end
  222 + end
  223 +
  224 + def find_free_space(time_range, space_base, space_step)
  225 + reserved = []
  226 + for day in time_range
  227 + reserved += @_reserved[day]
  228 + end
  229 + reserved.uniq!
  230 +
  231 + space = space_base
  232 + while reserved.include?(space) do
  233 + space += space_step
  234 + if space <= 0 then
  235 + space_step *= -1
  236 + space = space_base + space_step
  237 + end
  238 + end
  239 +
  240 + space
  241 + end
  242 +
  243 + # Takes most left subtree branch of commits
  244 + # which don't have space mark yet.
  245 + #
  246 + # @param [Graph::Commit] the commit object.
  247 + # @param [Hash<String,Graph::Commit>] map of commits
  248 + #
  249 + # @return [Array<Graph::Commit>] list of branch commits
  250 + def take_left_leaves(commit, map)
  251 + leaves = []
  252 + leaves.push(commit) if commit.space.zero?
  253 +
  254 + while true
  255 + return leaves if commit.parents.count.zero?
  256 + return leaves unless map.include? commit.parents.first.id
  257 +
  258 + commit = map[commit.parents.first.id]
  259 +
  260 + return leaves unless commit.space.zero?
  261 +
  262 + leaves.push(commit)
  263 + end
  264 + end
  265 + end
  266 +end
... ...
lib/gitlab/graph/commit.rb
... ... @@ -1,52 +0,0 @@
1   -require "grit"
2   -
3   -module Gitlab
4   - module Graph
5   - class Commit
6   - include ActionView::Helpers::TagHelper
7   -
8   - attr_accessor :time, :space, :refs, :parent_spaces
9   -
10   - def initialize(commit)
11   - @_commit = commit
12   - @time = -1
13   - @space = 0
14   - @parent_spaces = []
15   - end
16   -
17   - def method_missing(m, *args, &block)
18   - @_commit.send(m, *args, &block)
19   - end
20   -
21   - def to_graph_hash
22   - h = {}
23   - h[:parents] = self.parents.collect do |p|
24   - [p.id,0,0]
25   - end
26   - h[:author] = {
27   - name: author.name,
28   - email: author.email
29   - }
30   - h[:time] = time
31   - h[:space] = space
32   - h[:parent_spaces] = parent_spaces
33   - h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil?
34   - h[:id] = sha
35   - h[:date] = date
36   - h[:message] = message
37   - h
38   - end
39   -
40   - def add_refs(ref_cache, repo)
41   - if ref_cache.empty?
42   - repo.refs.each do |ref|
43   - ref_cache[ref.commit.id] ||= []
44   - ref_cache[ref.commit.id] << ref
45   - end
46   - end
47   - @refs = ref_cache[@_commit.id] if ref_cache.include?(@_commit.id)
48   - @refs ||= []
49   - end
50   - end
51   - end
52   -end
lib/gitlab/graph/json_builder.rb
... ... @@ -1,268 +0,0 @@
1   -require "grit"
2   -
3   -module Gitlab
4   - module Graph
5   - class JsonBuilder
6   - attr_accessor :days, :commits, :ref_cache, :repo
7   -
8   - def self.max_count
9   - @max_count ||= 650
10   - end
11   -
12   - def initialize project, ref, commit
13   - @project = project
14   - @ref = ref
15   - @commit = commit
16   - @repo = project.repo
17   - @ref_cache = {}
18   -
19   - @commits = collect_commits
20   - @days = index_commits
21   - end
22   -
23   - def to_json(*args)
24   - {
25   - days: @days.compact.map { |d| [d.day, d.strftime("%b")] },
26   - commits: @commits.map(&:to_graph_hash)
27   - }.to_json(*args)
28   - end
29   -
30   - protected
31   -
32   - # Get commits from repository
33   - #
34   - def collect_commits
35   -
36   - @commits = Grit::Commit.find_all(repo, nil, {topo_order: true, max_count: self.class.max_count, skip: to_commit}).dup
37   -
38   - # Decorate with app/models/commit.rb
39   - @commits.map! { |commit| ::Commit.new(commit) }
40   -
41   - # Decorate with lib/gitlab/graph/commit.rb
42   - @commits.map! { |commit| Gitlab::Graph::Commit.new(commit) }
43   -
44   - # add refs to each commit
45   - @commits.each { |commit| commit.add_refs(ref_cache, repo) }
46   -
47   - @commits
48   - end
49   -
50   - # Method is adding time and space on the
51   - # list of commits. As well as returns date list
52   - # corelated with time set on commits.
53   - #
54   - # @param [Array<Graph::Commit>] commits to index
55   - #
56   - # @return [Array<TimeDate>] list of commit dates corelated with time on commits
57   - def index_commits
58   - days, times = [], []
59   - map = {}
60   -
61   - commits.reverse.each_with_index do |c,i|
62   - c.time = i
63   - days[i] = c.committed_date
64   - map[c.id] = c
65   - times[i] = c
66   - end
67   -
68   - @_reserved = {}
69   - days.each_index do |i|
70   - @_reserved[i] = []
71   - end
72   -
73   - commits_sort_by_ref.each do |commit|
74   - if map.include? commit.id then
75   - place_chain(map[commit.id], map)
76   - end
77   - end
78   -
79   - # find parent spaces for not overlap lines
80   - times.each do |c|
81   - c.parent_spaces.concat(find_free_parent_spaces(c, map, times))
82   - end
83   -
84   - days
85   - end
86   -
87   - # Skip count that the target commit is displayed in center.
88   - def to_commit
89   - commits = Grit::Commit.find_all(repo, nil, {topo_order: true})
90   - commit_index = commits.index do |c|
91   - c.id == @commit.id
92   - end
93   -
94   - if commit_index && (self.class.max_count / 2 < commit_index) then
95   - # get max index that commit is displayed in the center.
96   - commit_index - self.class.max_count / 2
97   - else
98   - 0
99   - end
100   - end
101   -
102   - def commits_sort_by_ref
103   - commits.sort do |a,b|
104   - if include_ref?(a)
105   - -1
106   - elsif include_ref?(b)
107   - 1
108   - else
109   - b.committed_date <=> a.committed_date
110   - end
111   - end
112   - end
113   -
114   - def include_ref?(commit)
115   - heads = commit.refs.select do |ref|
116   - ref.is_a?(Grit::Head) or ref.is_a?(Grit::Remote) or ref.is_a?(Grit::Tag)
117   - end
118   -
119   - heads.map! do |head|
120   - head.name
121   - end
122   -
123   - heads.include?(@ref)
124   - end
125   -
126   - def find_free_parent_spaces(commit, map, times)
127   - spaces = []
128   -
129   - commit.parents.each do |p|
130   - if map.include?(p.id) then
131   - parent = map[p.id]
132   -
133   - range = if commit.time < parent.time then
134   - commit.time..parent.time
135   - else
136   - parent.time..commit.time
137   - end
138   -
139   - space = if commit.space >= parent.space then
140   - find_free_parent_space(range, parent.space, 1, commit.space, times)
141   - else
142   - find_free_parent_space(range, parent.space, -1, parent.space, times)
143   - end
144   -
145   - mark_reserved(range, space)
146   - spaces << space
147   - end
148   - end
149   -
150   - spaces
151   - end
152   -
153   - def find_free_parent_space(range, space_base, space_step, space_default, times)
154   - if is_overlap?(range, times, space_default) then
155   - find_free_space(range, space_base, space_step)
156   - else
157   - space_default
158   - end
159   - end
160   -
161   - def is_overlap?(range, times, overlap_space)
162   - range.each do |i|
163   - if i != range.first &&
164   - i != range.last &&
165   - times[i].space == overlap_space then
166   -
167   - return true;
168   - end
169   - end
170   -
171   - false
172   - end
173   -
174   - # Add space mark on commit and its parents
175   - #
176   - # @param [Graph::Commit] the commit object.
177   - # @param [Hash<String,Graph::Commit>] map of commits
178   - def place_chain(commit, map, parent_time = nil)
179   - leaves = take_left_leaves(commit, map)
180   - if leaves.empty?
181   - return
182   - end
183   - # and mark it as reserved
184   - min_time = leaves.last.time
185   - max_space = 1
186   - parents = leaves.last.parents.collect
187   - parents.each do |p|
188   - if map.include? p.id
189   - parent = map[p.id]
190   - if parent.time < min_time
191   - min_time = parent.time
192   - end
193   - if max_space < parent.space then
194   - max_space = parent.space
195   - end
196   - end
197   - end
198   - if parent_time.nil?
199   - max_time = leaves.first.time
200   - else
201   - max_time = parent_time - 1
202   - end
203   -
204   - time_range = leaves.last.time..leaves.first.time
205   - space = find_free_space(time_range, max_space, 2)
206   - leaves.each{|l| l.space = space}
207   -
208   - mark_reserved(min_time..max_time, space)
209   -
210   - # Visit branching chains
211   - leaves.each do |l|
212   - parents = l.parents.collect.select{|p| map.include? p.id and map[p.id].space.zero?}
213   - for p in parents
214   - place_chain(map[p.id], map, l.time)
215   - end
216   - end
217   - end
218   -
219   - def mark_reserved(time_range, space)
220   - for day in time_range
221   - @_reserved[day].push(space)
222   - end
223   - end
224   -
225   - def find_free_space(time_range, space_base, space_step)
226   - reserved = []
227   - for day in time_range
228   - reserved += @_reserved[day]
229   - end
230   - reserved.uniq!
231   -
232   - space = space_base
233   - while reserved.include?(space) do
234   - space += space_step
235   - if space <= 0 then
236   - space_step *= -1
237   - space = space_base + space_step
238   - end
239   - end
240   -
241   - space
242   - end
243   -
244   - # Takes most left subtree branch of commits
245   - # which don't have space mark yet.
246   - #
247   - # @param [Graph::Commit] the commit object.
248   - # @param [Hash<String,Graph::Commit>] map of commits
249   - #
250   - # @return [Array<Graph::Commit>] list of branch commits
251   - def take_left_leaves(commit, map)
252   - leaves = []
253   - leaves.push(commit) if commit.space.zero?
254   -
255   - while true
256   - return leaves if commit.parents.count.zero?
257   - return leaves unless map.include? commit.parents.first.id
258   -
259   - commit = map[commit.parents.first.id]
260   -
261   - return leaves unless commit.space.zero?
262   -
263   - leaves.push(commit)
264   - end
265   - end
266   - end
267   - end
268   -end
vendor/assets/javascripts/branch-graph.js
... ... @@ -1,385 +0,0 @@
1   -!function(){
2   -
3   - var BranchGraph = function(element, options){
4   - this.element = element;
5   - this.options = options;
6   -
7   - this.preparedCommits = {};
8   - this.mtime = 0;
9   - this.mspace = 0;
10   - this.parents = {};
11   - this.colors = ["#000"];
12   -
13   - this.load();
14   - };
15   -
16   - BranchGraph.prototype.load = function(){
17   - $.ajax({
18   - url: this.options.url,
19   - method: 'get',
20   - dataType: 'json',
21   - success: $.proxy(function(data){
22   - $('.loading', this.element).hide();
23   - this.prepareData(data.days, data.commits);
24   - this.buildGraph();
25   - }, this)
26   - });
27   - };
28   -
29   - BranchGraph.prototype.prepareData = function(days, commits){
30   - this.days = days;
31   - this.dayCount = days.length;
32   - this.commits = commits;
33   - this.commitCount = commits.length;
34   -
35   - this.collectParents();
36   -
37   - this.mtime += 4;
38   - this.mspace += 10;
39   - for (var i = 0; i < this.commitCount; i++) {
40   - if (this.commits[i].id in this.parents) {
41   - this.commits[i].isParent = true;
42   - }
43   - this.preparedCommits[this.commits[i].id] = this.commits[i];
44   - }
45   - this.collectColors();
46   - };
47   -
48   - BranchGraph.prototype.collectParents = function(){
49   - for (var i = 0; i < this.commitCount; i++) {
50   - for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) {
51   - this.parents[this.commits[i].parents[j][0]] = true;
52   - }
53   - this.mtime = Math.max(this.mtime, this.commits[i].time);
54   - this.mspace = Math.max(this.mspace, this.commits[i].space);
55   - }
56   - };
57   -
58   - BranchGraph.prototype.collectColors = function(){
59   - for (var k = 0; k < this.mspace; k++) {
60   - this.colors.push(Raphael.getColor(.8));
61   - // Skipping a few colors in the spectrum to get more contrast between colors
62   - Raphael.getColor();Raphael.getColor();
63   - }
64   - };
65   -
66   - BranchGraph.prototype.buildGraph = function(){
67   - var graphWidth = $(this.element).width()
68   - , ch = this.mspace * 20 + 100
69   - , cw = Math.max(graphWidth, this.mtime * 20 + 260)
70   - , r = Raphael(this.element.get(0), cw, ch)
71   - , top = r.set()
72   - , cuday = 0
73   - , cumonth = ""
74   - , offsetX = 20
75   - , offsetY = 60
76   - , barWidth = Math.max(graphWidth, this.dayCount * 20 + 320)
77   - , scrollLeft = cw;
78   -
79   - this.raphael = r;
80   -
81   - r.rect(0, 0, barWidth, 20).attr({fill: "#222"});
82   - r.rect(0, 20, barWidth, 20).attr({fill: "#444"});
83   -
84   - for (mm = 0; mm < this.dayCount; mm++) {
85   - if(this.days[mm] != null){
86   - if(cuday != this.days[mm][0]){
87   - // Dates
88   - r.text(offsetX + mm * 20, 31, this.days[mm][0]).attr({
89   - font: "12px Monaco, monospace",
90   - fill: "#DDD"
91   - });
92   - cuday = this.days[mm][0];
93   - }
94   - if(cumonth != this.days[mm][1]){
95   - // Months
96   - r.text(offsetX + mm * 20, 11, this.days[mm][1]).attr({
97   - font: "12px Monaco, monospace",
98   - fill: "#EEE"
99   - });
100   - cumonth = this.days[mm][1];
101   - }
102   - }
103   - }
104   -
105   - for (i = 0; i < this.commitCount; i++) {
106   - var x = offsetX + 20 * this.commits[i].time
107   - , y = offsetY + 10 * this.commits[i].space
108   - , c
109   - , ps;
110   -
111   - // Draw dot
112   - r.circle(x, y, 3).attr({
113   - fill: this.colors[this.commits[i].space],
114   - stroke: "none"
115   - });
116   -
117   - // Draw lines
118   - for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) {
119   - c = this.preparedCommits[this.commits[i].parents[j][0]];
120   - ps = this.commits[i].parent_spaces[j];
121   - if (c) {
122   - var cx = offsetX + 20 * c.time
123   - , cy = offsetY + 10 * c.space
124   - , psy = offsetY + 10 * ps;
125   - if (c.space == this.commits[i].space && c.space == ps) {
126   - r.path([
127   - "M", x, y,
128   - "L", cx, cy
129   - ]).attr({
130   - stroke: this.colors[c.space],
131   - "stroke-width": 2
132   - });
133   -
134   - } else if (c.space < this.commits[i].space) {
135   - r.path([
136   - "M", x - 5, y,
137   - "l-5-2,0,4,5,-2",
138   - "L", x - 10, y,
139   - "L", x - 15, psy,
140   - "L", cx + 5, psy,
141   - "L", cx, cy])
142   - .attr({
143   - stroke: this.colors[this.commits[i].space],
144   - "stroke-width": 2
145   - });
146   - } else {
147   - r.path([
148   - "M", x - 3, y + 6,
149   - "l-4,3,4,2,0,-5",
150   - "L", x - 5, y + 10,
151   - "L", x - 10, psy,
152   - "L", cx + 5, psy,
153   - "L", cx, cy])
154   - .attr({
155   - stroke: this.colors[c.space],
156   - "stroke-width": 2
157   - });
158   - }
159   - }
160   - }
161   -
162   - if (this.commits[i].refs) {
163   - this.appendLabel(x, y, this.commits[i].refs);
164   - }
165   -
166   - // mark commit and displayed in the center
167   - if (this.commits[i].id == this.options.commit_id) {
168   - r.path([
169   - 'M', x, y - 5,
170   - 'L', x + 4, y - 15,
171   - 'L', x - 4, y - 15,
172   - 'Z'
173   - ]).attr({
174   - "fill": "#000",
175   - "fill-opacity": .7,
176   - "stroke": "none"
177   - });
178   - scrollLeft = x - graphWidth / 2;
179   - }
180   -
181   - this.appendAnchor(top, this.commits[i], x, y);
182   - }
183   - top.toFront();
184   - this.element.scrollLeft(scrollLeft);
185   - this.bindEvents();
186   - };
187   -
188   - BranchGraph.prototype.bindEvents = function(){
189   - var drag = {}
190   - , element = this.element;
191   -
192   - var dragger = function(event){
193   - element.scrollLeft(drag.sl - (event.clientX - drag.x));
194   - element.scrollTop(drag.st - (event.clientY - drag.y));
195   - };
196   -
197   - element.on({
198   - mousedown: function (event) {
199   - drag = {
200   - x: event.clientX,
201   - y: event.clientY,
202   - st: element.scrollTop(),
203   - sl: element.scrollLeft()
204   - };
205   - $(window).on('mousemove', dragger);
206   - }
207   - });
208   - $(window).on({
209   - mouseup: function(){
210   - //bars.animate({opacity: 0}, 300);
211   - $(window).off('mousemove', dragger);
212   - },
213   - keydown: function(event){
214   - if(event.keyCode == 37){
215   - // left
216   - element.scrollLeft( element.scrollLeft() - 50);
217   - }
218   - if(event.keyCode == 38){
219   - // top
220   - element.scrollTop( element.scrollTop() - 50);
221   - }
222   - if(event.keyCode == 39){
223   - // right
224   - element.scrollLeft( element.scrollLeft() + 50);
225   - }
226   - if(event.keyCode == 40){
227   - // bottom
228   - element.scrollTop( element.scrollTop() + 50);
229   - }
230   - }
231   - });
232   - };
233   -
234   - BranchGraph.prototype.appendLabel = function(x, y, refs){
235   - var r = this.raphael
236   - , shortrefs = refs
237   - , text, textbox, rect;
238   -
239   - if (shortrefs.length > 17){
240   - // Truncate if longer than 15 chars
241   - shortrefs = shortrefs.substr(0,15) + "…";
242   - }
243   -
244   - text = r.text(x+5, y+8 + 10, shortrefs).attr({
245   - font: "10px Monaco, monospace",
246   - fill: "#FFF",
247   - title: refs
248   - });
249   -
250   - textbox = text.getBBox();
251   - text.transform([
252   - 't', textbox.height/-4, textbox.width/2 + 5,
253   - 'r90'
254   - ]);
255   -
256   - // Create rectangle based on the size of the textbox
257   - rect = r.rect(x, y, textbox.width + 15, textbox.height + 5, 4).attr({
258   - "fill": "#000",
259   - "fill-opacity": .7,
260   - "stroke": "none"
261   - });
262   -
263   - triangle = r.path([
264   - 'M', x, y + 5,
265   - 'L', x + 4, y + 15,
266   - 'L', x - 4, y + 15,
267   - 'Z'
268   - ]).attr({
269   - "fill": "#000",
270   - "fill-opacity": .7,
271   - "stroke": "none"
272   - });
273   -
274   - // Rotate and reposition rectangle over text
275   - rect.transform([
276   - 'r', 90, x, y,
277   - 't', 15, -9
278   - ]);
279   -
280   - // Set text to front
281   - text.toFront();
282   - };
283   -
284   - BranchGraph.prototype.appendAnchor = function(top, commit, x, y) {
285   - var r = this.raphael
286   - , options = this.options
287   - , anchor;
288   - anchor = r.circle(x, y, 10).attr({
289   - fill: "#000",
290   - opacity: 0,
291   - cursor: "pointer"
292   - })
293   - .click(function(){
294   - window.open(options.commit_url.replace('%s', commit.id), '_blank');
295   - })
296   - .hover(function(){
297   - this.tooltip = r.commitTooltip(x, y + 5, commit);
298   - top.push(this.tooltip.insertBefore(this));
299   - }, function(){
300   - this.tooltip && this.tooltip.remove() && delete this.tooltip;
301   - });
302   - top.push(anchor);
303   - };
304   -
305   - this.BranchGraph = BranchGraph;
306   -
307   -}(this);
308   -Raphael.fn.commitTooltip = function(x, y, commit){
309   - var nameText, idText, messageText
310   - , boxWidth = 300
311   - , boxHeight = 200;
312   -
313   - nameText = this.text(x, y + 10, commit.author.name);
314   - idText = this.text(x, y + 35, commit.id);
315   - messageText = this.text(x, y + 50, commit.message);
316   -
317   - textSet = this.set(nameText, idText, messageText).attr({
318   - "text-anchor": "start",
319   - "font": "12px Monaco, monospace"
320   - });
321   -
322   - nameText.attr({
323   - "font": "14px Arial",
324   - "font-weight": "bold"
325   - });
326   -
327   - idText.attr({
328   - "fill": "#AAA"
329   - });
330   -
331   - textWrap(messageText, boxWidth - 50);
332   -
333   - var rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({
334   - "fill": "#FFF",
335   - "stroke": "#000",
336   - "stroke-linecap": "round",
337   - "stroke-width": 2
338   - });
339   - var tooltip = this.set(rect, textSet);
340   -
341   - rect.attr({
342   - "height" : tooltip.getBBox().height + 10,
343   - "width" : tooltip.getBBox().width + 10
344   - });
345   -
346   - tooltip.transform([
347   - 't', 20, 20
348   - ]);
349   -
350   - return tooltip;
351   -};
352   -
353   -function textWrap(t, width) {
354   - var content = t.attr("text");
355   - var abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
356   - t.attr({
357   - "text" : abc
358   - });
359   - var letterWidth = t.getBBox().width / abc.length;
360   -
361   - t.attr({
362   - "text" : content
363   - });
364   -
365   - var words = content.split(" ");
366   - var x = 0, s = [];
367   - for ( var i = 0; i < words.length; i++) {
368   -
369   - var l = words[i].length;
370   - if (x + (l * letterWidth) > width) {
371   - s.push("\n");
372   - x = 0;
373   - }
374   - x += l * letterWidth;
375   - s.push(words[i] + " ");
376   - }
377   - t.attr({
378   - "text" : s.join("")
379   - });
380   - var b = t.getBBox()
381   - , h = Math.abs(b.y2) - Math.abs(b.y) + 1;
382   - t.attr({
383   - "y": b.y + h
384   - });
385   -}