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.summary = %q{HTML statistics generator from git repository}
|
||||
gem.homepage = "https://github.com/tomgi/git_stats"
|
||||
gem.executables = "git_stats"
|
||||
|
||||
gem.files = `git ls-files`.split($/)
|
||||
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('lazy_high_charts')
|
||||
|
||||
gem.add_development_dependency('rake')
|
||||
gem.add_development_dependency('pry')
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@ end
|
|||
require 'active_support/all'
|
||||
require 'action_pack'
|
||||
require 'action_view'
|
||||
require 'fileutils'
|
||||
require 'pathname'
|
||||
require 'tilt'
|
||||
require 'lazy_high_charts'
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
class GitStats::Generator
|
||||
module GitStats
|
||||
class Generator
|
||||
def initialize(repo_path, out_path)
|
||||
@repo_path, @out_path = repo_path, out_path
|
||||
end
|
||||
|
||||
def generate
|
||||
repo = GitStats::GitRepo.new(@repo_path)
|
||||
data = GitStats::GitData.new(repo)
|
||||
data.gather_all
|
||||
repo = GitData::Repo.new(@repo_path)
|
||||
repo.gather_all_data
|
||||
|
||||
view_data = GitStats::ViewData.new(data)
|
||||
view_data = StatsView::ViewData.new(repo)
|
||||
view_data.generate_charts
|
||||
|
||||
GitStats::View.render_all(view_data, @out_path)
|
||||
StatsView::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= git_data.project_version
|
||||
%p= repo.project_name
|
||||
%p= repo.project_version
|
||||
|
||||
= high_chart("my_id", @h)
|
Loading…
Reference in a new issue