require 'capistrano/recipes/deploy/scm/base' require 'yaml' module Capistrano module Deploy module SCM # Implements the Capistrano SCM interface for the Subversion revision # control system (http://subversion.tigris.org). class Subversion < Base # Sets the default command name for this SCM. Users may override this # by setting the :scm_command variable. default_command "svn" # Subversion understands 'HEAD' to refer to the latest revision in the # repository. def head "HEAD" end # Returns the command that will check out the given revision to the # given destination. def checkout(revision, destination) scm :checkout, arguments, arguments(:checkout), verbose, authentication, "-r#{revision}", repository, destination end # Returns the command that will do an "svn update" to the given # revision, for the working copy at the given destination. def sync(revision, destination) scm :switch, arguments, verbose, authentication, "-r#{revision}", repository, destination end # Returns the command that will do an "svn export" of the given revision # to the given destination. def export(revision, destination) scm :export, arguments, arguments(:export), verbose, authentication, "-r#{revision}", repository, destination end # Returns the command that will do an "svn diff" for the two revisions. def diff(from, to=nil) scm :diff, repository, arguments(:diff), authentication, "-r#{from}:#{to || head}" end # Returns an "svn log" command for the two revisions. def log(from, to=nil) scm :log, repository, arguments(:log), authentication, "-r#{from}:#{to || head}" end # Attempts to translate the given revision identifier to a "real" # revision. If the identifier is an integer, it will simply be returned. # Otherwise, this will yield a string of the commands it needs to be # executed (svn info), and will extract the revision from the response. def query_revision(revision) return revision if revision =~ /^\d+$/ command = scm(:info, arguments, arguments(:info), repository, authentication, "-r#{revision}") result = yield(command) yaml = YAML.load(result) raise "tried to run `#{command}' and got unexpected result #{result.inspect}" unless Hash === yaml [ (yaml['Last Changed Rev'] || 0).to_i, (yaml['Revision'] || 0).to_i ].max end # Increments the given revision number and returns it. def next_revision(revision) revision.to_i + 1 end # Determines what the response should be for a particular bit of text # from the SCM. Password prompts, connection requests, passphrases, # etc. are handled here. def handle_data(state, stream, text) host = state[:channel][:host] logger.info "[#{host} :: #{stream}] #{text}" case text when /\bpassword.*:/i # subversion is prompting for a password "#{scm_password_prompt}\n" when %r{\(yes/no\)} # subversion is asking whether or not to connect "yes\n" when /passphrase/i # subversion is asking for the passphrase for the user's key "#{variable(:scm_passphrase)}\n" when /The entry \'(.+?)\' is no longer a directory/ raise Capistrano::Error, "subversion can't update because directory '#{$1}' was replaced. Please add it to svn:ignore." when /accept \(t\)emporarily/ # subversion is asking whether to accept the certificate "t\n" end end private # If a username is configured for the SCM, return the command-line # switches for that. Note that we don't need to return the password # switch, since Capistrano will check for that prompt in the output # and will respond appropriately. def authentication username = variable(:scm_username) return "" unless username result = "--username #{variable(:scm_username)} " result << "--password #{variable(:scm_password)} " unless variable(:scm_auth_cache) || variable(:scm_prefer_prompt) result << "--no-auth-cache " unless variable(:scm_auth_cache) result end # If verbose output is requested, return nil, otherwise return the # command-line switch for "quiet" ("-q"). def verbose variable(:scm_verbose) ? nil : "-q" end def scm_password_prompt @scm_password_prompt ||= variable(:scm_password) || variable(:password) || Capistrano::CLI.password_prompt("Subversion password: ") end end end end end