git_statistics 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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