lib/puppet_library/util/git.rb
# Puppet Library
# Copyright (C) 2014 drrb
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
require 'fileutils'
require 'monitor'
require 'time'
require 'open4'
require 'puppet_library/util/logging'
require 'puppet_library/util/temp_dir'
module PuppetLibrary::Util
class Git
include Logging
DEFAULT_CACHE_TTL_SECONDS = 60
def initialize(source, cache_dir, cache_ttl_seconds = DEFAULT_CACHE_TTL_SECONDS)
@source = source
@cache_dir = cache_dir
@cache_ttl_seconds = cache_ttl_seconds
@git_dir = File.join(@cache_dir.path, ".git")
@mutex = Monitor.new
end
def tags
update_cache!
git("tag").split
end
def with_tag(tag)
update_cache!
PuppetLibrary::Util::TempDir.use "git" do |path|
git "checkout -f #{tag} .", path
yield(path)
end
end
def read_file(path, tag)
update_cache!
git "show refs/tags/#{tag}:#{path}"
end
def file_exists?(path, tag)
read_file(path, tag)
true
rescue GitCommandError
false
end
def clear_cache!
@mutex.synchronize do
info "Clearing cache for Git repository #{@source} from #{@git_dir}"
FileUtils.rm_rf @cache_dir.path
end
end
def update_cache!
create_cache unless cache_exists?
update_cache if cache_stale?
end
private
def create_cache
@mutex.synchronize do
info "Cloning Git repository from #{@source} to #{@git_dir}"
git "clone --mirror #{@source} #{@git_dir}"
FileUtils.touch fetch_file
end
end
def update_cache
@mutex.synchronize do
git "fetch origin --tags --update-head-ok"
end
end
def cache_exists?
File.directory? @git_dir
end
def cache_stale?
Time.now - last_fetch > @cache_ttl_seconds
end
def last_fetch
if File.exist? fetch_file
File.stat(fetch_file).mtime
else
Time.at(0)
end
end
def fetch_file
File.join(@git_dir, "FETCH_HEAD")
end
def git(command, work_tree = nil)
work_tree = @cache_dir.path unless work_tree
run "git --git-dir=#{@git_dir} --work-tree=#{work_tree} #{command}"
end
def run(command)
debug command
pid, stdin, stdout, stderr = Open4.popen4(command)
ignored, status = Process::waitpid2 pid
unless status.success?
raise GitCommandError, "Error running Git command: #{command}\n#{stdout.read}\n#{stderr.read}"
end
stdout.read
ensure
stdin.close
stdout.close
stderr.close
end
class GitCommandError < StandardError
end
end
end