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.
@@ -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