git_stats 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +21 -0
- data/.gitmodules +3 -0
- data/.rspec +1 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +44 -0
- data/Rakefile +1 -0
- data/bin/git_stats +11 -0
- data/config/locales/en.yml +56 -0
- data/config/locales/pl.yml +56 -0
- data/config/locales/pl_default.yml +224 -0
- data/git_stats.gemspec +27 -0
- data/lib/git_stats.rb +15 -0
- data/lib/git_stats/base.rb +30 -0
- data/lib/git_stats/by_field_finder.rb +7 -0
- data/lib/git_stats/cli.rb +17 -0
- data/lib/git_stats/core_extensions/hash.rb +9 -0
- data/lib/git_stats/core_extensions/string.rb +6 -0
- data/lib/git_stats/core_extensions/symbol.rb +6 -0
- data/lib/git_stats/generator.rb +32 -0
- data/lib/git_stats/git_data/activity.rb +78 -0
- data/lib/git_stats/git_data/author.rb +67 -0
- data/lib/git_stats/git_data/blob.rb +38 -0
- data/lib/git_stats/git_data/command_parser.rb +33 -0
- data/lib/git_stats/git_data/command_runner.rb +10 -0
- data/lib/git_stats/git_data/commit.rb +63 -0
- data/lib/git_stats/git_data/repo.rb +137 -0
- data/lib/git_stats/git_data/short_stat.rb +33 -0
- data/lib/git_stats/hash_initializable.rb +7 -0
- data/lib/git_stats/i18n.rb +2 -0
- data/lib/git_stats/stats_view/charts/activity_charts.rb +70 -0
- data/lib/git_stats/stats_view/charts/authors_charts.rb +35 -0
- data/lib/git_stats/stats_view/charts/chart.rb +119 -0
- data/lib/git_stats/stats_view/charts/charts.rb +35 -0
- data/lib/git_stats/stats_view/charts/repo_charts.rb +53 -0
- data/lib/git_stats/stats_view/template.rb +20 -0
- data/lib/git_stats/stats_view/view.rb +72 -0
- data/lib/git_stats/stats_view/view_data.rb +31 -0
- data/lib/git_stats/version.rb +4 -0
- data/spec/by_field_finder_spec.rb +25 -0
- data/spec/factories.rb +25 -0
- data/spec/git_data/activity_spec.rb +38 -0
- data/spec/git_data/author_spec.rb +25 -0
- data/spec/git_data/blob_spec.rb +25 -0
- data/spec/git_data/cli_spec.rb +20 -0
- data/spec/git_data/command_observer_spec.rb +35 -0
- data/spec/git_data/commit_range_spec.rb +47 -0
- data/spec/git_data/commit_spec.rb +48 -0
- data/spec/git_data/generator_spec.rb +46 -0
- data/spec/git_data/repo_spec.rb +46 -0
- data/spec/git_data/short_stat_spec.rb +28 -0
- data/spec/hash_extension_spec.rb +26 -0
- data/spec/integration/activity_spec.rb +33 -0
- data/spec/integration/author_spec.rb +67 -0
- data/spec/integration/file_spec.rb +31 -0
- data/spec/integration/repo_spec.rb +68 -0
- data/spec/integration/shared.rb +37 -0
- data/spec/spec_helper.rb +13 -0
- data/templates/activity/_activity.haml +101 -0
- data/templates/activity/by_date.haml +1 -0
- data/templates/activity/day_of_week.haml +1 -0
- data/templates/activity/hour_of_day.haml +1 -0
- data/templates/activity/hour_of_week.haml +1 -0
- data/templates/activity/month_of_year.haml +1 -0
- data/templates/activity/year.haml +1 -0
- data/templates/activity/year_month.haml +1 -0
- data/templates/assets/bootstrap/css/bootstrap-responsive.css +1058 -0
- data/templates/assets/bootstrap/css/bootstrap-responsive.min.css +9 -0
- data/templates/assets/bootstrap/css/bootstrap.css +5774 -0
- data/templates/assets/bootstrap/css/bootstrap.min.css +9 -0
- data/templates/assets/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/templates/assets/bootstrap/img/glyphicons-halflings.png +0 -0
- data/templates/assets/bootstrap/js/bootstrap.js +2027 -0
- data/templates/assets/bootstrap/js/bootstrap.min.js +6 -0
- data/templates/assets/highstock.js +305 -0
- data/templates/assets/jquery.min.js +2 -0
- data/templates/authors/_authors.haml +66 -0
- data/templates/authors/best_authors.haml +1 -0
- data/templates/authors/changed_lines_by_author_by_date.haml +1 -0
- data/templates/authors/commits_sum_by_author_by_date.haml +1 -0
- data/templates/authors/deletions_by_author_by_date.haml +1 -0
- data/templates/authors/insertions_by_author_by_date.haml +1 -0
- data/templates/files/_files.haml +15 -0
- data/templates/files/by_date.haml +1 -0
- data/templates/files/by_extension.haml +1 -0
- data/templates/general.haml +28 -0
- data/templates/layout.haml +29 -0
- data/templates/lines/_lines.haml +15 -0
- data/templates/lines/by_date.haml +1 -0
- data/templates/lines/by_extension.haml +1 -0
- data/templates/static/index.html +5 -0
- metadata +268 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'git_stats/hash_initializable'
|
3
|
+
|
4
|
+
module GitStats
|
5
|
+
module GitData
|
6
|
+
class Repo
|
7
|
+
include HashInitializable
|
8
|
+
|
9
|
+
attr_reader :path
|
10
|
+
|
11
|
+
delegate :files, :files_by_extension, :files_by_extension_count, :lines_by_extension,
|
12
|
+
:files_count, :binary_files, :text_files, :lines_count, to: :last_commit
|
13
|
+
|
14
|
+
def initialize(params)
|
15
|
+
super(params)
|
16
|
+
@path = File.expand_path(@path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def authors
|
20
|
+
@authors ||= run_and_parse("git shortlog -se #{commit_range}").map do |author|
|
21
|
+
Author.new(repo: self, name: author[:name], email: author[:email])
|
22
|
+
end.extend(ByFieldFinder)
|
23
|
+
end
|
24
|
+
|
25
|
+
def commits
|
26
|
+
@commits ||= run_and_parse("git rev-list --pretty=format:'%h|%at|%ai|%aE' #{commit_range} | grep -v commit").map do |commit_line|
|
27
|
+
Commit.new(
|
28
|
+
repo: self,
|
29
|
+
sha: commit_line[:sha],
|
30
|
+
stamp: commit_line[:stamp],
|
31
|
+
date: DateTime.parse(commit_line[:date]),
|
32
|
+
author: authors.by_email(commit_line[:author_email])
|
33
|
+
)
|
34
|
+
end.sort_by! { |e| e.date }.extend(ByFieldFinder)
|
35
|
+
end
|
36
|
+
|
37
|
+
def commits_period
|
38
|
+
commits.map(&:date).minmax
|
39
|
+
end
|
40
|
+
|
41
|
+
def commits_count_by_author(limit = 4)
|
42
|
+
Hash[authors.map { |author| [author, author.commits.size] }.sort_by { |author, commits| -commits }[0..limit]]
|
43
|
+
end
|
44
|
+
|
45
|
+
[:insertions, :deletions, :changed_lines].each do |method|
|
46
|
+
define_method "#{method}_by_author" do |limit = 4|
|
47
|
+
Hash[authors.map { |author| [author, author.send(method)] }.sort_by { |author, lines| -lines }[0..limit]]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def files_count_by_date
|
52
|
+
@files_count_each_day ||= Hash[commits.map { |commit|
|
53
|
+
[commit.date, commit.files_count]
|
54
|
+
}]
|
55
|
+
end
|
56
|
+
|
57
|
+
def lines_count_by_date
|
58
|
+
sum = 0
|
59
|
+
Hash[commits.map { |commit|
|
60
|
+
sum += commit.short_stat.insertions
|
61
|
+
sum -= commit.short_stat.deletions
|
62
|
+
[commit.date, sum]
|
63
|
+
}]
|
64
|
+
end
|
65
|
+
|
66
|
+
def last_commit
|
67
|
+
commits.last
|
68
|
+
end
|
69
|
+
|
70
|
+
def commit_range
|
71
|
+
@first_commit_sha ? "#@first_commit_sha..#{last_commit_sha}" : last_commit_sha
|
72
|
+
end
|
73
|
+
|
74
|
+
def last_commit_sha
|
75
|
+
@last_commit_sha ||= 'HEAD'
|
76
|
+
end
|
77
|
+
|
78
|
+
def short_stats
|
79
|
+
@short_stats ||= commits.map(&:short_stat)
|
80
|
+
end
|
81
|
+
|
82
|
+
def activity
|
83
|
+
@activity ||= Activity.new(commits)
|
84
|
+
end
|
85
|
+
|
86
|
+
def project_version
|
87
|
+
@project_version ||= run("git rev-parse --short #{commit_range}").strip
|
88
|
+
end
|
89
|
+
|
90
|
+
def project_name
|
91
|
+
@project_name ||= File.basename(path)
|
92
|
+
end
|
93
|
+
|
94
|
+
def run(command)
|
95
|
+
result = command_runner.run(path, command)
|
96
|
+
invoke_command_observers(command, result)
|
97
|
+
result
|
98
|
+
end
|
99
|
+
|
100
|
+
def run_and_parse(command)
|
101
|
+
result = run(command)
|
102
|
+
command_parser.parse(command, result)
|
103
|
+
end
|
104
|
+
|
105
|
+
def command_runner
|
106
|
+
@command_runner ||= CommandRunner.new
|
107
|
+
end
|
108
|
+
|
109
|
+
def command_parser
|
110
|
+
@command_parser ||= CommandParser.new
|
111
|
+
end
|
112
|
+
|
113
|
+
def add_command_observer(proc=nil, &block)
|
114
|
+
command_observers << block if block_given?
|
115
|
+
command_observers << proc if proc
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_s
|
119
|
+
"#{self.class} #@path"
|
120
|
+
end
|
121
|
+
|
122
|
+
def ==(other)
|
123
|
+
self.path == other.path
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
def command_observers
|
128
|
+
@command_observers ||= []
|
129
|
+
end
|
130
|
+
|
131
|
+
def invoke_command_observers(command, result)
|
132
|
+
command_observers.each { |o| o.call(command, result) }
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module GitStats
|
3
|
+
module GitData
|
4
|
+
class ShortStat
|
5
|
+
attr_reader :commit, :files_changed, :insertions, :deletions
|
6
|
+
|
7
|
+
def initialize(commit)
|
8
|
+
@commit = commit
|
9
|
+
calculate_stat
|
10
|
+
end
|
11
|
+
|
12
|
+
def changed_lines
|
13
|
+
insertions + deletions
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
"#{self.class} #@commit"
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def calculate_stat
|
22
|
+
stat_line = commit.repo.run("git show --shortstat --oneline #{commit.sha}").lines.to_a[1]
|
23
|
+
if stat_line.blank?
|
24
|
+
@files_changed = @insertions = @deletions = 0
|
25
|
+
else
|
26
|
+
@files_changed = stat_line[/(\d+) files? changed/, 1].to_i
|
27
|
+
@insertions = stat_line[/(\d+) insertions?/, 1].to_i
|
28
|
+
@deletions = stat_line[/(\d+) deletions?/, 1].to_i
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module GitStats
|
3
|
+
module StatsView
|
4
|
+
module Charts
|
5
|
+
class ActivityCharts
|
6
|
+
def initialize(repo)
|
7
|
+
@repo = repo
|
8
|
+
@activity = repo.activity
|
9
|
+
end
|
10
|
+
|
11
|
+
def activity_by_date(author)
|
12
|
+
Chart.new do |f|
|
13
|
+
f.date_column_chart(
|
14
|
+
data: author.activity.by_date,
|
15
|
+
title: :commits_by_date.t,
|
16
|
+
y_text: :commits.t
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def activity_by_hour(author)
|
22
|
+
Chart.new do |f|
|
23
|
+
f.simple_column_chart(
|
24
|
+
title: :commits_by_hour.t,
|
25
|
+
y_text: :commits.t,
|
26
|
+
x_text: :hour.t,
|
27
|
+
data_x: (0..23),
|
28
|
+
data_y: author.activity.by_hour_array
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def activity_by_wday(author)
|
34
|
+
Chart.new do |f|
|
35
|
+
f.simple_column_chart(
|
36
|
+
title: :commits_by_wday.t,
|
37
|
+
y_text: :commits.t,
|
38
|
+
x_text: :day.t,
|
39
|
+
data_x: I18n.t('date.abbr_day_names'),
|
40
|
+
data_y: author.activity.by_wday_array
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def activity_by_month(author)
|
46
|
+
Chart.new do |f|
|
47
|
+
f.simple_column_chart(
|
48
|
+
title: :commits_by_month.t,
|
49
|
+
y_text: :commits.t,
|
50
|
+
x_text: :month.t,
|
51
|
+
data_x: I18n.t('date.abbr_month_names')[1..-1],
|
52
|
+
data_y: author.activity.by_month_array
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def activity_by_year(author)
|
58
|
+
Chart.new do |f|
|
59
|
+
f.column_hash_chart(
|
60
|
+
title: :commits_by_year.t,
|
61
|
+
y_text: :commits.t,
|
62
|
+
x_text: :year.t,
|
63
|
+
data: author.activity.by_year
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module GitStats
|
3
|
+
module StatsView
|
4
|
+
module Charts
|
5
|
+
class AuthorsCharts
|
6
|
+
def initialize(authors)
|
7
|
+
@authors = authors
|
8
|
+
end
|
9
|
+
|
10
|
+
def commits_sum_by_author_by_date(limit = 4)
|
11
|
+
Chart.new do |f|
|
12
|
+
f.multi_date_chart(
|
13
|
+
data: @authors.sort_by { |author| -author.commits.size }[0..limit].map { |author| {name: author.name, data: author.commits_sum_by_date} },
|
14
|
+
title: :lines_by_date.t,
|
15
|
+
y_text: :lines.t
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
[:insertions, :deletions, :changed_lines].each do |method|
|
21
|
+
define_method "#{method}_by_author_by_date" do |limit = 4|
|
22
|
+
Chart.new do |f|
|
23
|
+
f.multi_date_chart(
|
24
|
+
data: @authors.sort_by { |author| -author.send(method) }[0..limit].map { |author| {name: author.name, data: author.send("#{method}_by_date")} },
|
25
|
+
title: :lines_by_date.t,
|
26
|
+
y_text: :lines.t
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module GitStats
|
3
|
+
module StatsView
|
4
|
+
module Charts
|
5
|
+
class Chart
|
6
|
+
|
7
|
+
def method_missing(name, *args, &block)
|
8
|
+
@chart.send(name, *args, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@chart = LazyHighCharts::HighChart.new('graph')
|
13
|
+
yield self if block_given?
|
14
|
+
end
|
15
|
+
|
16
|
+
def simple_column_chart(params)
|
17
|
+
column_chart(params)
|
18
|
+
series(name: params[:title], data: params[:data_y])
|
19
|
+
end
|
20
|
+
|
21
|
+
def multiple_column_chart(params)
|
22
|
+
column_chart(params)
|
23
|
+
params[:data_y].each do |s|
|
24
|
+
series(name: s[:name], data: s[:data])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def column_hash_chart(params)
|
29
|
+
simple_column_chart(params.merge(
|
30
|
+
data_x: params[:data].keys,
|
31
|
+
data_y: params[:data].values
|
32
|
+
)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def date_chart(params)
|
37
|
+
common_options(params)
|
38
|
+
date_series(name: params[:title], data: params[:data])
|
39
|
+
end
|
40
|
+
|
41
|
+
def multi_date_chart(params)
|
42
|
+
common_options(params)
|
43
|
+
default_legend
|
44
|
+
params[:data].each do |s|
|
45
|
+
date_series(s)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def date_column_chart(params)
|
50
|
+
date_chart(params)
|
51
|
+
data[0][:type] = 'column'
|
52
|
+
data[0][:dataGrouping] = {units: [['day', [1]], ['week', [1]]], forced: true}
|
53
|
+
end
|
54
|
+
|
55
|
+
def default_legend
|
56
|
+
legend(
|
57
|
+
enabled: true,
|
58
|
+
layout: 'vertical',
|
59
|
+
backgroundColor: '#FFFFFF',
|
60
|
+
align: 'left',
|
61
|
+
verticalAlign: 'top',
|
62
|
+
x: 100,
|
63
|
+
y: 70,
|
64
|
+
floating: true,
|
65
|
+
shadow: true
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def no_legend
|
70
|
+
legend(
|
71
|
+
enabled: false
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def type(type)
|
76
|
+
@chart.chart!(type: type)
|
77
|
+
end
|
78
|
+
|
79
|
+
def x_categories(categories)
|
80
|
+
@chart.xAxis!(categories: categories)
|
81
|
+
end
|
82
|
+
|
83
|
+
def x_text(text)
|
84
|
+
@chart.xAxis!(title: {text: text})
|
85
|
+
end
|
86
|
+
|
87
|
+
def y_text(text)
|
88
|
+
@chart.yAxis!(title: {text: text})
|
89
|
+
end
|
90
|
+
|
91
|
+
def title(title)
|
92
|
+
@chart.title!(text: title)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
def common_options(params)
|
97
|
+
no_legend
|
98
|
+
title ""
|
99
|
+
y_text params[:y_text]
|
100
|
+
x_text params[:x_text]
|
101
|
+
end
|
102
|
+
|
103
|
+
def date_series(params)
|
104
|
+
series(
|
105
|
+
name: params[:name],
|
106
|
+
type: "spline",
|
107
|
+
data: params[:data].map { |date, value| [date.to_i * 1000, value] }
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
def column_chart(params)
|
112
|
+
common_options(params)
|
113
|
+
type "column"
|
114
|
+
x_categories params[:data_x]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module GitStats
|
3
|
+
module StatsView
|
4
|
+
module Charts
|
5
|
+
class All
|
6
|
+
delegate :files_by_extension, :lines_by_extension, :files_by_date, :lines_by_date, to: :repo_charts
|
7
|
+
|
8
|
+
delegate :commits_sum_by_author_by_date, :changed_lines_by_author_by_date,
|
9
|
+
:insertions_by_author_by_date, :deletions_by_author_by_date, to: :authors_charts
|
10
|
+
|
11
|
+
delegate :activity_by_date, :activity_by_hour, :activity_by_wday, :activity_by_month,
|
12
|
+
:activity_by_year, to: :activity_charts
|
13
|
+
|
14
|
+
attr_reader :repo
|
15
|
+
|
16
|
+
def initialize(repo)
|
17
|
+
@repo = repo
|
18
|
+
end
|
19
|
+
|
20
|
+
def repo_charts
|
21
|
+
@repo_charts ||= Charts::RepoCharts.new(repo)
|
22
|
+
end
|
23
|
+
|
24
|
+
def authors_charts
|
25
|
+
@authors_charts ||= Charts::AuthorsCharts.new(repo.authors)
|
26
|
+
end
|
27
|
+
|
28
|
+
def activity_charts
|
29
|
+
@activity_charts ||= Charts::ActivityCharts.new(repo)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|