From d89f08f780ecc67b1afd750a8925dce619675cfa Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Wed, 17 Aug 2005 10:13:25 +0000 Subject: [PATCH] Added CVS module for SwitchTower (very, very experimental!) git-svn-id: http://svn.rubyonrails.org/rails/trunk/switchtower@2027 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- CHANGELOG | 2 + lib/switchtower/scm/cvs.rb | 82 +++++++++++++++++++ test/scm/cvs_test.rb | 159 +++++++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 lib/switchtower/scm/cvs.rb create mode 100644 test/scm/cvs_test.rb diff --git a/CHANGELOG b/CHANGELOG index f40d8977..7b3a24a4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Added CVS module (very very experimental!) + * Works with public keys now, for passwordless deployment * Subversion module recognizes the password prompt for HTTP authentication diff --git a/lib/switchtower/scm/cvs.rb b/lib/switchtower/scm/cvs.rb new file mode 100644 index 00000000..a4c588e6 --- /dev/null +++ b/lib/switchtower/scm/cvs.rb @@ -0,0 +1,82 @@ +require 'time' + +module SwitchTower + module SCM + + # An SCM module for using CVS as your source control tool. You can + # specify it by placing the following line in your configuration: + # + # set :scm, :cvs + # + # Also, this module accepts a :cvs configuration variable, + # which (if specified) will be used as the full path to the cvs + # executable on the remote machine: + # + # set :cvs, "/opt/local/bin/cvs" + # + # You can specify the location of your local copy (used to query + # the revisions, etc.) via the :local variable, which defaults to + # ".". + # + # 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 + + # 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 + # expensive operation...) + def latest_revision + return @latest_revision if @latest_revision + configuration.logger.debug "querying latest revision..." + @latest_revision = cvs_log(configuration.local). + split(/\r?\n/). + grep(/^date: (.*?);/) { Time.parse($1).strftime("%FT%T") }. + sort. + last + end + + # 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) + cvs = configuration[:cvs] || "cvs" + 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 + cd #{configuration.releases_path}; + CVS_RSH="#{cvs_rsh}" #{cvs} -d #{configuration.repository} -q co -D #{latest_revision} -d #{latest_revision} #{actor.application}; + fi + CMD + actor.run(command) do |ch, stream, out| + prefix = "#{stream} :: #{ch[:host]}" + actor.logger.info out, prefix + if out =~ %r{password:} + actor.logger.info "CVS is asking for a password", prefix + ch.send_data "#{actor.password}\n" + elsif out =~ %r{^Enter passphrase} + message = "CVS needs your key's passphrase and cannot proceed" + actor.logger.info message, prefix + raise message + end + end + end + + private + + def cvs_log(path) + `cd #{path || "."} && cvs -q log -N -rHEAD` + end + end + + end +end diff --git a/test/scm/cvs_test.rb b/test/scm/cvs_test.rb new file mode 100644 index 00000000..3df64cce --- /dev/null +++ b/test/scm/cvs_test.rb @@ -0,0 +1,159 @@ +$:.unshift File.dirname(__FILE__) + "/../../lib" + +require File.dirname(__FILE__) + "/../utils" +require 'test/unit' +require 'switchtower/scm/cvs' + +class ScmCvsTest < Test::Unit::TestCase + class CvsTest < SwitchTower::SCM::Cvs + attr_accessor :story + attr_reader :last_path + + def cvs_log(path) + @last_path = path + story.shift + end + end + + class MockChannel + attr_reader :sent_data + + def send_data(data) + @sent_data ||= [] + @sent_data << data + end + + def [](name) + "value" + end + end + + class MockActor + attr_reader :command + attr_reader :channels + attr_accessor :story + + def initialize(config) + @config = config + end + + def run(command) + @command = command + @channels ||= [] + @channels << MockChannel.new + story.each { |stream, line| yield @channels.last, stream, line } + end + + def method_missing(sym, *args) + @config.send(sym, *args) + end + end + + def setup + @config = MockConfiguration.new + @config[:repository] = ":ext:joetester@rubyforge.org:/hello/world" + @config[:local] = "/hello/world" + @config[:cvs] = "/path/to/cvs" + @config[:password] = "chocolatebrownies" + @scm = CvsTest.new(@config) + @actor = MockActor.new(@config) + @log_msg = <