Class: Metior::Repository Abstract

Inherits:
Object show all
Includes:
AutoIncludeVCS
Defined in:
lib/metior/repository.rb

Overview

This class is abstract.

It has to be subclassed to implement a repository representation for a specific VCS.

This class represents a source code repository.

Author:

  • Sebastian Staudt

Direct Known Subclasses

Git::Repository, GitHub::Repository

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from AutoIncludeVCS

included

Constructor Details

#initialize(path) ⇒ Repository

Creates a new repository instance with the given file system path

Parameters:

  • path (String)

    The file system path of the repository



27
28
29
30
31
32
33
34
# File 'lib/metior/repository.rb', line 27

def initialize(path)
  @actors      = {}
  @commits     = {}
  @description = nil
  @name        = nil
  @path        = path
  @refs        = {}
end

Instance Attribute Details

#pathString (readonly)

Returns The file system path of this repository.

Returns:

  • (String)

    The file system path of this repository



22
23
24
# File 'lib/metior/repository.rb', line 22

def path
  @path
end

Instance Method Details

#actor(actor) ⇒ Actor

Returns a single VCS specific actor object from the raw data of the actor provided by the VCS implementation

The actor object is either created from the given raw data or retrieved from the cache using the VCS specific unique identifier of the actor.

Parameters:

  • actor (Object)

    The raw data of the actor provided by the VCS

Returns:

  • (Actor)

    A object representing the actor

See Also:



45
46
47
48
# File 'lib/metior/repository.rb', line 45

def actor(actor)
  id = self.class::Actor.id_for(actor)
  @actors[id] ||= self.class::Actor.new(self, actor)
end

#authors(range = self.class::DEFAULT_BRANCH) ⇒ ActorCollection Also known as: contributors

Returns all authors from the given commit range in a hash where the IDs of the authors are the keys and the authors are the values

This will call commits(range) if the authors for the commit range are not known yet.

Parameters:

  • range (String, Range) (defaults to: self.class::DEFAULT_BRANCH)

    The range of commits for which the authors should be retrieved. This may be given as a string ('master..development'), a range ('master'..'development') or as a single ref ('master'). A single ref name means all commits reachable from that ref.

Returns:

See Also:



63
64
65
# File 'lib/metior/repository.rb', line 63

def authors(range = self.class::DEFAULT_BRANCH)
  commits(range).authors
end

#branchesArray<String>

Returns the names of all branches of this repository

Returns:

  • (Array<String>)

    The names of all branches



71
72
73
# File 'lib/metior/repository.rb', line 71

def branches
  load_branches.each { |name, id| @refs[name] = id }.keys.sort
end

#build_commits(raw_commits) ⇒ Array<Commit> (private)

Builds VCS specific commit objects for each given commit's raw data that is provided by the VCS implementation

The raw data will be transformed into commit objects that will also be saved into the commit cache. Authors and committers of the given commits will be created and stored into the cache or loaded from the cache if they already exist. Additionally this method will establish an association between the commits and their children.

Parameters:

  • raw_commits (Array<Object>)

    The commits' raw data provided by the VCS implementation

Returns:

  • (Array<Commit>)

    The commit objects representing the given commits

See Also:



305
306
307
308
309
310
311
312
313
314
315
# File 'lib/metior/repository.rb', line 305

def build_commits(raw_commits)
  child_commit_id = nil
  raw_commits.map do |commit|
    commit = self.class::Commit.new(self, commit)
    commit.add_child child_commit_id unless child_commit_id.nil?
    child_commit_id = commit.id
    @commits[commit.id] = commit
    @actors[commit.author.id] ||= commit.author
    commit
  end
end

#cached_commits(range) ⇒ Array<Commit> (private)

Tries to retrieve as many commits as possible in the given commit range from the commit cache

This method calls itself recursively to walk the given commit range either from the start to the end or vice versa depending on which commit could be found in the cache.

Parameters:

  • range (Range)

    The range of commits which should be retrieved from the cache. This may be given a range of commit IDs ('master'..'development').

Returns:

  • (Array<Commit>)

    A list of commit objects that could be retrieved from the cache

See Also:



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/metior/repository.rb', line 330

