git_statistics 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +7 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +15 -0
- data/Guardfile +7 -0
- data/README.md +7 -2
- data/git_statistics.gemspec +1 -0
- data/lib/git_statistics/collector.rb +130 -245
- data/lib/git_statistics/commits.rb +19 -24
- data/lib/git_statistics/initialize.rb +1 -0
- data/lib/git_statistics/results.rb +96 -0
- data/lib/git_statistics/utilities.rb +108 -0
- data/lib/git_statistics/version.rb +1 -1
- data/lib/git_statistics.rb +7 -4
- data/spec/collector_spec.rb +365 -0
- data/spec/commits_spec.rb +238 -0
- data/spec/fixtures/commit_buffer_changes.txt +9 -0
- data/spec/fixtures/commit_buffer_information.txt +1 -0
- data/spec/fixtures/commit_buffer_information_first.txt +1 -0
- data/spec/fixtures/commit_buffer_information_with_merge.txt +1 -0
- data/spec/fixtures/commit_buffer_whole.txt +6 -0
- data/spec/fixtures/git_many_branches.txt +2 -0
- data/spec/fixtures/git_zero_branches.txt +1 -0
- data/spec/fixtures/header_output.txt +4 -0
- data/spec/fixtures/language_data_output.txt +2 -0
- data/spec/fixtures/multiple_authors.json +1 -0
- data/spec/fixtures/single_author_pretty.json +79 -0
- data/spec/fixtures/summary_output.txt +17 -0
- data/spec/results_spec.rb +143 -0
- data/spec/utilities_spec.rb +169 -0
- metadata +54 -2
@@ -12,38 +12,28 @@ module GitStatistics
|
|
12
12
|
@totals[:languages] = {}
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def identify_authors_email
|
24
|
-
self.each do |key,value|
|
25
|
-
if not @author_list.include?(value[:author_email])
|
26
|
-
@author_list << value[:author_email]
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def author_top_n_type(type, n=0)
|
32
|
-
n = 0 if n < 0
|
33
|
-
return nil if @stats == nil || !@stats.first[1].has_key?(type)
|
34
|
-
return @stats.sorted_hash {|a,b| b[1][type.to_sym] <=> a[1][type]}.to_a[0..n-1]
|
15
|
+
def author_top_n_type(type, top_n=0)
|
16
|
+
top_n = 0 if top_n < 0
|
17
|
+
return nil if @stats.size == 0
|
18
|
+
return nil if !@stats.first[1].has_key?(type)
|
19
|
+
return Hash[*@stats.sorted_hash {|a,b| b[1][type.to_sym] <=> a[1][type]}.to_a[0..top_n-1].flatten]
|
35
20
|
end
|
36
21
|
|
37
22
|
def calculate_statistics(email, merge)
|
38
23
|
|
24
|
+
# Check that there are statistics to calculate
|
25
|
+
if self.size == 0
|
26
|
+
puts "Cannot calculate statistics as there is no data"
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
39
30
|
# Identify authors and author type
|
40
31
|
if email
|
41
|
-
identify_authors_email
|
42
32
|
type = :author_email
|
43
33
|
else
|
44
|
-
identify_authors
|
45
34
|
type = :author
|
46
35
|
end
|
36
|
+
@author_list = Utilities.unique_data_in_hash(self, type)
|
47
37
|
|
48
38
|
# Initialize the stats hash
|
49
39
|
@author_list.each do |author|
|
@@ -53,7 +43,7 @@ module GitStatistics
|
|
53
43
|
|
54
44
|
# Collect the stats from each commit
|
55
45
|
self.each do |key,value|
|
56
|
-
if
|
46
|
+
if !merge && value[:merge]
|
57
47
|
next
|
58
48
|
else
|
59
49
|
|
@@ -88,11 +78,15 @@ module GitStatistics
|
|
88
78
|
if data[:languages][file[:language].to_sym] == nil
|
89
79
|
data[:languages][file[:language].to_sym] = Hash.new(0)
|
90
80
|
end
|
81
|
+
|
91
82
|
data[:languages][file[:language].to_sym][:additions] += file[:additions]
|
92
83
|
data[:languages][file[:language].to_sym][:deletions] += file[:deletions]
|
93
|
-
|
84
|
+
|
85
|
+
if file[:status] != nil
|
94
86
|
data[:languages][file[:language].to_sym][file[:status].to_sym] += 1
|
95
87
|
end
|
88
|
+
|
89
|
+
return data
|
96
90
|
end
|
97
91
|
|
98
92
|
def add_commit_stats(data, commit)
|
@@ -105,6 +99,7 @@ module GitStatistics
|
|
105
99
|
data[:delete] += commit[:delete] if commit[:delete] != nil
|
106
100
|
data[:rename] += commit[:rename] if commit[:rename] != nil
|
107
101
|
data[:copy] += commit[:copy] if commit[:copy] != nil
|
102
|
+
return data
|
108
103
|
end
|
109
104
|
|
110
105
|
def load(file)
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module GitStatistics
|
2
|
+
class Results
|
3
|
+
|
4
|
+
attr_accessor :commits
|
5
|
+
|
6
|
+
def initialize(commits)
|
7
|
+
@commits = commits
|
8
|
+
end
|
9
|
+
|
10
|
+
def prepare_result_summary(sort, email, top_n=0)
|
11
|
+
# Default to a 0 if given a negative number to display
|
12
|
+
top_n = 0 if top_n < 0
|
13
|
+
|
14
|
+
# Acquire data based on sorty type and top # to show
|
15
|
+
data = @commits.author_top_n_type(sort.to_sym, top_n)
|
16
|
+
if data == nil
|
17
|
+
raise "Parameter for --sort is not valid"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Create config
|
21
|
+
config = {:data => data,
|
22
|
+
:author_length => Utilities.find_longest_length(data, 17),
|
23
|
+
:language_length => Utilities.find_longest_length(@commits.language_list, 8),
|
24
|
+
:sort => sort,
|
25
|
+
:email => email,
|
26
|
+
:top_n => top_n}
|
27
|
+
|
28
|
+
# Acquire formatting pattern for output
|
29
|
+
pattern = "%-#{config[:author_length]}s | %-#{config[:language_length]}s | %7s | %9s | %9s | %7s | %7s | %7s | %6s | %6s |"
|
30
|
+
config[:pattern] = pattern
|
31
|
+
return config
|
32
|
+
end
|
33
|
+
|
34
|
+
def print_summary(sort, email, top_n=0)
|
35
|
+
# Prepare and determine the config for the result summary based on parameters
|
36
|
+
config = prepare_result_summary(sort, email, top_n)
|
37
|
+
|
38
|
+
# Print query/header information
|
39
|
+
output = print_header(config)
|
40
|
+
|
41
|
+
# Print per author information
|
42
|
+
config[:data].each do |key,value|
|
43
|
+
output += config[:pattern] % [key, "", value[:commits], value[:additions],
|
44
|
+
value[:deletions], value[:create], value[:delete],
|
45
|
+
value[:rename], value[:copy], value[:merges]]
|
46
|
+
output += "\n"
|
47
|
+
output += print_language_data(config[:pattern], value)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Reprint query/header for repository information
|
51
|
+
output += "\n"
|
52
|
+
output += print_header(config)
|
53
|
+
data = @commits.totals
|
54
|
+
output += config[:pattern] % ["Repository Totals", "", data[:commits],
|
55
|
+
data[:additions], data[:deletions], data[:create],
|
56
|
+
data[:delete], data[:rename], data[:copy], data[:merges]]
|
57
|
+
output += "\n"
|
58
|
+
output += print_language_data(config[:pattern], data)
|
59
|
+
return output
|
60
|
+
end
|
61
|
+
|
62
|
+
def print_language_data(pattern, data)
|
63
|
+
output = ""
|
64
|
+
# Print information of each language for the data
|
65
|
+
data[:languages].each do |key,value|
|
66
|
+
output += pattern % ["", key, "", value[:additions], value[:deletions],
|
67
|
+
value[:create], value[:delete], value[:rename],
|
68
|
+
value[:copy], value[:merges]]
|
69
|
+
output += "\n"
|
70
|
+
end
|
71
|
+
return output
|
72
|
+
end
|
73
|
+
|
74
|
+
def print_header(config)
|
75
|
+
total_authors = @commits.author_list.length
|
76
|
+
|
77
|
+
output = ""
|
78
|
+
# Print summary information of displayed results
|
79
|
+
if config[:top_n] > 0 and config[:top_n] < total_authors
|
80
|
+
output += "Top #{config[:top_n]} authors(#{total_authors}) sorted by #{config[:sort]}\n"
|
81
|
+
else
|
82
|
+
output += "All authors(#{total_authors}) sorted by #{config[:sort]}\n"
|
83
|
+
end
|
84
|
+
|
85
|
+
# Print column headers
|
86
|
+
output += "-"*87 + "-"*config[:author_length] + "-"*config[:language_length]
|
87
|
+
output += "\n"
|
88
|
+
output += config[:pattern] % ['Name/Email', 'Language', 'Commits', 'Additions', 'Deletions', 'Creates', 'Deletes', 'Renames', 'Copies', 'Merges']
|
89
|
+
output += "\n"
|
90
|
+
output += "-"*87 + "-"*config[:author_length] + "-"*config[:language_length]
|
91
|
+
output += "\n"
|
92
|
+
return output
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module GitStatistics
|
2
|
+
module Utilities
|
3
|
+
def self.get_repository(path=Dir.pwd)
|
4
|
+
# Connect to git repository if it exists
|
5
|
+
directory = Pathname.new(path)
|
6
|
+
repo = nil
|
7
|
+
while !directory.root? do
|
8
|
+
begin
|
9
|
+
repo = Grit::Repo.new(directory)
|
10
|
+
return repo
|
11
|
+
rescue
|
12
|
+
directory = directory.parent
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.find_longest_length(list, max=nil)
|
18
|
+
return list if list == nil
|
19
|
+
list.each do |key,value|
|
20
|
+
max = key.length if max == nil || key.length > max
|
21
|
+
end
|
22
|
+
return max
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.unique_data_in_hash(data, type)
|
26
|
+
list = []
|
27
|
+
data.each do |key,value|
|
28
|
+
if not list.include?(value[type])
|
29
|
+
list << value[type]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
return list
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.clean_string(string)
|
36
|
+
return string.strip.force_encoding("iso-8859-1").encode("utf-8")
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.split_old_new_file(old, new)
|
40
|
+
# Split the old and new chunks up (separted by the =>)
|
41
|
+
split_old = old.split('{')
|
42
|
+
split_new = new.split('}')
|
43
|
+
|
44
|
+
# Handle recombine the file splits into their whole paths)
|
45
|
+
if split_old.size == 1 && split_new.size == 1
|
46
|
+
old_file = split_old[0]
|
47
|
+
new_file = split_new[0]
|
48
|
+
elsif split_new.size == 1
|
49
|
+
old_file = split_old[0] + split_old[1]
|
50
|
+
new_file = split_old[0] + split_new[0]
|
51
|
+
elsif split_old.size == 1
|
52
|
+
old_file = split_old[0] + split_new[1]
|
53
|
+
new_file = split_old[0] + split_new[0] + split_new[1]
|
54
|
+
else
|
55
|
+
old_file = split_old[0] + split_old[1] + split_new[1]
|
56
|
+
new_file = split_old[0] + split_new[0] + split_new[1]
|
57
|
+
end
|
58
|
+
|
59
|
+
# Return files, yet remove the '//' if present from combining splits
|
60
|
+
return {:old_file => old_file.gsub('//', '/'),
|
61
|
+
:new_file => new_file.gsub('//', '/')}
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.find_blob_in_tree(tree, file)
|
65
|
+
# Check If cannot find tree in commit or if we found a submodule as the changed file
|
66
|
+
if tree == nil
|
67
|
+
return nil
|
68
|
+
elsif file == nil
|
69
|
+
return nil
|
70
|
+
elsif tree.instance_of?(Grit::Submodule)
|
71
|
+
return tree
|
72
|
+
end
|
73
|
+
|
74
|
+
# If the blob is within the current directory (tree)
|
75
|
+
if file.size == 1
|
76
|
+
blob = tree / file.first
|
77
|
+
|
78
|
+
# Check if blob is nil (could not find changed file in tree)
|
79
|
+
if blob == nil
|
80
|
+
|
81
|
+
# Try looking for submodules as they cannot be found using tree / file notation
|
82
|
+
tree.contents.each do |content|
|
83
|
+
if file.first == content.name
|
84
|
+
return tree
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Exit through recusion with the base case of a nil tree/blob
|
89
|
+
return find_blob_in_tree(blob, file)
|
90
|
+
end
|
91
|
+
return blob
|
92
|
+
else
|
93
|
+
# Explore deeper in the tree to find the blob of the changed file
|
94
|
+
return find_blob_in_tree(tree / file.first, file[1..-1])
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.get_modified_time(file)
|
99
|
+
if OS.mac?
|
100
|
+
Time.at(`stat -f %m commits.json`.to_i)
|
101
|
+
elsif OS.linux?
|
102
|
+
Time.at(`stat -c %Y commits.json`.to_i)
|
103
|
+
else
|
104
|
+
raise "Update on the Windows operating system is not supported"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/git_statistics.rb
CHANGED
@@ -7,7 +7,7 @@ module GitStatistics
|
|
7
7
|
opt :email, "Use author's email instead of name", :default => false
|
8
8
|
opt :merges, "Factor in merges when calculating statistics", :default => false
|
9
9
|
opt :save, "Save the commits as commits.json", :default => false
|
10
|
-
opt :pretty, "
|
10
|
+
opt :pretty, "Save the commits as commits.json in pretty print (larger file size)", :default => false
|
11
11
|
opt :load, "Load commits.json instead of re-collecting data", :default => false
|
12
12
|
opt :update, "Update commits.json with new data (same as save and load together)", :default => false
|
13
13
|
opt :sort, "Sort authors by {commits, additions, deletions, create, delete, rename, copy, merges}", :default => "commits"
|
@@ -29,17 +29,20 @@ module GitStatistics
|
|
29
29
|
|
30
30
|
# Collect incremental recent data
|
31
31
|
if @opts[:update]
|
32
|
-
|
32
|
+
time = Utilities.get_modified_time("commit.json")
|
33
|
+
collector.collect(@opts[:branch], "--since=\"#{time}\"")
|
33
34
|
end
|
34
35
|
|
35
36
|
# Save data
|
36
|
-
if @opts[:save] || @opts[:update]
|
37
|
+
if @opts[:save] || @opts[:update] || @opts[:pretty]
|
37
38
|
collector.commits.save("commits.json", @opts[:pretty])
|
38
39
|
end
|
39
40
|
|
40
41
|
collector.commits.calculate_statistics(@opts[:email], @opts[:merges])
|
41
42
|
|
42
|
-
|
43
|
+
# Print results
|
44
|
+
results = Results.new(collector.commits)
|
45
|
+
puts results.print_summary(@opts[:sort], @opts[:email], @opts[:top])
|
43
46
|
end
|
44
47
|
end
|
45
48
|
end
|