module Braid
module Operations
module Git
def git_commit(message)
status, out, err = exec("git commit -m #{message.inspect} --no-verify")
if status == 0
true
elsif out.match(/nothing.* to commit/)
false
else
raise Braid::Commands::ShellExecutionError, err
end
end
def git_fetch(remote)
# open4 messes with the pipes of index-pack
system("git fetch -n #{remote} &> /dev/null")
raise Braid::Commands::ShellExecutionError unless $? == 0
true
end
def git_checkout(treeish)
# TODO debug
msg "Checking out '#{treeish}'."
exec!("git checkout #{treeish}")
true
end
# Returns the base commit or nil.
def git_merge_base(target, source)
status, out, err = exec!("git merge-base #{target} #{source}")
out.strip
rescue Braid::Commands::ShellExecutionError
nil
end
def git_rev_parse(commit)
status, out, err = exec!("git rev-parse #{commit}")
out.strip
end
# Implies tracking.
def git_remote_add(remote, path, branch)
exec!("git remote add -t #{branch} -m #{branch} #{remote} #{path}")
true
end
def git_reset_hard(target)
exec!("git reset --hard #{target}")
true
end
# Implies no commit.
def git_merge_ours(commit)
exec!("git merge -s ours --no-commit #{commit}")
true
end
# Implies no commit.
def git_merge_subtree(commit)
# TODO which options are needed?
exec!("git merge -s subtree --no-commit --no-ff #{commit}")
true
end
def git_read_tree(treeish, prefix)
exec!("git read-tree --prefix=#{prefix}/ -u #{treeish}")
true
end
def git_rm_r(path)
exec!("git rm -r #{path}")
true
end
def local_changes?
status, out, err = exec("git status")
out.split("\n").grep(/nothing to commit \(working directory clean\)/).empty?
end
end
module Svn
# FIXME move
def svn_remote_head_revision(path)
# not using svn info because it's retarded and doesn't show the actual last changed rev for the url
# git svn has no clue on how to get the actual HEAD revision number on it's own
status, out, err = exec!("svn log -q --limit 1 #{path}")
out.split(/\n/).find { |x| x.match /^r\d+/ }.split(" | ")[0][1..-1].to_i
end
# FIXME move
def svn_git_commit_hash(remote, revision)
status, out, err = exec!("git svn log --show-commit --oneline -r #{revision} #{remote}")
part = out.split(" | ")[1]
raise Braid::Svn::UnknownRevision, "unknown revision: #{revision}" unless part
invoke(:git_rev_parse, part)
end
def git_svn_fetch(remote)
# open4 messes with the pipes of index-pack
system("git svn fetch #{remote} &> /dev/null")
true
end
def git_svn_init(remote, path)
exec!("git svn init -R #{remote} --id=#{remote} #{path}")
true
end
end
module Helpers
[:invoke, :exec, :exec!].each do |method|
define_method(method) do |*args|
Braid::Operations.send(method, *args)
end
end
def extract_version(cmd)
status, out, err = exec!("#{cmd} --version")
return out.sub(/^.* version/, "").strip
end
def verify_version(cmd, required)
required_version = required.split(".")
actual_version = extract_version(cmd).split(".")
actual_version.each_with_index do |actual_piece, idx|
required_piece = required_version[idx]
return true unless required_piece
case (actual_piece <=> required_piece)
when -1
return false
when 1
return true
when 0
next
end
end
return actual_version.length >= required_version.length
end
def find_git_revision(commit)
invoke(:git_rev_parse, commit)
rescue Braid::Commands::ShellExecutionError
raise Braid::Git::UnknownRevision, "unknown revision: #{commit}"
end
def clean_svn_revision(revision)
if revision
revision.to_i
else
nil
end
end
def validate_svn_revision(old_revision, new_revision, path)
return unless new_revision = clean_svn_revision(new_revision)
old_revision = clean_svn_revision(old_revision)
# TODO add checks for unlocked mirrors
if old_revision
if new_revision < old_revision
raise Braid::Commands::LocalRevisionIsHigherThanRequestedRevision
elsif new_revision == old_revision
raise Braid::Commands::MirrorAlreadyUpToDate
end
end
if path && invoke(:svn_remote_head_revision, path) < new_revision
raise Braid::Commands::RequestedRevisionIsHigherThanRemoteRevision
end
true
end
# Make sure the revision is valid, then clean it.
def validate_revision_option(params, options)
if options["revision"]
case params["type"]
when "git"
options["revision"] = find_git_revision(options["revision"])
when "svn"
validate_svn_revision(params["revision"], options["revision"], params["remote"])
options["revision"] = clean_svn_revision(options["revision"])
end
end
true
end
def determine_target_commit(params, options)
if options["revision"]
if params["type"] == "svn"
invoke(:svn_git_commit_hash, params["local_branch"], options["revision"])
else
invoke(:git_rev_parse, options["revision"])
end
else
invoke(:git_rev_parse, params["local_branch"])
end
end
def display_revision(type, revision)
type == "svn" ? "r#{revision}" : "'#{revision[0, 7]}'"
end
end
module Mirror
def get_current_branch
status, out, err = exec!("git branch | grep '*'")
out[2..-1]
end
def create_work_branch
# check if branch exists
status, out, err = exec("git branch | grep '#{WORK_BRANCH}'")
if status != 0
# then create it
msg "Creating work branch '#{WORK_BRANCH}'."
exec!("git branch #{WORK_BRANCH}")
end
true
end
def get_work_head
find_git_revision(WORK_BRANCH)
end
def add_config_file
exec!("git add #{CONFIG_FILE}")
true
end
def check_merge_status(commit)
commit = find_git_revision(commit)
# tip from spearce in #git:
# `test z$(git merge-base A B) = z$(git rev-parse --verify A)`
if invoke(:git_merge_base, commit, "HEAD") == commit
raise Braid::Commands::MirrorAlreadyUpToDate
end
true
end
def fetch_remote(type, remote)
msg "Fetching data from '#{remote}'."
case type
when "git"
invoke(:git_fetch, remote)
when "svn"
invoke(:git_svn_fetch, remote)
end
end
def find_remote(remote)
# TODO clean up and maybe return more information
!!File.readlines(".git/config").find { |line| line =~ /^\[(svn-)?remote "#{remote}"\]/ }
end
end
extend Git
extend Svn
def self.invoke(*args)
send(*args)
end
def self.exec(cmd)
#puts cmd
out = ""
err = ""
cmd.strip!
ENV['LANG'] = 'C' unless ENV['LANG'] == 'C'
status = Open4::popen4(cmd) do |pid, stdin, stdout, stderr|
out = stdout.read.strip
err = stderr.read.strip
end
[status.exitstatus, out, err]
end
def self.exec!(cmd)
status, out, err = exec(cmd)
raise Braid::Commands::ShellExecutionError, err unless status == 0
return status, out, err
end
private
def self.msg(str)
Braid::Command.msg(str)
end
end
end