def cached_commits(range)
  commits = []

  direction = nil
  if @commits.key? range.last
    current_commits = [@commits[range.last]]
    direction = :parents
  elsif @commits.key? range.first
    current_commits = [@commits[range.first]]
    direction = :children
  end

  unless direction.nil?
    while !current_commits.empty? do
      new_commits = []
      current_commits.each do |commit|
        new_commits += commit.send direction
        commits << commit if commit.id != range.first
        if direction == :parents && new_commits.include?(range.first)
          new_commits = []
          break
        end
      end
      unless new_commits.include? range.first
        current_commits = new_commits.uniq.map do |commit|
          commit = @commits[commit]
          commits.include?(commit) ? nil : commit
        end.compact
      end
    end
  end

  commits.sort_by { |c| c.committed_date }.reverse
end

#commits(range = self.class::DEFAULT_BRANCH) ⇒ CommitCollection

Loads all commits including their committers and authors from the given commit range

Parameters:

  • range (String, Range) (defaults to: self.class::DEFAULT_BRANCH)

    The range of commits for which the commits should be retrieved. This may be given as a string ('master..development'), a range ('master'..'development') or as a single ref ('master'). A single ref name means all commits reachable from that ref.

Returns:



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/metior/repository.rb', line 84

def commits(range = self.class::DEFAULT_BRANCH)
  range = parse_range range
  commits = cached_commits range

  if commits.empty?
    base_commit, raw_commits = load_commits(range)
    commits = commits + build_commits(raw_commits)
    unless base_commit.nil?
      base_commit = self.class::Commit.new(self, base_commit)
      base_commit.add_child commits.last.id
      @commits[base_commit.id] = base_commit
    end
  else
    if range.first == ''
      unless commits.last.parents.empty?
        raw_commits = load_commits(''..commits.last.id).last
        commits += build_commits raw_commits[0..-2]
      end
    else
      if commits.first.id != range.last
        raw_commits = load_commits(commits.first.id..range.last).last
        commits = build_commits(raw_commits) + commits
      end
      unless commits.last.parents.include? range.first
        raw_commits = load_commits(range.first..commits.last.id).last
        commits += build_commits raw_commits
      end
    end
  end

  CommitCollection.new commits
end

#committers(range = self.class::DEFAULT_BRANCH) ⇒ ActorCollection Also known as: collaborators

Returns all committers from the given commit range in a hash where the IDs of the committers are the keys and the committers are the values

This will call commits(range) if the committers for the commit range are not known yet.

Parameters:

  • range (String, Range) (defaults to: self.class::DEFAULT_BRANCH)

    The range of commits for which the committers should be retrieved. This may be given as a string ('master..development'), a range ('master'..'development') or as a single ref ('master'). A single ref name means all commits reachable from that ref.

Returns:

See Also:



130
131
132
# File 'lib/metior/repository.rb', line 130

def committers(range = self.class::DEFAULT_BRANCH)
  commits(range).committers
end

#descriptionString

Returns the description of the project contained in the repository

This will load the description through a VCS specific mechanism if required.

Returns:

  • (String)

    The description of the project in the repository

See Also:



142
143
144
145
# File 'lib/metior/repository.rb', line 142

def description
  load_description if @description.nil?
  @description
end

#file_stats(range = self.class::DEFAULT_BRANCH) ⇒ Hash<String, Hash<Symbol, Object>>

This evaluates basic statistics about the files in a given commit range.

Examples:

repo.file_stats
=> {
     'a_file.rb' => {
       :added_date => Tue Mar 29 16:13:47 +0200 2011,
       :deleted_date => Sun Jun 05 12:56:18 +0200 2011,
       :last_modified_date => Thu Apr 21 20:08:00 +0200 2011,
       :modifications => 9
     }
   }

Parameters:

  • range (String, Range) (defaults to: self.class::DEFAULT_BRANCH)

    The range of commits for which the file stats should be retrieved. This may be given as a string ('master..development'), a range ('master'..'development') or as a single ref ('master'). A single ref name means all commits reachable from that ref.

Returns:

  • (Hash<String, Hash<Symbol, Object>>)

    Each file is returned as a key in this hash. The value of this key is another hash containing the stats for this file. Depending on the state of the file this includes :added_date, :last_modified_date, :last_modified_date and 'master..development'.

