#!/usr/local/bin/ruby require 'benchmark' require 'fileutils' require 'sub/sys' class Sub include Sys::Client DEFAULT_BASE_URL = "svn+ssh://rubyforge.org/var/svn" def self.from_args(args) still_parsing_options = true command = defaults[:command] options = {} real_args = [] while !args.empty? case args[0] # commands when 'up', 'co', 'help', 'ext', 'ignore' options[:command] = args[0] # no-param options when '--version' puts "Sub version #{Sub::VERSION::STRING}" exit 0 when '-v', '--verbose' options[:verbosity] = VERBOSE when '-q', '--quiet' options[:verbosity] = QUIET when '-h', '--help' options[:command] = 'help' still_parsing_options = false when '-c', '--clean' options[:clean] = true # one-param options when '--url' args.shift options[:url] = args[0] when '--revision', '-r' args.shift options[:revision] = args[0] when '--directory', '--dir', '-d' args.shift options[:dir] = args[0] else real_args << args[0] end args.shift end Sub.new(options, real_args) end attr_reader :clean, :command, :args, :url, :revision, :dir def initialize(options = {:verbosity => NORMAL}, args = []) options = Sub.defaults.merge(options) sys.verbosity = options[:verbosity] @clean = options[:clean] @command = options[:command] @url = options[:url] @revision = options[:revision] @dir = options[:dir] || '.' @args = args end def self.defaults { :verbosity => NORMAL, :command => :up, } end def execute result = self.send(@command) puts result unless result.nil? end # commands def up if @args.empty? @args = [`pwd`.chomp] end update_many(@args) verbosity == QUIET ? nil : "Update complete." end def co if @args.empty? raise "Please specify a project to check out" end project = args.shift dir_name = args.shift || project svn("co #{base}/#{project}/trunk #{dir_name}") verbosity == QUIET ? nil : "Checkout complete." end def base url || ENV['SUB_BASE_URL'] || ENV['SVN'] || DEFAULT_BASE_URL end #todo: test def ext external_name = args.shift if external_name.nil? External.externals_of(dir).join("\n") else old_external = get_external(dir, external_name) if old_external.nil? "No external found named #{external_name} in directory #{dir}" + ((dir == '.') ? "\nDid you mean to use the --dir parameter?" : "") elsif url || revision revision_to_set = revision == 'HEAD' ? nil : revision set_external(dir, external_name, url || old_external.url, revision_to_set) else old_external end end end def ignore now_ignoring = svn("propget svn:ignore #{dir}", true).strip if args.empty? now_ignoring else to_ignore = now_ignoring.split("\n") + args svn("propset svn:ignore '#{to_ignore.join("\n")}' #{dir}") "#{dir} now ignoring #{args.join(", ")}" end end def help puts """ sub - Alex's wrapper for subversion Version: #{Sub::VERSION::STRING} Usage: sub co [--url base_url] project [dir] checks out [base_url]/project/trunk into ./project (or dir if specified) default base_url is ENV['SUB_BASE_URL'] or ENV['SVN'] or #{DEFAULT_BASE_URL} sub up [--clean] [dir]* fast update (default command, so 'sub dir...' or just 'sub' work too) if --clean is specified, removes all unversioned files and directories sub help prints this message sub ext [--dir directory] [external_name [--revision rev] [--url url]]] prints the value of the named svn:external property for directory dir if no dir is specified, uses '.' if no external_name is specified, prints all externals in that directory if --url or --revision is specified, sets the value and commits the change if rev is HEAD then the revision is removed from that external sub ignore [--dir directory] [pattern] prints the value of the svn:ignore property for directory dir if pattern is specified, ignores that pattern if no dir is specified, uses '.' Options: --verbose, -v lots of output --quiet, -q no output at all except for errors --help, -h prints this message --version prints release version """ end # methods def update_many(roots) roots.each do |root| say "Updating #{root}" b = Benchmark.measure { update(root) } say "Updated %s in %.2f sec" % [root, b.real] end end def update(root) Root.new(root, clean).update end def all_externals(root) Root.new(root, clean).all_externals end def get_external(dir, external_name) External.externals_of(dir).select do |external| external.name == external_name end.first end def set_external(dir, external_name, url, revision = nil) if get_external(dir, external_name) remove_external(dir, external_name) end new_external = External.new(dir, external_name, url, revision) externals = External.externals_of(dir) << new_external set_externals(dir, externals) new_external end def set_externals(externals_path, externals) property_lines = externals.collect { |x| x.to_s } svn "propset svn:externals '#{property_lines.join("\n")}' #{externals_path}" end def remove_external(dir, name) set_externals(dir, External.externals_of(dir).select { |x| x.name != name }) svn("ci --non-recursive -m 'removing external #{name}' #{dir}") FileUtils.rm_rf("#{dir}/#{name}") # TODO: unless modified end end