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.
@@ -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