shortstat data

This commit is contained in:
Tomasz Gieniusz 2012-10-09 22:34:02 +02:00
parent a2a059d5eb
commit a2e81f6330
22 changed files with 281 additions and 214 deletions

View file

@ -11,7 +11,6 @@ Gem::Specification.new do |gem|
gem.description = %q{Git history statistics generator} gem.description = %q{Git history statistics generator}
gem.summary = %q{HTML statistics generator from git repository} gem.summary = %q{HTML statistics generator from git repository}
gem.homepage = "https://github.com/tomgi/git_stats" gem.homepage = "https://github.com/tomgi/git_stats"
gem.executables = "git_stats"
gem.files = `git ls-files`.split($/) gem.files = `git ls-files`.split($/)
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@ -25,5 +24,6 @@ Gem::Specification.new do |gem|
gem.add_dependency('launchy') gem.add_dependency('launchy')
gem.add_dependency('lazy_high_charts') gem.add_dependency('lazy_high_charts')
gem.add_development_dependency('rake')
gem.add_development_dependency('pry') gem.add_development_dependency('pry')
end end

View file

@ -4,6 +4,7 @@ end
require 'active_support/all' require 'active_support/all'
require 'action_pack' require 'action_pack'
require 'action_view' require 'action_view'
require 'fileutils'
require 'pathname' require 'pathname'
require 'tilt' require 'tilt'
require 'lazy_high_charts' require 'lazy_high_charts'

View file

@ -1,16 +1,18 @@
class GitStats::Generator module GitStats
def initialize(repo_path, out_path) class Generator
@repo_path, @out_path = repo_path, out_path def initialize(repo_path, out_path)
@repo_path, @out_path = repo_path, out_path
end
def generate
repo = GitData::Repo.new(@repo_path)
repo.gather_all_data
view_data = StatsView::ViewData.new(repo)
view_data.generate_charts
StatsView::View.render_all(view_data, @out_path)
end
end end
def generate end
repo = GitStats::GitRepo.new(@repo_path)
data = GitStats::GitData.new(repo)
data.gather_all
view_data = GitStats::ViewData.new(data)
view_data.generate_charts
GitStats::View.render_all(view_data, @out_path)
end
end

View file

@ -0,0 +1,44 @@
module GitStats
module GitData
class Activity
def add_commit(commit)
add_commit_at(commit.date)
end
def add_commit_at(date)
self.by_hour[date.hour] += 1
self.by_wday[date.wday] += 1
self.by_wday_hour[date.wday][date.hour] += 1
self.by_month[date.month] += 1
self.by_year[date.year] += 1
self.by_year_week[date.year][date.cweek] += 1
end
def by_hour
@by_hour ||= Hash.new(0)
end
def by_wday
@by_wday ||= Hash.new(0)
end
def by_wday_hour
@by_wday_hour ||= Hash.new { |h, k| h[k] = Hash.new(0) }
end
def by_month
@by_month ||= Hash.new(0)
end
def by_year
@by_year ||= Hash.new(0)
end
def by_year_week
@by_year_week ||= Hash.new { |h, k| h[k] = Hash.new(0) }
end
end
end
end

View file

@ -0,0 +1,24 @@
require 'git_stats/hash_initializable'
module GitStats
module GitData
class Author
include HashInitializable
attr_accessor :name, :email
def add_commit(commit)
commits << commit
activity.add_commit(commit)
end
def commits
@commits ||= []
end
def activity
@activity ||= Activity.new
end
end
end
end

View file

