lib/braid/operations.rb in evilchelu-braid-0.4.0 vs lib/braid/operations.rb in evilchelu-braid-0.4.10

- old
+ new

@@ -1,297 +1,322 @@ +require 'singleton' +require 'rubygems' +require 'open4' + module Braid module Operations - module Git - def git_commit(message) - status, out, err = exec("git commit -m #{message.inspect} --no-verify") + class ShellExecutionError < BraidError + def initialize(err = nil) + @err = err + end + def message + @err.to_s.split("\n").first + end + end + class VersionTooLow < BraidError + def initialize(command, version) + @command = command + @version = version.to_s.split("\n").first + end + + def message + "#{@command} version too low: #{@version}" + end + end + class UnknownRevision < BraidError + def message + "unknown revision: #{super}" + end + end + class LocalChangesPresent < BraidError + def message + "local changes are present" + end + end + + # The command proxy is meant to encapsulate commands such as git, git-svn and svn, that work with subcommands. + class Proxy + include Singleton + + def self.command; name.split('::').last.downcase; end # hax! + + def version + status, out, err = exec!("#{self.class.command} --version") + out.sub(/^.* version/, "").strip + end + + def require_version(required) + required = required.split(".") + actual = version.split(".") + + actual.each_with_index do |actual_piece, idx| + required_piece = required[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.length >= required.length + end + + def require_version!(required) + require_version(required) || raise(VersionTooLow.new(self.class.command, version)) + end + + private + def command(name) + # stub + name + end + + def invoke(arg, *args) + exec!("#{command(arg)} #{args.join(' ')}".strip)[1].strip # return stdout + end + + def method_missing(name, *args) + invoke(name, *args) + end + + def exec(cmd) + cmd.strip! + + previous_lang = ENV['LANG'] + ENV['LANG'] = 'C' + + out, err = nil + status = Open4.popen4(cmd) do |pid, stdin, stdout, stderr| + out = stdout.read + err = stderr.read + end.exitstatus + [status, out, err] + + ensure + ENV['LANG'] = previous_lang + end + + def exec!(cmd) + status, out, err = exec(cmd) + raise ShellExecutionError, err unless status == 0 + [status, out, err] + end + + def msg(str) + puts str + end + + end + + class Git < Proxy + def commit(message, *args) + status, out, err = exec("git commit -m #{message.inspect} --no-verify #{args.join(' ')}") + if status == 0 true - elsif out.match("nothing to commit") + elsif out.match(/nothing.* to commit/) false else - raise Braid::Commands::ShellExecutionError, err + raise ShellExecutionError, err end end - def git_fetch(remote) + def fetch(remote) # open4 messes with the pipes of index-pack system("git fetch -n #{remote} &> /dev/null") - raise Braid::Commands::ShellExecutionError unless $? == 0 + raise ShellExecutionError, "could not fetch" unless $? == 0 true end - def git_checkout(treeish) + def checkout(treeish) # TODO debug msg "Checking out '#{treeish}'." - exec!("git checkout #{treeish}") + invoke(: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 + def merge_base(target, source) + invoke(:merge_base, target, source) + rescue ShellExecutionError nil end - def git_rev_parse(commit) - status, out, err = exec!("git rev-parse #{commit}") - out.strip + def rev_parse(opt) + invoke(:rev_parse, opt) + rescue ShellExecutionError + raise UnknownRevision, opt end # Implies tracking. - def git_remote_add(remote, path, branch) - exec!("git remote add -t #{branch} -m #{branch} #{remote} #{path}") + def remote_add(remote, path, branch) + invoke(:remote, "add", "-t #{branch} -m #{branch}", remote, path) true end - def git_reset_hard(target) - exec!("git reset --hard #{target}") + # Checks git and svn remotes. + def remote_exists?(remote) + # TODO clean up and maybe return more information + !!File.readlines(".git/config").find { |line| line =~ /^\[(svn-)?remote "#{Regexp.escape(remote)}"\]/ } + end + + def reset_hard(target) + invoke(:reset, "--hard", target) true end # Implies no commit. - def git_merge_ours(commit) - exec!("git merge -s ours --no-commit #{commit}") + def merge_ours(opt) + invoke(:merge, "-s ours --no-commit", opt) true end # Implies no commit. - def git_merge_subtree(commit) + def merge_subtree(opt) # TODO which options are needed? - exec!("git merge -s subtree --no-commit --no-ff #{commit}") + invoke(:merge, "-s subtree --no-commit --no-ff", opt) true end - def git_read_tree(treeish, prefix) - exec!("git read-tree --prefix=#{prefix}/ -u #{treeish}") + def read_tree(treeish, prefix) + invoke(:read_tree, "--prefix=#{prefix}/ -u", treeish) true end - def git_rm_r(path) - exec!("git rm -r #{path}") + def rm_r(path) + invoke(: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? + def tree_hash(path, treeish = "HEAD") + out = invoke(:ls_tree, treeish, "-d", path) + out.split[2] 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 + def diff_tree(src_tree, dst_tree, prefix = nil) + cmd = "git diff-tree -p --binary #{src_tree} #{dst_tree}" + cmd << " --src-prefix=a/#{prefix}/ --dst-prefix=b/#{prefix}/" if prefix + status, out, err = exec!(cmd) + out 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) + def status_clean? + status, out, err = exec("git status") + !out.split("\n").grep(/nothing to commit/).empty? end - def git_svn_fetch(remote) - # open4 messes with the pipes of index-pack - system("git svn fetch #{remote} &> /dev/null") - true + def ensure_clean! + status_clean? || raise(LocalChangesPresent) end - def git_svn_init(remote, path) - exec!("git svn init -R #{remote} --id=#{remote} #{path}") - true + def head + rev_parse("HEAD") end - end - module Helpers - [:invoke, :exec, :exec!].each do |method| - define_method(method) do |*args| - Braid::Operations.send(method, *args) - end + def branch + status, out, err = exec!("git branch | grep '*'") + out[2..-1] end - def extract_git_version - status, out, err = exec!("git --version") - return out.sub(/^git version/, "").strip + def apply(diff, *args) + err = nil + status = Open4.popen4("git apply --index --whitespace=nowarn #{args.join(' ')} -") do |pid, stdin, stdout, stderr| + stdin.puts(diff) + stdin.close + + err = stderr.read + end.exitstatus + raise ShellExecutionError, err unless status == 0 + true end - def verify_git_version(required) - required_version = required.split(".") - actual_version = extract_git_version.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 + private + def command(name) + "#{self.class.command} #{name.to_s.gsub('_', '-')}" end + end - return actual_version.length >= required_version.length - end + class GitSvn < Proxy + def self.command; "git svn"; end - def find_git_revision(commit) - invoke(:git_rev_parse, commit) - rescue Braid::Commands::ShellExecutionError - raise Braid::Git::UnknownRevision, "unknown revision: #{commit}" + def commit_hash(remote, revision) + out = invoke(:log, "--show-commit --oneline", "-r #{revision}", remote) + part = out.to_s.split(" | ")[1] + raise UnknownRevision, "r#{revision}" unless part + Git.instance.rev_parse(part) # FIXME ugly ugly ugly 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 - + def fetch(remote) + # open4 messes with the pipes of index-pack + system("git svn fetch #{remote} &> /dev/null") + raise ShellExecutionError, "could not fetch" unless $? == 0 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 - + def init(remote, path) + invoke(:init, "-R", remote, "--id=#{remote}", path) 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"]) + private + def command(name) + "#{self.class.command} #{name}" 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] + class Svn < Proxy + def clean_revision(revision) + revision.to_i if revision 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 + def 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 + end - def get_work_head - find_git_revision(WORK_BRANCH) - end + class GitCache < Proxy + def init_or_fetch(url, dir) + if File.exists? dir + msg "Updating local cache of '#{url}' into '#{dir}'." + FileUtils.cd(dir) do |d| + status, out, err = exec!("git fetch") + end + else + FileUtils.mkdir_p(Braid::LOCAL_CACHE_DIR) - 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 + msg "Caching '#{url}' into '#{dir}'." + status, out, err = exec!("git clone --mirror #{url} #{dir}") end - - true end + 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 + module VersionControl + def git + Git.instance end - def find_remote(remote) - # TODO clean up and maybe return more information - !!File.readlines(".git/config").find { |line| line =~ /^\[(svn-)?remote "#{remote}"\]/ } + def git_svn + GitSvn.instance 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 + def svn + Svn.instance 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) + def git_cache + GitCache.instance end + end end end + +