From d8a40d8c933da8e89013e989940f8b60d0f2e247 Mon Sep 17 00:00:00 2001 From: Sato Hiroyuki Date: Tue, 26 Feb 2013 13:28:11 +0900 Subject: [PATCH] Move graph module from lib or vendor directory to app directory. --- app/assets/javascripts/branch-graph.js | 385 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app/controllers/graph_controller.rb | 2 +- app/controllers/projects_controller.rb | 2 -- app/models/graph/commit.rb | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ app/models/graph/json_builder.rb | 266 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/gitlab/graph/commit.rb | 52 ---------------------------------------------------- lib/gitlab/graph/json_builder.rb | 268 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- vendor/assets/javascripts/branch-graph.js | 385 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 8 files changed, 702 insertions(+), 708 deletions(-) create mode 100644 app/assets/javascripts/branch-graph.js create mode 100644 app/models/graph/commit.rb create mode 100644 app/models/graph/json_builder.rb delete mode 100644 lib/gitlab/graph/commit.rb delete mode 100644 lib/gitlab/graph/json_builder.rb delete mode 100644 vendor/assets/javascripts/branch-graph.js diff --git a/app/assets/javascripts/branch-graph.js b/app/assets/javascripts/branch-graph.js new file mode 100644 index 0000000..fb22953 --- /dev/null +++ b/app/assets/javascripts/branch-graph.js @@ -0,0 +1,385 @@ +!function(){ + + var BranchGraph = function(element, options){ + this.element = element; + this.options = options; + + this.preparedCommits = {}; + this.mtime = 0; + this.mspace = 0; + this.parents = {}; + this.colors = ["#000"]; + + this.load(); + }; + + BranchGraph.prototype.load = function(){ + $.ajax({ + url: this.options.url, + method: 'get', + dataType: 'json', + success: $.proxy(function(data){ + $('.loading', this.element).hide(); + this.prepareData(data.days, data.commits); + this.buildGraph(); + }, this) + }); + }; + + BranchGraph.prototype.prepareData = function(days, commits){ + this.days = days; + this.dayCount = days.length; + this.commits = commits; + this.commitCount = commits.length; + + this.collectParents(); + + this.mtime += 4; + this.mspace += 10; + for (var i = 0; i < this.commitCount; i++) { + if (this.commits[i].id in this.parents) { + this.commits[i].isParent = true; + } + this.preparedCommits[this.commits[i].id] = this.commits[i]; + } + this.collectColors(); + }; + + BranchGraph.prototype.collectParents = function(){ + for (var i = 0; i < this.commitCount; i++) { + for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) { + this.parents[this.commits[i].parents[j][0]] = true; + } + this.mtime = Math.max(this.mtime, this.commits[i].time); + this.mspace = Math.max(this.mspace, this.commits[i].space); + } + }; + + BranchGraph.prototype.collectColors = function(){ + for (var k = 0; k < this.mspace; k++) { + this.colors.push(Raphael.getColor(.8)); + // Skipping a few colors in the spectrum to get more contrast between colors + Raphael.getColor();Raphael.getColor(); + } + }; + + BranchGraph.prototype.buildGraph = function(){ + var graphWidth = $(this.element).width() + , ch = this.mspace * 20 + 100 + , cw = Math.max(graphWidth, this.mtime * 20 + 260) + , r = Raphael(this.element.get(0), cw, ch) + , top = r.set() + , cuday = 0 + , cumonth = "" + , offsetX = 20 + , offsetY = 60 + , barWidth = Math.max(graphWidth, this.dayCount * 20 + 320) + , scrollLeft = cw; + + this.raphael = r; + + r.rect(0, 0, barWidth, 20).attr({fill: "#222"}); + r.rect(0, 20, barWidth, 20).attr({fill: "#444"}); + + for (mm = 0; mm < this.dayCount; mm++) { + if(this.days[mm] != null){ + if(cuday != this.days[mm][0]){ + // Dates + r.text(offsetX + mm * 20, 31, this.days[mm][0]).attr({ + font: "12px Monaco, monospace", + fill: "#DDD" + }); + cuday = this.days[mm][0]; + } + if(cumonth != this.days[mm][1]){ + // Months + r.text(offsetX + mm * 20, 11, this.days[mm][1]).attr({ + font: "12px Monaco, monospace", + fill: "#EEE" + }); + cumonth = this.days[mm][1]; + } + } + } + + for (i = 0; i < this.commitCount; i++) { + var x = offsetX + 20 * this.commits[i].time + , y = offsetY + 10 * this.commits[i].space + , c + , ps; + + // Draw dot + r.circle(x, y, 3).attr({ + fill: this.colors[this.commits[i].space], + stroke: "none" + }); + + // Draw lines + for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) { + c = this.preparedCommits[this.commits[i].parents[j][0]]; + ps = this.commits[i].parent_spaces[j]; + if (c) { + var cx = offsetX + 20 * c.time + , cy = offsetY + 10 * c.space + , psy = offsetY + 10 * ps; + if (c.space == this.commits[i].space && c.space == ps) { + r.path([ + "M", x, y, + "L", cx, cy + ]).attr({ + stroke: this.colors[c.space], + "stroke-width": 2 + }); + + } else if (c.space < this.commits[i].space) { + r.path([ + "M", x - 5, y, + "l-5-2,0,4,5,-2", + "L", x - 10, y, + "L", x - 15, psy, + "L", cx + 5, psy, + "L", cx, cy]) + .attr({ + stroke: this.colors[this.commits[i].space], + "stroke-width": 2 + }); + } else { + r.path([ + "M", x - 3, y + 6, + "l-4,3,4,2,0,-5", + "L", x - 5, y + 10, + "L", x - 10, psy, + "L", cx + 5, psy, + "L", cx, cy]) + .attr({ + stroke: this.colors[c.space], + "stroke-width": 2 + }); + } + } + } + + if (this.commits[i].refs) { + this.appendLabel(x, y, this.commits[i].refs); + } + + // mark commit and displayed in the center + if (this.commits[i].id == this.options.commit_id) { + r.path([ + 'M', x, y - 5, + 'L', x + 4, y - 15, + 'L', x - 4, y - 15, + 'Z' + ]).attr({ + "fill": "#000", + "fill-opacity": .7, + "stroke": "none" + }); + scrollLeft = x - graphWidth / 2; + } + + this.appendAnchor(top, this.commits[i], x, y); + } + top.toFront(); + this.element.scrollLeft(scrollLeft); + this.bindEvents(); + }; + + BranchGraph.prototype.bindEvents = function(){ + var drag = {} + , element = this.element; + + var dragger = function(event){ + element.scrollLeft(drag.sl - (event.clientX - drag.x)); + element.scrollTop(drag.st - (event.clientY - drag.y)); + }; + + element.on({ + mousedown: function (event) { + drag = { + x: event.clientX, + y: event.clientY, + st: element.scrollTop(), + sl: element.scrollLeft() + }; + $(window).on('mousemove', dragger); + } + }); + $(window).on({ + mouseup: function(){ + //bars.animate({opacity: 0}, 300); + $(window).off('mousemove', dragger); + }, + keydown: function(event){ + if(event.keyCode == 37){ + // left + element.scrollLeft( element.scrollLeft() - 50); + } + if(event.keyCode == 38){ + // top + element.scrollTop( element.scrollTop() - 50); + } + if(event.keyCode == 39){ + // right + element.scrollLeft( element.scrollLeft() + 50); + } + if(event.keyCode == 40){ + // bottom + element.scrollTop( element.scrollTop() + 50); + } + } + }); + }; + + BranchGraph.prototype.appendLabel = function(x, y, refs){ + var r = this.raphael + , shortrefs = refs + , text, textbox, rect; + + if (shortrefs.length > 17){ + // Truncate if longer than 15 chars + shortrefs = shortrefs.substr(0,15) + "…"; + } + + text = r.text(x+5, y+8 + 10, shortrefs).attr({ + font: "10px Monaco, monospace", + fill: "#FFF", + title: refs + }); + + textbox = text.getBBox(); + text.transform([ + 't', textbox.height/-4, textbox.width/2 + 5, + 'r90' + ]); + + // Create rectangle based on the size of the textbox + rect = r.rect(x, y, textbox.width + 15, textbox.height + 5, 4).attr({ + "fill": "#000", + "fill-opacity": .7, + "stroke": "none" + }); + + triangle = r.path([ + 'M', x, y + 5, + 'L', x + 4, y + 15, + 'L', x - 4, y + 15, + 'Z' + ]).attr({ + "fill": "#000", + "fill-opacity": .7, + "stroke": "none" + }); + + // Rotate and reposition rectangle over text + rect.transform([ + 'r', 90, x, y, + 't', 15, -9 + ]); + + // Set text to front + text.toFront(); + }; + + BranchGraph.prototype.appendAnchor = function(top, commit, x, y) { + var r = this.raphael + , options = this.options + , anchor; + anchor = r.circle(x, y, 10).attr({ + fill: "#000", + opacity: 0, + cursor: "pointer" + }) + .click(function(){ + window.open(options.commit_url.replace('%s', commit.id), '_blank'); + }) + .hover(function(){ + this.tooltip = r.commitTooltip(x, y + 5, commit); + top.push(this.tooltip.insertBefore(this)); + }, function(){ + this.tooltip && this.tooltip.remove() && delete this.tooltip; + }); + top.push(anchor); + }; + + this.BranchGraph = BranchGraph; + +}(this); +Raphael.fn.commitTooltip = function(x, y, commit){ + var nameText, idText, messageText + , boxWidth = 300 + , boxHeight = 200; + + nameText = this.text(x, y + 10, commit.author.name); + idText = this.text(x, y + 35, commit.id); + messageText = this.text(x, y + 50, commit.message); + + textSet = this.set(nameText, idText, messageText).attr({ + "text-anchor": "start", + "font": "12px Monaco, monospace" + }); + + nameText.attr({ + "font": "14px Arial", + "font-weight": "bold" + }); + + idText.attr({ + "fill": "#AAA" + }); + + textWrap(messageText, boxWidth - 50); + + var rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({ + "fill": "#FFF", + "stroke": "#000", + "stroke-linecap": "round", + "stroke-width": 2 + }); + var tooltip = this.set(rect, textSet); + + rect.attr({ + "height" : tooltip.getBBox().height + 10, + "width" : tooltip.getBBox().width + 10 + }); + + tooltip.transform([ + 't', 20, 20 + ]); + + return tooltip; +}; + +function textWrap(t, width) { + var content = t.attr("text"); + var abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + t.attr({ + "text" : abc + }); + var letterWidth = t.getBBox().width / abc.length; + + t.attr({ + "text" : content + }); + + var words = content.split(" "); + var x = 0, s = []; + for ( var i = 0; i < words.length; i++) { + + var l = words[i].length; + if (x + (l * letterWidth) > width) { + s.push("\n"); + x = 0; + } + x += l * letterWidth; + s.push(words[i] + " "); + } + t.attr({ + "text" : s.join("") + }); + var b = t.getBBox() + , h = Math.abs(b.y2) - Math.abs(b.y) + 1; + t.attr({ + "y": b.y + h + }); +} diff --git a/app/controllers/graph_controller.rb b/app/controllers/graph_controller.rb index c370433..8aadcfe 100644 --- a/app/controllers/graph_controller.rb +++ b/app/controllers/graph_controller.rb @@ -20,7 +20,7 @@ class GraphController < ProjectResourceController respond_to do |format| format.html format.json do - graph = Gitlab::Graph::JsonBuilder.new(project, @ref, @commit) + graph = Graph::JsonBuilder.new(project, @ref, @commit) render :json => graph.to_json end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 5da3fbf..f703cf6 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,5 +1,3 @@ -require Rails.root.join('lib', 'gitlab', 'graph', 'json_builder') - class ProjectsController < ProjectResourceController skip_before_filter :project, only: [:new, :create] skip_before_filter :repository, only: [:new, :create] diff --git a/app/models/graph/commit.rb b/app/models/graph/commit.rb new file mode 100644 index 0000000..2b09d53 --- /dev/null +++ b/app/models/graph/commit.rb @@ -0,0 +1,50 @@ +require "grit" + +module Graph + class Commit + include ActionView::Helpers::TagHelper + + attr_accessor :time, :space, :refs, :parent_spaces + + def initialize(commit) + @_commit = commit + @time = -1 + @space = 0 + @parent_spaces = [] + end + + def method_missing(m, *args, &block) + @_commit.send(m, *args, &block) + end + + def to_graph_hash + h = {} + h[:parents] = self.parents.collect do |p| + [p.id,0,0] + end + h[:author] = { + name: author.name, + email: author.email + } + h[:time] = time + h[:space] = space + h[:parent_spaces] = parent_spaces + h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil? + h[:id] = sha + h[:date] = date + h[:message] = message + h + end + + def add_refs(ref_cache, repo) + if ref_cache.empty? + repo.refs.each do |ref| + ref_cache[ref.commit.id] ||= [] + ref_cache[ref.commit.id] << ref + end + end + @refs = ref_cache[@_commit.id] if ref_cache.include?(@_commit.id) + @refs ||= [] + end + end +end diff --git a/app/models/graph/json_builder.rb b/app/models/graph/json_builder.rb new file mode 100644 index 0000000..8440b5d --- /dev/null +++ b/app/models/graph/json_builder.rb @@ -0,0 +1,266 @@ +require "grit" + +module Graph + class JsonBuilder + attr_accessor :days, :commits, :ref_cache, :repo + + def self.max_count + @max_count ||= 650 + end + + def initialize project, ref, commit + @project = project + @ref = ref + @commit = commit + @repo = project.repo + @ref_cache = {} + + @commits = collect_commits + @days = index_commits + end + + def to_json(*args) + { + days: @days.compact.map { |d| [d.day, d.strftime("%b")] }, + commits: @commits.map(&:to_graph_hash) + }.to_json(*args) + end + + protected + + # Get commits from repository + # + def collect_commits + + @commits = Grit::Commit.find_all(repo, nil, {topo_order: true, max_count: self.class.max_count, skip: to_commit}).dup + + # Decorate with app/models/commit.rb + @commits.map! { |commit| Commit.new(commit) } + + # Decorate with lib/gitlab/graph/commit.rb + @commits.map! { |commit| Graph::Commit.new(commit) } + + # add refs to each commit + @commits.each { |commit| commit.add_refs(ref_cache, repo) } + + @commits + end + + # Method is adding time and space on the + # list of commits. As well as returns date list + # corelated with time set on commits. + # + # @param [Array] commits to index + # + # @return [Array] list of commit dates corelated with time on commits + def index_commits + days, times = [], [] + map = {} + + commits.reverse.each_with_index do |c,i| + c.time = i + days[i] = c.committed_date + map[c.id] = c + times[i] = c + end + + @_reserved = {} + days.each_index do |i| + @_reserved[i] = [] + end + + commits_sort_by_ref.each do |commit| + if map.include? commit.id then + place_chain(map[commit.id], map) + end + end + + # find parent spaces for not overlap lines + times.each do |c| + c.parent_spaces.concat(find_free_parent_spaces(c, map, times)) + end + + days + end + + # Skip count that the target commit is displayed in center. + def to_commit + commits = Grit::Commit.find_all(repo, nil, {topo_order: true}) + commit_index = commits.index do |c| + c.id == @commit.id + end + + if commit_index && (self.class.max_count / 2 < commit_index) then + # get max index that commit is displayed in the center. + commit_index - self.class.max_count / 2 + else + 0 + end + end + + def commits_sort_by_ref + commits.sort do |a,b| + if include_ref?(a) + -1 + elsif include_ref?(b) + 1 + else + b.committed_date <=> a.committed_date + end + end + end + + def include_ref?(commit) + heads = commit.refs.select do |ref| + ref.is_a?(Grit::Head) or ref.is_a?(Grit::Remote) or ref.is_a?(Grit::Tag) + end + + heads.map! do |head| + head.name + end + + heads.include?(@ref) + end + + def find_free_parent_spaces(commit, map, times) + spaces = [] + + commit.parents.each do |p| + if map.include?(p.id) then + parent = map[p.id] + + range = if commit.time < parent.time then + commit.time..parent.time + else + parent.time..commit.time + end + + space = if commit.space >= parent.space then + find_free_parent_space(range, parent.space, 1, commit.space, times) + else + find_free_parent_space(range, parent.space, -1, parent.space, times) + end + + mark_reserved(range, space) + spaces << space + end + end + + spaces + end + + def find_free_parent_space(range, space_base, space_step, space_default, times) + if is_overlap?(range, times, space_default) then + find_free_space(range, space_base, space_step) + else + space_default + end + end + + def is_overlap?(range, times, overlap_space) + range.each do |i| + if i != range.first && + i != range.last && + times[i].space == overlap_space then + + return true; + end + end + + false + end + + # Add space mark on commit and its parents + # + # @param [Graph::Commit] the commit object. + # @param [Hash] map of commits + def place_chain(commit, map, parent_time = nil) + leaves = take_left_leaves(commit, map) + if leaves.empty? + return + end + # and mark it as reserved + min_time = leaves.last.time + max_space = 1 + parents = leaves.last.parents.collect + parents.each do |p| + if map.include? p.id + parent = map[p.id] + if parent.time < min_time + min_time = parent.time + end + if max_space < parent.space then + max_space = parent.space + end + end + end + if parent_time.nil? + max_time = leaves.first.time + else + max_time = parent_time - 1 + end + + time_range = leaves.last.time..leaves.first.time + space = find_free_space(time_range, max_space, 2) + leaves.each{|l| l.space = space} + + mark_reserved(min_time..max_time, space) + + # Visit branching chains + leaves.each do |l| + parents = l.parents.collect.select{|p| map.include? p.id and map[p.id].space.zero?} + for p in parents + place_chain(map[p.id], map, l.time) + end + end + end + + def mark_reserved(time_range, space) + for day in time_range + @_reserved[day].push(space) + end + end + + def find_free_space(time_range, space_base, space_step) + reserved = [] + for day in time_range + reserved += @_reserved[day] + end + reserved.uniq! + + space = space_base + while reserved.include?(space) do + space += space_step + if space <= 0 then + space_step *= -1 + space = space_base + space_step + end + end + + space + end + + # Takes most left subtree branch of commits + # which don't have space mark yet. + # + # @param [Graph::Commit] the commit object. + # @param [Hash] map of commits + # + # @return [Array] list of branch commits + def take_left_leaves(commit, map) + leaves = [] + leaves.push(commit) if commit.space.zero? + + while true + return leaves if commit.parents.count.zero? + return leaves unless map.include? commit.parents.first.id + + commit = map[commit.parents.first.id] + + return leaves unless commit.space.zero? + + leaves.push(commit) + end + end + end +end diff --git a/lib/gitlab/graph/commit.rb b/lib/gitlab/graph/commit.rb deleted file mode 100644 index 13c8ebc..0000000 --- a/lib/gitlab/graph/commit.rb +++ /dev/null @@ -1,52 +0,0 @@ -require "grit" - -module Gitlab - module Graph - class Commit - include ActionView::Helpers::TagHelper - - attr_accessor :time, :space, :refs, :parent_spaces - - def initialize(commit) - @_commit = commit - @time = -1 - @space = 0 - @parent_spaces = [] - end - - def method_missing(m, *args, &block) - @_commit.send(m, *args, &block) - end - - def to_graph_hash - h = {} - h[:parents] = self.parents.collect do |p| - [p.id,0,0] - end - h[:author] = { - name: author.name, - email: author.email - } - h[:time] = time - h[:space] = space - h[:parent_spaces] = parent_spaces - h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil? - h[:id] = sha - h[:date] = date - h[:message] = message - h - end - - def add_refs(ref_cache, repo) - if ref_cache.empty? - repo.refs.each do |ref| - ref_cache[ref.commit.id] ||= [] - ref_cache[ref.commit.id] << ref - end - end - @refs = ref_cache[@_commit.id] if ref_cache.include?(@_commit.id) - @refs ||= [] - end - end - end -end diff --git a/lib/gitlab/graph/json_builder.rb b/lib/gitlab/graph/json_builder.rb deleted file mode 100644 index cc971a2..0000000 --- a/lib/gitlab/graph/json_builder.rb +++ /dev/null @@ -1,268 +0,0 @@ -require "grit" - -module Gitlab - module Graph - class JsonBuilder - attr_accessor :days, :commits, :ref_cache, :repo - - def self.max_count - @max_count ||= 650 - end - - def initialize project, ref, commit - @project = project - @ref = ref - @commit = commit - @repo = project.repo - @ref_cache = {} - - @commits = collect_commits - @days = index_commits - end - - def to_json(*args) - { - days: @days.compact.map { |d| [d.day, d.strftime("%b")] }, - commits: @commits.map(&:to_graph_hash) - }.to_json(*args) - end - - protected - - # Get commits from repository - # - def collect_commits - - @commits = Grit::Commit.find_all(repo, nil, {topo_order: true, max_count: self.class.max_count, skip: to_commit}).dup - - # Decorate with app/models/commit.rb - @commits.map! { |commit| ::Commit.new(commit) } - - # Decorate with lib/gitlab/graph/commit.rb - @commits.map! { |commit| Gitlab::Graph::Commit.new(commit) } - - # add refs to each commit - @commits.each { |commit| commit.add_refs(ref_cache, repo) } - - @commits - end - - # Method is adding time and space on the - # list of commits. As well as returns date list - # corelated with time set on commits. - # - # @param [Array] commits to index - # - # @return [Array] list of commit dates corelated with time on commits - def index_commits - days, times = [], [] - map = {} - - commits.reverse.each_with_index do |c,i| - c.time = i - days[i] = c.committed_date - map[c.id] = c - times[i] = c - end - - @_reserved = {} - days.each_index do |i| - @_reserved[i] = [] - end - - commits_sort_by_ref.each do |commit| - if map.include? commit.id then - place_chain(map[commit.id], map) - end - end - - # find parent spaces for not overlap lines - times.each do |c| - c.parent_spaces.concat(find_free_parent_spaces(c, map, times)) - end - - days - end - - # Skip count that the target commit is displayed in center. - def to_commit - commits = Grit::Commit.find_all(repo, nil, {topo_order: true}) - commit_index = commits.index do |c| - c.id == @commit.id - end - - if commit_index && (self.class.max_count / 2 < commit_index) then - # get max index that commit is displayed in the center. - commit_index - self.class.max_count / 2 - else - 0 - end - end - - def commits_sort_by_ref - commits.sort do |a,b| - if include_ref?(a) - -1 - elsif include_ref?(b) - 1 - else - b.committed_date <=> a.committed_date - end - end - end - - def include_ref?(commit) - heads = commit.refs.select do |ref| - ref.is_a?(Grit::Head) or ref.is_a?(Grit::Remote) or ref.is_a?(Grit::Tag) - end - - heads.map! do |head| - head.name - end - - heads.include?(@ref) - end - - def find_free_parent_spaces(commit, map, times) - spaces = [] - - commit.parents.each do |p| - if map.include?(p.id) then - parent = map[p.id] - - range = if commit.time < parent.time then - commit.time..parent.time - else - parent.time..commit.time - end - - space = if commit.space >= parent.space then - find_free_parent_space(range, parent.space, 1, commit.space, times) - else - find_free_parent_space(range, parent.space, -1, parent.space, times) - end - - mark_reserved(range, space) - spaces << space - end - end - - spaces - end - - def find_free_parent_space(range, space_base, space_step, space_default, times) - if is_overlap?(range, times, space_default) then - find_free_space(range, space_base, space_step) - else - space_default - end - end - - def is_overlap?(range, times, overlap_space) - range.each do |i| - if i != range.first && - i != range.last && - times[i].space == overlap_space then - - return true; - end - end - - false - end - - # Add space mark on commit and its parents - # - # @param [Graph::Commit] the commit object. - # @param [Hash] map of commits - def place_chain(commit, map, parent_time = nil) - leaves = take_left_leaves(commit, map) - if leaves.empty? - return - end - # and mark it as reserved - min_time = leaves.last.time - max_space = 1 - parents = leaves.last.parents.collect - parents.each do |p| - if map.include? p.id - parent = map[p.id] - if parent.time < min_time - min_time = parent.time - end - if max_space < parent.space then - max_space = parent.space - end - end - end - if parent_time.nil? - max_time = leaves.first.time - else - max_time = parent_time - 1 - end - - time_range = leaves.last.time..leaves.first.time - space = find_free_space(time_range, max_space, 2) - leaves.each{|l| l.space = space} - - mark_reserved(min_time..max_time, space) - - # Visit branching chains - leaves.each do |l| - parents = l.parents.collect.select{|p| map.include? p.id and map[p.id].space.zero?} - for p in parents - place_chain(map[p.id], map, l.time) - end - end - end - - def mark_reserved(time_range, space) - for day in time_range - @_reserved[day].push(space) - end - end - - def find_free_space(time_range, space_base, space_step) - reserved = [] - for day in time_range - reserved += @_reserved[day] - end - reserved.uniq! - - space = space_base - while reserved.include?(space) do - space += space_step - if space <= 0 then - space_step *= -1 - space = space_base + space_step - end - end - - space - end - - # Takes most left subtree branch of commits - # which don't have space mark yet. - # - # @param [Graph::Commit] the commit object. - # @param [Hash] map of commits - # - # @return [Array] list of branch commits - def take_left_leaves(commit, map) - leaves = [] - leaves.push(commit) if commit.space.zero? - - while true - return leaves if commit.parents.count.zero? - return leaves unless map.include? commit.parents.first.id - - commit = map[commit.parents.first.id] - - return leaves unless commit.space.zero? - - leaves.push(commit) - end - end - end - end -end diff --git a/vendor/assets/javascripts/branch-graph.js b/vendor/assets/javascripts/branch-graph.js deleted file mode 100644 index fb22953..0000000 --- a/vendor/assets/javascripts/branch-graph.js +++ /dev/null @@ -1,385 +0,0 @@ -!function(){ - - var BranchGraph = function(element, options){ - this.element = element; - this.options = options; - - this.preparedCommits = {}; - this.mtime = 0; - this.mspace = 0; - this.parents = {}; - this.colors = ["#000"]; - - this.load(); - }; - - BranchGraph.prototype.load = function(){ - $.ajax({ - url: this.options.url, - method: 'get', - dataType: 'json', - success: $.proxy(function(data){ - $('.loading', this.element).hide(); - this.prepareData(data.days, data.commits); - this.buildGraph(); - }, this) - }); - }; - - BranchGraph.prototype.prepareData = function(days, commits){ - this.days = days; - this.dayCount = days.length; - this.commits = commits; - this.commitCount = commits.length; - - this.collectParents(); - - this.mtime += 4; - this.mspace += 10; - for (var i = 0; i < this.commitCount; i++) { - if (this.commits[i].id in this.parents) { - this.commits[i].isParent = true; - } - this.preparedCommits[this.commits[i].id] = this.commits[i]; - } - this.collectColors(); - }; - - BranchGraph.prototype.collectParents = function(){ - for (var i = 0; i < this.commitCount; i++) { - for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) { - this.parents[this.commits[i].parents[j][0]] = true; - } - this.mtime = Math.max(this.mtime, this.commits[i].time); - this.mspace = Math.max(this.mspace, this.commits[i].space); - } - }; - - BranchGraph.prototype.collectColors = function(){ - for (var k = 0; k < this.mspace; k++) { - this.colors.push(Raphael.getColor(.8)); - // Skipping a few colors in the spectrum to get more contrast between colors - Raphael.getColor();Raphael.getColor(); - } - }; - - BranchGraph.prototype.buildGraph = function(){ - var graphWidth = $(this.element).width() - , ch = this.mspace * 20 + 100 - , cw = Math.max(graphWidth, this.mtime * 20 + 260) - , r = Raphael(this.element.get(0), cw, ch) - , top = r.set() - , cuday = 0 - , cumonth = "" - , offsetX = 20 - , offsetY = 60 - , barWidth = Math.max(graphWidth, this.dayCount * 20 + 320) - , scrollLeft = cw; - - this.raphael = r; - - r.rect(0, 0, barWidth, 20).attr({fill: "#222"}); - r.rect(0, 20, barWidth, 20).attr({fill: "#444"}); - - for (mm = 0; mm < this.dayCount; mm++) { - if(this.days[mm] != null){ - if(cuday != this.days[mm][0]){ - // Dates - r.text(offsetX + mm * 20, 31, this.days[mm][0]).attr({ - font: "12px Monaco, monospace", - fill: "#DDD" - }); - cuday = this.days[mm][0]; - } - if(cumonth != this.days[mm][1]){ - // Months - r.text(offsetX + mm * 20, 11, this.days[mm][1]).attr({ - font: "12px Monaco, monospace", - fill: "#EEE" - }); - cumonth = this.days[mm][1]; - } - } - } - - for (i = 0; i < this.commitCount; i++) { - var x = offsetX + 20 * this.commits[i].time - , y = offsetY + 10 * this.commits[i].space - , c - , ps; - - // Draw dot - r.circle(x, y, 3).attr({ - fill: this.colors[this.commits[i].space], - stroke: "none" - }); - - // Draw lines - for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) { - c = this.preparedCommits[this.commits[i].parents[j][0]]; - ps = this.commits[i].parent_spaces[j]; - if (c) { - var cx = offsetX + 20 * c.time - , cy = offsetY + 10 * c.space - , psy = offsetY + 10 * ps; - if (c.space == this.commits[i].space && c.space == ps) { - r.path([ - "M", x, y, - "L", cx, cy - ]).attr({ - stroke: this.colors[c.space], - "stroke-width": 2 - }); - - } else if (c.space < this.commits[i].space) { - r.path([ - "M", x - 5, y, - "l-5-2,0,4,5,-2", - "L", x - 10, y, - "L", x - 15, psy, - "L", cx + 5, psy, - "L", cx, cy]) - .attr({ - stroke: this.colors[this.commits[i].space], - "stroke-width": 2 - }); - } else { - r.path([ - "M", x - 3, y + 6, - "l-4,3,4,2,0,-5", - "L", x - 5, y + 10, - "L", x - 10, psy, - "L", cx + 5, psy, - "L", cx, cy]) - .attr({ - stroke: this.colors[c.space], - "stroke-width": 2 - }); - } - } - } - - if (this.commits[i].refs) { - this.appendLabel(x, y, this.commits[i].refs); - } - - // mark commit and displayed in the center - if (this.commits[i].id == this.options.commit_id) { - r.path([ - 'M', x, y - 5, - 'L', x + 4, y - 15, - 'L', x - 4, y - 15, - 'Z' - ]).attr({ - "fill": "#000", - "fill-opacity": .7, - "stroke": "none" - }); - scrollLeft = x - graphWidth / 2; - } - - this.appendAnchor(top, this.commits[i], x, y); - } - top.toFront(); - this.element.scrollLeft(scrollLeft); - this.bindEvents(); - }; - - BranchGraph.prototype.bindEvents = function(){ - var drag = {} - , element = this.element; - - var dragger = function(event){ - element.scrollLeft(drag.sl - (event.clientX - drag.x)); - element.scrollTop(drag.st - (event.clientY - drag.y)); - }; - - element.on({ - mousedown: function (event) { - drag = { - x: event.clientX, - y: event.clientY, - st: element.scrollTop(), - sl: element.scrollLeft() - }; - $(window).on('mousemove', dragger); - } - }); - $(window).on({ - mouseup: function(){ - //bars.animate({opacity: 0}, 300); - $(window).off('mousemove', dragger); - }, - keydown: function(event){ - if(event.keyCode == 37){ - // left - element.scrollLeft( element.scrollLeft() - 50); - } - if(event.keyCode == 38){ - // top - element.scrollTop( element.scrollTop() - 50); - } - if(event.keyCode == 39){ - // right - element.scrollLeft( element.scrollLeft() + 50); - } - if(event.keyCode == 40){ - // bottom - element.scrollTop( element.scrollTop() + 50); - } - } - }); - }; - - BranchGraph.prototype.appendLabel = function(x, y, refs){ - var r = this.raphael - , shortrefs = refs - , text, textbox, rect; - - if (shortrefs.length > 17){ - // Truncate if longer than 15 chars - shortrefs = shortrefs.substr(0,15) + "…"; - } - - text = r.text(x+5, y+8 + 10, shortrefs).attr({ - font: "10px Monaco, monospace", - fill: "#FFF", - title: refs - }); - - textbox = text.getBBox(); - text.transform([ - 't', textbox.height/-4, textbox.width/2 + 5, - 'r90' - ]); - - // Create rectangle based on the size of the textbox - rect = r.rect(x, y, textbox.width + 15, textbox.height + 5, 4).attr({ - "fill": "#000", - "fill-opacity": .7, - "stroke": "none" - }); - - triangle = r.path([ - 'M', x, y + 5, - 'L', x + 4, y + 15, - 'L', x - 4, y + 15, - 'Z' - ]).attr({ - "fill": "#000", - "fill-opacity": .7, - "stroke": "none" - }); - - // Rotate and reposition rectangle over text - rect.transform([ - 'r', 90, x, y, - 't', 15, -9 - ]); - - // Set text to front - text.toFront(); - }; - - BranchGraph.prototype.appendAnchor = function(top, commit, x, y) { - var r = this.raphael - , options = this.options - , anchor; - anchor = r.circle(x, y, 10).attr({ - fill: "#000", - opacity: 0, - cursor: "pointer" - }) - .click(function(){ - window.open(options.commit_url.replace('%s', commit.id), '_blank'); - }) - .hover(function(){ - this.tooltip = r.commitTooltip(x, y + 5, commit); - top.push(this.tooltip.insertBefore(this)); - }, function(){ - this.tooltip && this.tooltip.remove() && delete this.tooltip; - }); - top.push(anchor); - }; - - this.BranchGraph = BranchGraph; - -}(this); -Raphael.fn.commitTooltip = function(x, y, commit){ - var nameText, idText, messageText - , boxWidth = 300 - , boxHeight = 200; - - nameText = this.text(x, y + 10, commit.author.name); - idText = this.text(x, y + 35, commit.id); - messageText = this.text(x, y + 50, commit.message); - - textSet = this.set(nameText, idText, messageText).attr({ - "text-anchor": "start", - "font": "12px Monaco, monospace" - }); - - nameText.attr({ - "font": "14px Arial", - "font-weight": "bold" - }); - - idText.attr({ - "fill": "#AAA" - }); - - textWrap(messageText, boxWidth - 50); - - var rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({ - "fill": "#FFF", - "stroke": "#000", - "stroke-linecap": "round", - "stroke-width": 2 - }); - var tooltip = this.set(rect, textSet); - - rect.attr({ - "height" : tooltip.getBBox().height + 10, - "width" : tooltip.getBBox().width + 10 - }); - - tooltip.transform([ - 't', 20, 20 - ]); - - return tooltip; -}; - -function textWrap(t, width) { - var content = t.attr("text"); - var abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - t.attr({ - "text" : abc - }); - var letterWidth = t.getBBox().width / abc.length; - - t.attr({ - "text" : content - }); - - var words = content.split(" "); - var x = 0, s = []; - for ( var i = 0; i < words.length; i++) { - - var l = words[i].length; - if (x + (l * letterWidth) > width) { - s.push("\n"); - x = 0; - } - x += l * letterWidth; - s.push(words[i] + " "); - } - t.attr({ - "text" : s.join("") - }); - var b = t.getBBox() - , h = Math.abs(b.y2) - Math.abs(b.y) + 1; - t.attr({ - "y": b.y + h - }); -} -- libgit2 0.21.2