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