metior 0.1.4 → 0.2.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/.travis.yml +2 -0
- data/.yardopts +1 -0
- data/Changelog.md +23 -0
- data/Gemfile +1 -17
- data/Gemfile.lock +28 -21
- data/README.md +66 -20
- data/lib/metior.rb +30 -14
- data/lib/metior/actor.rb +28 -22
- data/lib/metior/auto_include_vcs.rb +43 -0
- data/lib/metior/collections/actor_collection.rb +97 -0
- data/lib/metior/collections/collection.rb +84 -0
- data/lib/metior/collections/commit_collection.rb +309 -0
- data/lib/metior/commit.rb +128 -48
- data/lib/metior/errors.rb +2 -2
- data/lib/metior/git/commit.rb +32 -31
- data/lib/metior/git/repository.rb +71 -6
- data/lib/metior/github/commit.rb +5 -16
- data/lib/metior/github/repository.rb +68 -40
- data/lib/metior/report.rb +139 -0
- data/lib/metior/report/view.rb +120 -0
- data/lib/metior/report/view_helper.rb +47 -0
- data/lib/metior/repository.rb +225 -56
- data/lib/metior/vcs.rb +12 -3
- data/lib/metior/version.rb +1 -1
- data/metior.gemspec +28 -26
- data/reports/default.rb +17 -0
- data/reports/default/images/favicon.png +0 -0
- data/reports/default/stylesheets/default.css +128 -0
- data/reports/default/templates/actor/minimal.mustache +1 -0
- data/reports/default/templates/commit/minimal.mustache +1 -0
- data/reports/default/templates/index.mustache +27 -0
- data/reports/default/templates/most_significant_authors.mustache +11 -0
- data/reports/default/templates/most_significant_commits.mustache +13 -0
- data/reports/default/templates/repository_information.mustache +17 -0
- data/reports/default/templates/top_committers.mustache +11 -0
- data/reports/default/views/index.rb +33 -0
- data/reports/default/views/most_significant_authors.rb +19 -0
- data/reports/default/views/most_significant_commits.rb +19 -0
- data/reports/default/views/repository_information.rb +47 -0
- data/reports/default/views/top_committers.rb +21 -0
- data/test/fixtures.rb +54 -36
- data/test/helper.rb +10 -3
- data/test/{test_class_loading.rb → test_1st_class_loading.rb} +1 -1
- data/test/test_actor_colletion.rb +78 -0
- data/test/test_collection.rb +61 -0
- data/test/test_commit_collection.rb +139 -0
- data/test/test_git.rb +58 -5
- data/test/test_github.rb +52 -9
- data/test/test_metior.rb +22 -1
- data/test/test_report.rb +49 -0
- data/test/test_repository.rb +46 -9
- data/test/test_vcs.rb +36 -13
- metadata +105 -43
@@ -0,0 +1,47 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2011, Sebastian Staudt
|
5
|
+
|
6
|
+
class Metior::Report
|
7
|
+
|
8
|
+
# This helper module implements generic functionality that is included in all
|
9
|
+
# report views
|
10
|
+
#
|
11
|
+
# @author Sebastian Staudt
|
12
|
+
module ViewHelper
|
13
|
+
|
14
|
+
# Increases the counter by one and returns it
|
15
|
+
#
|
16
|
+
# This can, for example, be used to count the values in an array that is
|
17
|
+
# iterated in a view.
|
18
|
+
#
|
19
|
+
# @return [Fixnum] The current counter value
|
20
|
+
# @see #reset_count
|
21
|
+
def count
|
22
|
+
@count ||= 0
|
23
|
+
@count += 1
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns whether the current counter value is even or odd
|
27
|
+
#
|
28
|
+
# This is specifically useful for e.g. generating alternating colors of
|
29
|
+
# table rows in the output of the view.
|
30
|
+
#
|
31
|
+
# @return ['even', 'odd'] `'even'` if the current counter value is even,
|
32
|
+
# `'odd'` otherwise.
|
33
|
+
# @see #reset_count
|
34
|
+
def even_odd
|
35
|
+
(count % 2 == 0) ? 'even' : 'odd'
|
36
|
+
end
|
37
|
+
|
38
|
+
# Resets the current counter to 0
|
39
|
+
#
|
40
|
+
# @see #count
|
41
|
+
def reset_count
|
42
|
+
@count = 0
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/lib/metior/repository.rb
CHANGED
@@ -3,7 +3,9 @@
|
|
3
3
|
#
|
4
4
|
# Copyright (c) 2011, Sebastian Staudt
|
5
5
|
|
6
|
-
require 'metior/
|
6
|
+
require 'metior/auto_include_vcs'
|
7
|
+
require 'metior/collections/actor_collection'
|
8
|
+
require 'metior/collections/commit_collection'
|
7
9
|
|
8
10
|
module Metior
|
9
11
|
|
@@ -14,6 +16,8 @@ module Metior
|
|
14
16
|
# @author Sebastian Staudt
|
15
17
|
class Repository
|
16
18
|
|
19
|
+
include AutoIncludeVCS
|
20
|
+
|
17
21
|
# @return [String] The file system path of this repository
|
18
22
|
attr_reader :path
|
19
23
|
|
@@ -21,10 +25,26 @@ module Metior
|
|
21
25
|
#
|
22
26
|
# @param [String] path The file system path of the repository
|
23
27
|
def initialize(path)
|
24
|
-
@
|
25
|
-
@commits
|
26
|
-
@
|
27
|
-
@
|
28
|
+
@actors = {}
|
29
|
+
@commits = {}
|
30
|
+
@description = nil
|
31
|
+
@name = nil
|
32
|
+
@path = path
|
33
|
+
@refs = {}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns a single VCS specific actor object from the raw data of the actor
|
37
|
+
# provided by the VCS implementation
|
38
|
+
#
|
39
|
+
# The actor object is either created from the given raw data or retrieved
|
40
|
+
# from the cache using the VCS specific unique identifier of the actor.
|
41
|
+
#
|
42
|
+
# @param [Object] actor The raw data of the actor provided by the VCS
|
43
|
+
# @return [Actor] A object representing the actor
|
44
|
+
# @see Actor.id_for
|
45
|
+
def actor(actor)
|
46
|
+
id = self.class::Actor.id_for(actor)
|
47
|
+
@actors[id] ||= self.class::Actor.new(self, actor)
|
28
48
|
end
|
29
49
|
|
30
50
|
# Returns all authors from the given commit range in a hash where the IDs
|
@@ -38,15 +58,20 @@ module Metior
|
|
38
58
|
# (`'master..development'`), a range (`'master'..'development'`) or
|
39
59
|
# as a single ref (`'master'`). A single ref name means all commits
|
40
60
|
# reachable from that ref.
|
41
|
-
# @return [
|
61
|
+
# @return [ActorCollection] All authors from the given commit range
|
42
62
|
# @see #commits
|
43
63
|
def authors(range = self.class::DEFAULT_BRANCH)
|
44
|
-
range
|
45
|
-
commits(range) if @authors[range].nil?
|
46
|
-
@authors[range]
|
64
|
+
commits(range).authors
|
47
65
|
end
|
48
66
|
alias_method :contributors, :authors
|
49
67
|
|
68
|
+
# Returns the names of all branches of this repository
|
69
|
+
#
|
70
|
+
# @return [Array<String>] The names of all branches
|
71
|
+
def branches
|
72
|
+
load_branches.each { |name, id| @refs[name] = id }.keys.sort
|
73
|
+
end
|
74
|
+
|
50
75
|
# Loads all commits including their committers and authors from the given
|
51
76
|
# commit range
|
52
77
|
#
|
@@ -55,22 +80,38 @@ module Metior
|
|
55
80
|
# (`'master..development'`), a range (`'master'..'development'`) or
|
56
81
|
# as a single ref (`'master'`). A single ref name means all commits
|
57
82
|
# reachable from that ref.
|
58
|
-
# @return [
|
83
|
+
# @return [CommitCollection] All commits from the given commit range
|
59
84
|
def commits(range = self.class::DEFAULT_BRANCH)
|
60
85
|
range = parse_range range
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
@
|
86
|
+
commits = cached_commits range
|
87
|
+
|
88
|
+
if commits.empty?
|
89
|
+
base_commit, raw_commits = load_commits(range)
|
90
|
+
commits = commits + build_commits(raw_commits)
|
91
|
+
unless base_commit.nil?
|
92
|
+
base_commit = self.class::Commit.new(self, base_commit)
|
93
|
+
base_commit.add_child commits.last.id
|
94
|
+
@commits[base_commit.id] = base_commit
|
95
|
+
end
|
96
|
+
else
|
97
|
+
if range.first == ''
|
98
|
+
unless commits.last.parents.empty?
|
99
|
+
raw_commits = load_commits(''..commits.last.id).last
|
100
|
+
commits += build_commits raw_commits[0..-2]
|
101
|
+
end
|
102
|
+
else
|
103
|
+
if commits.first.id != range.last
|
104
|
+
raw_commits = load_commits(commits.first.id..range.last).last
|
105
|
+
commits = build_commits(raw_commits) + commits
|
106
|
+
end
|
107
|
+
unless commits.last.parents.include? range.first
|
108
|
+
raw_commits = load_commits(range.first..commits.last.id).last
|
109
|
+
commits += build_commits raw_commits
|
110
|
+
end
|
70
111
|
end
|
71
112
|
end
|
72
113
|
|
73
|
-
|
114
|
+
CommitCollection.new commits
|
74
115
|
end
|
75
116
|
|
76
117
|
# Returns all committers from the given commit range in a hash where the
|
@@ -84,15 +125,25 @@ module Metior
|
|
84
125
|
# (`'master..development'`), a range (`'master'..'development'`) or
|
85
126
|
# as a single ref (`'master'`). A single ref name means all commits
|
86
127
|
# reachable from that ref.
|
87
|
-
# @return [
|
128
|
+
# @return [ActorCollection] All committers from the given commit range
|
88
129
|
# @see #commits
|
89
130
|
def committers(range = self.class::DEFAULT_BRANCH)
|
90
|
-
range
|
91
|
-
commits(range) if @committers[range].nil?
|
92
|
-
@committers[range]
|
131
|
+
commits(range).committers
|
93
132
|
end
|
94
133
|
alias_method :collaborators, :committers
|
95
134
|
|
135
|
+
# Returns the description of the project contained in the repository
|
136
|
+
#
|
137
|
+
# This will load the description through a VCS specific mechanism if
|
138
|
+
# required.
|
139
|
+
#
|
140
|
+
# @return [String] The description of the project in the repository
|
141
|
+
# @see #load_description
|
142
|
+
def description
|
143
|
+
load_description if @description.nil?
|
144
|
+
@description
|
145
|
+
end
|
146
|
+
|
96
147
|
# This evaluates basic statistics about the files in a given commit range.
|
97
148
|
#
|
98
149
|
# @example
|
@@ -122,7 +173,7 @@ module Metior
|
|
122
173
|
support! :line_stats
|
123
174
|
|
124
175
|
stats = {}
|
125
|
-
commits(range).
|
176
|
+
commits(range).each_value do |commit|
|
126
177
|
commit.added_files.each do |file|
|
127
178
|
stats[file] = { :modifications => 0 } unless stats.key? file
|
128
179
|
stats[file][:added_date] = commit.authored_date
|
@@ -160,18 +211,20 @@ module Metior
|
|
160
211
|
# @return [Hash<Symbol, Array>] Added lines are returned in an `Array`
|
161
212
|
# assigned to key `:additions`, deleted lines are assigned to
|
162
213
|
# `:deletions`
|
163
|
-
# @see
|
164
|
-
# @see Commit#deletions
|
214
|
+
# @see CommitCollection#line_history
|
165
215
|
def line_history(range = self.class::DEFAULT_BRANCH)
|
166
|
-
|
167
|
-
|
168
|
-
history = { :additions => [], :deletions => [] }
|
169
|
-
commits(range).reverse.each do |commit|
|
170
|
-
history[:additions] << commit.additions
|
171
|
-
history[:deletions] << -commit.deletions
|
172
|
-
end
|
216
|
+
commits(range).line_history
|
217
|
+
end
|
173
218
|
|
174
|
-
|
219
|
+
# Returns the name of the project contained in the repository
|
220
|
+
#
|
221
|
+
# This will load the name through a VCS specific mechanism if required.
|
222
|
+
#
|
223
|
+
# @return [String] The name of the project in the repository
|
224
|
+
# @see #load_name
|
225
|
+
def name
|
226
|
+
load_name if @name.nil?
|
227
|
+
@name
|
175
228
|
end
|
176
229
|
|
177
230
|
# Returns a list of authors with the biggest impact on the repository, i.e.
|
@@ -187,11 +240,7 @@ module Metior
|
|
187
240
|
# @return [Array<Actor>] An array of the given number of the most
|
188
241
|
# significant authors in the given commit range
|
189
242
|
def significant_authors(range = self.class::DEFAULT_BRANCH, count = 3)
|
190
|
-
|
191
|
-
|
192
|
-
authors = authors(range).values.sort_by { |author| author.modifications }
|
193
|
-
count = [count, authors.size].min
|
194
|
-
authors[-count..-1].reverse
|
243
|
+
authors(range).most_significant(count)
|
195
244
|
end
|
196
245
|
alias_method :significant_contributors, :significant_authors
|
197
246
|
|
@@ -208,11 +257,14 @@ module Metior
|
|
208
257
|
# @return [Array<Actor>] An array of the given number of the most
|
209
258
|
# significant commits in the given commit range
|
210
259
|
def significant_commits(range = self.class::DEFAULT_BRANCH, count = 10)
|
211
|
-
|
260
|
+
commits(range).most_significant(count)
|
261
|
+
end
|
212
262
|
|
213
|
-
|
214
|
-
|
215
|
-
|
263
|
+
# Returns the names of all tags of this repository
|
264
|
+
#
|
265
|
+
# @return [Array<String>] The names of all tags
|
266
|
+
def tags
|
267
|
+
load_tags.each { |name, id| @refs[name] = id }.keys.sort
|
216
268
|
end
|
217
269
|
|
218
270
|
# Returns a list of top contributors in the given commit range
|
@@ -230,17 +282,109 @@ module Metior
|
|
230
282
|
# in the given commit range
|
231
283
|
# @see #authors
|
232
284
|
def top_authors(range = self.class::DEFAULT_BRANCH, count = 3)
|
233
|
-
authors
|
234
|
-
count = [count, authors.size].min
|
235
|
-
authors[-count..-1].reverse
|
285
|
+
authors(range).top(count)
|
236
286
|
end
|
237
287
|
alias_method :top_contributors, :top_authors
|
238
288
|
|
239
289
|
private
|
240
290
|
|
291
|
+
# Builds VCS specific commit objects for each given commit's raw data that
|
292
|
+
# is provided by the VCS implementation
|
293
|
+
#
|
294
|
+
# The raw data will be transformed into commit objects that will also be
|
295
|
+
# saved into the commit cache. Authors and committers of the given commits
|
296
|
+
# will be created and stored into the cache or loaded from the cache if
|
297
|
+
# they already exist. Additionally this method will establish an
|
298
|
+
# association between the commits and their children.
|
299
|
+
#
|
300
|
+
# @param [Array<Object>] raw_commits The commits' raw data provided by the
|
301
|
+
# VCS implementation
|
302
|
+
# @return [Array<Commit>] The commit objects representing the given commits
|
303
|
+
# @see Commit
|
304
|
+
# @see Commit#add_child
|
305
|
+
def build_commits(raw_commits)
|
306
|
+
child_commit_id = nil
|
307
|
+
raw_commits.map do |commit|
|
308
|
+
commit = self.class::Commit.new(self, commit)
|
309
|
+
commit.add_child child_commit_id unless child_commit_id.nil?
|
310
|
+
child_commit_id = commit.id
|
311
|
+
@commits[commit.id] = commit
|
312
|
+
@actors[commit.author.id] ||= commit.author
|
313
|
+
commit
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Tries to retrieve as many commits as possible in the given commit range
|
318
|
+
# from the commit cache
|
319
|
+
#
|
320
|
+
# This method calls itself recursively to walk the given commit range
|
321
|
+
# either from the start to the end or vice versa depending on which commit
|
322
|
+
# could be found in the cache.
|
323
|
+
#
|
324
|
+
# @param [Range] range The range of commits which should be retrieved from
|
325
|
+
# the cache. This may be given a range of commit IDs
|
326
|
+
# (`'master'..'development'`).
|
327
|
+
# @return [Array<Commit>] A list of commit objects that could be retrieved
|
328
|
+
# from the cache
|
329
|
+
# @see Commit#children
|
330
|
+
def cached_commits(range)
|
331
|
+
commits = []
|
332
|
+
|
333
|
+
direction = nil
|
334
|
+
if @commits.key? range.last
|
335
|
+
current_commits = [@commits[range.last]]
|
336
|
+
direction = :parents
|
337
|
+
elsif @commits.key? range.first
|
338
|
+
current_commits = [@commits[range.first]]
|
339
|
+
direction = :children
|
340
|
+
end
|
341
|
+
|
342
|
+
unless direction.nil?
|
343
|
+
while !current_commits.empty? do
|
344
|
+
new_commits = []
|
345
|
+
current_commits.each do |commit|
|
346
|
+
new_commits += commit.send direction
|
347
|
+
commits << commit if commit.id != range.first
|
348
|
+
if direction == :parents && new_commits.include?(range.first)
|
349
|
+
new_commits = []
|
350
|
+
break
|
351
|
+
end
|
352
|
+
end
|
353
|
+
unless new_commits.include? range.first
|
354
|
+
current_commits = new_commits.uniq.map do |commit|
|
355
|
+
commit = @commits[commit]
|
356
|
+
commits.include?(commit) ? nil : commit
|
357
|
+
end.compact
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
commits.sort_by { |c| c.committed_date }.reverse
|
363
|
+
end
|
364
|
+
|
365
|
+
# Returns the unique identifier for the commit the given reference – like a
|
366
|
+
# branch name – is pointing to
|
367
|
+
#
|
368
|
+
# @abstract Has to be implemented by VCS subclasses
|
369
|
+
# @param [String] ref A symbolic reference name
|
370
|
+
# @return [Object] The unique identifier of the commit the reference is
|
371
|
+
# pointing to
|
372
|
+
def id_for_ref(ref)
|
373
|
+
raise NotImplementedError
|
374
|
+
end
|
375
|
+
|
376
|
+
# Loads all branches and the corresponding commit IDs of this repository
|
377
|
+
#
|
378
|
+
# @abstract Has to be implemented by VCS specific subclasses
|
379
|
+
# @return [Hash<String, Object>] The names of all branches and the
|
380
|
+
# corresponding commit IDs
|
381
|
+
def load_branches
|
382
|
+
raise NotImplementedError
|
383
|
+
end
|
384
|
+
|
241
385
|
# Loads all commits from the given commit range
|
242
386
|
#
|
243
|
-
# @abstract
|
387
|
+
# @abstract Has to be implemented by VCS specific subclasses
|
244
388
|
# @param [String, Range] range The range of commits for which the commits
|
245
389
|
# should be retrieved. This may be given as a string
|
246
390
|
# (`'master..development'`), a range (`'master'..'development'`) or
|
@@ -251,21 +395,46 @@ module Metior
|
|
251
395
|
raise NotImplementedError
|
252
396
|
end
|
253
397
|
|
254
|
-
#
|
398
|
+
# Loads the description of the project contained in the repository
|
399
|
+
#
|
400
|
+
# @abstract Has to be implemented by VCS specific subclasses
|
401
|
+
# @see #description
|
402
|
+
def load_description
|
403
|
+
raise NotImplementedError
|
404
|
+
end
|
405
|
+
|
406
|
+
# Loads the name of the project contained in the repository
|
407
|
+
#
|
408
|
+
# @abstract Has to be implemented by VCS specific subclasses
|
409
|
+
# @see #description
|
410
|
+
def load_name
|
411
|
+
raise NotImplementedError
|
412
|
+
end
|
413
|
+
|
414
|
+
# Loads all tags and the corresponding commit IDs of this repository
|
255
415
|
#
|
256
|
-
#
|
416
|
+
# @abstract Has to be implemented by VCS specific subclasses
|
417
|
+
# @return [Hash<String, Object>] The names of all tags and the
|
418
|
+
# corresponding commit IDs
|
419
|
+
def load_tags
|
420
|
+
raise NotImplementedError
|
421
|
+
end
|
422
|
+
|
423
|
+
# Parses a string or range of commit IDs or ref names into the coresponding
|
424
|
+
# range of unique commit IDs
|
257
425
|
#
|
258
426
|
# @param [String, Range] range The string that should be parsed for a range
|
259
427
|
# or an existing range
|
260
|
-
# @return [Range] The range
|
261
|
-
#
|
428
|
+
# @return [Range] The range of commit IDs parsed from the given parameter
|
429
|
+
# @see #id_for_ref
|
262
430
|
def parse_range(range)
|
263
|
-
|
264
|
-
range
|
265
|
-
else
|
431
|
+
unless range.is_a? Range
|
266
432
|
range = range.to_s.split '..'
|
267
|
-
((range.size == 1) ? '' : range.first)..range.last
|
433
|
+
range = ((range.size == 1) ? '' : range.first)..range.last
|
268
434
|
end
|
435
|
+
|
436
|
+
range = id_for_ref(range.first)..range.last if range.first != ''
|
437
|
+
range.first..id_for_ref(range.last)
|
269
438
|
end
|
270
439
|
|
271
440
|
end
|
data/lib/metior/vcs.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
#
|
4
4
|
# Copyright (c) 2011, Sebastian Staudt
|
5
5
|
|
6
|
-
require 'metior'
|
7
6
|
require 'metior/errors'
|
8
7
|
|
9
8
|
module Metior
|
@@ -41,6 +40,8 @@ module Metior
|
|
41
40
|
|
42
41
|
# This module provided class methods for VCS implementation `Module`s that
|
43
42
|
# implement smart auto-loading of dependencies and classes.
|
43
|
+
#
|
44
|
+
# @author Sebastian Staudt
|
44
45
|
module ClassMethods
|
45
46
|
|
46
47
|
# Missing constants may indicate
|
@@ -128,7 +129,7 @@ module Metior
|
|
128
129
|
# its implementation)
|
129
130
|
# @see #supports?
|
130
131
|
def support!(feature)
|
131
|
-
raise UnsupportedError unless supports? feature
|
132
|
+
raise UnsupportedError.new(vcs) unless supports? feature
|
132
133
|
end
|
133
134
|
|
134
135
|
# Checks if a specific feature is supported by the VCS (or its
|
@@ -137,7 +138,15 @@ module Metior
|
|
137
138
|
# @return [true, false] `true` if the feature is supported
|
138
139
|
# @see ClassMethods#supports?
|
139
140
|
def supports?(feature)
|
140
|
-
|
141
|
+
vcs.supports? feature
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns the VCS module that is included by this object
|
145
|
+
#
|
146
|
+
# @return [Metior::VCS] The VCS implementation module of this object
|
147
|
+
# @see Metior.vcs_types
|
148
|
+
def vcs
|
149
|
+
Metior.vcs_types[singleton_class::NAME.to_sym]
|
141
150
|
end
|
142
151
|
|
143
152
|
end
|