metior 0.1.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.
- data/.gitignore +4 -0
- data/.travis.yml +7 -0
- data/.yardopts +4 -0
- data/Changelog.md +22 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +44 -0
- data/LICENSE +25 -0
- data/README.md +117 -0
- data/Rakefile +48 -0
- data/lib/core_ext/object.rb +25 -0
- data/lib/metior.rb +50 -0
- data/lib/metior/actor.rb +85 -0
- data/lib/metior/commit.rb +118 -0
- data/lib/metior/errors.rb +19 -0
- data/lib/metior/git.rb +25 -0
- data/lib/metior/git/actor.rb +43 -0
- data/lib/metior/git/commit.rb +68 -0
- data/lib/metior/git/repository.rb +72 -0
- data/lib/metior/github.rb +27 -0
- data/lib/metior/github/actor.rb +43 -0
- data/lib/metior/github/commit.rb +55 -0
- data/lib/metior/github/repository.rb +99 -0
- data/lib/metior/repository.rb +273 -0
- data/lib/metior/vcs.rb +150 -0
- data/lib/metior/version.rb +11 -0
- data/metior.gemspec +26 -0
- data/test/fixtures/mojombo-grit-master-1b2fe77.txt +0 -0
- data/test/helper.rb +26 -0
- data/test/mock_vcs/actor.rb +28 -0
- data/test/mock_vcs/commit.rb +53 -0
- data/test/mock_vcs/repository.rb +72 -0
- data/test/test_class_loading.rb +61 -0
- data/test/test_git.rb +22 -0
- data/test/test_github.rb +43 -0
- data/test/test_metior.rb +34 -0
- data/test/test_repository.rb +150 -0
- data/test/test_vcs.rb +40 -0
- metadata +223 -0
@@ -0,0 +1,118 @@
|
|
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
|
+
module Metior
|
7
|
+
|
8
|
+
# This class represents a commit in a source code repository
|
9
|
+
#
|
10
|
+
# Although not all VCSs distinguish authors from committers this
|
11
|
+
# implementation forces a differentiation between the both.
|
12
|
+
#
|
13
|
+
# @abstract It has to be subclassed to implement a commit representation for
|
14
|
+
# a specific VCS.
|
15
|
+
# @author Sebastian Staudt
|
16
|
+
class Commit
|
17
|
+
|
18
|
+
# @return [Array<String>] A list of file paths added in this commit
|
19
|
+
attr_reader :added_files
|
20
|
+
|
21
|
+
# @return [Fixnum] The lines of code that have been added in this commit
|
22
|
+
attr_reader :additions
|
23
|
+
|
24
|
+
# @return [Actor] This commit's author
|
25
|
+
attr_reader :author
|
26
|
+
|
27
|
+
# @return [Time] The date this commit has been authored
|
28
|
+
attr_reader :authored_date
|
29
|
+
|
30
|
+
# @return [Object] A unique identifier of the commit in the repository
|
31
|
+
attr_reader :id
|
32
|
+
|
33
|
+
# @return [Time] The date this commit has been committed
|
34
|
+
attr_reader :committed_date
|
35
|
+
|
36
|
+
# @return [Actor] This commit's committer
|
37
|
+
attr_reader :committer
|
38
|
+
|
39
|
+
# @return [Array<String>] A list of file paths deleted in this commit
|
40
|
+
attr_reader :deleted_files
|
41
|
+
|
42
|
+
# @return [Fixnum] The lines of code that have been deleted in this commit
|
43
|
+
attr_reader :deletions
|
44
|
+
|
45
|
+
# @return [String] The commit message of this commit
|
46
|
+
attr_reader :message
|
47
|
+
|
48
|
+
# @return [Array<String>] A list of file paths modified in this commit
|
49
|
+
attr_reader :modified_files
|
50
|
+
|
51
|
+
# @return [Range] The commit range this commit belongs to
|
52
|
+
attr_reader :range
|
53
|
+
|
54
|
+
# @return [Repository] The repository this commit belongs to
|
55
|
+
attr_reader :repo
|
56
|
+
|
57
|
+
# Calculate some predefined activity statistics for the given set of
|
58
|
+
# commits
|
59
|
+
#
|
60
|
+
# @param [Array<Commit>] commits The commits to analyze
|
61
|
+
# @return [Hash<Symbol, Object>] The calculated statistics for the commits
|
62
|
+
def self.activity(commits)
|
63
|
+
activity = {}
|
64
|
+
|
65
|
+
commit_count = commits.size
|
66
|
+
|
67
|
+
active_days = {}
|
68
|
+
commits.each do |commit|
|
69
|
+
date = commit.committed_date.utc
|
70
|
+
day = Time.utc(date.year, date.month, date.day)
|
71
|
+
if active_days.key? day
|
72
|
+
active_days[day] += 1
|
73
|
+
else
|
74
|
+
active_days[day] = 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
most_active_day = active_days.sort_by { |day, count| count }.last.first
|
79
|
+
|
80
|
+
activity[:first_commit_date] = commits.last.committed_date
|
81
|
+
activity[:last_commit_date] = commits.first.committed_date
|
82
|
+
|
83
|
+
age_in_days = (Time.now - activity[:first_commit_date]) / 86400.0
|
84
|
+
|
85
|
+
activity[:active_days] = active_days
|
86
|
+
activity[:most_active_day] = most_active_day
|
87
|
+
activity[:commits_per_day] = commit_count / age_in_days
|
88
|
+
activity[:commits_per_active_day] = commit_count.to_f / active_days.size
|
89
|
+
|
90
|
+
activity
|
91
|
+
end
|
92
|
+
|
93
|
+
# Creates a new commit instance linked to the given repository and branch
|
94
|
+
#
|
95
|
+
# @param [Repository] repo The repository this commit belongs to
|
96
|
+
# @param [String] range The commit range this commit belongs to
|
97
|
+
def initialize(repo, range)
|
98
|
+
@repo = repo
|
99
|
+
@range = range
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns the total of changed lines in this commit
|
103
|
+
#
|
104
|
+
# @return [Fixnum] The total number of changed lines
|
105
|
+
def modifications
|
106
|
+
additions + deletions
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns the subject line of the commit message, i.e. the first line
|
110
|
+
#
|
111
|
+
# @return [String] The subject of the commit
|
112
|
+
def subject
|
113
|
+
@message.split(/$/).first
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
@@ -0,0 +1,19 @@
|
|
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
|
+
module Metior
|
7
|
+
|
8
|
+
# This error is raised when an operation is not supported by the currently
|
9
|
+
# used VCS (or its implementation)
|
10
|
+
class UnsupportedError < RuntimeError
|
11
|
+
|
12
|
+
# Creates a new instance of this error
|
13
|
+
def initialize
|
14
|
+
super 'Operation not supported by the current VCS.'
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/lib/metior/git.rb
ADDED
@@ -0,0 +1,25 @@
|
|
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
|
+
require 'metior/vcs'
|
7
|
+
|
8
|
+
module Metior
|
9
|
+
|
10
|
+
# The Metior implementation for Git
|
11
|
+
#
|
12
|
+
# @author Sebastian Staudt
|
13
|
+
module Git
|
14
|
+
|
15
|
+
# Git will be registered as `:git`
|
16
|
+
NAME = :git
|
17
|
+
|
18
|
+
include Metior::VCS
|
19
|
+
|
20
|
+
# Git's default branch is _master_
|
21
|
+
DEFAULT_BRANCH = 'master'
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,43 @@
|
|
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
|
+
require 'metior/actor'
|
7
|
+
|
8
|
+
module Metior
|
9
|
+
|
10
|
+
module Git
|
11
|
+
|
12
|
+
# Represents an actor in a Git source code repository, i.e. an author or
|
13
|
+
# committer.
|
14
|
+
#
|
15
|
+
# @author Sebastian Staudt
|
16
|
+
class Actor < Metior::Actor
|
17
|
+
|
18
|
+
alias_method :email, :id
|
19
|
+
|
20
|
+
# Returns the email address as an identifier for the given actor.
|
21
|
+
# Git uses email addresses as identifiers for its actors.
|
22
|
+
#
|
23
|
+
# @param [Grit::Actor] actor The actor object from Grit
|
24
|
+
# @return [String] The email address of the given actor
|
25
|
+
def self.id_for(actor)
|
26
|
+
actor.email
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates a new actor instance
|
30
|
+
#
|
31
|
+
# @param [Repository] repo The repository this actor belongs to
|
32
|
+
# @param [Grit::Actor] actor The actor object from Grit
|
33
|
+
def initialize(repo, actor)
|
34
|
+
super repo
|
35
|
+
@id = actor.email
|
36
|
+
@name = actor.name
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,68 @@
|
|
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
|
+
require 'metior/commit'
|
7
|
+
require 'metior/git'
|
8
|
+
require 'metior/git/actor'
|
9
|
+
|
10
|
+
module Metior
|
11
|
+
|
12
|
+
module Git
|
13
|
+
|
14
|
+
# Represents a commit in a Git source code repository
|
15
|
+
#
|
16
|
+
# @author Sebastian Staudt
|
17
|
+
class Commit < Metior::Commit
|
18
|
+
|
19
|
+
include Metior::Git
|
20
|
+
|
21
|
+
# Creates a new Git commit object linked to the repository and branch it
|
22
|
+
# belongs to and the data from the corresponding `Grit::Commit` object
|
23
|
+
#
|
24
|
+
# @param [Repository] repo The Git repository this commit belongs to
|
25
|
+
# @param [String] range The commit range this commits belongs to
|
26
|
+
# @param [Grit::Commit] commit The commit object from Grit
|
27
|
+
def initialize(repo, range, commit)
|
28
|
+
super repo, range
|
29
|
+
|
30
|
+
@additions = commit.stats.additions
|
31
|
+
@authored_date = commit.authored_date
|
32
|
+
@committed_date = commit.committed_date
|
33
|
+
@deletions = commit.stats.deletions
|
34
|
+
@id = commit.id
|
35
|
+
@message = commit.message
|
36
|
+
|
37
|
+
authors = repo.authors range
|
38
|
+
@author = authors[Actor.id_for commit.author]
|
39
|
+
@author = Actor.new repo, commit.author if author.nil?
|
40
|
+
@author.add_commit self
|
41
|
+
|
42
|
+
committers = repo.committers range
|
43
|
+
@committer = committers[Actor.id_for commit.committer]
|
44
|
+
@committer = Actor.new repo, commit.committer if @committer.nil?
|
45
|
+
@committer.add_commit self
|
46
|
+
|
47
|
+
@added_files = []
|
48
|
+
@modified_files = []
|
49
|
+
@deleted_files = []
|
50
|
+
commit.diffs.each do |diff|
|
51
|
+
if diff.new_file
|
52
|
+
@added_files << diff.b_path
|
53
|
+
elsif diff.deleted_file
|
54
|
+
@deleted_files << diff.b_path
|
55
|
+
elsif diff.renamed_file
|
56
|
+
@added_files << diff.b_path
|
57
|
+
@deleted_files << diff.a_path
|
58
|
+
else
|
59
|
+
@modified_files << diff.b_path
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,72 @@
|
|
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
|
+
require 'grit'
|
7
|
+
|
8
|
+
require 'metior/git'
|
9
|
+
require 'metior/git/commit'
|
10
|
+
require 'metior/repository'
|
11
|
+
|
12
|
+
module Metior
|
13
|
+
|
14
|
+
module Git
|
15
|
+
|
16
|
+
# Represents a Git source code repository
|
17
|
+
#
|
18
|
+
# @author Sebastian Staudt
|
19
|
+
class Repository < Metior::Repository
|
20
|
+
|
21
|
+
include Metior::Git
|
22
|
+
|
23
|
+
# Creates a new Git repository based on the given path
|
24
|
+
#
|
25
|
+
# This creates a new `Grit::Repo` instance to interface with the
|
26
|
+
# repository.
|
27
|
+
#
|
28
|
+
# @param [String] path The file system path of the repository
|
29
|
+
def initialize(path)
|
30
|
+
super path
|
31
|
+
|
32
|
+
@grit_repo = Grit::Repo.new(path)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# This method uses Grit to load all commits from the given commit range
|
38
|
+
#
|
39
|
+
# Because of some Grit internal limitations, the commits have to be
|
40
|
+
# loaded in batches of up to 500 commits.
|
41
|
+
#
|
42
|
+
# @note Grit will choke on huge repositories, like Homebrew or the Linux
|
43
|
+
# kernel. You will have to raise the timeout limit using
|
44
|
+
# `Grit.git_timeout=`.
|
45
|
+
# @param [String, Range] range The range of commits for which the commits
|
46
|
+
# should be loaded. This may be given as a string
|
47
|
+
# (`'master..development'`), a range (`'master'..'development'`)
|
48
|
+
# or as a single ref (`'master'`). A single ref name means all
|
49
|
+
# commits reachable from that ref.
|
50
|
+
# @return [Array<Commit>] All commits in the given commit range
|
51
|
+
# @see Grit::Repo#commits
|
52
|
+
def load_commits(range)
|
53
|
+
if range.first == ''
|
54
|
+
range = range.last
|
55
|
+
else
|
56
|
+
range = '%s..%s' % [range.first, range.last]
|
57
|
+
end
|
58
|
+
|
59
|
+
commits = []
|
60
|
+
skip = 0
|
61
|
+
begin
|
62
|
+
commits += @grit_repo.commits(range, 500, skip)
|
63
|
+
skip += 500
|
64
|
+
end while commits.size == skip
|
65
|
+
commits
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,27 @@
|
|
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
|
+
require 'metior/vcs'
|
7
|
+
|
8
|
+
module Metior
|
9
|
+
|
10
|
+
# The Metior implementation for GitHub's API
|
11
|
+
#
|
12
|
+
# @author Sebastian Staudt
|
13
|
+
module GitHub
|
14
|
+
|
15
|
+
# GitHub will be registered as `:github`
|
16
|
+
NAME = :github
|
17
|
+
|
18
|
+
include Metior::VCS
|
19
|
+
|
20
|
+
# Git's default branch is _master_
|
21
|
+
DEFAULT_BRANCH = 'master'
|
22
|
+
|
23
|
+
not_supported :file_stats, :line_stats
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,43 @@
|
|
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
|
+
require 'metior/actor'
|
7
|
+
|
8
|
+
module Metior
|
9
|
+
|
10
|
+
module GitHub
|
11
|
+
|
12
|
+
# Represents an actor in a GitHub source code repository, i.e. an author or
|
13
|
+
# committer.
|
14
|
+
#
|
15
|
+
# @author Sebastian Staudt
|
16
|
+
class Actor < Metior::Actor
|
17
|
+
|
18
|
+
alias_method :login, :id
|
19
|
+
|
20
|
+
# Returns the GitHub login as an identifier for the given actor.
|
21
|
+
#
|
22
|
+
# @param [Hashie::Mash] actor The actor's data parsed from the JSON API
|
23
|
+
# @return [String] The GitHub login of the given actor
|
24
|
+
def self.id_for(actor)
|
25
|
+
actor.login
|
26
|
+
end
|
27
|
+
|
28
|
+
# Creates a new actor instance
|
29
|
+
#
|
30
|
+
# @param [Repository] repo The repository this actor belongs to
|
31
|
+
# @param [Hashie::Mash] actor The actor's data parsed from the JSON API
|
32
|
+
def initialize(repo, actor)
|
33
|
+
super repo
|
34
|
+
@email = actor.email
|
35
|
+
@id = actor.login
|
36
|
+
@name = actor.name
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,55 @@
|
|
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
|
+
require 'metior/commit'
|
7
|
+
require 'metior/github'
|
8
|
+
require 'metior/github/actor'
|
9
|
+
|
10
|
+
module Metior
|
11
|
+
|
12
|
+
module GitHub
|
13
|
+
|
14
|
+
# Represents a commit in a GitHub source code repository
|
15
|
+
#
|
16
|
+
# @author Sebastian Staudt
|
17
|
+
class Commit < Metior::Commit
|
18
|
+
|
19
|
+
include Metior::GitHub
|
20
|
+
|
21
|
+
# Creates a new GitHub commit object linked to the repository and branch
|
22
|
+
# it belongs to and the data parsed from the corresponding JSON data
|
23
|
+
#
|
24
|
+
# @param [Repository] repo The GitHub repository this commit belongs to
|
25
|
+
# @param [String] range The commit range this commits belongs to
|
26
|
+
# @param [Hashie:Mash] commit The commit data parsed from the JSON API
|
27
|
+
def initialize(repo, range, commit)
|
28
|
+
super repo, range
|
29
|
+
|
30
|
+
@added_files = []
|
31
|
+
@additions = 0
|
32
|
+
@authored_date = commit.authored_date
|
33
|
+
@committed_date = commit.committed_date
|
34
|
+
@deleted_files = []
|
35
|
+
@deletions = 0
|
36
|
+
@id = commit.id
|
37
|
+
@message = commit.message
|
38
|
+
@modified_files = []
|
39
|
+
|
40
|
+
authors = repo.authors(branch)
|
41
|
+
@author = authors[Actor.id_for commit.author]
|
42
|
+
@author = Actor.new repo, commit.author if author.nil?
|
43
|
+
@author.add_commit self
|
44
|
+
|
45
|
+
committers = repo.committers branch
|
46
|
+
@committer = committers[Actor.id_for commit.committer]
|
47
|
+
@committer = Actor.new repo, commit.committer if @committer.nil?
|
48
|
+
@committer.add_commit self
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|