mirror of
https://github.com/tomgi/git_stats.git
synced 2024-12-22 13:32:17 +01:00
shortstat data
This commit is contained in:
parent
a2a059d5eb
commit
a2e81f6330
22 changed files with 281 additions and 214 deletions
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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
|
|
||||||
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
|
end
|
44
lib/git_stats/git_data/activity.rb
Normal file
44
lib/git_stats/git_data/activity.rb
Normal 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
|
24
lib/git_stats/git_data/author.rb
Normal file
24
lib/git_stats/git_data/author.rb
Normal 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
|
19
lib/git_stats/git_data/command.rb
Normal file
19
lib/git_stats/git_data/command.rb
Normal 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
|
24
lib/git_stats/git_data/commit.rb
Normal file
24
lib/git_stats/git_data/commit.rb
Normal 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
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -1,7 +0,0 @@
|
||||||
class GitStats::GitRepo
|
|
||||||
attr_reader :path
|
|
||||||
|
|
||||||
def initialize(path)
|
|
||||||
@path = path
|
|
||||||
end
|
|
||||||
end
|
|
59
lib/git_stats/git_data/repo.rb
Normal file
59
lib/git_stats/git_data/repo.rb
Normal 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
|
23
lib/git_stats/git_data/short_stat.rb
Normal file
23
lib/git_stats/git_data/short_stat.rb
Normal 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
|
15
lib/git_stats/stats_view/template.rb
Normal file
15
lib/git_stats/stats_view/template.rb
Normal 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
|
17
lib/git_stats/stats_view/view.rb
Normal file
17
lib/git_stats/stats_view/view.rb
Normal 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
|
36
lib/git_stats/stats_view/view_data.rb
Normal file
36
lib/git_stats/stats_view/view_data.rb
Normal 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
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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)
|
Loading…
Reference in a new issue