Skip to content

Commit

Permalink
Merge pull request #308 from adrienthebo/issue/master/rk-16-extract-g…
Browse files Browse the repository at this point in the history
…it-implementation

REVIEW: Refactor Git implementation, extract platform specific code
  • Loading branch information
andersonmills committed Feb 23, 2015
2 parents e9c0f56 + 4f4b9da commit 06d43c7
Show file tree
Hide file tree
Showing 23 changed files with 835 additions and 181 deletions.
29 changes: 10 additions & 19 deletions lib/r10k/environment/git.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'r10k/logging'
require 'r10k/puppetfile'
require 'r10k/git/working_dir'
require 'r10k/git/stateful_repository'
require 'forwardable'

# This class implements an environment based on a Git branch.
#
Expand All @@ -17,10 +18,10 @@ class R10K::Environment::Git < R10K::Environment::Base
# @return [String] The git reference to use for this environment
attr_reader :ref

# @!attribute [r] working_dir
# @!attribute [r] repo
# @api private
# @return [R10K::Git::WorkingDir] The git working directory backing this environment
attr_reader :working_dir
# @return [R10K::Git::StatefulRepository] The git repo backing this environment
attr_reader :repo

# Initialize the given SVN environment.
#
Expand All @@ -36,7 +37,7 @@ def initialize(name, basedir, dirname, options = {})
@remote = options[:remote]
@ref = options[:ref]

@working_dir = R10K::Git::WorkingDir.new(@ref, @remote, @basedir, @dirname)
@repo = R10K::Git::StatefulRepository.new(@ref, @remote, @basedir, @dirname)
end

# Clone or update the given Git environment.
Expand All @@ -47,23 +48,13 @@ def initialize(name, basedir, dirname, options = {})
# @api public
# @return [void]
def sync
@working_dir.sync
@repo.sync
@synced = true
end

def status
if !@working_dir.exist?
:absent
elsif !@working_dir.git?
:mismatched
elsif !(@remote == @working_dir.remote)
:mismatched
elsif !@synced
:outdated
else
:insync
end
end
extend Forwardable

def_delegators :@repo, :status

# @deprecated
# @api private
Expand Down
4 changes: 2 additions & 2 deletions lib/r10k/git/alternates.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ class R10K::Git::Alternates
# @return [Pathname] The alternates file
attr_reader :file

# @param git_dir [String] The path to the git repository
# @param git_dir [Pathname] The path to the git repository
def initialize(git_dir)
@file = Pathname.new(File.join(git_dir, 'objects', 'info', 'alternates'))
@file = git_dir + File.join('objects', 'info', 'alternates')
end

def to_a
Expand Down
30 changes: 30 additions & 0 deletions lib/r10k/git/bare_repository.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require 'r10k/git'
require 'r10k/git/base_repository'
require 'r10k/logging'

# Create and manage Git bare repositories.
class R10K::Git::BareRepository < R10K::Git::BaseRepository

# @return [Pathname] The path to this Git repository
def git_dir
@path
end

# @param basedir [String] The base directory of the Git repository
# @param dirname [String] The directory name of the Git repository
def initialize(basedir, dirname)
@path = Pathname.new(File.join(basedir, dirname))
end

def clone(remote)
git ['clone', '--mirror', remote, git_dir.to_s]
end

def fetch
git ['fetch', '--prune'], :git_dir => git_dir.to_s
end

def exist?
@path.exist?
end
end
102 changes: 102 additions & 0 deletions lib/r10k/git/base_repository.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
require 'r10k/git'
require 'r10k/logging'

class R10K::Git::BaseRepository

# @abstract
# @return [Pathname] The path to the Git directory
def git_dir
raise NotImplementedError
end

# Resolve the given Git ref to a commit
#
# @param pattern [String] The git ref to resolve
# @return [String, nil] The commit SHA if the ref could be resolved, nil otherwise.
def resolve(pattern)
result = git ['rev-parse', "#{pattern}^{commit}"], :git_dir => git_dir.to_s, :raise_on_fail => false
if result.success?
result.stdout
end
end

# For compatibility with R10K::Git::Ref
# @todo remove alias
alias rev_parse resolve

# @return [Array<String>] All local branches in this repository
def branches
for_each_ref('refs/heads')
end

# @return [Array<String>] All tags in this repository
def tags
for_each_ref('refs/tags')
end

# @return [Symbol] The type of the given ref, one of :branch, :tag, :commit, or :unknown
def ref_type(pattern)
if branches.include? pattern
:branch
elsif tags.include? pattern
:tag
elsif resolve(pattern)
:commit
else
:unknown
end
end

include R10K::Logging

private

