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
+
+