See Also:



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/metior/repository.rb', line 172

def file_stats(range = self.class::DEFAULT_BRANCH)
  support! :line_stats

  stats = {}
  commits(range).each_value do |commit|
    commit.added_files.each do |file|
      stats[file] = { :modifications => 0 } unless stats.key? file
      stats[file][:added_date] = commit.authored_date
      stats[file][:modifications] += 1
    end
    commit.modified_files.each do |file|
      stats[file] = { :modifications => 0 } unless stats.key? file
      stats[file][:last_modified_date] = commit.authored_date
      stats[file][:modifications] += 1
    end
    commit.deleted_files.each do |file|
      stats[file] = { :modifications => 0 } unless stats.key? file
      stats[file][:deleted_date] = commit.authored_date
    end
  end

  stats
end

#id_for_ref(ref) ⇒ Object (private)

This method is abstract.

Has to be implemented by VCS subclasses

Returns the unique identifier for the commit the given reference – like a branch name – is pointing to

Parameters:

  • ref (String)

    A symbolic reference name

Returns:

  • (Object)

    The unique identifier of the commit the reference is pointing to

Raises:

  • (NotImplementedError)


372
373
374
# File 'lib/metior/repository.rb', line 372

def id_for_ref(ref)
  raise NotImplementedError
end

#line_history(range = self.class::DEFAULT_BRANCH) ⇒ Hash<Symbol, Array>

This evaluates the changed lines in each commit of the given commit range.

For easier use, the values are stored in separate arrays where each number represents the number of changed (i.e. added or deleted) lines in one commit.

Examples:

repo.line_history
=> { :additions => [10, 5, 0], :deletions => [0, -2, -1] }

Parameters:

  • range (String, Range) (defaults to: self.class::DEFAULT_BRANCH)

    The range of commits for which the commit stats should be retrieved. This may be given as a string ('master..development'), a range ('master'..'development') or as a single ref ('master'). A single ref name means all commits reachable from that ref.

Returns:

  • (Hash<Symbol, Array>)

    Added lines are returned in an Array assigned to key :additions, deleted lines are assigned to :deletions

See Also:



215
216
217
# File 'lib/metior/repository.rb', line 215

def line_history(range = self.class::DEFAULT_BRANCH)
  commits(range).line_history
end

#load_branchesHash<String, Object> (private)

This method is abstract.

Has to be implemented by VCS specific subclasses

Loads all branches and the corresponding commit IDs of this repository

Returns:

  • (Hash<String, Object>)

    The names of all branches and the corresponding commit IDs

Raises:

  • (NotImplementedError)


381
382
383
# File 'lib/metior/repository.rb', line 381

def load_branches
  raise NotImplementedError
end

#load_commits(range = self.class::DEFAULT_BRANCH) ⇒ Array<Commit> (private)

This method is abstract.

Has to be implemented by VCS specific subclasses

Loads all commits from the given commit range

Parameters:

  • range (String, Range) (defaults to: self.class::DEFAULT_BRANCH)

    The range of commits for which the commits should be retrieved. This may be given as a string ('master..development'), a range ('master'..'development') or as a single ref ('master'). A single ref name means all commits reachable from that ref.

Returns:

  • (Array<Commit>)

    All commits from the given commit range

Raises:

  • (NotImplementedError)


394
395
396
# File 'lib/metior/repository.rb', line 394

def load_commits(range = self.class::DEFAULT_BRANCH)
  raise NotImplementedError
end

#load_descriptionObject (private)

This method is abstract.

Has to be implemented by VCS specific subclasses

Loads the description of the project contained in the repository

Raises:

  • (NotImplementedError)

See Also:



402
403
404
# File 'lib/metior/repository.rb', line 402

def load_description
  raise NotImplementedError
end

#load_nameObject (private)

This method is abstract.

Has to be implemented by VCS specific subclasses

Loads the name of the project contained in the repository

Raises:

  • (NotImplementedError)

See Also:



410
411
412
# File 'lib/metior/repository.rb', line 410

def load_name
  raise NotImplementedError
end

#load_tagsHash<String, Object> (private)

