git_statistics 0.2.0 → 0.3.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.
@@ -12,38 +12,28 @@ module GitStatistics
12
12
  @totals[:languages] = {}
13
13
  end
14
14
 
15
- def identify_authors
16
- self.each do |key,value|
17
- if not @author_list.include?(value[:author])
18
- @author_list << value[:author]
19
- end
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 not merge and value[:merge]
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
- if file[:status] != nil || file[:status] == "submodule"
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)
@@ -2,6 +2,7 @@ require 'json'
2
2
  require 'trollop'
3
3
  require 'grit'
4
4
  require 'linguist'
5
+ require 'os'
5
6
  require 'pathname'
6
7
 
7
8
  # Custom Blob for Grit to enable Linguist
@@ -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
@@ -1,3 +1,3 @@
1
1
  module GitStatistics
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -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, "Pretty print saved commits (larger file size)", :default => false
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
- collector.collect(@opts[:branch], "--since=\"`date -r commits.json \"+%F %T\"`\"")
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
- collector.print_summary(@opts[:sort].to_sym, @opts[:email], @opts[:top])
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