metior 0.1.1

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