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 @@ @@ -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,7 +20,7 @@ class GraphController &lt; ProjectResourceController
20 respond_to do |format| 20 respond_to do |format|
21 format.html 21 format.html
22 format.json do 22 format.json do
23 - graph = Gitlab::Graph::JsonBuilder.new(project, @ref, @commit) 23 + graph = Graph::JsonBuilder.new(project, @ref, @commit)
24 render :json => graph.to_json 24 render :json => graph.to_json
25 end 25 end
26 end 26 end
app/controllers/projects_controller.rb
1 -require Rails.root.join('lib', 'gitlab', 'graph', 'json_builder')  
2 -  
3 class ProjectsController < ProjectResourceController 1 class ProjectsController < ProjectResourceController
4 skip_before_filter :project, only: [:new, :create] 2 skip_before_filter :project, only: [:new, :create]
5 skip_before_filter :repository, only: [:new, :create] 3 skip_before_filter :repository, only: [:new, :create]
app/models/graph/commit.rb 0 → 100644
@@ -0,0 +1,50 @@ @@ -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 @@ @@ -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,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,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,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 -}