# @param pattern [String]
def for_each_ref(pattern)
matcher = %r[#{pattern}/(.*)$]
output = git ['for-each-ref', pattern, '--format', '%(refname)'], :git_dir => git_dir.to_s
output.stdout.scan(matcher).flatten
end

# Wrap git commands
#
# @param cmd [Array<String>] cmd The arguments for the git prompt
# @param opts [Hash] opts
#
# @option opts [String] :path
# @option opts [String] :git_dir
# @option opts [String] :work_tree
# @option opts [String] :raise_on_fail
#
# @raise [R10K::ExecutionFailure] If the executed command exited with a
# nonzero exit code.
#
# @return [String] The git command output
def git(cmd, opts = {})
raise_on_fail = opts.fetch(:raise_on_fail, true)

argv = %w{git}

if opts[:path]
argv << "--git-dir" << File.join(opts[:path], '.git')
argv << "--work-tree" << opts[:path]
else
if opts[:git_dir]
argv << "--git-dir" << opts[:git_dir]
end
if opts[:work_tree]
argv << "--work-tree" << opts[:work_tree]
end
end

argv.concat(cmd)

subproc = R10K::Util::Subprocess.new(argv)
subproc.raise_on_fail = raise_on_fail
subproc.logger = self.logger

result = subproc.execute

result
end
end
47 changes: 27 additions & 20 deletions lib/r10k/git/cache.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
require 'r10k/git'
require 'r10k/git/repository'
require 'r10k/git/bare_repository'

require 'r10k/settings'
require 'r10k/registry'
require 'r10k/instance_cache'
require 'forwardable'

# Mirror a git repository for use shared git object repositories
# Cache Git repository mirrors for object database reuse.
#
# @see man git-clone(1)
class R10K::Git::Cache < R10K::Git::Repository
class R10K::Git::Cache

include R10K::Settings::Mixin

Expand All @@ -31,56 +33,61 @@ def self.generate(remote)

include R10K::Logging

extend Forwardable

def_delegators :@repo, :git_dir, :branches, :tags, :exist?, :resolve, :ref_type

# @!attribute [r] path
# @deprecated
# @return [String] The path to the git cache repository
def path
logger.warn "#{self.class}#path is deprecated; use #git_dir"
@git_dir
git_dir
end

# @!attribute [r] repo
# @api private
attr_reader :repo

# @param [String] remote
# @param [String] cache_root
def initialize(remote)
@remote = remote

@git_dir = File.join(settings[:cache_root], sanitized_dirname)
@repo = R10K::Git::BareRepository.new(settings[:cache_root], sanitized_dirname)
end

def sync
if not @synced
if !@synced
sync!
@synced = true
end
end

def synced?
@synced
end

def sync!
if cached?
fetch
@repo.fetch
else
logger.debug "Creating new git cache for #{@remote.inspect}"

# TODO extract this to an initialization step
unless File.exist? settings[:cache_root]
if !File.exist?(settings[:cache_root])
FileUtils.mkdir_p settings[:cache_root]
end

git ['clone', '--mirror', @remote, git_dir]
@repo.clone(@remote)
end
rescue R10K::Util::Subprocess::SubprocessError => e
raise R10K::Git::GitError.wrap(e, "Couldn't update git cache for #{@remote}")
end

# @return [Array<String>] A list the branches for the git repository
def branches
output = git %w[for-each-ref refs/heads --format %(refname)], :git_dir => git_dir
output.stdout.scan(%r[refs/heads/(.*)$]).flatten
# @api private
def reset!
@synced = false
end

# @return [true, false] If the repository has been locally cached
def cached?
File.exist? git_dir
end
alias cached? exist?

private

Expand Down
65 changes: 65 additions & 0 deletions lib/r10k/git/stateful_repository.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
require 'r10k/git/thin_repository'
require 'r10k/git/errors'
require 'forwardable'

# Manage how Git repositories are created and set to specific refs
class R10K::Git::StatefulRepository

# @!attribute [r] repo
# @api private
attr_reader :repo

extend Forwardable
def_delegators :@repo, :head

# Create a new shallow git working directory
#
# @param ref [String] The git ref to check out
# @param remote [String] The git remote to use for the repo
# @param basedir [String] The path containing the Git repo
# @param dirname [String] The directory name of the Git repo
def initialize(ref, remote, basedir, dirname)
@ref = ref
@remote = remote

@repo = R10K::Git::ThinRepository.new(basedir, dirname)
@cache = R10K::Git::Cache.generate(remote)
end

def sync
@cache.sync

sha = @cache.resolve(@ref)

if sha.nil?
raise R10K::Git::UnresolvableRefError.new("Unable to sync repo to unresolvable ref '#{@ref}'", :git_dir => @repo.git_dir)
end

case status
when :absent
@repo.clone(@remote, {:ref => sha})
when :mismatched
@repo.path.rmtree
@repo.clone(@remote, {:ref => sha})
when :outdated
@repo.fetch
@repo.checkout(sha)
end
end

def status
if !@repo.exist?
:absent
elsif !@repo.git_dir.exist?
:mismatched
elsif !(@repo.origin == @remote)
:mismatched
elsif !(@repo.head == @cache.resolve(@ref))
:outdated
elsif @cache.ref_type(@ref) == :branch && !@cache.synced?
:outdated
else
:insync
end
end
end

0 comments on commit 06d43c7

Please sign in to comment.