diff --git a/Rakefile b/Rakefile index 220baf6..e64ac8f 100644 --- a/Rakefile +++ b/Rakefile @@ -8,7 +8,7 @@ begin s.email = "francois@teksol.info" s.homepage = "http://francois.github.com/piston" s.description = "Piston makes it easy to merge vendor branches into your own repository, without worrying about which revisions were grabbed or not. Piston will also keep your local changes in addition to the remote changes." - s.authors = "François Beausoleil" + s.authors = "Francois Beausoleil" s.has_rdoc = false s.rubyforge_project = "piston" @@ -21,3 +21,5 @@ begin rescue LoadError puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com" end + +task :default => :test diff --git a/lib/piston.rb b/lib/piston.rb index 9ace1b4..1393d84 100644 --- a/lib/piston.rb +++ b/lib/piston.rb @@ -6,13 +6,14 @@ require "piston/git" require "piston/svn" +require "piston/commands" require "pathname" module Piston class << self def version_message - "Piston %s\nCopyright 2006-2008, François Beausoleil \nhttp://piston.rubyforge.org/\nDistributed under an MIT-like license." % Piston::VERSION::STRING + "Piston %s\nCopyright 2006-2008, Francois Beausoleil \nhttp://piston.rubyforge.org/\nDistributed under an MIT-like license." % Piston::VERSION::STRING end end end diff --git a/lib/piston/cli.rb b/lib/piston/cli.rb index 3c8172b..22bc178 100644 --- a/lib/piston/cli.rb +++ b/lib/piston/cli.rb @@ -1,391 +1,121 @@ -require "main" require "log4r" require "activesupport" require "piston/version" -require "piston/commands" - -Main { - program "piston" - author "François Beausoleil " - version Piston::VERSION::STRING - - mixin :standard_options do - option("verbose", "v") { - argument_optional - cast :integer - default 0 - validate {|value| (0..5).include?(value)} - description "Verbosity level (0 to 5, 0 being the default)" - } - option("quiet", "q") { default false } - option("force") { default false } - option("dry-run") { default false } - end - - mixin :revision_or_commit do - option "revision", "r" do - argument_required - description "The revision you wish to operate on" - end - - option "commit" do - argument_required - description "The commit you wish to operate on" - end - - def target_revision - case - when params["revision"].given? - params["revision"].value - when params["commit"].given? - params["commit"].value - else - :head - end - end - end - - mixin :lock_options do - option("lock") do - default false - description "Automatically lock down the revision/commit to protect against blanket updates" - end - end - - mode "import" do - mixin :standard_options - mixin :revision_or_commit - mixin :lock_options - - argument "repository" do - required - description "The repository you wish to Pistonize" - end - - argument "directory" do - optional - default nil - description "Where to put the Pistonized repository" - end - - option("repository-type") do - argument :required - default nil - description "Force a specific repository type, for when it's not possible to guess" - end - - logger_level Logger::DEBUG - def run - configure_logging! - - if params["revision"].given? && params["commit"].given? then - raise ArgumentError, "Only one of --revision or --commit can be given. Received both." - end - - cmd = Piston::Commands::Import.new(:verbose => params["verbose"].value, - :quiet => params["quiet"].value, - :force => params["force"].value, - :dry_run => params["dry-run"].value, - :repository_type => params["repository-type"].value) - - begin - cmd.run(params[:repository].value, self.target_revision, params[:directory].value) - rescue Piston::Repository::UnhandledUrl => e - supported_types = Piston::Repository.handlers.collect do |handler| - handler.repository_type - end - puts "Unsure how to handle:" - puts "\t#{params[:repository].value.inspect}." - puts "You should try using --repository-type. Supported types are:" - supported_types.each do |type| - puts "\t#{type}" +require "optparse" + +module Piston + class Cli + def self.start(args=ARGV) + options = {:lock => false, :force => false, :dry_run => false, :quiet => false, :verbose => 0} + opts = OptionParser.new do |opts| + opts.banner = "Usage: piston COMMAND [options]" + opts.version = Piston::VERSION::STRING + + # Many!!! + opts.on("-r", "--revision REVISION", "Revision to operate on") do |r| + options[:revision] = r.to_i end - exit_failure! - end - - # Lock the working copy, if the user asked for it - cmd = Piston::Commands::LockUnlock.new(:verbose => params["verbose"].value, - :quiet => params["quiet"].value, - :force => params["force"].value, - :dry_run => params["dry-run"].value) - cmd.run(params["directory"].value, params["lock"].value) if params["lock"].value - end - end - - mode "convert" do - mixin :standard_options - - argument "directories" do - argument_required - optional - arity -1 - description "Which directory/directories to convert from svn:externals to Piston. Not specifying this argument recursively converts the whole directory tree starting from the current dir." - end - - logger_level Logger::DEBUG - def run - configure_logging! - - cmd = Piston::Commands::Convert.new(:verbose => params["verbose"].value, - :quiet => params["quiet"].value, - :force => params["force"].value) - dirs = cmd.run(params["directories"].values.map {|dir| Pathname.new(dir)}) - puts "#{dirs.length} directories converted" - end - end - - mode "upgrade" do - mixin :standard_options - - argument "directories" do - optional - default '.' - arity -1 - description "Which directory/directories to convert from Piston 1.x to Piston 2.x. Not specifying this argument recursively converts the whole directory tree starting from the current dir." - end - - logger_level Logger::DEBUG - def run - configure_logging! - - cmd = Piston::Commands::Upgrade.new(:verbose => params["verbose"].value, - :quiet => params["quiet"].value, - :force => params["force"].value) - dirs = cmd.run(params["directories"].values.map { |dir| Pathname.new(dir).expand_path }) - puts "#{dirs.length} directories upgraded" - end - end - - mode "lock" do - mixin :standard_options - - argument "directory" do - argument_required - optional - description "Which directory to lock" - end - logger_level Logger::DEBUG - def run - configure_logging! - - cmd = Piston::Commands::LockUnlock.new(:wcdir => params["directory"].value, - :verbose => params["verbose"].value, - :quiet => params["quiet"].value, - :force => params["force"].value) - begin - cmd.run(true) - puts "#{params["directory"].value} locked" - rescue Piston::WorkingCopy::NotWorkingCopy - puts "The #{params["directory"].value} is not Pistonized" - end - end - end - - mode "unlock" do - mixin :standard_options - - argument "directory" do - argument_required - optional - description "Which directory to lock" - end - - logger_level Logger::DEBUG - def run - configure_logging! - - cmd = Piston::Commands::LockUnlock.new(:wcdir => params["directory"].value, - :verbose => params["verbose"].value, - :quiet => params["quiet"].value, - :force => params["force"].value) - begin - cmd.run(false) - puts "#{params["directory"].value} unlocked" - rescue Piston::WorkingCopy::NotWorkingCopy - puts "The #{params["directory"].value} is not Pistonized" - end - end - end - - mode "status" do - mixin :standard_options + opts.on("--commit TREEISH", "Commit to operate on") do |c| + options[:commit] = c + end - option "show-updates", "s" do - default false - description "Query the remote repository for out of dateness information" - end + # Import + opts.on("--repository-type TYPE", [:git, :svn], "Force selection of a repository handler (git or svn)") do |type| + options[:repository_type] = type + end - argument "directories" do - optional - default '.' - arity -1 - description "Which directory/directories to get status. Not specifying this argument recursively gets status from the whole directory tree starting from the current dir." - end + # Import, Update and Switch + opts.on("--lock", "Lock down the revision against mass-updates") do + options[:lock] = true + end - logger_level Logger::DEBUG - def run - configure_logging! + opts.on("--show-updates", "Query the remote repository for out-of-dateness information") do + options[:show_updates] = true + end - cmd = Piston::Commands::Status.new(:show_updates => params["show-updates"].value, - :verbose => params["verbose"].value, - :quiet => params["quiet"].value, - :force => params["force"].value) - params["directories"].values.each do |path| - begin - cmd.run(Pathname.new(path).expand_path) - rescue Piston::WorkingCopy::NotWorkingCopy - puts "#{path} is not a working copy" + # All + opts.on("--force", "Force the operation to go ahead") do + options[:force] = true end - end - end - end - - mode "diff" do - mixin :standard_options - argument "directory" do - argument_required - description "Which directory to get differences between local and remote repositories." - end + opts.on("--dry-run", "Run but do not change anything") do + options[:dry_run] = true + end - logger_level Logger::DEBUG - def run - configure_logging! + opts.on("-q", "--quiet", "Operate silently") do + options[:quiet] = true + end - cmd = Piston::Commands::Diff.new(:wcdir => File.expand_path(params["directory"].value), - :verbose => params["verbose"].value, - :quiet => params["quiet"].value, - :force => params["force"].value) - begin - cmd.run - rescue Piston::WorkingCopy::NotWorkingCopy - puts "#{params["directory"].value} is not Pistonized" + opts.on("-v", "--verbose [LEVEL]", ("0".."5").to_a, "Increase verbosity (default 0)") do |level| + options[:verbose] = level.to_i || 1 + end end - end - end - - mode "info" do - mixin :standard_options - - argument "directory" do - argument_required - optional - description "Which directory to get info" - end - - logger_level Logger::DEBUG - def run - configure_logging! + opts.parse!(args) - cmd = Piston::Commands::Info.new(:wcdir => params["directory"].value, - :verbose => params["verbose"].value, - :quiet => params["quiet"].value, - :force => params["force"].value) - begin - puts cmd.run(params["directory"].value) - rescue Piston::WorkingCopy::NotWorkingCopy - puts "#{params["directory"].value} is not Pistonized" + if args.empty? + puts opts.help + exit 0 end - end - end - - mode "update" do - mixin :standard_options - mixin :revision_or_commit - mixin :lock_options - - argument("directory") do - optional - default '.' - end - - logger_level Logger::DEBUG - def run - configure_logging! - if params["revision"].given? && params["commit"].given? then + if options.has_key?(:revision) && options.has_key?(:commit) then raise ArgumentError, "Only one of --revision or --commit can be given. Received both." end - cmd = Piston::Commands::Update.new(:lock => params["lock"].value, - :verbose => params["verbose"].value, - :quiet => params["quiet"].value, - :force => params["force"].value, - :dry_run => params["dry-run"].value) - - begin - cmd.run(File.expand_path(params["directory"].value), target_revision) - rescue - $stderr.puts $!.message - $stderr.puts $!.backtrace.join("\n") - exit_failure! + configure_logging(options) + command = Piston::Commands.const_get(args.shift.classify).new(options) + command.start(*args) + end + + def self.configure_logging(options) + Log4r::Logger.root.level = Log4r::DEBUG + + case options[:verbose] + when 0 + main_level = Log4r::INFO + handler_level = Log4r::WARN + client_level = Log4r::WARN + client_out_level = Log4r::WARN + stdout_level = Log4r::INFO + when 1 + main_level = Log4r::DEBUG + handler_level = Log4r::INFO + client_level = Log4r::WARN + client_out_level = Log4r::WARN + stdout_level = Log4r::DEBUG + when 2 + main_level = Log4r::DEBUG + handler_level = Log4r::DEBUG + client_level = Log4r::INFO + client_out_level = Log4r::WARN + stdout_level = Log4r::DEBUG + when 3 + main_level = Log4r::DEBUG + handler_level = Log4r::DEBUG + client_level = Log4r::DEBUG + client_out_level = Log4r::INFO + stdout_level = Log4r::DEBUG + when 4, 5 + main_level = Log4r::DEBUG + handler_level = Log4r::DEBUG + client_level = Log4r::DEBUG + client_out_level = Log4r::DEBUG + stdout_level = Log4r::DEBUG + else + raise ArgumentError, "Did not expect verbosity to be outside 0..5: #{options[:verbose]}" end - end - end - option("version", "v") + Log4r::Logger.new("main", main_level) + Log4r::Logger.new("handler", handler_level) + Log4r::Logger.new("handler::client", client_level) + Log4r::Logger.new("handler::client::out", client_out_level) - def run - if params["version"].given? || ARGV.first == "version" then - puts Piston.version_message - exit_success! - elsif ARGV.empty? - puts Piston.version_message - puts "\nNo mode given. Call with help to find out the available options." - exit_failure! - else - puts "Unrecognized mode: #{ARGV.first.inspect}. Use the help mode to find the available options." - exit_warn! - end - end + Log4r::StdoutOutputter.new("stdout", :level => stdout_level) - def configure_logging! - Log4r::Logger.root.level = Log4r::DEBUG - - case params["verbose"].value - when 0 - main_level = Log4r::INFO - handler_level = Log4r::WARN - client_level = Log4r::WARN - client_out_level = Log4r::WARN - stdout_level = Log4r::INFO - when 1 - main_level = Log4r::DEBUG - handler_level = Log4r::INFO - client_level = Log4r::WARN - client_out_level = Log4r::WARN - stdout_level = Log4r::DEBUG - when 2 - main_level = Log4r::DEBUG - handler_level = Log4r::DEBUG - client_level = Log4r::INFO - client_out_level = Log4r::WARN - stdout_level = Log4r::DEBUG - when 3 - main_level = Log4r::DEBUG - handler_level = Log4r::DEBUG - client_level = Log4r::DEBUG - client_out_level = Log4r::INFO - stdout_level = Log4r::DEBUG - when 4, 5 - main_level = Log4r::DEBUG - handler_level = Log4r::DEBUG - client_level = Log4r::DEBUG - client_out_level = Log4r::DEBUG - stdout_level = Log4r::DEBUG - else - raise ArgumentError, "Did not expect verbosity to be outside 0..5: #{params["verbose"].value}" + Log4r::Logger["main"].add "stdout" + Log4r::Logger["handler"].add "stdout" end - - Log4r::Logger.new("main", main_level) - Log4r::Logger.new("handler", handler_level) - Log4r::Logger.new("handler::client", client_level) - Log4r::Logger.new("handler::client::out", client_out_level) - - Log4r::StdoutOutputter.new("stdout", :level => stdout_level) - - Log4r::Logger["main"].add "stdout" - Log4r::Logger["handler"].add "stdout" end -} +end + +Piston::Cli.start diff --git a/lib/piston/commands/convert.rb b/lib/piston/commands/convert.rb index c5fc0f5..39734f9 100644 --- a/lib/piston/commands/convert.rb +++ b/lib/piston/commands/convert.rb @@ -21,6 +21,12 @@ def run(targets) wc.remove_external_references(*targets) end end - end - end + + def start(*args) + targets = args.flatten.map {|d| Pathname.new(d).expand_path} + run(targets) + puts "#{targets.length} directories converted" + end + end + end end diff --git a/lib/piston/commands/diff.rb b/lib/piston/commands/diff.rb index 7d9fc12..3bb58a3 100644 --- a/lib/piston/commands/diff.rb +++ b/lib/piston/commands/diff.rb @@ -7,6 +7,17 @@ def run working_copy = working_copy!(options[:wcdir]) working_copy.diff end + + def start(*args) + args.flatten.map {|d| Pathname.new(d).expand_path}.each do |wcdir| + begin + options[:wcdir] = wcdir + run + rescue Piston::WorkingCopy::NotWorkingCopy + puts "#{wcdir} is not a working copy" + end + end + end end end end diff --git a/lib/piston/commands/import.rb b/lib/piston/commands/import.rb index 84a24b6..29bc599 100644 --- a/lib/piston/commands/import.rb +++ b/lib/piston/commands/import.rb @@ -38,6 +38,29 @@ def run(repository_url, target_revision, wcdir) working_copy.import(revision, options[:lock]) logger.info {"Imported #{revision} from #{repository}"} end + + def start(*args) + repository_url = args.shift + wcdir = args.shift + + raise ArgumentError, "Required REPOSITORY argument missing" if repository_url.blank? + + begin + self.run(repository_url, options[:revision] || options[:commit] || :head, wcdir) + rescue Piston::Repository::UnhandledUrl => e + supported_types = Piston::Repository.handlers.collect do |handler| + handler.repository_type + end + puts "Unsure how to handle:" + puts "\t#{repository_url.inspect}." + puts "You should try using --repository-type. Supported types are:" + supported_types.each do |type| + puts "\t#{type}" + end + + exit 1 + end + end end end end diff --git a/lib/piston/commands/info.rb b/lib/piston/commands/info.rb index 5f70363..4965d0f 100644 --- a/lib/piston/commands/info.rb +++ b/lib/piston/commands/info.rb @@ -9,6 +9,16 @@ def run(wcdir) working_copy = working_copy!(wcdir) working_copy.info.to_yaml end + + def start(*args) + args.flatten.map {|d| Pathname.new(d).expand_path}.each do |wcdir| + begin + run(wcdir) + rescue Piston::WorkingCopy::NotWorkingCopy + puts "#{wcdir} is not a working copy" + end + end + end end end end diff --git a/lib/piston/commands/lock_unlock.rb b/lib/piston/commands/lock_unlock.rb index 5a7f34f..69dd3af 100644 --- a/lib/piston/commands/lock_unlock.rb +++ b/lib/piston/commands/lock_unlock.rb @@ -16,6 +16,11 @@ def run(lock) text = lock ? "Locked" : "Unlocked" logger.info "#{text} #{working_copy} against automatic updates" end + + def start(*args) + options[:wcdir] = args.first + run(true) + end end end end diff --git a/lib/piston/commands/status.rb b/lib/piston/commands/status.rb index 66d9f4d..cadd78a 100644 --- a/lib/piston/commands/status.rb +++ b/lib/piston/commands/status.rb @@ -35,6 +35,16 @@ def run(wcdir) def show_updates options[:show_updates] end + + def start(*args) + args.flatten.map {|d| Pathname.new(d).expand_path}.each do |wcdir| + begin + run(wcdir) + rescue Piston::WorkingCopy::NotWorkingCopy + puts "#{wcdir} is not a working copy" + end + end + end end end end diff --git a/lib/piston/commands/update.rb b/lib/piston/commands/update.rb index 7fb9de7..9dfb2b1 100644 --- a/lib/piston/commands/update.rb +++ b/lib/piston/commands/update.rb @@ -29,6 +29,16 @@ def run(wcdir, to) logger.info {"Upstream #{repository} was unchanged from #{from_revision}"} end end + + def start(*args) + args.flatten.map {|d| Pathname.new(d).expand_path}.each do |wcdir| + begin + run(wcdir, options[:revision] || options[:commit] || :head) + rescue Piston::WorkingCopy::NotWorkingCopy + puts "#{wcdir} is not a working copy" + end + end + end end end end diff --git a/lib/piston/commands/upgrade.rb b/lib/piston/commands/upgrade.rb index 48fd176..5b6ce2d 100644 --- a/lib/piston/commands/upgrade.rb +++ b/lib/piston/commands/upgrade.rb @@ -15,6 +15,12 @@ def run(*directories) repositories end + + def start(*args) + targets = args.flatten.map {|d| Pathname.new(d).expand_path} + run(targets) + puts "#{targets.length} directories upgraded" + end end end end diff --git a/lib/piston/svn/revision.rb b/lib/piston/svn/revision.rb index 1394e2c..25daba9 100644 --- a/lib/piston/svn/revision.rb +++ b/lib/piston/svn/revision.rb @@ -64,7 +64,7 @@ def remember_values def each raise ArgumentError, "Revision #{revision} of #{repository.url} was never checked out -- can't iterate over files" unless @dir - svn(:ls, "--recursive", @dir).each do |relpath| + svn(:ls, "--recursive", @dir).split("\n").each do |relpath| next if relpath =~ %r{/$} yield relpath.chomp end diff --git a/lib/piston/svn/working_copy.rb b/lib/piston/svn/working_copy.rb index 1b7ff83..9224fc2 100644 --- a/lib/piston/svn/working_copy.rb +++ b/lib/piston/svn/working_copy.rb @@ -70,7 +70,7 @@ def after_remember(path) def finalize targets = [] - Dir[path + "*"].each do |item| + Dir[(path + "*").to_s].each do |item| svn(:add, item) end end diff --git a/test/integration_helpers.rb b/test/integration_helpers.rb index 1476610..a2b2eef 100644 --- a/test/integration_helpers.rb +++ b/test/integration_helpers.rb @@ -1,3 +1,4 @@ +require "pathname" PISTON_ROOT = Pathname.new(File.dirname(__FILE__)).parent.realpath def logger diff --git a/test/test_helper.rb b/test/test_helper.rb index ce65446..0a142bc 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -5,6 +5,7 @@ require "fileutils" require "pathname" require "piston" +require "active_support" begin require "turn" @@ -49,7 +50,14 @@ def teardown end def run(*args) - return if method_name.to_sym == :default_test && self.class == Piston::TestCase + test_name = if self.respond_to?(:name) then + name + elsif self.respond_to?(:method_name) then + method_name + else + raise "Don't know how to get the test's name: neither #name or #method_name is available" + end + return if test_name.to_sym == :default_test && self.class == Piston::TestCase super end