Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
478 additions
and
127 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
guard 'rspec', :version => 2, :notification => false do | ||
watch(%r{^spec/.+_spec\.rb$}) | ||
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,66 +1,160 @@ | ||
# interface for working on git repos, insulates app from underlying implementation | ||
|
||
# todo: only call git via array so no shell interp issues | ||
# todo: make a way for caller to tell Repo to indent all messages | ||
require 'gitrb' | ||
require 'retryable' | ||
|
||
|
||
class GitRepo | ||
include Retryable # only for network operations | ||
|
||
class GitError < RuntimeError; end | ||
attr_reader :root, :bare | ||
|
||
# required: :root, the directory to contain the repo | ||
# optional: :clone a repo to clone (:bare => true if it should be bare) | ||
# :create to create a new empty repo if it doesn't already exist | ||
def initialize opts | ||
@root = opts[:root] | ||
@bare = opts[:bare] | ||
|
||
retryable_options opts[:retryable_options] if opts[:retryable_options] | ||
|
||
if opts[:clone] | ||
retryable(:task => "cloning #{opts[:clone]}") do | ||
# todo: add support for :bare | ||
output = `git clone #{opts[:clone]} #{opts[:root]} 2>&1` | ||
raise GitError.new("git clone failed: #{output}") unless $?.success? | ||
args = [opts[:clone], opts[:root]] | ||
args.push '--bare' if opts[:bare] | ||
git_exec :clone, *args | ||
end | ||
elsif opts[:create] | ||
Dir.mkdir opts[:root] unless test ?d, opts[:root] | ||
if opts[:bare] | ||
git :init, '--bare' | ||
else | ||
git :init | ||
end | ||
end | ||
|
||
# a little sanity checking, catch simple errors early | ||
raise GitError.new "#{@root} does not exist" unless test ?d, @root | ||
if opts[:bare] | ||
raise GitError.new "#{@root} does not appear to be a bare repo" unless test(?d, File.join(@root, 'objects')) | ||
else | ||
raise "#{@root} doesn't exist" unless test ?d, @root | ||
raise GitError.new "#{@root}/.git does not exist" unless test(?d, File.join(@root, '.git')) | ||
raise GitError.new "#{@root}/.git does not appear to be a git repo" unless test(?d, File.join(@root, '.git', 'objects')) | ||
end | ||
end | ||
|
||
def git_exec *args | ||
options = { :before_exec => lambda { } } | ||
options.merge! args.pop if args[-1].is_a? Hash | ||
args = args.map { |a| a.to_s } | ||
|
||
out = IO.popen('-', 'r') do |io| | ||
if io # parent | ||
block_given? ? yield(io) : io.read | ||
else # child | ||
STDERR.reopen STDOUT | ||
options[:before_exec].call | ||
exec 'git', *args | ||
end | ||
end | ||
|
||
if $?.exitstatus > 0 | ||
raise GitError.new("git #{args.join(' ')}: #{out}") | ||
end | ||
|
||
out | ||
end | ||
|
||
def git *args | ||
Dir.chdir(@root) do | ||
git_exec *args | ||
end | ||
end | ||
|
||
# i.e. remote_add 'rails', 'http://github.com/rails/rails.git' | ||
def remote_add name, remote | ||
Dir.chdir(@root) { | ||
output = `git remote add #{name} #{remote} 2>&1` | ||
raise GitError.new("generate_docs: git remote add #{name} failed: #{output}") unless $?.success? | ||
} | ||
git :remote, :add, name, remote | ||
end | ||
|
||
# todo: get rid of this call, should be regular git add / git commit | ||
def commit_all message | ||
Dir.chdir(@root) { | ||
output = `git commit -a -m '#{message}' 2>&1` | ||
if output =~ /nothing to commit/ | ||
puts " no changes to generated files" | ||
else | ||
raise GitError.new("generate_docs: git commit failed: #{output}") unless $?.success? | ||
end | ||
} | ||
def remote_remove name | ||
git :remote, :rm, name | ||
end | ||
|
||
|
||
def pull *args | ||
Dir.chdir(@root) do | ||
retryable(:task => "pulling #{args.join ' '}") do | ||
# Can we tell the difference between a network error, which we want to retry, | ||
# and a merge error, which we want to fail immediately? | ||
output = `git pull --no-rebase #{args.join ' '} 2>&1` | ||
raise GitError.new("generate_docs: git pull failed: #{output}") unless $?.success? | ||
end | ||
# Can we tell the difference between a network error, which we want to retry, | ||
# and a merge error, which we want to fail immediately? | ||
retryable(:task => "pulling #{args.join ' '}") do | ||
git :pull, '--no-rebase', *args | ||
end | ||
end | ||
|
||
def push *args | ||
Dir.chdir(@root) do | ||
retryable(:task => "pushing #{args.join ' '}") do | ||
output = `git push #{args.join ' '} 2>&1` | ||
raise "generate_docs: git push failed: #{output}" unless $?.success? | ||
end | ||
# like pull, can we tell the difference between a network error and local error? | ||
retryable(:task => "pushing #{args.join ' '}") do | ||
git :push, *args | ||
end | ||
end | ||
|
||
|
||
def create_tag name, message, committer | ||
# gitrb doesn't handle annotated tags so we call git directly | ||
git :tag, '-a', name, '-m', message, :before_exec => lambda { | ||
ENV['GIT_COMMITTER_NAME'] = committer[:name] | ||
ENV['GIT_COMMITTER_EMAIL'] = committer[:email] | ||
ENV['GIT_COMMITTER_DATE'] = (committer[:date] || Time.now).strftime("%s %z") | ||
} | ||
end | ||
|
||
def find_tag tagname | ||
tag = git :tag, '-l', tagname | ||
return !tag || tag =~ /^\s*$/ ? nil : tag.chomp | ||
end | ||
|
||
|
||
# all the things you can do while committing | ||
class CommitHelper | ||
def initialize repo | ||
@repo = repo | ||
end | ||
|
||
# this empties out the commit tree so you can start fresh | ||
def empty_index | ||
entries.each { |name| remove name } | ||
end | ||
|
||
def remove name | ||
@repo.root.delete(name).dump | ||
end | ||
|
||
def add name, contents | ||
# an empty file is represented by the empty string so contents==nil is an error | ||
raise GitError.new "no data in #{name}: #{contents.inspect}" unless contents | ||
@repo.root[name] = Gitrb::Blob.new(:data => contents) | ||
end | ||
|
||
def entries | ||
@repo.root.to_a.map { |name,value| name } | ||
end | ||
|
||
# If the entry is a tree, returns an array of the names in the tree. | ||
# If a blob, returns the blob data. Othewise returns nil. | ||
def entry name, type=nil | ||
data = @repo.root[name] | ||
raise GitError.new "type was #{data.type} not #{type}" if type && type != data.type | ||
return data.map { |n,v| n } if data && data.type == :tree | ||
return data.dump if data && data.type == :blob | ||
end | ||
end | ||
|
||
|
||
def commit message, author, committer=author | ||
author = Gitrb::User.new(author[:name], author[:email], author[:date] || Time.now) | ||
committer = Gitrb::User.new(committer[:name], committer[:email], committer[:date] || Time.now) | ||
# gitrb has a bug where it will complain about frozen strings unless you dup the path | ||
repo = Gitrb::Repository.new(:path => root.dup, :bare => bare, :create => false) | ||
repo.transaction(message, author, committer) do | ||
yield CommitHelper.new(repo) | ||
end | ||
end | ||
end |
Oops, something went wrong.