#!/usr/bin/env ruby

# A small ruby script to extract a git history to a tikz picture
# Author: Michael Hauspie <Michael.Hauspie@lifl.fr>
#
# Not clean code, not always working well, but does its job in most of the cases I needed :)

# A commit object
class Commit
  attr_accessor :hash
  attr_accessor :children
  attr_accessor :parents
  attr_accessor :message
  attr_reader   :node_pos
  
  # Construct a commit from a line
  # A line is commit_hash [child1_hash ... childN_hash] message
  def initialize()
    @hash = nil
    @children = Hash.new()
    @parents = Hash.new()
    @message = ""
    @node_pos = 0
    @message_pos = 0
  end
  def build(line)
    # Parse each word to match a hash or the commmit message
    pos=0
    line.split(" ").each do |word|
      if word =~ /[a-f0-9]{7}/ &&  @message == "" # match a short sha-1 commit tag
        if ! @hash
          @hash = word
        else
          @parents[word] = nil
        end
      elsif word == '*' # Position of the node
        @node_pos = pos
      elsif word =~ /[^|\/\\]/
        @message_pos = pos
        @message << " #{word}"
      end
      pos = pos + 1
    end
    @message.delete!("!")
    @message.lstrip!()
    if !@hash
      return false
    end
    return true
  end

  # sets a child object
  def set_parent(c)
    @parents[c.hash] = c
    c.children[@hash] = self
  end

  def export_to_tikz(ypos)
    puts "\\node[commit] (#{@hash}) at (#{0.5*@node_pos},#{ypos}) {};"
    puts "\\node[right,xshift=#{@message_pos*0.5}] (label_#{@hash}) at (#{@hash}.east) {\\verb!#{@hash} #{@message}!};"
    @children.each_value do |child|
      puts "\\draw[->] (#{@hash}) -- (#{child.hash});"
    end
  end

  def to_s()
    "#{@hash}: #{@message} #{@node_pos} #{@message_pos}"
  end
end

class Branch
  attr_accessor :name
  attr_accessor :hash
  attr_accessor :commit

  def initialize(line)
    words = line.split(" ")
    @name = words[0]
    @hash = words[1]
    @commit = nil
  end
end

# A repository, which is a collection of commit objects and branches
class Repository
  def initialize()
    @commits = Hash.new()
    @branches = Array.new()
  end
  
  def add_commit(commit)
    @commits[commit.hash] = commit
  end

  def add_branch(branch)
    if ! @commits.has_key?(branch.hash)
      return false
    end
    c = @commits.fetch(branch.hash)
    branch.commit = c
    @branches << branch
    return true
  end

  # This iterates other commits and resolves its parents
  def resolve_parents()
    @commits.each_value do |commit|
      commit.parents.keys.each do |parent_hash|
        c = @commits.fetch(parent_hash)
        commit.set_parent(c) # Link the commit object to its parent
      end
    end
  end

  def export_to_tikz
    puts "\\begin{tikzpicture}"
    ypos=0
    ystep=-0.5
    @commits.each_value do |commit|
      commit.export_to_tikz(ypos)
      ypos = ypos + ystep
    end
    @branches.each do |branch|
      puts "\\node[branch,right,xshift=.1] (#{branch.name}) at (label_#{branch.hash}.east) {\\texttt{#{branch.name}}};"
    end
    puts "\\end{tikzpicture}"
  end
end




r = Repository.new()

# Extract commits
`git log --graph --branches --oneline --parents`.lines().each do |line|
  c = Commit.new()
  if c.build(line)
    r.add_commit(c)
  end
end
r.resolve_parents()

# Extract branches
`git branch -av | cut -b 3-`.lines().each do |line|
  r.add_branch(Branch.new(line))
end

r.export_to_tikz()
