diff --git a/switchtower/CHANGELOG b/switchtower/CHANGELOG
index 7b3a24a42d..27f69cef2b 100644
--- a/switchtower/CHANGELOG
+++ b/switchtower/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* Changed behavior of checkout to use the timestamp as the release name, instead of the revision number
+
* Added CVS module (very very experimental!)
* Works with public keys now, for passwordless deployment
diff --git a/switchtower/README b/switchtower/README
index 8c7b0fae62..45bad8bbe3 100644
--- a/switchtower/README
+++ b/switchtower/README
@@ -21,7 +21,9 @@ As with the rest of Rails, if you can abide by these assumptions, you can use Sw
== Usage
-More documentation is always pending, but you'll want to see the user manual for detailed usage instructions. In general, you'll use SwitchTower as follows:
+More documentation is always pending, but you'll want to see the user manual for detailed usage instructions. (The manual is online at http://manuals.rubyonrails.com/read/book/17).
+
+In general, you'll use SwitchTower as follows:
* Create a deployment recipe ("deploy.rb") for your application. You can use the sample recipe in examples/sample.rb as a starting point.
* Use the +switchtower+ script to execute your recipe (see below).
diff --git a/switchtower/lib/switchtower/actor.rb b/switchtower/lib/switchtower/actor.rb
index 012f4a1f55..a74ea95a24 100644
--- a/switchtower/lib/switchtower/actor.rb
+++ b/switchtower/lib/switchtower/actor.rb
@@ -248,7 +248,7 @@ module SwitchTower
buffer << out if str == :out
raise "could not determine releases #{out.inspect}" if str == :err
end
- @releases = buffer.split.map { |i| i.to_i }.sort
+ @releases = buffer.split.sort
end
@releases
diff --git a/switchtower/lib/switchtower/configuration.rb b/switchtower/lib/switchtower/configuration.rb
index 937618a727..c6ddff5568 100644
--- a/switchtower/lib/switchtower/configuration.rb
+++ b/switchtower/lib/switchtower/configuration.rb
@@ -26,12 +26,17 @@ module SwitchTower
# The load paths used for locating recipe files.
attr_reader :load_paths
+ # The time (in UTC) at which this configuration was created, used for
+ # determining the release path.
+ attr_reader :now
+
def initialize(actor_class=Actor) #:nodoc:
@roles = Hash.new { |h,k| h[k] = [] }
@actor = actor_class.new(self)
@logger = Logger.new
@load_paths = [".", File.join(File.dirname(__FILE__), "recipes")]
@variables = {}
+ @now = Time.now.utc
set :application, nil
set :repository, nil
@@ -165,10 +170,10 @@ module SwitchTower
File.join(deploy_to, shared_dir)
end
- # Return the full path to the named revision (defaults to the most current
- # revision in the repository).
- def release_path(revision=source.latest_revision)
- File.join(releases_path, revision)
+ # Return the full path to the named release. If a release is not specified,
+ # +now+ is used (the time at which the configuration was created).
+ def release_path(release=now.strftime("%Y%m%d%H%M%S"))
+ File.join(releases_path, release)
end
def respond_to?(sym) #:nodoc:
diff --git a/switchtower/lib/switchtower/scm/base.rb b/switchtower/lib/switchtower/scm/base.rb
new file mode 100644
index 0000000000..4e079d2758
--- /dev/null
+++ b/switchtower/lib/switchtower/scm/base.rb
@@ -0,0 +1,18 @@
+module SwitchTower
+ module SCM
+
+ # The ancestor class of the various SCM module implementations.
+ class Base
+ attr_reader :configuration
+
+ def initialize(configuration) #:nodoc:
+ @configuration = configuration
+ end
+
+ def checkout(actor)
+ raise NotImplementedError, "subclasses must implement checkout"
+ end
+ end
+
+ end
+end
diff --git a/switchtower/lib/switchtower/scm/cvs.rb b/switchtower/lib/switchtower/scm/cvs.rb
index a4c588e611..db3eae9261 100644
--- a/switchtower/lib/switchtower/scm/cvs.rb
+++ b/switchtower/lib/switchtower/scm/cvs.rb
@@ -1,4 +1,5 @@
require 'time'
+require 'switchtower/scm/base'
module SwitchTower
module SCM
@@ -21,13 +22,7 @@ module SwitchTower
# Also, you can specify the CVS_RSH variable to use on the remote machine(s)
# via the :cvs_rsh variable. This defaults to the value of the
# CVS_RSH environment variable locally, or if it is not set, to "ssh".
- class Cvs
- attr_reader :configuration
-
- def initialize(configuration) #:nodoc:
- @configuration = configuration
- end
-
+ class Cvs < Base
# Return a string representing the date of the last revision (CVS is
# seriously retarded, in that it does not give you a way to query when
# the last revision was made to the repository, so this is a fairly
@@ -37,7 +32,7 @@ module SwitchTower
configuration.logger.debug "querying latest revision..."
@latest_revision = cvs_log(configuration.local).
split(/\r?\n/).
- grep(/^date: (.*?);/) { Time.parse($1).strftime("%FT%T") }.
+ grep(/^date: (.*?);/) { Time.parse($1).strftime("%F %T") }.
sort.
last
end
@@ -49,12 +44,9 @@ module SwitchTower
cvs_rsh = configuration[:cvs_rsh] || ENV['CVS_RSH'] || "ssh"
command = <<-CMD
- if [[ -d #{actor.release_path} ]]; then
- cd #{actor.release_path};
- CVS_RSH="#{cvs_rsh}" #{cvs} -q up -d#{latest_revision};
- else
+ if [[ ! -d #{actor.release_path} ]]; then
cd #{configuration.releases_path};
- CVS_RSH="#{cvs_rsh}" #{cvs} -d #{configuration.repository} -q co -D #{latest_revision} -d #{latest_revision} #{actor.application};
+ CVS_RSH="#{cvs_rsh}" #{cvs} -d #{configuration.repository} -Q co -D "#{latest_revision}" -d #{File.basename(actor.release_path)} #{actor.application};
fi
CMD
actor.run(command) do |ch, stream, out|
diff --git a/switchtower/lib/switchtower/scm/darcs.rb b/switchtower/lib/switchtower/scm/darcs.rb
index 93735446ed..acaa006951 100644
--- a/switchtower/lib/switchtower/scm/darcs.rb
+++ b/switchtower/lib/switchtower/scm/darcs.rb
@@ -1,4 +1,4 @@
-require 'time'
+require 'switchtower/scm/base'
module SwitchTower
module SCM
@@ -13,26 +13,7 @@ module SwitchTower
# executable on the remote machine:
#
# set :darcs, "/opt/local/bin/darcs"
- class Darcs
- attr_reader :configuration
-
- def initialize(configuration) #:nodoc:
- @configuration = configuration
- end
-
- # Return an integer identifying the last known revision (patch) in the
- # darcs repository. (This integer is currently the 14-digit timestamp
- # of the last known patch.)
- def latest_revision
- unless @latest_revision
- configuration.logger.debug "querying latest revision..."
- @latest_revision = Time.
- parse(`darcs changes --last 1 --repo #{configuration.repository}`).
- strftime("%Y%m%d%H%M%S").to_i
- end
- @latest_revision
- end
-
+ class Darcs < Base
# Check out (on all servers associated with the current task) the latest
# revision. Uses the given actor instance to execute the command.
def checkout(actor)
@@ -40,7 +21,7 @@ module SwitchTower
command = <<-CMD
if [[ ! -d #{actor.release_path} ]]; then
- #{darcs} get --set-scripts-executable #{configuration.repository} #{actor.release_path};
+ #{darcs} get -q --set-scripts-executable #{configuration.repository} #{actor.release_path};
fi
CMD
actor.run(command)
diff --git a/switchtower/lib/switchtower/scm/subversion.rb b/switchtower/lib/switchtower/scm/subversion.rb
index cc5d78c3e6..368c965dec 100644
--- a/switchtower/lib/switchtower/scm/subversion.rb
+++ b/switchtower/lib/switchtower/scm/subversion.rb
@@ -1,3 +1,5 @@
+require 'switchtower/scm/base'
+
module SwitchTower
module SCM
@@ -12,13 +14,7 @@ module SwitchTower
# executable on the remote machine:
#
# set :svn, "/opt/local/bin/svn"
- class Subversion
- attr_reader :configuration
-
- def initialize(configuration) #:nodoc:
- @configuration = configuration
- end
-
+ class Subversion < Base
# Return an integer identifying the last known revision in the svn
# repository. (This integer is currently the revision number.) If latest
# revision does not exist in the given repository, this routine will
@@ -64,9 +60,9 @@ module SwitchTower
prefix
ch.send_data "yes\n"
elsif out =~ %r{passphrase}
- message = "subversion needs your key's passphrase and cannot proceed"
+ message = "subversion needs your key's passphrase, sending empty string"
actor.logger.info message, prefix
- raise message
+ ch.send_data "\n"
elsif out =~ %r{The entry \'(\w+)\' is no longer a directory}
message = "subversion can't update because directory '#{$1}' was replaced. Please add it to svn:ignore."
actor.logger.info message, prefix
diff --git a/switchtower/test/configuration_test.rb b/switchtower/test/configuration_test.rb
index 2ec299ab6d..ecbc0a5607 100644
--- a/switchtower/test/configuration_test.rb
+++ b/switchtower/test/configuration_test.rb
@@ -18,7 +18,6 @@ class ConfigurationTest < Test::Unit::TestCase
class MockSCM
attr_reader :configuration
- attr_accessor :latest_revision
def initialize(config)
@configuration = config
@@ -184,8 +183,7 @@ class ConfigurationTest < Test::Unit::TestCase
def test_release_path_implicit
@config.set :deploy_to, "/start/of/path"
- @config.source.latest_revision = 2257
- assert_equal "/start/of/path/releases/2257", @config.release_path
+ assert_equal "/start/of/path/releases/#{@config.now.strftime("%Y%m%d%H%M%S")}", @config.release_path
end
def test_release_path_explicit
diff --git a/switchtower/test/scm/cvs_test.rb b/switchtower/test/scm/cvs_test.rb
index 3df64cce0e..4532870826 100644
--- a/switchtower/test/scm/cvs_test.rb
+++ b/switchtower/test/scm/cvs_test.rb
@@ -44,6 +44,10 @@ class ScmCvsTest < Test::Unit::TestCase
story.each { |stream, line| yield @channels.last, stream, line }
end
+ def release_path
+ (@config[:now] || Time.now.utc).strftime("%Y%m%d%H%M%S")
+ end
+
def method_missing(sym, *args)
@config.send(sym, *args)
end
@@ -55,6 +59,7 @@ class ScmCvsTest < Test::Unit::TestCase
@config[:local] = "/hello/world"
@config[:cvs] = "/path/to/cvs"
@config[:password] = "chocolatebrownies"
+ @config[:now] = Time.utc(2005,8,24,12,0,0)
@scm = CvsTest.new(@config)
@actor = MockActor.new(@config)
@log_msg = <