@ -0,0 +1,19 @@
module GitStats
module GitData
class Command
def initialize(repo, command)
@repo = repo
@command = command
end
def run
puts "running #@command"
in_repo { %x[#@command] }
end
def in_repo
Dir.chdir(@repo.path) { yield }
end
end
end
end

View file

@ -0,0 +1,24 @@
require 'git_stats/hash_initializable'
module GitStats
module GitData
class Commit
include HashInitializable
attr_reader :repo, :hash, :stamp, :date, :author
def gather_all_data
files_count
short_stat
end
def files_count
@files_count ||= Command.new(repo, "git ls-tree -r --name-only #{self.hash} | wc -l").run.to_i
end
def short_stat
@short_stat ||= ShortStat.new(self)
end
end
end
end

View file

@ -1,45 +0,0 @@
class GitStats::GitActivity
def add_commit(commit)
add_commit_at(commit.date)
files_count[commit.date] = commit.files.size
end
def add_commit_at(date)
self.by_hour[date.hour] += 1
self.by_wday[date.wday] += 1
self.by_wday_hour[date.wday][date.hour] += 1
self.by_month[date.month] += 1
self.by_year[date.year] += 1
self.by_year_week[date.year][date.cweek] += 1
end
def by_hour
@by_hour ||= Array.new(24, 0)
end
def by_wday
@by_wday ||= Array.new(7, 0)
end
def by_wday_hour
@by_wday_hour ||= Array.new(7) { Array.new(24, 0) }
end
def by_month
@by_month ||= Array.new(12, 0)
end
def by_year
@by_year ||= Hash.new(0)
end
def by_year_week
@by_year_week ||= Hash.new { |h, k| h[k] = Hash.new(0) }
end
def files_count
@files_count ||= {}
end
end

View file

@ -1,11 +0,0 @@
require 'git_stats/hash_initializable'
class GitStats::GitAuthor
include HashInitializable
attr_accessor :name, :email
def activity
@activity ||= GitStats::GitActivity.new
end
end

View file

@ -1,14 +0,0 @@
class GitStats::GitCommand
def initialize(repo, command)
@repo = repo
@command = command
end
def run
in_repo { %x[#@command] }
end
def in_repo
Dir.chdir(@repo.path) { yield }
end
end

View file

@ -1,11 +0,0 @@
require 'git_stats/hash_initializable'
class GitStats::GitCommit
include HashInitializable
attr_accessor :repo, :hash, :stamp, :date, :author
def files
@files ||= GitStats::GitCommand.new(repo, "git ls-tree -r --name-only #{self.hash}").run.split(/\r?\n/).map(&:strip)
end
end

View file

@ -1,53 +0,0 @@
class GitStats::GitData
attr_reader :repo
def initialize(repo)
@repo = repo
end
def gather_all
gather_authors
gather_commits
end
def gather_authors
GitStats::GitCommand.new(repo, 'git shortlog -se HEAD').run.each_line do |author|
name, email = author.split(/\t/)[1].strip.scan(/(.*)<(.*)>/).first.map(&:strip)
authors[email] = GitStats::GitAuthor.new(name: name, email: email)
end
end
def gather_commits
GitStats::GitCommand.new(repo, 'git rev-list --pretty=format:"%h|%at|%ai|%aE" HEAD | grep -v commit').run.each_line do |commit_line|
hash, stamp, date, author_email = commit_line.split('|').map(&:strip)
author = authors[author_email]
date = DateTime.parse(date)
commit = commits[hash] = GitStats::GitCommit.new(repo: repo, hash: hash, stamp: stamp, date: date, author: author)
activity.add_commit(commit)
author.activity.add_commit(commit)
end
end
def authors
@authors ||= {}
end
def commits
@commits ||= {}
end
def activity
@activity ||= GitStats::GitActivity.new
end
def project_version
@project_version ||= GitStats::GitCommand.new(repo, 'git rev-parse --short HEAD').run
end
def project_name
@project_name ||= Pathname(repo.path).basename.to_s
end
end

View file

@ -1,7 +0,0 @@
class GitStats::GitRepo
attr_reader :path
def initialize(path)
@path = path
end
end

View file

@ -0,0 +1,59 @@
module GitStats
module GitData
class Repo
attr_reader :path
def initialize(path)
@path = path
end
def gather_all_data
project_version
project_name
gather_authors
gather_commits
end
def gather_authors
Command.new(self, 'git shortlog -se HEAD').run.each_line do |author|
name, email = author.split(/\t/)[1].strip.scan(/(.*)<(.*)>/).first.map(&:strip)
authors[email] = Author.new(name: name, email: email)
end
end
def gather_commits
Command.new(self, 'git rev-list --pretty=format:"%h|%at|%ai|%aE" HEAD | grep -v commit').run.lines.each_with_index do |commit_line, i|
hash, stamp, date, author_email = commit_line.split('|').map(&:strip)
author = authors[author_email]
date = DateTime.parse(date)
commit = commits[hash] = Commit.new(repo: self, hash: hash, stamp: stamp, date: date, author: author)
commit.gather_all_data
activity.add_commit(commit)
author.add_commit(commit)
end
end
def authors
@authors ||= {}
end
def commits
@commits ||= {}
end
def activity
@activity ||= Activity.new
end
def project_version
@project_version ||= Command.new(self, 'git rev-parse --short HEAD').run
end
def project_name
@project_name ||= Pathname(path).basename.to_s
end
end
end
end

View file

@ -0,0 +1,23 @@
module GitStats
module GitData
class ShortStat
attr_reader :commit, :files_changed, :insertions, :deletions
def initialize(commit)
@commit = commit
calculate_stat
end
def calculate_stat
stat_line = Command.new(commit.repo, "git show --shortstat --oneline #{commit.hash}").run.lines.to_a[1]
if stat_line.blank?
@files_changed, @insertions, @deletions = 0
else
@files_changed = stat_line[/(\d+) files? changed/, 1].to_i
@insertions = stat_line[/(\d+) insertions?/, 1].to_i
@deletions = stat_line[/(\d+) deletions?/, 1].to_i
end
end
end
end
end

View file

@ -0,0 +1,15 @@
module GitStats
module StatsView
class Template
def initialize(name, layout)
@name = name
@layout = layout
@template = Tilt.new("templates/#@name.haml")
end
def render(data)
@layout.render { @template.render(data) }
end
end
end
end

View file

@ -0,0 +1,17 @@
module GitStats
module StatsView
class View
def self.render_all(data, out_path)
prepare_assets(out_path)
layout = Tilt.new("templates/layout.haml")
output = Template.new('index', layout).render(data)
File.open("#{out_path}/index.html", 'w') { |f| f.write output }
end
def self.prepare_assets(out_path)
FileUtils.cp_r('templates/assets', out_path)
end
end
end
end

View file

@ -0,0 +1,36 @@
module GitStats
module StatsView
class ViewData
include ActionView::Helpers::TagHelper
include LazyHighCharts::LayoutHelper
attr_reader :repo
def initialize(repo)
@repo = repo
end
def generate_charts
@h = LazyHighCharts::HighChart.new('graph') do |f|
f.chart(type: "column")
f.title(text: "Commits")
f.xAxis(categories: Date::ABBR_DAYNAMES)
f.yAxis(min: 0, title: {text: 'Commits'})
f.legend(
layout: 'vertical',
backgroundColor: '#FFFFFF',
align: 'left',
verticalAlign: 'top',
x: 100,
y: 70,
floating: true,
shadow: true
)
repo.authors.each do |email, author|
f.series(:name => email, :data => author.activity.by_wday.inject([]) { |acc, (k, v)| acc[k] = v; acc })
end
end
end
end
end
end

View file

@ -1,11 +0,0 @@
class GitStats::Template
def initialize(name, layout)
@name = name
@layout = layout
@template = Tilt.new("templates/#@name.haml")
end
def render(data)
@layout.render { @template.render(data) }
end
end

View file

@ -1,13 +0,0 @@
class GitStats::View
def self.render_all(data, out_path)
prepare_assets(out_path)
layout = Tilt.new("templates/layout.haml")
output = GitStats::Template.new('index', layout).render(data)
File.open("#{out_path}/index.html", 'w') { |f| f.write output }
end
def self.prepare_assets(out_path)
FileUtils.cp_r('templates/assets', out_path)
end
end

View file

@ -1,32 +0,0 @@
class GitStats::ViewData
include ActionView::Helpers::TagHelper
include LazyHighCharts::LayoutHelper
attr_reader :git_data
def initialize(git_data)
@git_data = git_data
end
def generate_charts
@h = LazyHighCharts::HighChart.new('graph') do |f|
f.chart(type: "column")
f.title(text: "Commits")
f.xAxis(categories: Date::ABBR_DAYNAMES)
f.yAxis(min: 0, title: {text: 'Commits'})
f.legend(
layout: 'vertical',
backgroundColor: '#FFFFFF',
align: 'left',
verticalAlign: 'top',
x: 100,
y: 70,
floating: true,
shadow: true
)
git_data.authors.each do |email, author|
f.series(:name => email, :data => author.activity.by_wday)
end
end
end
end

View file

@ -1,4 +1,4 @@
%p= git_data.project_name %p= repo.project_name
%p= git_data.project_version %p= repo.project_version
= high_chart("my_id", @h) = high_chart("my_id", @h)