git_statistics 0.4.1 → 0.5.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.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # CHANGELOG
2
2
 
3
+ * [0.5.0 - January 16, 2013](https://github.com/kevinjalbert/git_statistics/compare/v0.4.1...v0.5.0)
3
4
  * [0.4.1 - October 9, 2012](https://github.com/kevinjalbert/git_statistics/compare/v0.4.0...v0.4.1)
4
5
  * [0.4.0 - September 25, 2012](https://github.com/kevinjalbert/git_statistics/compare/v0.3.0...v0.4.0)
5
6
  * [0.3.0 - September 12, 2012](https://github.com/kevinjalbert/git_statistics/compare/v0.2.0...v0.3.0)
data/Guardfile CHANGED
@@ -1,7 +1,7 @@
1
1
  # A sample Guardfile
2
2
  # More info at https://github.com/guard/guard#readme
3
- guard :rspec, :version => 2, :all_on_start => false do
3
+ guard :rspec, :version => 2, :notifications => false, :all_on_start => false do
4
4
  watch(%r{^spec/.+_spec\.rb$})
5
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
5
+ watch(%r{^lib/git_statistics/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
6
6
  watch('spec/spec_helper.rb') { "spec" }
7
7
  end
@@ -2,7 +2,7 @@ require 'git_statistics/initialize'
2
2
 
3
3
  module GitStatistics
4
4
  class GitStatistics
5
- def initialize(args=nil)
5
+ def initialize(args = nil)
6
6
  @opts = Trollop::options do
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
@@ -23,7 +23,7 @@ module GitStatistics
23
23
  collector = Collector.new(@opts[:verbose], @opts[:limit], false, @opts[:pretty])
24
24
  commits_directory = collector.repo_path + ".git_statistics" + File::Separator
25
25
  FileUtils.mkdir_p(commits_directory)
26
- file_count = Utilities.get_number_of_files(commits_directory, /\d+\.json/) - 1
26
+ file_count = Utilities.number_of_matching_files(commits_directory, /\d+\.json/) - 1
27
27
 
28
28
  # Only use --since if there is data present
29
29
  if file_count >= 0
@@ -34,7 +34,7 @@ module GitStatistics
34
34
  end
35
35
 
36
36
  # If no data was collected as there was no present data then start fresh
37
- if !collected
37
+ unless collected
38
38
  collector = Collector.new(@opts[:verbose], @opts[:limit], true, @opts[:pretty])
39
39
  collector.collect(@opts[:branch])
40
40
  end
@@ -7,29 +7,25 @@ module GitStatistics
7
7
  @verbose = verbose
8
8
  @repo = Utilities.get_repository
9
9
 
10
- # Abort if no git repository is found
11
- if @repo == nil
12
- raise ("No git Repository Found")
13
- end
10
+ raise "No Git repository found" if @repo.nil?
14
11
 
15
12
  @repo_path = File.expand_path("..", @repo.path) + File::Separator
16
13
  @commits_path = @repo_path + ".git_statistics" + File::Separator
17
14
  @commits = Commits.new(@commits_path, fresh, limit, pretty)
18
15
  end
19
16
 
20
- def collect(branch, time_since="", time_until="")
17
+ def collect(branch, time_since = "", time_until = "")
21
18
  # Create pipe for git log to acquire branches
22
19
  pipe = open("|git --no-pager branch --no-color")
23
20
 
24
21
  # Collect branches to use for git log
25
- branches = collect_branches(pipe)
26
- branches = ["", ""] if branch
22
+ branches = branch ? ["", ""] : collect_branches(pipe)
27
23
 
28
24
  # Create pipe for the git log to acquire commits
29
25
  pipe = open("|git --no-pager log #{branches.join(' ')} --date=iso --reverse"\
30
- " --no-color --find-copies-harder --numstat --encoding=utf-8 "\
31
- "--summary #{time_since} #{time_until} "\
32
- "--format=\"%H,%an,%ae,%ad,%p\"")
26
+ " --no-color --find-copies-harder --numstat --encoding=utf-8"\
27
+ " --summary #{time_since} #{time_until}"\
28
+ " --format=\"%H,%an,%ae,%ad,%p\"")
33
29
 
34
30
  # Use a buffer approach to queue up lines from the log for each commit
35
31
  buffer = []
@@ -41,9 +37,9 @@ module GitStatistics
41
37
  if line.split(',').size == 5
42
38
 
43
39
  # Sometimes 'git log' doesn't populate the buffer (i.e., merges), try fallback option if so
44
- buffer = fall_back_collect_commit(buffer[0].split(',').first) if buffer.size == 1
40
+ buffer = fall_back_collect_commit(buffer[0].split(',').first) if buffer.one?
45
41
 
46
- extract_commit(buffer) if not buffer.empty?
42
+ extract_commit(buffer) unless buffer.empty?
47
43
  buffer = []
48
44
 
49
45
  # Save commits to file if size exceeds limit or forced
@@ -55,7 +51,7 @@ module GitStatistics
55
51
  end
56
52
 
57
53
  # Extract the last commit
58
- extract_commit(buffer) if not buffer.empty?
54
+ extract_commit(buffer) unless buffer.empty?
59
55
  @commits.flush_commits(true)
60
56
  end
61
57
 
@@ -65,18 +61,13 @@ module GitStatistics
65
61
  " --no-color --find-copies-harder --numstat --encoding=utf-8 "\
66
62
  "--summary --format=\"%H,%an,%ae,%ad,%p\"")
67
63
 
68
- buffer = []
69
- pipe.each do |line|
70
- buffer << Utilities.clean_string(line)
71
- end
64
+ buffer = pipe.map { |line| Utilities.clean_string(line) }
72
65
 
73
66
  # Check that the buffer has valid information (i.e., sha was valid)
74
- if buffer.empty?
75
- return nil
76
- elsif buffer.first.split(',').first == sha
77
- return buffer
67
+ if !buffer.empty? && buffer.first.split(',').first == sha
68
+ buffer
78
69
  else
79
- return nil
70
+ nil
80
71
  end
81
72
  end
82
73
 
@@ -105,7 +96,7 @@ module GitStatistics
105
96
  data[:files] = []
106
97
 
107
98
  # Flag commit as merge if necessary (determined if two parents)
108
- if commit_info[4] == nil or commit_info[4].split(' ').size == 1
99
+ if commit_info[4].nil? || commit_info[4].split(' ').one?
109
100
  data[:merge] = false
110
101
  else
111
102
  data[:merge] = true
@@ -121,7 +112,7 @@ module GitStatistics
121
112
  puts "Extracting #{commit_data[:sha]}" if @verbose
122
113
 
123
114
  # Abort if the commit sha extracted form the buffer is invalid
124
- if commit_data[:sha].scan(/[\d|a-f]{40}/)[0] == nil
115
+ if commit_data[:sha].scan(/[\d|a-f]{40}/)[0].nil?
125
116
  puts "Invalid buffer containing commit information"
126
117
  return
127
118
  end
@@ -130,7 +121,7 @@ module GitStatistics
130
121
  files = identify_changed_files(buffer[2..-1])
131
122
 
132
123
  # No files were changed in this commit, abort commit
133
- if files == nil
124
+ if files.nil?
134
125
  puts "No files were changed"
135
126
  return
136
127
  end
@@ -159,9 +150,9 @@ module GitStatistics
159
150
  blob = Utilities.find_blob_in_tree(@repo.tree(sha), file)
160
151
 
161
152
  # If we cannot find blob in current commit (deleted file), check previous commit
162
- if blob == nil || blob.instance_of?(Grit::Tree)
153
+ if blob.nil? || blob.instance_of?(Grit::Tree)
163
154
  prev_commit = @repo.commits(sha).first.parents[0]
164
- return nil if prev_commit == nil
155
+ return nil if prev_commit.nil?
165
156
 
166
157
  prev_tree = @repo.tree(prev_commit.id)
167
158
  blob = Utilities.find_blob_in_tree(prev_tree, file)
@@ -170,54 +161,56 @@ module GitStatistics
170
161
  end
171
162
 
172
163
  def identify_changed_files(buffer)
173
- return buffer if buffer == nil
164
+ return buffer if buffer.nil?
174
165
 
175
166
  # For each modification extract the details
176
167
  changed_files = []
177
168
  buffer.each do |line|
169
+ extracted_line = CommitLineExtractor.new(line)
178
170
 
179
171
  # Extract changed file information if it exists
180
- data = extract_change_file(line)
181
- if data != nil
182
- changed_files << data
172
+ changed_file_information = extracted_line.changed
173
+ if changed_file_information.any?
174
+ changed_files << changed_file_information
183
175
  next # This line is processed, skip to next
184
176
  end
185
177
 
186
178
  # Extract details of create/delete files if it exists
187
- data = extract_create_delete_file(line)
188
- if data != nil
179
+ created_or_deleted = extracted_line.created_or_deleted
180
+ if created_or_deleted.any?
189
181
  augmented = false
190
182
  # Augment changed file with create/delete information if possible
191
183
  changed_files.each do |file|
192
- if file[:file] == data[:file]
193
- file[:status] = data[:status]
184
+ if file[:file] == created_or_deleted[:file]
185
+ file[:status] = created_or_deleted[:status]
194
186
  augmented = true
195
187
  break
196
188
  end
197
189
  end
198
- changed_files << data if !augmented
190
+ changed_files << created_or_deleted unless augmented
199
191
  next # This line is processed, skip to next
200
192
  end
201
193
 
202
194
  # Extract details of rename/copy files if it exists
203
- data = extract_rename_copy_file(line)
204
- if data != nil
195
+ renamed_or_copied = extracted_line.renamed_or_copied
196
+ if renamed_or_copied.any?
205
197
  augmented = false
206
198
  # Augment changed file with rename/copy information if possible
207
199
  changed_files.each do |file|
208
- if file[:file] == data[:new_file]
209
- file[:status] = data[:status]
210
- file[:old_file] = data[:old_file]
211
- file[:similar] = data[:similar]
200
+ if file[:file] == renamed_or_copied[:new_file]
201
+ file[:status] = renamed_or_copied[:status]
202
+ file[:old_file] = renamed_or_copied[:old_file]
203
+ file[:similar] = renamed_or_copied[:similar]
212
204
  augmented = true
213
205
  break
214
206
  end
215
207
  end
216
- changed_files << data if !augmented
208
+ changed_files << renamed_or_copied unless augmented
217
209
  next # This line is processed, skip to next
218
210
  end
219
211
  end
220
- return changed_files
212
+
213
+ changed_files
221
214
  end
222
215
 
223
216
  def process_blob(data, blob, file)
@@ -240,60 +233,11 @@ module GitStatistics
240
233
  file_hash[:generated] = blob.generated?
241
234
 
242
235
  # Identify the language of the blob if possible
243
- if blob.language == nil
244
- file_hash[:language] = "Unknown"
245
- else
246
- file_hash[:language] = blob.language.name
247
- end
236
+ file_hash[:language] = blob.language.nil? ? "Unknown" : blob.language.name
248
237
  data[:files] << file_hash
249
238
 
250
239
  return data
251
240
  end
252
241
 
253
- def extract_change_file(line)
254
- # Use regex to detect a rename/copy changed file | 1 2 /path/{test => new}/file.txt
255
- changes = line.scan(/^([-|\d]+)\s+([-|\d]+)\s+(.+)\s+=>\s+(.+)/)[0]
256
- if changes != nil and changes.size == 4
257
- # Split up the file into the old and new file
258
- split_file = Utilities.split_old_new_file(changes[2], changes[3])
259
- return {:additions => changes[0].to_i,
260
- :deletions => changes[1].to_i,
261
- :file => Utilities.clean_string(split_file[:new_file]),
262
- :old_file => Utilities.clean_string(split_file[:old_file])}
263
- end
264
-
265
- # Use regex to detect a changed file | 1 2 /path/test/file.txt
266
- changes = line.scan(/^([-|\d]+)\s+([-|\d]+)\s+(.+)/)[0]
267
- if changes != nil and changes.size == 3
268
- return {:additions => changes[0].to_i,
269
- :deletions => changes[1].to_i,
270
- :file => Utilities.clean_string(changes[2])}
271
- end
272
- return nil
273
- end
274
-
275
- def extract_create_delete_file(line)
276
- # Use regex to detect a create/delete file | create mode 100644 /path/test/file.txt
277
- changes = line.scan(/^(create|delete) mode \d+ ([^\\\n]*)/)[0]
278
- if changes != nil and changes.size == 2
279
- return {:status => Utilities.clean_string(changes[0]),
280
- :file => Utilities.clean_string(changes[1])}
281
- end
282
- return nil
283
- end
284
-
285
- def extract_rename_copy_file(line)
286
- # Use regex to detect a rename/copy file | copy /path/{test => new}/file.txt
287
- changes = line.scan(/^(rename|copy)\s+(.+)\s+=>\s+(.+)\s+\((\d+)/)[0]
288
- if changes != nil and changes.size == 4
289
- # Split up the file into the old and new file
290
- split_file = Utilities.split_old_new_file(changes[1], changes[2])
291
- return {:status => Utilities.clean_string(changes[0]),
292
- :old_file => Utilities.clean_string(split_file[:old_file]),
293
- :new_file => Utilities.clean_string(split_file[:new_file]),
294
- :similar => changes[3].to_i}
295
- end
296
- return nil
297
- end
298
242
  end
299
243
  end
@@ -0,0 +1,64 @@
1
+ module GitStatistics
2
+ class CommitLineExtractor
3
+
4
+ RENAMED_OR_COPIED = /^(rename|copy)\s+(.+)\s+=>\s+(.+)\s+\((\d+)/i
5
+ CREATED_OR_DELETED = /^(create|delete) mode \d+ ([^\\\n]*)/i
6
+ MODIFIED_OR_RENAMED = /^([-|\d]+)\s+([-|\d]+)\s+(.+)\s+=>\s+(.+)/i
7
+ ADDITIONS_OR_DELETIONS = /^([-|\d]+)\s+([-|\d]+)\s+(.+)/i
8
+
9
+ attr_reader :line
10
+
11
+ def initialize(line)
12
+ @line = line
13
+ end
14
+
15
+ def changed
16
+ modified_or_renamed = line.scan(MODIFIED_OR_RENAMED).first
17
+ modified_or_renamed = changes_are_right_size(modified_or_renamed, 4) do |changes|
18
+ split_file = Utilities.split_old_new_file(changes[2], changes[3])
19
+ {:additions => changes[0].to_i,
20
+ :deletions => changes[1].to_i,
21
+ :file => Utilities.clean_string(split_file[:new_file]),
22
+ :old_file => Utilities.clean_string(split_file[:old_file])}
23
+ end
24
+ return modified_or_renamed unless modified_or_renamed.empty?
25
+
26
+ addition_or_deletion = line.scan(ADDITIONS_OR_DELETIONS).first
27
+ changes_are_right_size(addition_or_deletion, 3) do |changes|
28
+ {:additions => changes[0].to_i,
29
+ :deletions => changes[1].to_i,
30
+ :file => Utilities.clean_string(changes[2])}
31
+ end
32
+ end
33
+
34
+ def created_or_deleted
35
+ changes = line.scan(CREATED_OR_DELETED).first
36
+ changes_are_right_size(changes, 2) do |changes|
37
+ {:status => Utilities.clean_string(changes[0]),
38
+ :file => Utilities.clean_string(changes[1])}
39
+ end
40
+ end
41
+
42
+ def renamed_or_copied
43
+ changes = line.scan(RENAMED_OR_COPIED).first
44
+ changes_are_right_size(changes, 4) do |changes|
45
+ split_file = Utilities.split_old_new_file(changes[1], changes[2])
46
+ {:status => Utilities.clean_string(changes[0]),
47
+ :old_file => Utilities.clean_string(split_file[:old_file]),
48
+ :new_file => Utilities.clean_string(split_file[:new_file]),
49
+ :similar => changes[3].to_i}
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def changes_are_right_size(changes, size = 4)
56
+ if !changes.nil? && changes.size == size
57
+ yield changes
58
+ else
59
+ {}
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -19,8 +19,8 @@ module GitStatistics
19
19
  # Remove all files within path if saving
20
20
  if @fresh
21
21
  Dir.entries(@path).each do |file|
22
- next if file == "." || file == ".."
23
- File.delete(path + File::Separator + file)
22
+ next if %w[. ..].include? file
23
+ File.delete(File.join(path, file))
24
24
  end
25
25
  end
26
26
 
@@ -30,85 +30,79 @@ module GitStatistics
30
30
  @totals[:languages] = {}
31
31
  end
32
32
 
33
- def flush_commits(force=false)
34
- if self.size >= limit || force
35
- file_count = Utilities.get_number_of_files(path, /\d+\.json/)
36
- save(path + File::Separator + file_count.to_s + ".json", @pretty)
37
- self.clear
33
+ def flush_commits(force = false)
34
+ if size >= limit || force
35
+ file_count = Utilities.number_of_matching_files(path, /\d+\.json/)
36
+ save(File.join(path, "#{file_count}.json"), @pretty)
37
+ clear
38
38
  end
39
39
  end
40
40
 
41
- def author_top_n_type(type, top_n=0)
41
+ def author_top_n_type(type, top_n = 0)
42
42
  top_n = 0 if top_n < 0
43
- return nil if @stats.size == 0
44
- return nil if !@stats.first[1].has_key?(type)
45
- return Hash[*@stats.sorted_hash {|a,b| b[1][type.to_sym] <=> a[1][type]}.to_a[0..top_n-1].flatten]
43
+ if @stats.empty? || !@stats.first[1].has_key?(type)
44
+ nil
45
+ else
46
+ Hash[*@stats.sorted_hash {|a,b| b[1][type.to_sym] <=> a[1][type]}.to_a[0..top_n-1].flatten]
47
+ end
46
48
  end
47
49
 
48
50
  def calculate_statistics(email, merge)
49
51
  # Identify authors and author type
50
- if email
51
- type = :author_email
52
- else
53
- type = :author
54
- end
52
+ type = email ? :author_email : :author
55
53
 
56
54
  # For all the commit files created
57
55
  Dir.entries(path).each do |file|
58
56
  # Load commit file and extract the commits
59
57
  if file =~ /\d+\.json/
60
- load(path + File::Separator + file)
58
+ load(File.join(path, file))
61
59
  process_commits(type, merge)
62
- self.clear
60
+ clear
63
61
  end
64
62
  end
65
63
  end
66
64
 
67
65
  def process_commits(type, merge)
68
66
  # Collect the stats from each commit
69
- self.each do |key,value|
70
- if !merge && value[:merge]
71
- next
72
- else
67
+ each do |key,value|
68
+ next if !merge && value[:merge]
69
+ # If there are no changed files move to next commit
70
+ next if value[:files].empty?
73
71
 
74
- # Acquire author (make if not seen before)
75
- author = @stats[value[type]]
76
-
77
- if author == nil
78
- @stats[value[type]] = Hash.new(0)
79
- author = @stats[value[type]]
80
- author[:languages] = {}
81
- end
72
+ # Acquire author (make if not seen before)
73
+ author = @stats[value[type]]
82
74
 
83
- # If there are no changed files move to next commit
84
- next if value[:files].size == 0
75
+ if author.nil?
76
+ @stats[value[type]] = Hash.new(0)
77
+ author = @stats[value[type]]
78
+ author[:languages] = {}
79
+ end
85
80
 
86
- # Collect language stats
87
- value[:files].each do |file|
81
+ # Collect language stats
82
+ value[:files].each do |file|
88
83
 
89
- # Add to author's languages
90
- add_language_stats(author, file)
84
+ # Add to author's languages
85
+ add_language_stats(author, file)
91
86
 
92
- # Add to repository's languages
93
- add_language_stats(@totals, file)
94
- end
87
+ # Add to repository's languages
88
+ add_language_stats(@totals, file)
89
+ end
95
90
 
96
- # Add commit stats to author
97
- add_commit_stats(author, value)
91
+ # Add commit stats to author
92
+ add_commit_stats(author, value)
98
93
 
99
- # Add commit stats to repository
100
- add_commit_stats(@totals, value)
94
+ # Add commit stats to repository
95
+ add_commit_stats(@totals, value)
101
96
 
102
- # Save new changes back to stats
103
- @stats[value[type]] = author
104
- author = nil
105
- end
97
+ # Save new changes back to stats
98
+ @stats[value[type]] = author
99
+ author = nil
106
100
  end
107
101
  end
108
102
 
109
103
  def add_language_stats(data, file)
110
104
  # Add stats to data's languages
111
- if data[:languages][file[:language].to_sym] == nil
105
+ if data[:languages][file[:language].to_sym].nil?
112
106
  data[:languages][file[:language].to_sym] = Hash.new(0)
113
107
  end
114
108
 
@@ -136,21 +130,19 @@ module GitStatistics
136
130
  end
137
131
 
138
132
  def load(file)
139
- self.merge!(JSON.parse(File.read(file), :symbolize_names => true))
133
+ merge!(JSON.parse(File.read(file), :symbolize_names => true))
140
134
  end
141
135
 
142
136
  def save(file, pretty)
143
137
  # Don't save if there is no information (i.e., using updates)
144
- return if self.size == 0
145
-
146
- # Ensure the path to the file exists
147
- FileUtils.mkdir_p(File.dirname(file))
148
-
149
- # Save file in a simple or pretty format
150
- if pretty
151
- File.open(file, 'w') {|file| file.write(JSON.pretty_generate(self))}
152
- else
153
- File.open(file, 'w') {|file| file.write(self.to_json)}
138
+ unless empty?
139
+ # Ensure the path to the file exists
140
+ FileUtils.mkdir_p(File.dirname(file))
141
+ # Save file in a simple or pretty format
142
+ File.open(file, 'w') do |file|
143
+ json_content = pretty ? JSON.pretty_generate(self) : self.to_json
144
+ file.write(json_content)
145
+ end
154
146
  end
155
147
  end
156
148
  end
@@ -7,20 +7,18 @@ module GitStatistics
7
7
  @commits = commits
8
8
  end
9
9
 
10
- def prepare_result_summary(sort, email, top_n=0)
10
+ def prepare_result_summary(sort, email, top_n = 0)
11
11
  # Default to a 0 if given a negative number to display
12
12
  top_n = 0 if top_n < 0
13
13
 
14
14
  # Acquire data based on sort type and top # to show
15
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
16
+ raise "Parameter for --sort is not valid" if data.nil?
19
17
 
20
18
  # Create config
21
19
  config = {:data => data,
22
- :author_length => Utilities.find_longest_length(data.keys, 17),
23
- :language_length => Utilities.find_longest_length(@commits.totals[:languages].keys, 8),
20
+ :author_length => Utilities.max_length_in_list(data.keys, 17),
21
+ :language_length => Utilities.max_length_in_list(@commits.totals[:languages].keys, 8),
24
22
  :sort => sort,
25
23
  :email => email,
26
24
  :top_n => top_n}
@@ -31,7 +29,7 @@ module GitStatistics
31
29
  return config
32
30
  end
33
31
 
34
- def print_summary(sort, email, top_n=0)
32
+ def print_summary(sort, email, top_n = 0)
35
33
  # Prepare and determine the config for the result summary based on parameters
36
34
  config = prepare_result_summary(sort, email, top_n)
37
35
 
@@ -1,6 +1,6 @@
1
1
  module GitStatistics
2
2
  module Utilities
3
- def self.get_repository(path=Dir.pwd)
3
+ def self.get_repository(path = Dir.pwd)
4
4
  # Connect to git repository if it exists
5
5
  directory = Pathname.new(path)
6
6
  repo = nil
@@ -14,26 +14,16 @@ module GitStatistics
14
14
  end
15
15
  end
16
16
 
17
- def self.find_longest_length(list, max=nil)
18
- return list if list == nil
17
+ def self.max_length_in_list(list, max = nil)
18
+ return nil if list.nil?
19
19
  list.each do |key,value|
20
- max = key.length if max == nil || key.length > max
20
+ max = key.length if max.nil? || key.length > max
21
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
22
+ max
33
23
  end
34
24
 
35
25
  def self.clean_string(string)
36
- return string.strip.force_encoding("iso-8859-1").encode("utf-8")
26
+ string.strip.force_encoding("iso-8859-1").encode("utf-8")
37
27
  end
38
28
 
39
29
  def self.split_old_new_file(old, new)
@@ -42,13 +32,13 @@ module GitStatistics
42
32
  split_new = new.split('}')
43
33
 
44
34
  # Handle recombine the file splits into their whole paths)
45
- if split_old.size == 1 && split_new.size == 1
35
+ if split_old.one? && split_new.one?
46
36
  old_file = split_old[0]
47
37
  new_file = split_new[0]
48
- elsif split_new.size == 1
38
+ elsif split_new.one?
49
39
  old_file = split_old[0] + split_old[1]
50
40
  new_file = split_old[0] + split_new[0]
51
- elsif split_old.size == 1
41
+ elsif split_old.one?
52
42
  old_file = split_old[0] + split_new[1]
53
43
  new_file = split_old[0] + split_new[0] + split_new[1]
54
44
  else
@@ -63,20 +53,18 @@ module GitStatistics
63
53
 
64
54
  def self.find_blob_in_tree(tree, file)
65
55
  # 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
56
+ if tree.nil? || file.nil?
69
57
  return nil
70
58
  elsif tree.instance_of?(Grit::Submodule)
71
59
  return tree
72
60
  end
73
61
 
74
62
  # If the blob is within the current directory (tree)
75
- if file.size == 1
63
+ if file.one?
76
64
  blob = tree / file.first
77
65
 
78
66
  # Check if blob is nil (could not find changed file in tree)
79
- if blob == nil
67
+ if blob.nil?
80
68
 
81
69
  # Try looking for submodules as they cannot be found using tree / file notation
82
70
  tree.contents.each do |content|
@@ -96,24 +84,24 @@ module GitStatistics
96
84
  end
97
85
 
98
86
  def self.get_modified_time(file)
99
- if OS.mac?
100
- Time.at(`stat -f %m #{file}`.to_i)
101
- elsif OS.linux?
102
- Time.at(`stat -c %Y #{file}`.to_i)
103
- else
104
- raise "Update on the Windows operating system is not supported"
105
- end
87
+ command = case
88
+ when OS.mac? then "stat -f %m #{file}"
89
+ when OS.linux? then "stat -c %Y #{file}"
90
+ else raise "Update on the Windows operating system is not supported"; end
91
+ time_at(command)
106
92
  end
107
93
 
108
- def self.get_number_of_files(directory, pattern)
109
- count = 0
110
- files = Dir.entries(directory)
111
-
112
- files.each do |file|
113
- count += 1 if file =~ pattern
114
- end
94
+ def self.time_at(cmd)
95
+ Time.at(%x{#{cmd}}.to_i)
96
+ end
115
97
 
116
- return count
98
+ def self.number_of_matching_files(directory, pattern)
99
+ Dir.entries(directory)
100
+ .select { |file| file =~ pattern }
101
+ .size
102
+ rescue SystemCallError
103
+ ::Kernel.warn "No such directory #{File.expand_path(directory)}"
104
+ 0
117
105
  end
118
106
  end
119
107
  end
@@ -1,3 +1,3 @@
1
1
  module GitStatistics
2
- VERSION = "0.4.1"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -10,11 +10,9 @@ describe Collector do
10
10
 
11
11
  # Create buffer which is an array of cleaned lines
12
12
  let(:buffer) {
13
- buffer = []
14
- fixture(fixture_file).readlines.each do |line|
15
- buffer << Utilities.clean_string(line)
13
+ fixture(fixture_file).readlines.collect do |line|
14
+ Utilities.clean_string(line)
16
15
  end
17
- buffer
18
16
  }
19
17
 
20
18
  describe "#collect" do
@@ -102,69 +100,6 @@ describe Collector do
102
100
  end
103
101
  end
104
102
 
105
- describe "#extract_change_file" do
106
- let(:file) {collector.extract_change_file(line)}
107
-
108
- context "with a simple changed file" do
109
- let(:line) {"37 30 lib/file.rb"}
110
- it {file[:additions].should == 37}
111
- it {file[:deletions].should == 30}
112
- it {file[:file].should == "lib/file.rb"}
113
- end
114
-
115
- context "with a simple rename/copy changed file" do
116
- let(:line) {"11 3 old_file.rb => lib/file.rb"}
117
- it {file[:additions].should == 11}
118
- it {file[:deletions].should == 3}
119
- it {file[:file].should == "lib/file.rb"}
120
- it {file[:old_file].should == "old_file.rb"}
121
- end
122
-
123
- context "with a complex rename/copy changed file" do
124
- let(:line) {"- - lib/{old_dir => new_dir}/file.rb"}
125
- it {file[:additions].should == 0}
126
- it {file[:deletions].should == 0}
127
- it {file[:file].should == "lib/new_dir/file.rb"}
128
- it {file[:old_file].should == "lib/old_dir/file.rb"}
129
- end
130
- end
131
-
132
- describe "#extract_create_delete_file" do
133
- let(:file) {collector.extract_create_delete_file(line)}
134
-
135
- context "with a create changed file" do
136
- let(:line) {"create mode 100644 lib/dir/file.rb"}
137
- it {file[:status].should == "create"}
138
- it {file[:file].should == "lib/dir/file.rb"}
139
- end
140
-
141
- context "with a delete changed file" do
142
- let(:line) {"delete mode 100644 lib/file.rb"}
143
- it {file[:status].should == "delete"}
144
- it {file[:file].should == "lib/file.rb"}
145
- end
146
- end
147
-
148
- describe "#extract_rename_copy_file" do
149
- let(:file) {collector.extract_rename_copy_file(line)}
150
-
151
- context "with a rename changed file" do
152
- let(:line) {"rename lib/{old_dir => new_dir}/file.rb (100%)"}
153
- it {file[:status].should == "rename"}
154
- it {file[:old_file].should == "lib/old_dir/file.rb"}
155
- it {file[:new_file].should == "lib/new_dir/file.rb"}
156
- it {file[:similar].should == 100}
157
- end
158
-
159
- context "with a copy changed file" do
160
- let(:line) {"copy lib/dir/{old_file.rb => new_file.rb} (75%)"}
161
- it {file[:status].should == "copy"}
162
- it {file[:old_file].should == "lib/dir/old_file.rb"}
163
- it {file[:new_file].should == "lib/dir/new_file.rb"}
164
- it {file[:similar].should == 75}
165
- end
166
- end
167
-
168
103
  describe "#acquire_commit_data" do
169
104
  let(:input) {fixture(fixture_file).read}
170
105
  let(:data) {collector.acquire_commit_data(input)}
@@ -204,7 +139,7 @@ describe Collector do
204
139
  context "with no changes" do
205
140
  let(:buffer) {[]}
206
141
  it {files.size.should == 0}
207
- it {files[0].should == nil}
142
+ it {files[0].should.nil?}
208
143
  end
209
144
 
210
145
  context "with all types (create,delete,rename,copy) of files" do
@@ -256,7 +191,7 @@ describe Collector do
256
191
  it {data[:files][0][:name].should == "Gemfile"}
257
192
  it {data[:files][0][:additions].should == 0}
258
193
  it {data[:files][0][:deletions].should == 1}
259
- it {data[:files][0][:status].should == nil}
194
+ it {data[:files][0][:status].should.nil?}
260
195
  it {data[:files][0][:binary].should == false}
261
196
  it {data[:files][0][:image].should == false}
262
197
  it {data[:files][0][:vendored].should == false}
@@ -276,7 +211,7 @@ describe Collector do
276
211
  it {data[:files][2][:name].should == "lib/git_statistics/initialize.rb"}
277
212
  it {data[:files][2][:additions].should == 0}
278
213
  it {data[:files][2][:deletions].should == 1}
279
- it {data[:files][2][:status].should == nil}
214
+ it {data[:files][2][:status].should.nil?}
280
215
  it {data[:files][2][:binary].should == false}
281
216
  it {data[:files][2][:image].should == false}
282
217
  it {data[:files][2][:vendored].should == false}
@@ -286,12 +221,12 @@ describe Collector do
286
221
 
287
222
  context "with buffer that has no file changes" do
288
223
  let(:fixture_file) {"commit_buffer_information.txt"}
289
- it {data.should == nil}
224
+ it {data.should.nil?}
290
225
  end
291
226
 
292
227
  context "with invalid buffer" do
293
228
  let(:buffer) {"invalid input"}
294
- it {data.should == nil}
229
+ it {data.should.nil?}
295
230
  end
296
231
  end
297
232
 
@@ -305,7 +240,7 @@ describe Collector do
305
240
 
306
241
  context "with invalid sha" do
307
242
  let(:sha) {"111111aa111a11111a11aa11aaaa11a111111a11"}
308
- it {results.should == nil}
243
+ it {results.should.nil?}
309
244
  end
310
245
  end
311
246
 
@@ -321,7 +256,7 @@ describe Collector do
321
256
 
322
257
  context "with invalid blob" do
323
258
  let(:file) {{:file => "dir/nothing.rb"}}
324
- it {blob.should == nil}
259
+ it {blob.should.nil?}
325
260
  end
326
261
 
327
262
  context "with deleted file" do
@@ -373,7 +308,7 @@ describe Collector do
373
308
  it {data_file[:name].should == file[:file]}
374
309
  it {data_file[:additions].should == file[:additions]}
375
310
  it {data_file[:deletions].should == file[:deletions]}
376
- it {data_file[:status].should == nil}
311
+ it {data_file[:status].should.nil?}
377
312
  it {data_file[:binary].should == false}
378
313
  it {data_file[:image].should == false}
379
314
  it {data_file[:vendored].should == false}
@@ -393,7 +328,7 @@ describe Collector do
393
328
  it {data_file[:name].should == file[:file]}
394
329
  it {data_file[:additions].should == file[:additions]}
395
330
  it {data_file[:deletions].should == file[:deletions]}
396
- it {data_file[:status].should == nil}
331
+ it {data_file[:status].should.nil?}
397
332
  it {data_file[:binary].should == false}
398
333
  it {data_file[:image].should == false}
399
334
  it {data_file[:vendored].should == false}
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+ include GitStatistics
3
+
4
+ describe CommitLineExtractor do
5
+
6
+ let(:extractor) { CommitLineExtractor.new(line) }
7
+
8
+ describe "#extract_change_file" do
9
+ let(:file) { extractor.changed }
10
+
11
+ context "with a simple changed file" do
12
+ let(:line) {"37 30 lib/file.rb"}
13
+ it {file[:additions].should == 37}
14
+ it {file[:deletions].should == 30}
15
+ it {file[:file].should == "lib/file.rb"}
16
+ end
17
+
18
+ context "with a simple rename/copy changed file" do
19
+ let(:line) {"11 3 old_file.rb => lib/file.rb"}
20
+ it {file[:additions].should == 11}
21
+ it {file[:deletions].should == 3}
22
+ it {file[:file].should == "lib/file.rb"}
23
+ it {file[:old_file].should == "old_file.rb"}
24
+ end
25
+
26
+ context "with a complex rename/copy changed file" do
27
+ let(:line) {"- - lib/{old_dir => new_dir}/file.rb"}
28
+ it {file[:additions].should == 0}
29
+ it {file[:deletions].should == 0}
30
+ it {file[:file].should == "lib/new_dir/file.rb"}
31
+ it {file[:old_file].should == "lib/old_dir/file.rb"}
32
+ end
33
+ end
34
+
35
+ describe "#extract_create_delete_file" do
36
+ let(:file) { extractor.created_or_deleted }
37
+
38
+ context "with a create changed file" do
39
+ let(:line) {"create mode 100644 lib/dir/file.rb"}
40
+ it {file[:status].should == "create"}
41
+ it {file[:file].should == "lib/dir/file.rb"}
42
+ end
43
+
44
+ context "with a delete changed file" do
45
+ let(:line) {"delete mode 100644 lib/file.rb"}
46
+ it {file[:status].should == "delete"}
47
+ it {file[:file].should == "lib/file.rb"}
48
+ end
49
+ end
50
+
51
+ describe "#extract_rename_copy_file" do
52
+ let(:file) { extractor.renamed_or_copied }
53
+
54
+ context "with a rename changed file" do
55
+ let(:line) {"rename lib/{old_dir => new_dir}/file.rb (100%)"}
56
+ it {file[:status].should == "rename"}
57
+ it {file[:old_file].should == "lib/old_dir/file.rb"}
58
+ it {file[:new_file].should == "lib/new_dir/file.rb"}
59
+ it {file[:similar].should == 100}
60
+ end
61
+
62
+ context "with a copy changed file" do
63
+ let(:line) {"copy lib/dir/{old_file.rb => new_file.rb} (75%)"}
64
+ it {file[:status].should == "copy"}
65
+ it {file[:old_file].should == "lib/dir/old_file.rb"}
66
+ it {file[:new_file].should == "lib/dir/new_file.rb"}
67
+ it {file[:similar].should == 75}
68
+ end
69
+ end
70
+ end
data/spec/commits_spec.rb CHANGED
@@ -170,12 +170,12 @@ describe Commits do
170
170
 
171
171
  context "with invalid type" do
172
172
  let(:sort) {:wrong}
173
- it {stats.should == nil}
173
+ it {stats.should.nil?}
174
174
  end
175
175
 
176
176
  context "with invalid data" do
177
177
  let(:fixture_file) {nil}
178
- it {stats.should == nil}
178
+ it {stats.should.nil?}
179
179
  end
180
180
  end
181
181
 
data/spec/spec_helper.rb CHANGED
@@ -11,7 +11,7 @@ def fixture(file)
11
11
  end
12
12
 
13
13
  def setup_commits(commits, file_load, file_save, pretty)
14
- return if file_load == nil || file_save == nil
14
+ return if file_load.nil? || file_save.nil?
15
15
  commits.load(fixture(file_load))
16
16
  commits.save(file_save, pretty)
17
17
  end
@@ -18,22 +18,22 @@ describe Utilities do
18
18
 
19
19
  context "when not in a repository directory" do
20
20
  let(:dir) {Dir.pwd + "../"} # git_statistics/../
21
- it {repo.should == nil}
21
+ it {repo.should.nil?}
22
22
  end
23
23
  end
24
24
 
25
- describe "#find_longest_length" do
25
+ describe "#max_length_in_list" do
26
26
  let(:max) {nil}
27
27
  let(:list) {[]}
28
- let(:results) {Utilities.find_longest_length(list, max)}
28
+ let(:results) {Utilities.max_length_in_list(list, max)}
29
29
 
30
30
  context "with empty list" do
31
- it {results.should == nil}
31
+ it {results.should.nil?}
32
32
  end
33
33
 
34
34
  context "with nil list" do
35
35
  let(:list) {nil}
36
- it {results.should == nil}
36
+ it {results.should.nil?}
37
37
  end
38
38
 
39
39
  context "with preset minimum length" do
@@ -52,35 +52,6 @@ describe Utilities do
52
52
  end
53
53
  end
54
54
 
55
- describe "#unique_data_in_hash" do
56
- let(:type) {:word}
57
- let(:list) {Utilities.unique_data_in_hash(data, type)}
58
-
59
- context "with valid type" do
60
- let(:data) {
61
- {:entry_a => {type => "test"},
62
- :entry_b => {type => "a"},
63
- :entry_c => {type => "a"},
64
- :entry_d => {type => "is"},
65
- :entry_e => {type => "test"}}
66
- }
67
-
68
- it {list.size.should == 3}
69
- it {list.include?("is").should be_true}
70
- it {list.include?("a").should be_true}
71
- it {list.include?("test").should be_true}
72
- end
73
-
74
- context "with invalid type" do
75
- let(:data) {
76
- {:entry_a => {:wrong => "test"},
77
- :entry_b => {:wrong => "is"}}
78
- }
79
-
80
- it {list.should == [nil]}
81
- end
82
- end
83
-
84
55
  describe "#clean_string" do
85
56
  let(:unclean) {" master "}
86
57
  let(:clean) {Utilities.clean_string(unclean)}
@@ -90,6 +61,23 @@ describe Utilities do
90
61
  end
91
62
  end
92
63
 
64
+ describe "#get_modified_time" do
65
+ let(:file) { 'file' }
66
+ before do
67
+ OS.stub(:mac?) { false }
68
+ OS.stub(:linux?) { false }
69
+ end
70
+ after { Utilities.get_modified_time(file) }
71
+ context "on a Mac" do
72
+ before { OS.stub(:mac?) { true } }
73
+ it { Utilities.should_receive(:time_at).with(%[stat -f %m file]) { 10 } }
74
+ end
75
+ context "on a Linux" do
76
+ before { OS.stub(:linux?) { true } }
77
+ it { Utilities.should_receive(:time_at).with(%[stat -c %Y file]) { 10 } }
78
+ end
79
+ end
80
+
93
81
  describe "#split_old_new_file" do
94
82
  let(:files) {Utilities.split_old_new_file(old, new)}
95
83
  context "with a change in middle" do
@@ -147,12 +135,12 @@ describe Utilities do
147
135
 
148
136
  context "file is nil" do
149
137
  let(:blob) {Utilities.find_blob_in_tree(tree, nil)}
150
- it {blob.should == nil}
138
+ it {blob.should.nil?}
151
139
  end
152
140
 
153
141
  context "file is empty" do
154
142
  let(:file) {""}
155
- it {blob.should == nil}
143
+ it {blob.should.nil?}
156
144
  end
157
145
 
158
146
  context "file is submodule" do
@@ -163,8 +151,8 @@ describe Utilities do
163
151
  end
164
152
  end
165
153
 
166
- describe "#get_number_of_files" do
167
- let(:files) {Utilities.get_number_of_files(directory, pattern)}
154
+ describe "#number_of_matching_files" do
155
+ let(:files) {Utilities.number_of_matching_files(directory, pattern)}
168
156
  let(:pattern) {/\d+\.json/}
169
157
  let(:directory) {Dir.pwd + File::Separator + "tmp_dir_for_spec" + File::Separator}
170
158
 
@@ -172,6 +160,12 @@ describe Utilities do
172
160
  FileUtils.mkdir_p(directory)
173
161
  end
174
162
 
163
+ context "with missing directory" do
164
+ subject { files }
165
+ before { FileUtils.rmdir(directory) }
166
+ it { should == 0 }
167
+ end
168
+
175
169
  after(:each) do
176
170
  FileUtils.rmdir(directory)
177
171
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git_statistics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-09 00:00:00.000000000 Z
12
+ date: 2013-01-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
@@ -113,12 +113,14 @@ files:
113
113
  - lib/git_statistics.rb
114
114
  - lib/git_statistics/blob.rb
115
115
  - lib/git_statistics/collector.rb
116
+ - lib/git_statistics/commit_line_extractor.rb
116
117
  - lib/git_statistics/commits.rb
117
118
  - lib/git_statistics/initialize.rb
118
119
  - lib/git_statistics/results.rb
119
120
  - lib/git_statistics/utilities.rb
120
121
  - lib/git_statistics/version.rb
121
122
  - spec/collector_spec.rb
123
+ - spec/commit_line_extractor_spec.rb
122
124
  - spec/commits_spec.rb
123
125
  - spec/fixtures/commit_buffer_changes.txt
124
126
  - spec/fixtures/commit_buffer_information.txt
@@ -161,6 +163,7 @@ specification_version: 3
161
163
  summary: Gem that provides the ability to gather detailed git statistics
162
164
  test_files:
163
165
  - spec/collector_spec.rb
166
+ - spec/commit_line_extractor_spec.rb
164
167
  - spec/commits_spec.rb
165
168
  - spec/fixtures/commit_buffer_changes.txt
166
169
  - spec/fixtures/commit_buffer_information.txt