This method is abstract.

Has to be implemented by VCS specific subclasses

Loads all tags and the corresponding commit IDs of this repository

Returns:

  • (Hash<String, Object>)

    The names of all tags and the corresponding commit IDs

Raises:

  • (NotImplementedError)


419
420
421
# File 'lib/metior/repository.rb', line 419

def load_tags
  raise NotImplementedError
end

#nameString

Returns the name of the project contained in the repository

This will load the name through a VCS specific mechanism if required.

Returns:

  • (String)

    The name of the project in the repository

See Also:



225
226
227
228
# File 'lib/metior/repository.rb', line 225

def name
  load_name if @name.nil?
  @name
end

#parse_range(range) ⇒ Range (private)

Parses a string or range of commit IDs or ref names into the coresponding range of unique commit IDs

Parameters:

  • range (String, Range)

    The string that should be parsed for a range or an existing range

Returns:

  • (Range)

    The range of commit IDs parsed from the given parameter

See Also:



430
431
432
433
434
435
436
437
438
# File 'lib/metior/repository.rb', line 430

def parse_range(range)
  unless range.is_a? Range
    range = range.to_s.split '..'
    range = ((range.size == 1) ? '' : range.first)..range.last
  end

  range = id_for_ref(range.first)..range.last if range.first != ''
  range.first..id_for_ref(range.last)
end

#significant_authors(range = self.class::DEFAULT_BRANCH, count = 3) ⇒ Array<Actor> Also known as: significant_contributors

Returns a list of authors with the biggest impact on the repository, i.e. changing the most code

Parameters:

  • range (String, Range) (defaults to: self.class::DEFAULT_BRANCH)

    The range of commits for which the authors should be retrieved. This may be given as a string ('master..development'), a range ('master'..'development') or as a single ref ('master'). A single ref name means all commits reachable from that ref.

  • count (Fixnum) (defaults to: 3)

    The number of authors to return

Returns:

  • (Array<Actor>)

    An array of the given number of the most significant authors in the given commit range

Raises:



242
243
244
# File 'lib/metior/repository.rb', line 242

def significant_authors(range = self.class::DEFAULT_BRANCH, count = 3)
  authors(range).most_significant(count)
end

#significant_commits(range = self.class::DEFAULT_BRANCH, count = 10) ⇒ Array<Actor>

Returns a list of commits with the biggest impact on the repository, i.e. changing the most code

Parameters:

  • range (String, Range) (defaults to: self.class::DEFAULT_BRANCH)

    The range of commits for which the commits should be retrieved. This may be given as a string ('master..development'), a range ('master'..'development') or as a single ref ('master'). A single ref name means all commits reachable from that ref.

  • count (Fixnum) (defaults to: 10)

    The number of commits to return

Returns:

  • (Array<Actor>)

    An array of the given number of the most significant commits in the given commit range

Raises:



259
260
261
# File 'lib/metior/repository.rb', line 259

def significant_commits(range = self.class::DEFAULT_BRANCH, count = 10)
  commits(range).most_significant(count)
end

#tagsArray<String>

Returns the names of all tags of this repository

Returns:

  • (Array<String>)

    The names of all tags



266
267
268
# File 'lib/metior/repository.rb', line 266

def tags
  load_tags.each { |name, id| @refs[name] = id }.keys.sort
end

#top_authors(range = self.class::DEFAULT_BRANCH, count = 3) ⇒ Array<Actor> Also known as: top_contributors

Returns a list of top contributors in the given commit range

This will first have to load all authors (and i.e. commits) from the given commit range.

Parameters:

  • range (String, Range) (defaults to: self.class::DEFAULT_BRANCH)

    The range of commits for which the top contributors should be retrieved. This may be given as a string ('master..development'), a range ('master'..'development') or as a single ref ('master'). A single ref name means all commits reachable from that ref.

  • count (Fixnum) (defaults to: 3)

    The number of contributors to return

Returns:

  • (Array<Actor>)

    An array of the given number of top contributors in the given commit range

See Also:



284
285
286
# File 'lib/metior/repository.rb', line 284

def top_authors(range = self.class::DEFAULT_BRANCH, count = 3)
  authors(range).top(count)
end