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
data/git_stats.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'git_stats/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "git_stats"
|
8
|
+
gem.version = GitStats::VERSION
|
9
|
+
gem.authors = ["Tomasz Gieniusz"]
|
10
|
+
gem.email = ["tomasz.gieniusz@gmail.com"]
|
11
|
+
gem.description = %q{Git history statistics generator}
|
12
|
+
gem.summary = %q{HTML statistics generator from git repository}
|
13
|
+
gem.homepage = "https://github.com/tomgi/git_stats"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency('activesupport')
|
21
|
+
gem.add_dependency('actionpack')
|
22
|
+
gem.add_dependency('tilt')
|
23
|
+
gem.add_dependency('haml')
|
24
|
+
gem.add_dependency('launchy')
|
25
|
+
gem.add_dependency('lazy_high_charts_tomgi')
|
26
|
+
gem.add_dependency('i18n')
|
27
|
+
end
|
data/lib/git_stats.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module GitStats
|
3
|
+
end
|
4
|
+
|
5
|
+
require 'active_support/all'
|
6
|
+
require 'action_pack'
|
7
|
+
require 'action_view'
|
8
|
+
require 'fileutils'
|
9
|
+
require 'tilt'
|
10
|
+
require 'pathname'
|
11
|
+
require 'lazy_high_charts'
|
12
|
+
require 'launchy'
|
13
|
+
require 'i18n'
|
14
|
+
|
15
|
+
require 'git_stats/base'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'git_stats/core_extensions/hash'
|
3
|
+
require 'git_stats/core_extensions/string'
|
4
|
+
require 'git_stats/core_extensions/symbol'
|
5
|
+
|
6
|
+
require 'git_stats/version'
|
7
|
+
require 'git_stats/i18n'
|
8
|
+
require 'git_stats/by_field_finder'
|
9
|
+
require 'git_stats/cli'
|
10
|
+
require 'git_stats/generator'
|
11
|
+
|
12
|
+
require 'git_stats/git_data/activity'
|
13
|
+
require 'git_stats/git_data/author'
|
14
|
+
require 'git_stats/git_data/blob'
|
15
|
+
require 'git_stats/git_data/command_parser'
|
16
|
+
require 'git_stats/git_data/command_runner'
|
17
|
+
require 'git_stats/git_data/commit'
|
18
|
+
require 'git_stats/git_data/repo'
|
19
|
+
require 'git_stats/git_data/short_stat'
|
20
|
+
|
21
|
+
require 'git_stats/stats_view/template'
|
22
|
+
require 'git_stats/stats_view/view'
|
23
|
+
require 'git_stats/stats_view/view_data'
|
24
|
+
|
25
|
+
require 'git_stats/stats_view/charts/activity_charts'
|
26
|
+
require 'git_stats/stats_view/charts/authors_charts'
|
27
|
+
require 'git_stats/stats_view/charts/chart'
|
28
|
+
require 'git_stats/stats_view/charts/charts'
|
29
|
+
require 'git_stats/stats_view/charts/repo_charts'
|
30
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require "git_stats"
|
3
|
+
|
4
|
+
class GitStats::CLI
|
5
|
+
|
6
|
+
def start(*args)
|
7
|
+
raise "Wrong number of arguments\nUsage: git_stats repo_path output_path <output_lang>" unless [2, 3].include? args.size
|
8
|
+
|
9
|
+
repo_path, out_path, lang = args
|
10
|
+
I18n.locale = lang || :en
|
11
|
+
|
12
|
+
GitStats::Generator.new(repo_path, out_path) { |g|
|
13
|
+
g.add_command_observer { |command, result| puts "#{command}" }
|
14
|
+
}.render_all
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
class Hash
|
3
|
+
def to_key_indexed_array(params = {})
|
4
|
+
raise ArgumentError.new('all the keys must be numbers to convert to key indexed array') unless all? { |k, v| k.is_a? Numeric }
|
5
|
+
min_size = params[:min_size] || 0
|
6
|
+
default = params[:default]
|
7
|
+
inject(Array.new(min_size, default)) { |acc, (k, v)| acc[k] = v; acc }.map { |e| e || default }
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module GitStats
|
3
|
+
class Generator
|
4
|
+
delegate :add_command_observer, to: :@repo
|
5
|
+
delegate :render_all, to: :@view
|
6
|
+
|
7
|
+
def initialize(repo_path, out_path)
|
8
|
+
validate_paths(repo_path, out_path)
|
9
|
+
|
10
|
+
@repo = GitData::Repo.new(path: repo_path)
|
11
|
+
view_data = StatsView::ViewData.new(@repo)
|
12
|
+
@view = StatsView::View.new(view_data, out_path)
|
13
|
+
|
14
|
+
yield self if block_given?
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def validate_paths(repo_path, out_path)
|
19
|
+
validate_repo_path(repo_path)
|
20
|
+
validate_out_path(out_path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate_repo_path(repo_path)
|
24
|
+
raise ArgumentError, "#{repo_path} is not a git repository" unless Dir.exists?("#{repo_path}/.git")
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate_out_path(out_path)
|
28
|
+
raise ArgumentError, "#{out_path} is not a directory" unless Dir.exists?(out_path)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module GitStats
|
3
|
+
module GitData
|
4
|
+
class Activity
|
5
|
+
|
6
|
+
def initialize(commits)
|
7
|
+
add_commits(commits)
|
8
|
+
end
|
9
|
+
|
10
|
+
def by_date
|
11
|
+
@by_date ||= default_hash
|
12
|
+
end
|
13
|
+
|
14
|
+
def by_hour
|
15
|
+
@by_hour ||= default_hash
|
16
|
+
end
|
17
|
+
|
18
|
+
def by_hour_array
|
19
|
+
by_hour.to_key_indexed_array(min_size: 24, default: 0)
|
20
|
+
end
|
21
|
+
|
22
|
+
def by_wday
|
23
|
+
@by_wday ||= default_hash
|
24
|
+
end
|
25
|
+
|
26
|
+
def by_wday_array
|
27
|
+
by_wday.to_key_indexed_array(min_size: 7, default: 0)
|
28
|
+
end
|
29
|
+
|
30
|
+
def by_wday_hour
|
31
|
+
@by_wday_hour ||= default_double_hash
|
32
|
+
end
|
33
|
+
|
34
|
+
def by_month
|
35
|
+
@by_month ||= default_hash
|
36
|
+
end
|
37
|
+
|
38
|
+
def by_month_array
|
39
|
+
by_month.to_key_indexed_array(min_size: 13, default: 0)[1..-1]
|
40
|
+
end
|
41
|
+
|
42
|
+
def by_year
|
43
|
+
@by_year ||= default_hash
|
44
|
+
end
|
45
|
+
|
46
|
+
def by_year_month
|
47
|
+
@by_year_week ||= default_double_hash
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def add_commits(commits)
|
52
|
+
commits = commits.values if commits.is_a? Hash
|
53
|
+
commits.each do |commit|
|
54
|
+
add_commit_at(commit.date)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_commit_at(date)
|
59
|
+
self.by_date[date] += 1
|
60
|
+
self.by_hour[date.hour] += 1
|
61
|
+
self.by_wday[date.wday] += 1
|
62
|
+
self.by_wday_hour[date.wday][date.hour] += 1
|
63
|
+
self.by_month[date.month] += 1
|
64
|
+
self.by_year[date.year] += 1
|
65
|
+
self.by_year_month[date.year][date.month] += 1
|
66
|
+
end
|
67
|
+
|
68
|
+
def default_hash
|
69
|
+
Hash.new(0)
|
70
|
+
end
|
71
|
+
|
72
|
+
def default_double_hash
|
73
|
+
Hash.new { |h, k| h[k] = Hash.new(0) }
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'git_stats/hash_initializable'
|
3
|
+
|
4
|
+
module GitStats
|
5
|
+
module GitData
|
6
|
+
class Author
|
7
|
+
include HashInitializable
|
8
|
+
|
9
|
+
attr_reader :repo, :name, :email
|
10
|
+
|
11
|
+
def commits
|
12
|
+
@commits ||= repo.commits.select { |commit| commit.author == self }
|
13
|
+
end
|
14
|
+
|
15
|
+
def changed_lines
|
16
|
+
insertions + deletions
|
17
|
+
end
|
18
|
+
|
19
|
+
def insertions
|
20
|
+
short_stats.map(&:insertions).sum
|
21
|
+
end
|
22
|
+
|
23
|
+
def deletions
|
24
|
+
short_stats.map(&:deletions).sum
|
25
|
+
end
|
26
|
+
|
27
|
+
def commits_sum_by_date
|
28
|
+
sum = 0
|
29
|
+
commits.map { |commit|
|
30
|
+
sum += 1
|
31
|
+
[commit.date, sum]
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
[:insertions, :deletions, :changed_lines].each do |method|
|
36
|
+
define_method "#{method}_by_date" do
|
37
|
+
sum = 0
|
38
|
+
commits.map { |commit|
|
39
|
+
sum += commit.short_stat.send(method)
|
40
|
+
[commit.date, sum]
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def short_stats
|
46
|
+
commits.map(&:short_stat)
|
47
|
+
end
|
48
|
+
|
49
|
+
def activity
|
50
|
+
@activity ||= Activity.new(commits)
|
51
|
+
end
|
52
|
+
|
53
|
+
def dirname
|
54
|
+
@name.underscore.split.join '_'
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
"#{self.class} #@name <#@email>"
|
59
|
+
end
|
60
|
+
|
61
|
+
def ==(other)
|
62
|
+
[self.repo, self.name, self.email] == [other.repo, other.name, other.email]
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'git_stats/hash_initializable'
|
4
|
+
|
5
|
+
module GitStats
|
6
|
+
module GitData
|
7
|
+
class Blob
|
8
|
+
include HashInitializable
|
9
|
+
|
10
|
+
attr_reader :repo, :sha, :filename
|
11
|
+
|
12
|
+
def lines_count
|
13
|
+
@lines_count ||= binary? ? 0 : repo.run("git cat-file blob #{self.sha} | wc -l").to_i
|
14
|
+
end
|
15
|
+
|
16
|
+
def content
|
17
|
+
@content ||= repo.run("git cat-file blob #{self.sha}")
|
18
|
+
end
|
19
|
+
|
20
|
+
def extension
|
21
|
+
@ext ||= File.extname(filename)
|
22
|
+
end
|
23
|
+
|
24
|
+
def binary?
|
25
|
+
repo.run("git cat-file blob #{self.sha} | grep -m 1 '^'") =~ /Binary file/
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"#{self.class} #@sha #@filename"
|
30
|
+
end
|
31
|
+
|
32
|
+
def ==(other)
|
33
|
+
[self.repo, self.sha, self.filename] == [other.repo, other.sha, other.filename]
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module GitStats
|
3
|
+
module GitData
|
4
|
+
class CommandParser
|
5
|
+
def parse(command, result)
|
6
|
+
cmd, params = command.scan(/git (.*) (.*)/).first.map(&:split).flatten
|
7
|
+
send("parse_#{cmd.underscore}", result, params)
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse_shortlog(result, params)
|
11
|
+
result.lines.map do |line|
|
12
|
+
commits, name, email = line.scan(/(.*)\t(.*)<(.*)>/).first.map(&:strip)
|
13
|
+
{commits: commits.to_i, name: name, email: email}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_ls_tree(result, params)
|
18
|
+
result.lines.map do |line|
|
19
|
+
mode, type, sha, filename = line.scan(/(.*) (.*) (.*)\t(.*)/).first.map(&:strip)
|
20
|
+
{mode: mode, type: type, sha: sha, filename: filename}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_rev_list(result, params)
|
25
|
+
result.lines.map do |line|
|
26
|
+
sha, stamp, date, author_email = line.split('|').map(&:strip)
|
27
|
+
{sha: sha, stamp: stamp, date: date, author_email: author_email}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'git_stats/hash_initializable'
|
3
|
+
|
4
|
+
module GitStats
|
5
|
+
module GitData
|
6
|
+
class Commit
|
7
|
+
include HashInitializable
|
8
|
+
|
9
|
+
attr_reader :repo, :sha, :stamp, :date, :author
|
10
|
+
|
11
|
+
def files
|
12
|
+
@files ||= repo.run_and_parse("git ls-tree -r #{self.sha}").map do |file|
|
13
|
+
Blob.new(repo: repo, filename: file[:filename], sha: file[:sha])
|
14
|
+
end.extend(ByFieldFinder)
|
15
|
+
end
|
16
|
+
|
17
|
+
def binary_files
|
18
|
+
@binary_files ||= files.select { |f| f.binary? }
|
19
|
+
end
|
20
|
+
|
21
|
+
def text_files
|
22
|
+
@text_files ||= files - binary_files
|
23
|
+
end
|
24
|
+
|
25
|
+
def files_by_extension
|
26
|
+
@files_by_extension ||= files.inject({}) { |acc, f| acc[f.extension] ||= []; acc[f.extension] << f; acc }
|
27
|
+
end
|
28
|
+
|
29
|
+
def files_by_extension_count
|
30
|
+
@files_by_extension_count ||= Hash[files_by_extension.map { |ext, files| [ext, files.count] }]
|
31
|
+
end
|
32
|
+
|
33
|
+
def lines_by_extension
|
34
|
+
@lines_by_extension ||= Hash[files_by_extension.map { |ext, files|
|
35
|
+
[ext, files.map(&:lines_count).sum]
|
36
|
+
}.delete_if { |ext, lines_count| lines_count == 0 }]
|
37
|
+
end
|
38
|
+
|
39
|
+
def files_count
|
40
|
+
@files_count ||= repo.run("git ls-tree -r --name-only #{self.sha} | wc -l").to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
def lines_count
|
44
|
+
@lines_count ||= repo.run("git diff --shortstat `git hash-object -t tree /dev/null` #{self.sha}").lines.map do |line|
|
45
|
+
line[/(\d+) insertions?/, 1].to_i
|
46
|
+
end.sum
|
47
|
+
end
|
48
|
+
|
49
|
+
def short_stat
|
50
|
+
@short_stat ||= ShortStat.new(self)
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
"#{self.class} #@sha"
|
55
|
+
end
|
56
|
+
|
57
|
+
def ==(other)
|
58
|
+
[self.repo, self.sha, self.stamp, self.date, self.author] ==
|
59
|
+
[other.repo, other.sha, other.stamp, other.date, other.author]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|