git_fame 1.6.0 → 1.7.1
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rspec +2 -2
- data/README.md +4 -1
- data/bin/git-fame +0 -1
- data/git_fame.gemspec +3 -5
- data/lib/git_fame/author.rb +28 -6
- data/lib/git_fame/base.rb +196 -164
- data/lib/git_fame/file.rb +13 -0
- data/lib/git_fame/result.rb +6 -0
- data/lib/git_fame/version.rb +1 -1
- data/spec/git_fame_spec.rb +13 -32
- data/spec/spec_helper.rb +10 -5
- data/spec/support/startup.rb +7 -0
- metadata +38 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f884e24c83b7a966af59ab96a045ea3ece06b6ff
|
4
|
+
data.tar.gz: fa4e2967c967867b568ace60dc7bd62bf1a191d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a3f5a220b5e3a6211c3d8aad13d881bcb500e1e0bff4ad8b16bf984e0038c3acd1f4d1673ee6577a495d7be8387fe5c24d33b6f6f612010cee8d0e5084781d79
|
7
|
+
data.tar.gz: 53101b66a4a44b3eabf739c43bbabf33664e5ccbcee1225566e2dcf41aeb47217eac3225b945f2870de0549c7e89854c8fca6ddf29c31c82d4fb1846b441dab3
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/README.md
CHANGED
@@ -131,7 +131,10 @@ The list of authors may include duplicate people. If a git user's configured nam
|
|
131
131
|
## Testing
|
132
132
|
|
133
133
|
1. Download fixtures (`spec/fixtures`) using `git submodule update --init`.
|
134
|
-
2. Run rspec using `rspec
|
134
|
+
2. Run rspec using `bundle exec rspec`.
|
135
|
+
|
136
|
+
Note that `puts` has been disabled to avoid unnecessary output during testing.
|
137
|
+
Visit `spec/spec_helper.rb` to enable it again.
|
135
138
|
|
136
139
|
## Requirements
|
137
140
|
|
data/bin/git-fame
CHANGED
@@ -18,7 +18,6 @@ opts = Trollop::options do
|
|
18
18
|
opt :format, "Format (pretty/csv)", default: "pretty", type: String
|
19
19
|
end
|
20
20
|
|
21
|
-
Trollop::die :repository, "is not a git repository" unless GitFame::Base.git_repository?(opts[:repository])
|
22
21
|
Trollop::die :sort, "must be one of the following; #{sort.join(", ")}" unless sort.include?(opts[:sort])
|
23
22
|
fame = GitFame::Base.new({
|
24
23
|
repository: opts[:repository],
|
data/git_fame.gemspec
CHANGED
@@ -28,12 +28,10 @@ Generates data like:
|
|
28
28
|
gem.add_dependency("trollop")
|
29
29
|
gem.add_dependency("hirb")
|
30
30
|
gem.add_dependency("mimer_plus")
|
31
|
+
gem.add_dependency("scrub_rb")
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
gem.add_development_dependency("rspec", "2.10.0")
|
33
|
+
gem.add_development_dependency("rspec", "~> 3.0")
|
34
|
+
gem.add_development_dependency("rspec-collection_matchers")
|
37
35
|
gem.add_development_dependency("rake")
|
38
36
|
gem.add_development_dependency("coveralls")
|
39
37
|
|
data/lib/git_fame/author.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
module GitFame
|
2
2
|
class Author
|
3
3
|
include GitFame::Helper
|
4
|
-
attr_accessor :name, :raw_files, :raw_commits,
|
4
|
+
attr_accessor :name, :raw_files, :raw_commits,
|
5
5
|
:raw_loc, :files_list, :file_type_counts
|
6
6
|
|
7
|
+
FIELDS = [:loc, :commits, :files]
|
8
|
+
|
7
9
|
#
|
8
10
|
# @args Hash
|
9
11
|
#
|
@@ -12,7 +14,7 @@ module GitFame
|
|
12
14
|
@raw_commits = 0
|
13
15
|
@raw_files = 0
|
14
16
|
@file_type_counts = Hash.new(0)
|
15
|
-
args.keys.each do |name|
|
17
|
+
args.keys.each do |name|
|
16
18
|
instance_variable_set "@" + name.to_s, args[name]
|
17
19
|
end
|
18
20
|
end
|
@@ -22,13 +24,15 @@ module GitFame
|
|
22
24
|
# @return String Distribution (in %) between users
|
23
25
|
#
|
24
26
|
def distribution
|
25
|
-
"
|
26
|
-
|
27
|
+
"%s / %s / %s" % FIELDS.map do |field|
|
28
|
+
("%.1f" % (percent_for_field(field) * 100)).rjust(4, " ")
|
29
|
+
end
|
27
30
|
end
|
31
|
+
alias_method :"distribution (%)", :distribution
|
28
32
|
|
29
|
-
|
33
|
+
FIELDS.each do |method|
|
30
34
|
define_method(method) do
|
31
|
-
number_with_delimiter(
|
35
|
+
number_with_delimiter(raw(method))
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
@@ -38,5 +42,23 @@ module GitFame
|
|
38
42
|
def method_missing(m, *args, &block)
|
39
43
|
file_type_counts[m.to_s]
|
40
44
|
end
|
45
|
+
|
46
|
+
def raw(method)
|
47
|
+
unless FIELDS.include?(method.to_sym)
|
48
|
+
raise "can't access raw '#{method}' on author"
|
49
|
+
end
|
50
|
+
|
51
|
+
send("raw_#{method}")
|
52
|
+
end
|
53
|
+
|
54
|
+
def inc(method, amount)
|
55
|
+
send("raw_#{method}=", raw(method) + amount)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def percent_for_field(field)
|
61
|
+
raw(field) / @parent.send(field).to_f
|
62
|
+
end
|
41
63
|
end
|
42
64
|
end
|
data/lib/git_fame/base.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
require "csv"
|
2
2
|
require_relative "./errors"
|
3
|
+
require_relative "./result"
|
4
|
+
require_relative "./file"
|
5
|
+
require "open3"
|
6
|
+
|
3
7
|
if RUBY_VERSION.to_f < 2.1
|
4
8
|
require "scrub_rb"
|
5
9
|
end
|
@@ -7,6 +11,7 @@ end
|
|
7
11
|
module GitFame
|
8
12
|
class Base
|
9
13
|
include GitFame::Helper
|
14
|
+
attr_accessor :file_extensions
|
10
15
|
|
11
16
|
#
|
12
17
|
# @args[:repository] String Absolute path to git repository
|
@@ -17,25 +22,31 @@ module GitFame
|
|
17
22
|
# @args[:branch] String Branch to run from
|
18
23
|
#
|
19
24
|
def initialize(args)
|
20
|
-
@
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
@
|
25
|
-
@exclude = ""
|
26
|
-
@include = ""
|
27
|
-
@authors = {}
|
25
|
+
@default_settings = {
|
26
|
+
branch: "master",
|
27
|
+
sorting: "loc"
|
28
|
+
}
|
29
|
+
@progressbar = args.fetch(:progressbar, false)
|
28
30
|
@file_authors = Hash.new { |h,k| h[k] = {} }
|
29
|
-
|
30
|
-
|
31
|
+
# Create array out of comma separated list
|
32
|
+
@exclude = args.fetch(:exclude, "").split(",").
|
33
|
+
map{ |path| path.strip.sub(/\A\//, "") }
|
34
|
+
@extensions = args.fetch(:extensions, "").split(",")
|
35
|
+
# Default sorting option is by loc
|
36
|
+
@include = args.fetch(:include, "")
|
37
|
+
@sort = args.fetch(:sort, @default_settings.fetch(:sorting))
|
38
|
+
@repository = args.fetch(:repository)
|
39
|
+
@bytype = args.fetch(:bytype, false)
|
40
|
+
@branch = args.fetch(:branch, default_branch)
|
41
|
+
|
42
|
+
# User defined branch must exist
|
43
|
+
if not blank?(@branch) and not default_branch_exists?
|
44
|
+
raise GitFame::BranchNotFound, "Branch '#{@branch}' does not exist"
|
31
45
|
end
|
32
|
-
@exclude = convert_exclude_paths_to_array
|
33
|
-
@extensions = convert_extensions_to_array
|
34
|
-
@branch = (@branch.nil? or @branch.empty?) ? "master" : @branch
|
35
46
|
|
36
47
|
# Fields that should be visible in the final table
|
37
48
|
# Used by #csv_puts, #to_csv and #pretty_puts
|
38
|
-
# Format: [ [:method_on_author, "custom column name"] ]
|
49
|
+
# Format: [ [ :method_on_author, "custom column name" ] ]
|
39
50
|
@visible_fields = [
|
40
51
|
:name,
|
41
52
|
:loc,
|
@@ -43,17 +54,10 @@ module GitFame
|
|
43
54
|
:files,
|
44
55
|
[:distribution, "distribution (%)"]
|
45
56
|
]
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
# @dir Path (relative or absolute) to git repository
|
51
|
-
#
|
52
|
-
def self.git_repository?(dir)
|
53
|
-
return false unless File.directory?(dir)
|
54
|
-
Dir.chdir(dir) do
|
55
|
-
system "git rev-parse --git-dir > /dev/null 2>&1"
|
56
|
-
end
|
57
|
+
@cache = {}
|
58
|
+
@file_extensions = []
|
59
|
+
@wopt = args.fetch(:whitespace, false) ? "-w" : ""
|
60
|
+
@authors = {}
|
57
61
|
end
|
58
62
|
|
59
63
|
#
|
@@ -66,7 +70,7 @@ module GitFame
|
|
66
70
|
puts "Total number of lines: #{number_with_delimiter(loc)}"
|
67
71
|
puts "Total number of commits: #{number_with_delimiter(commits)}\n"
|
68
72
|
|
69
|
-
table(authors, fields:
|
73
|
+
table(authors, fields: printable_fields)
|
70
74
|
end
|
71
75
|
|
72
76
|
#
|
@@ -81,7 +85,7 @@ module GitFame
|
|
81
85
|
#
|
82
86
|
def to_csv
|
83
87
|
CSV.generate do |csv|
|
84
|
-
csv <<
|
88
|
+
csv << fields
|
85
89
|
authors.each do |author|
|
86
90
|
csv << fields.map do |f|
|
87
91
|
author.send(f)
|
@@ -92,95 +96,195 @@ module GitFame
|
|
92
96
|
|
93
97
|
#
|
94
98
|
# @return Fixnum Total number of files
|
99
|
+
# TODO: Rename this
|
95
100
|
#
|
96
101
|
def files
|
97
|
-
|
102
|
+
file_list.count
|
98
103
|
end
|
99
104
|
|
100
105
|
#
|
101
106
|
# @return Array list of repo files processed
|
102
107
|
#
|
103
108
|
def file_list
|
104
|
-
populate
|
109
|
+
populate { current_files }
|
105
110
|
end
|
106
111
|
|
107
112
|
#
|
108
113
|
# @return Fixnum Total number of commits
|
109
114
|
#
|
110
115
|
def commits
|
111
|
-
authors.inject(0){ |result, author| author.
|
116
|
+
authors.inject(0) { |result, author| author.raw(:commits) + result }
|
112
117
|
end
|
113
118
|
|
114
119
|
#
|
115
120
|
# @return Fixnum Total number of lines
|
116
121
|
#
|
117
122
|
def loc
|
118
|
-
|
119
|
-
inject(0){ |result, author| author.raw_loc + result }
|
123
|
+
authors.inject(0) { |result, author| author.raw(:loc) + result }
|
120
124
|
end
|
121
125
|
|
122
126
|
#
|
123
127
|
# @return Array<Author> A list of authors
|
124
128
|
#
|
125
129
|
def authors
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
if @sort == "name"
|
131
|
-
author.send(@sort)
|
132
|
-
else
|
133
|
-
-1 * author.send("raw_#{@sort}")
|
134
|
-
end
|
130
|
+
cache(:authors) do
|
131
|
+
populate do
|
132
|
+
@authors.values.sort_by do |author|
|
133
|
+
@sort == "name" ? author.send(@sort) : -1 * author.raw(@sort)
|
135
134
|
end
|
136
|
-
else
|
137
|
-
authors
|
138
135
|
end
|
139
136
|
end
|
140
137
|
end
|
141
138
|
|
142
|
-
|
143
|
-
|
144
|
-
#
|
145
|
-
|
146
|
-
|
147
|
-
|
139
|
+
private
|
140
|
+
|
141
|
+
# Populates @authors and @file_extensions with data
|
142
|
+
# Block is called on every call to populate, but
|
143
|
+
# the data is only calculated once
|
144
|
+
def populate(&block)
|
145
|
+
cache(:populate) do
|
146
|
+
# Display progressbar with the number of files as countdown
|
147
|
+
progressbar = init_progressbar(current_files.count)
|
148
|
+
|
149
|
+
# Extract the blame history from all checked in files
|
150
|
+
current_files.each do |file|
|
151
|
+
progressbar.inc
|
152
|
+
|
153
|
+
# Skip if mimetype can't be decided
|
154
|
+
next unless type = Mimer.identify(File.join(@repository, file.path))
|
155
|
+
# Binary types isn't very usefull to run git-blame on
|
156
|
+
next if type.binary?
|
157
|
+
|
158
|
+
@file_extensions << file.extname
|
159
|
+
|
160
|
+
execute("git blame #{@wopt} --line-porcelain #{@branch} -- '#{file}'") do |result|
|
161
|
+
# Authors from git blame has to be parsed
|
162
|
+
result.to_s.scan(/^author (.+)$/).each do |raw_author, _|
|
163
|
+
# Create or find already existing user
|
164
|
+
author = fetch(raw_author)
|
165
|
+
|
166
|
+
# Get author by name and increase the number of loc by 1
|
167
|
+
author.inc(:loc, 1)
|
168
|
+
|
169
|
+
# Store the files and authors together
|
170
|
+
@file_authors[raw_author][file] ||= 1
|
171
|
+
|
172
|
+
@bytype && author.file_type_counts[file.extname] += 1
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Get repository summery and update each author accordingly
|
178
|
+
execute("git shortlog #{@branch} -se") do |result|
|
179
|
+
result.to_s.split("\n").map do |line|
|
180
|
+
_, commits, raw_author = line.match(%r{^\s*(\d+)\s+(.+?)\s+<.+?>}).to_a
|
181
|
+
author = fetch(raw_author)
|
182
|
+
# There might be duplicate authors using git shortlog
|
183
|
+
# (same name, different emails). Update already existing authors
|
184
|
+
if author.raw(:commits).zero?
|
185
|
+
update(raw_author, {
|
186
|
+
raw_commits: commits.to_i,
|
187
|
+
raw_files: @file_authors[raw_author].keys.count,
|
188
|
+
files_list: @file_authors[raw_author].keys
|
189
|
+
})
|
190
|
+
else
|
191
|
+
# Calculate the number of files edited by users
|
192
|
+
files = (author.files_list + @file_authors[raw_author].keys).uniq
|
193
|
+
update(raw_author, {
|
194
|
+
raw_commits: commits.to_i + author.raw(:commits),
|
195
|
+
raw_files: files.count,
|
196
|
+
files_list: files
|
197
|
+
})
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
progressbar.finish
|
148
203
|
end
|
149
|
-
end
|
150
204
|
|
151
|
-
|
205
|
+
block.call
|
206
|
+
end
|
152
207
|
|
208
|
+
# Uses the more printable names in @visible_fields
|
153
209
|
def printable_fields
|
154
|
-
|
155
|
-
|
210
|
+
cache(:printable_fields) do
|
211
|
+
raw_fields.map do |field|
|
212
|
+
field.is_a?(Array) ? field.last : field
|
213
|
+
end
|
156
214
|
end
|
157
215
|
end
|
158
216
|
|
217
|
+
# Check to see if a string is empty (nil or "")
|
218
|
+
def blank?(value)
|
219
|
+
value.nil? or value.empty?
|
220
|
+
end
|
221
|
+
|
222
|
+
# Includes fields from file extensions
|
159
223
|
def raw_fields
|
160
224
|
return @visible_fields unless @bytype
|
161
|
-
|
162
|
-
|
163
|
-
|
225
|
+
cache(:raw_fields) do
|
226
|
+
populate do
|
227
|
+
(@visible_fields + file_extensions).uniq
|
228
|
+
end
|
229
|
+
end
|
164
230
|
end
|
165
231
|
|
232
|
+
# Method fields used by #to_csv and #pretty_puts
|
166
233
|
def fields
|
167
|
-
|
168
|
-
|
234
|
+
cache(:fields) do
|
235
|
+
raw_fields.map do |field|
|
236
|
+
field.is_a?(Array) ? field.first : field
|
237
|
+
end
|
169
238
|
end
|
170
239
|
end
|
171
240
|
|
172
|
-
#
|
173
|
-
# @
|
174
|
-
|
175
|
-
|
176
|
-
|
241
|
+
# Command to be executed at @repository
|
242
|
+
# @silent = true wont raise an error on exit code =! 0
|
243
|
+
def execute(command, silent = false, &block)
|
244
|
+
result = Open3.popen2e(command, chdir: @repository) do |_, out, thread|
|
245
|
+
Result.new(out.read, thread.value.success?)
|
246
|
+
end
|
247
|
+
|
248
|
+
return block.call(result) if result.success? or silent
|
249
|
+
raise cmd_error_message(command, result.data)
|
250
|
+
rescue Errno::ENOENT
|
251
|
+
raise cmd_error_message(command, $!.message)
|
177
252
|
end
|
178
253
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
#
|
254
|
+
def cmd_error_message(command, message)
|
255
|
+
"Could not run '#{command}' => #{message}"
|
256
|
+
end
|
257
|
+
|
258
|
+
# Boolean Does the branch exist?
|
259
|
+
def default_branch_exists?
|
260
|
+
branch_exists?(@branch)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Does @branch exist in the current git repo?
|
264
|
+
def branch_exists?(branch)
|
265
|
+
execute("git show-ref '#{branch}'", true) do |result|
|
266
|
+
result.success?
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# In those cases the users havent defined a branch
|
271
|
+
# We try to define it for him/her by
|
272
|
+
# 1. check if { @default_settings.fetch(:branch) } exists
|
273
|
+
# 1. look at .git/HEAD (basically)
|
274
|
+
def default_branch
|
275
|
+
if branch_exists?(@default_settings.fetch(:branch))
|
276
|
+
return @default_settings.fetch(:branch)
|
277
|
+
end
|
278
|
+
|
279
|
+
execute("git rev-parse HEAD") do |result|
|
280
|
+
return result.data if result.success?
|
281
|
+
end
|
282
|
+
|
283
|
+
raise BranchNotFound.new("No branch found")
|
284
|
+
end
|
285
|
+
|
286
|
+
# Tries to create an author, unless it already exists in cache
|
287
|
+
# User is always updated with the passed @args
|
184
288
|
def update(author, args)
|
185
289
|
fetch(author).tap do |found|
|
186
290
|
args.keys.each do |key|
|
@@ -189,115 +293,43 @@ module GitFame
|
|
189
293
|
end
|
190
294
|
end
|
191
295
|
|
192
|
-
#
|
193
|
-
# @return Author
|
194
|
-
# @author String
|
195
|
-
#
|
296
|
+
# Fetches user from cache
|
196
297
|
def fetch(author)
|
197
|
-
@authors[author] ||= Author.new({name: author, parent: self})
|
298
|
+
@authors[author] ||= Author.new({ name: author, parent: self })
|
198
299
|
end
|
199
300
|
|
200
|
-
#
|
201
|
-
# @
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
end
|
208
|
-
|
209
|
-
command = "git ls-tree -r #{@branch} --name-only #{@include}"
|
210
|
-
command += " | grep \"\\.\\(#{@extensions.join("\\|")}\\)$\"" unless @extensions.empty?
|
211
|
-
@files = execute(command).split("\n")
|
212
|
-
@file_extensions = []
|
213
|
-
remove_excluded_files
|
214
|
-
progressbar = SilentProgressbar.new(
|
215
|
-
"Blame",
|
216
|
-
@files.count,
|
217
|
-
@progressbar
|
218
|
-
)
|
219
|
-
blame_opts = @whitespace ? "-w" : ""
|
220
|
-
@files.each do |file|
|
221
|
-
progressbar.inc
|
222
|
-
if @bytype
|
223
|
-
file_extension = File.extname(file).gsub(/^\./, "")
|
224
|
-
file_extension = "unknown" if file_extension.empty?
|
225
|
-
end
|
226
|
-
|
227
|
-
unless type = Mimer.identify(File.join(@repository, file))
|
228
|
-
next
|
229
|
-
end
|
230
|
-
|
231
|
-
if type.binary?
|
232
|
-
next
|
301
|
+
# List all files in current git directory, excluding
|
302
|
+
# extensions in @extensions defined by the user
|
303
|
+
def current_files
|
304
|
+
cache(:current_files) do
|
305
|
+
execute("git ls-tree -r #{@branch} --name-only #{@include}") do |result|
|
306
|
+
files = remove_excluded_files(result.to_s.split("\n")).map do |path|
|
307
|
+
GitFame::FileUnit.new(path)
|
233
308
|
end
|
234
309
|
|
235
|
-
|
236
|
-
@
|
237
|
-
|
238
|
-
output = execute(
|
239
|
-
"git blame #{blame_opts} --line-porcelain #{@branch} -- '#{file}'"
|
240
|
-
)
|
241
|
-
output.scan(/^author (.+)$/).each do |author|
|
242
|
-
fetch(author.first).raw_loc += 1
|
243
|
-
@file_authors[author.first][file] ||= 1
|
244
|
-
if @bytype
|
245
|
-
fetch(author.first).
|
246
|
-
file_type_counts[file_extension] += 1
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
execute("git shortlog #{@branch} -se").split("\n").map do |l|
|
252
|
-
_, commits, u = l.match(%r{^\s*(\d+)\s+(.+?)\s+<.+?>}).to_a
|
253
|
-
user = fetch(u)
|
254
|
-
# Has this user been updated before?
|
255
|
-
if user.raw_commits.zero?
|
256
|
-
update(u, {
|
257
|
-
raw_commits: commits.to_i,
|
258
|
-
raw_files: @file_authors[u].keys.count,
|
259
|
-
files_list: @file_authors[u].keys
|
260
|
-
})
|
261
|
-
else
|
262
|
-
# Calculate the number of files edited by users
|
263
|
-
files = (user.files_list + @file_authors[u].keys).uniq
|
264
|
-
update(u, {
|
265
|
-
raw_commits: commits.to_i + user.raw_commits,
|
266
|
-
raw_files: files.count,
|
267
|
-
files_list: files
|
268
|
-
})
|
269
|
-
end
|
310
|
+
return files if @extensions.empty?
|
311
|
+
files.select { |file| @extensions.include?(file.extname) }
|
270
312
|
end
|
271
|
-
|
272
|
-
progressbar.finish
|
273
|
-
|
274
313
|
end
|
275
|
-
return self
|
276
314
|
end
|
277
315
|
|
278
|
-
#
|
279
|
-
#
|
280
|
-
|
281
|
-
|
282
|
-
@exclude.split(",").map{|path| path.strip.sub(/\A\//, "") }
|
316
|
+
# The block is only called once for every unique key
|
317
|
+
# Used to ensure methods are only called once
|
318
|
+
def cache(key, &block)
|
319
|
+
@cache[key] ||= block.call
|
283
320
|
end
|
284
321
|
|
285
|
-
#
|
286
|
-
#
|
287
|
-
|
288
|
-
|
289
|
-
|
322
|
+
# Removes files excluded by the user
|
323
|
+
# Defined using --exclude
|
324
|
+
def remove_excluded_files(files)
|
325
|
+
return files if @exclude.empty?
|
326
|
+
files.reject do |file|
|
327
|
+
@exclude.any? { |exclude| file.match(exclude) }
|
328
|
+
end
|
290
329
|
end
|
291
330
|
|
292
|
-
|
293
|
-
|
294
|
-
#
|
295
|
-
def remove_excluded_files
|
296
|
-
return if @exclude.empty?
|
297
|
-
@files = @files.map do |path|
|
298
|
-
next if path =~ /\A(#{@exclude.join("|")})/
|
299
|
-
path
|
300
|
-
end.compact
|
331
|
+
def init_progressbar(files_count)
|
332
|
+
SilentProgressbar.new("GitBlame", files_count, @progressbar)
|
301
333
|
end
|
302
334
|
end
|
303
335
|
end
|
data/lib/git_fame/version.rb
CHANGED
data/spec/git_fame_spec.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
describe GitFame::Base do
|
2
|
-
let(:subject) { GitFame::Base.new({repository:
|
2
|
+
let(:subject) { GitFame::Base.new({repository: repository}) }
|
3
3
|
describe "#authors" do
|
4
4
|
it "should have a list of authors" do
|
5
5
|
subject.should have(3).authors
|
6
6
|
end
|
7
7
|
|
8
8
|
describe "author" do
|
9
|
+
require "pp"
|
9
10
|
let(:author) { subject.authors.last }
|
10
11
|
it "should have a bunch of commits" do
|
11
12
|
author.raw_commits.should eq(23)
|
@@ -61,7 +62,7 @@ describe GitFame::Base do
|
|
61
62
|
describe "sort" do
|
62
63
|
it "should be able to sort #authors by name" do
|
63
64
|
authors = GitFame::Base.new({
|
64
|
-
repository:
|
65
|
+
repository: repository,
|
65
66
|
sort: "name"
|
66
67
|
}).authors
|
67
68
|
authors.map(&:name).
|
@@ -70,7 +71,7 @@ describe GitFame::Base do
|
|
70
71
|
|
71
72
|
it "should be able to sort #authors by commits" do
|
72
73
|
authors = GitFame::Base.new({
|
73
|
-
repository:
|
74
|
+
repository: repository,
|
74
75
|
sort: "commits"
|
75
76
|
}).authors
|
76
77
|
authors.map(&:name).
|
@@ -79,7 +80,7 @@ describe GitFame::Base do
|
|
79
80
|
|
80
81
|
it "should be able to sort #authors by files" do
|
81
82
|
authors = GitFame::Base.new({
|
82
|
-
repository:
|
83
|
+
repository: repository,
|
83
84
|
sort: "files"
|
84
85
|
}).authors
|
85
86
|
authors.map(&:name).
|
@@ -90,7 +91,7 @@ describe GitFame::Base do
|
|
90
91
|
describe "#command_line_arguments" do
|
91
92
|
let(:subject) do
|
92
93
|
GitFame::Base.new({
|
93
|
-
repository:
|
94
|
+
repository: repository,
|
94
95
|
exclude: "lib",
|
95
96
|
bytype: true,
|
96
97
|
extensions: "rb,rdoc"
|
@@ -98,11 +99,11 @@ describe GitFame::Base do
|
|
98
99
|
end
|
99
100
|
|
100
101
|
it "should exclude the lib folder" do
|
101
|
-
subject.file_list.include?("lib/gash.rb").should
|
102
|
+
subject.file_list.include?("lib/gash.rb").should be_falsey
|
102
103
|
end
|
103
104
|
|
104
105
|
it "should exclude non rb or rdoc files" do
|
105
|
-
subject.file_list.include?("HISTORY").should
|
106
|
+
subject.file_list.include?("HISTORY").should be_falsey
|
106
107
|
end
|
107
108
|
|
108
109
|
let(:author) { subject.authors.find { |author| author.name == "7rans" } }
|
@@ -131,37 +132,17 @@ describe GitFame::Base do
|
|
131
132
|
end
|
132
133
|
|
133
134
|
it "should be equal to" do
|
134
|
-
subject.to_csv.should eq("name,loc,commits,files,distribution
|
135
|
+
subject.to_csv.should eq("name,loc,commits,files,distribution\n" \
|
135
136
|
"Magnus Holm,586,41,4,54.2 / 58.6 / 25.0\n" \
|
136
|
-
"7rans,360,6,10,33.3 /
|
137
|
+
"7rans,360,6,10,33.3 / 8.6 / 62.5\n" \
|
137
138
|
"Linus Oleander,136,23,7,12.6 / 32.9 / 43.8\n")
|
138
139
|
end
|
139
140
|
end
|
140
141
|
|
141
|
-
describe ".git_repository?" do
|
142
|
-
it "should know if a folder is a git repository [absolute path]" do
|
143
|
-
GitFame::Base.git_repository?(@repository).should eq(true)
|
144
|
-
end
|
145
|
-
|
146
|
-
it "should know if a folder exists or not [absolute path]" do
|
147
|
-
GitFame::Base.git_repository?("/f67c2bcbfcfa30fccb36f72dca22a817").
|
148
|
-
should eq(false)
|
149
|
-
end
|
150
|
-
|
151
|
-
it "should know if a folder is a git repository [relative path]" do
|
152
|
-
GitFame::Base.git_repository?("spec/fixtures/gash").should eq(true)
|
153
|
-
end
|
154
|
-
|
155
|
-
it "should know if a folder exists or not [relative path]" do
|
156
|
-
GitFame::Base.git_repository?("f67c2bcbfcfa30fccb36f72dca22a817").
|
157
|
-
should eq(false)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
142
|
describe "branches" do
|
162
143
|
it "should handle existing branches" do
|
163
144
|
authors = GitFame::Base.new({
|
164
|
-
repository:
|
145
|
+
repository: repository,
|
165
146
|
branch: "0.1.0"
|
166
147
|
}).authors
|
167
148
|
|
@@ -172,8 +153,8 @@ describe GitFame::Base do
|
|
172
153
|
it "should raise an error if branch doesn't exist" do
|
173
154
|
expect {
|
174
155
|
GitFame::Base.new({
|
175
|
-
repository:
|
176
|
-
branch: "
|
156
|
+
repository: repository,
|
157
|
+
branch: "-----"
|
177
158
|
}).authors
|
178
159
|
}.to raise_error(GitFame::BranchNotFound)
|
179
160
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,16 +1,21 @@
|
|
1
1
|
require "rspec"
|
2
2
|
require "git_fame"
|
3
3
|
require "coveralls"
|
4
|
+
require "rspec/collection_matchers"
|
5
|
+
require_relative "./support/startup"
|
4
6
|
|
5
7
|
Coveralls.wear!
|
6
8
|
|
7
9
|
RSpec.configure do |config|
|
10
|
+
config.include GitFame::Startup
|
8
11
|
config.mock_with :rspec
|
9
12
|
config.order = "random"
|
10
|
-
config.
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
13
|
+
config.expect_with(:rspec) { |c| c.syntax = [:should, :expect] }
|
14
|
+
config.fail_fast = false
|
15
|
+
config.before(:all) do
|
16
|
+
Dir.chdir(repository) { `git checkout 7ab01bc5a720` }
|
15
17
|
end
|
18
|
+
|
19
|
+
# Remove this line to allow Kernel#puts
|
20
|
+
config.before { allow($stdout).to receive(:puts) }
|
16
21
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: git_fame
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Linus Oleander
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-05-
|
11
|
+
date: 2016-05-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: progressbar
|
@@ -66,20 +66,48 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: scrub_rb
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: rspec
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
72
86
|
requirements:
|
73
|
-
- -
|
87
|
+
- - "~>"
|
74
88
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
89
|
+
version: '3.0'
|
76
90
|
type: :development
|
77
91
|
prerelease: false
|
78
92
|
version_requirements: !ruby/object:Gem::Requirement
|
79
93
|
requirements:
|
80
|
-
- -
|
94
|
+
- - "~>"
|
81
95
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
96
|
+
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec-collection_matchers
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
83
111
|
- !ruby/object:Gem::Dependency
|
84
112
|
name: rake
|
85
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -130,11 +158,14 @@ files:
|
|
130
158
|
- lib/git_fame/author.rb
|
131
159
|
- lib/git_fame/base.rb
|
132
160
|
- lib/git_fame/errors.rb
|
161
|
+
- lib/git_fame/file.rb
|
133
162
|
- lib/git_fame/helper.rb
|
163
|
+
- lib/git_fame/result.rb
|
134
164
|
- lib/git_fame/silent_progressbar.rb
|
135
165
|
- lib/git_fame/version.rb
|
136
166
|
- spec/git_fame_spec.rb
|
137
167
|
- spec/spec_helper.rb
|
168
|
+
- spec/support/startup.rb
|
138
169
|
homepage: https://github.com/oleander/git-fame-rb
|
139
170
|
licenses: []
|
140
171
|
metadata: {}
|
@@ -163,3 +194,4 @@ summary: 'Generates awesome stats from git-blame A Ruby wrapper for git-blame.
|
|
163
194
|
test_files:
|
164
195
|
- spec/git_fame_spec.rb
|
165
196
|
- spec/spec_helper.rb
|
197
|
+
- spec/support/startup.rb
|