class Twigg::Repo

Abstraction around a Git repository on disk.

Constants

STDERR_TO_STDOUT

Public Class Methods

new(path) click to toggle source

Given `path` to a Git repository on disk sets up a `Repo` instance.

Raises an {InvalidRepoError} if `path` does not point to the top level of an existent Git repo.

# File lib/twigg/repo.rb, line 13
def initialize(path)
  @path = Pathname.new(path)
  raise InvalidRepoError unless valid?
end

Public Instance Methods

commits(all: true, since: nil) click to toggle source

Returns an array of {Commit} objects reachable from the HEAD of the repo.

There are a number of keyword arguments that correspond to the options of the same name to `git log`:

- `all:` : return reachable commits from all branches, not just HEAD
- `since:`: only return commits made since this Time
# File lib/twigg/repo.rb, line 26
def commits(all: true, since: nil)
  args = []
  args << '--all' if all
  args << "--since=#{since.to_i}" if since
  @commits ||= {}
  @commits[args] ||= parse_log(log(*args))
end
name() click to toggle source

Returns the name of the repo.

The name is inferred from the final component of the repo path.

# File lib/twigg/repo.rb, line 37
def name
  @path.basename.to_s
end

Private Instance Methods

git(command, *args) click to toggle source

Runs the Git command, `command`, with args `args`.

# File lib/twigg/repo.rb, line 74
def git(command, *args)
  IO.popen([{ 'GIT_DIR' => git_dir },
            'git', command, *args, *STDERR_TO_STDOUT], 'r') do |io|
    io.read
  end
end
git_dir() click to toggle source
# File lib/twigg/repo.rb, line 51
def git_dir
  @git_dir ||= begin
    # first try repo "foo" (bare repo), then "foo/.git" (non-bare repo)
    [@path, @path + '.git'].map(&:to_s).find do |path|
      Process.wait(
        IO.popen({ 'GIT_DIR' => path },
                 %w[git rev-parse --git-dir] + STDERR_TO_STDOUT).pid
      )
      $?.success?
    end
  end
end
Also aliased as: valid?
log(*args) click to toggle source
# File lib/twigg/repo.rb, line 81
def log(*args)
  format = [
    '%H',          # commit hash
    '%n',          # newline
    '%aN',         # author name (respecting .mailmap)
    '%n',          # newline
    '%ct',         # committer date, UNIX timestamp
    '%n',          # newline
    '%s',          # subject
    '%n',          # newline
    '%w(0,4,4)%b', # body, indented 4 spaces
  ].join

  git 'log',
    '--encoding=UTF-8', "--pretty=format:#{format}", '--numstat', *args
end
parse_log(string) click to toggle source
# File lib/twigg/repo.rb, line 98
def parse_log(string)
  [].tap do |commits|
    lines = string.each_line
    loop do
      begin
        commit           = { repo: self }
        commit[:body]    = []
        commit[:stat]    = Hash.new(0)
        commit[:commit]  = lines.next.chomp
        commit[:author]  = lines.next.chomp
        commit[:date]    = Time.at(lines.next.chomp.to_i).to_date
        commit[:subject] = lines.next.chomp rescue ''

        while lines.peek =~ /^ {4}(.*)$/ && lines.next
          commit[:body] << $~[1]
        end
        lines.next if lines.peek == "\n" # blank separator line

        while lines.peek =~ /^(\d+|-)\t(\d+|-)\t.+$/ && lines.next
          commit[:stat][:additions] += $~[1].to_i
          commit[:stat][:deletions] += $~[2].to_i
        end
        lines.next if lines.peek == "\n" # blank separator line
      rescue StopIteration
        break # end of output
      ensure
        # if the underlying repo is bad (eg. no commits yet) this could
        # raise an ArgumentError, so we rescue
        commit = Commit.new(commit) rescue nil
        commits << commit if commit
      end
    end
  end
end
valid?()

Check to see if this is a valid repo:

- the repo path should exist
- the path should point to the top level of the repo
- the check should work for both bare and non-bare repos

Delegates to `#git_dir`

Alias for: git_dir