diff --git a/CHANGELOG b/CHANGELOG index 5b501169..ee681d38 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* CVS SCM module [Brian Phillips] + * Fix typo in Perforce SCM module [Chris Bailey] * ssh_options < server options when connecting [Jamis Buck] diff --git a/lib/capistrano/recipes/deploy/scm/cvs.rb b/lib/capistrano/recipes/deploy/scm/cvs.rb new file mode 100644 index 00000000..e3de995e --- /dev/null +++ b/lib/capistrano/recipes/deploy/scm/cvs.rb @@ -0,0 +1,151 @@ +require 'capistrano/recipes/deploy/scm/base' + +module Capistrano + module Deploy + module SCM + + # Implements the Capistrano SCM interface for the Subversion revision + # control system (http://subversion.tigris.org). + class Cvs < Base + # Sets the default command name for this SCM. Users may override this + # by setting the :scm_command variable. + default_command "cvs" + + # CVS 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) + [ prep_destination(destination), + scm(verbose, cvs_root, :checkout, cvs_revision(revision), cvs_destination(destination), variable(:scm_module)) + ].join(' && ') + end + + # Returns the command that will do an "cvs update" to the given + # revision, for the working copy at the given destination. + def sync(revision, destination) + [ prep_destination(destination), + scm(verbose, cvs_root, :update, cvs_revision(revision), cvs_destination(destination)) + ].join(' && ') + end + + # Returns the command that will do an "cvs export" of the given revision + # to the given destination. + def export(revision, destination) + [ prep_destination(destination), + scm(verbose, cvs_root, :export, cvs_revision(revision), cvs_destination(destination), variable(:scm_module)) + ].join(' && ') + end + + # Returns the command that will do an "cvs diff" for the two revisions. + def diff(from, to=nil) + rev_type = revision_type(from) + if rev_type == :date + range_args = "-D '#{from}' -D '#{to || 'now'}'" + else + range_args = "-r '#{from}' -r '#{to || head}'" + end + scm cvs_root, :diff, range_args + end + + # Returns an "cvs log" command for the two revisions. + def log(from, to=nil) + rev_type = revision_type(from) + if rev_type == :date + range_arg = "-d '#{from}<#{to || 'now'}'" + else + range_arg = "-r '#{from}:#{to || head}'" + end + scm cvs_root, :log, range_arg + end + + # Unfortunately, cvs doesn't support the concept of a revision number like + # subversion and other SCM's do. For now, we'll rely on getting the timestamp + # of the latest checkin under the revision that's passed to us. + def query_revision(revision) + return revision if revision_type(revision) == :date + revision = yield(scm(cvs_root, :log, "-r#{revision}")). + grep(/^date:/). + map { |line| line[/^date: (.*?);/, 1] }. + sort.last + return revision + 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) + logger.info "[#{stream}] #{text}" + case text + when /\bpassword.*:/i + # prompting for a password + "#{variable(:scm_password) || variable(:password)}\n" + when %r{\(yes/no\)} + # let's be agreeable... + "yes\n" + end + end + + private + + # Constructs the CVSROOT command-line option + def cvs_root + root = "" + root << "-d #{repository} " if repository + root + end + + # Constructs the destination dir command-line option + def cvs_destination(destination) + dest = "" + if destination + dest_parts = destination.split(/\//); + dest << "-d #{dest_parts.pop}" + end + dest + end + + # attempts to guess what type of revision we're working with + def revision_type(rev) + return :date if rev =~ /^\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}$/ # i.e 2007-05-15 08:13:25 + return :revision if rev =~ /^\d/ # i.e. 1.2.1 + return :tag # i.e. RELEASE_1_2 + end + + # constructs the appropriate command-line switch for specifying a + # "revision" in CVS. This could be a tag, branch, revision (i.e. 1.3) + # or a date (to be used with -d) + def cvs_revision(rev) + revision = "" + revision << case revision_type(rev) + when :date: + "-D \"#{rev}\"" if revision_type(rev) == :date + when :revision: + "-r #{rev}" + else + "-r #{head}" + end + return revision + 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 prep_destination(destination) + dest_parts = destination.split(/\//); + checkout_dir = dest_parts.pop + dest = dest_parts.join('/') + "mkdir -p #{ dest } && cd #{ dest }" + end + end + + end + end +end