git_stats 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. data/.gitignore +21 -0
  2. data/.gitmodules +3 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +13 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +44 -0
  7. data/Rakefile +1 -0
  8. data/bin/git_stats +11 -0
  9. data/config/locales/en.yml +56 -0
  10. data/config/locales/pl.yml +56 -0
  11. data/config/locales/pl_default.yml +224 -0
  12. data/git_stats.gemspec +27 -0
  13. data/lib/git_stats.rb +15 -0
  14. data/lib/git_stats/base.rb +30 -0
  15. data/lib/git_stats/by_field_finder.rb +7 -0
  16. data/lib/git_stats/cli.rb +17 -0
  17. data/lib/git_stats/core_extensions/hash.rb +9 -0
  18. data/lib/git_stats/core_extensions/string.rb +6 -0
  19. data/lib/git_stats/core_extensions/symbol.rb +6 -0
  20. data/lib/git_stats/generator.rb +32 -0
  21. data/lib/git_stats/git_data/activity.rb +78 -0
  22. data/lib/git_stats/git_data/author.rb +67 -0
  23. data/lib/git_stats/git_data/blob.rb +38 -0
  24. data/lib/git_stats/git_data/command_parser.rb +33 -0
  25. data/lib/git_stats/git_data/command_runner.rb +10 -0
  26. data/lib/git_stats/git_data/commit.rb +63 -0
  27. data/lib/git_stats/git_data/repo.rb +137 -0
  28. data/lib/git_stats/git_data/short_stat.rb +33 -0
  29. data/lib/git_stats/hash_initializable.rb +7 -0
  30. data/lib/git_stats/i18n.rb +2 -0
  31. data/lib/git_stats/stats_view/charts/activity_charts.rb +70 -0
  32. data/lib/git_stats/stats_view/charts/authors_charts.rb +35 -0
  33. data/lib/git_stats/stats_view/charts/chart.rb +119 -0
  34. data/lib/git_stats/stats_view/charts/charts.rb +35 -0
  35. data/lib/git_stats/stats_view/charts/repo_charts.rb +53 -0
  36. data/lib/git_stats/stats_view/template.rb +20 -0
  37. data/lib/git_stats/stats_view/view.rb +72 -0
  38. data/lib/git_stats/stats_view/view_data.rb +31 -0
  39. data/lib/git_stats/version.rb +4 -0
  40. data/spec/by_field_finder_spec.rb +25 -0
  41. data/spec/factories.rb +25 -0
  42. data/spec/git_data/activity_spec.rb +38 -0
  43. data/spec/git_data/author_spec.rb +25 -0
  44. data/spec/git_data/blob_spec.rb +25 -0
  45. data/spec/git_data/cli_spec.rb +20 -0
  46. data/spec/git_data/command_observer_spec.rb +35 -0
  47. data/spec/git_data/commit_range_spec.rb +47 -0
  48. data/spec/git_data/commit_spec.rb +48 -0
  49. data/spec/git_data/generator_spec.rb +46 -0
  50. data/spec/git_data/repo_spec.rb +46 -0
  51. data/spec/git_data/short_stat_spec.rb +28 -0
  52. data/spec/hash_extension_spec.rb +26 -0
  53. data/spec/integration/activity_spec.rb +33 -0
  54. data/spec/integration/author_spec.rb +67 -0
  55. data/spec/integration/file_spec.rb +31 -0
  56. data/spec/integration/repo_spec.rb +68 -0
  57. data/spec/integration/shared.rb +37 -0
  58. data/spec/spec_helper.rb +13 -0
  59. data/templates/activity/_activity.haml +101 -0
  60. data/templates/activity/by_date.haml +1 -0
  61. data/templates/activity/day_of_week.haml +1 -0
  62. data/templates/activity/hour_of_day.haml +1 -0
  63. data/templates/activity/hour_of_week.haml +1 -0
  64. data/templates/activity/month_of_year.haml +1 -0
  65. data/templates/activity/year.haml +1 -0
  66. data/templates/activity/year_month.haml +1 -0
  67. data/templates/assets/bootstrap/css/bootstrap-responsive.css +1058 -0
  68. data/templates/assets/bootstrap/css/bootstrap-responsive.min.css +9 -0
  69. data/templates/assets/bootstrap/css/bootstrap.css +5774 -0
  70. data/templates/assets/bootstrap/css/bootstrap.min.css +9 -0
  71. data/templates/assets/bootstrap/img/glyphicons-halflings-white.png +0 -0
  72. data/templates/assets/bootstrap/img/glyphicons-halflings.png +0 -0
  73. data/templates/assets/bootstrap/js/bootstrap.js +2027 -0
  74. data/templates/assets/bootstrap/js/bootstrap.min.js +6 -0
  75. data/templates/assets/highstock.js +305 -0
  76. data/templates/assets/jquery.min.js +2 -0
  77. data/templates/authors/_authors.haml +66 -0
  78. data/templates/authors/best_authors.haml +1 -0
  79. data/templates/authors/changed_lines_by_author_by_date.haml +1 -0
  80. data/templates/authors/commits_sum_by_author_by_date.haml +1 -0
  81. data/templates/authors/deletions_by_author_by_date.haml +1 -0
  82. data/templates/authors/insertions_by_author_by_date.haml +1 -0
  83. data/templates/files/_files.haml +15 -0
  84. data/templates/files/by_date.haml +1 -0
  85. data/templates/files/by_extension.haml +1 -0
  86. data/templates/general.haml +28 -0
  87. data/templates/layout.haml +29 -0
  88. data/templates/lines/_lines.haml +15 -0
  89. data/templates/lines/by_date.haml +1 -0
  90. data/templates/lines/by_extension.haml +1 -0
  91. data/templates/static/index.html +5 -0
  92. 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,7 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module HashInitializable
3
+ def initialize(params = {})
4
+ raise "pass a Hash to initialize #{self.class}" unless params.is_a? Hash
5
+ params.each { |k, v| instance_variable_set("@#{k}", v) }
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ # -*- encoding : utf-8 -*-
2
+ I18n.load_path += Dir['../../../../config/locales/*.yml'.absolute_path]
@@ -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