require 'capistrano/scm/base' module Capistrano module SCM # An SCM module for using perforce as your source control tool. # This module can explicitly selected by placing the following line # in your configuration: # # set :scm, :perforce # # Also, this module accepts a :p4 configuration variable, # which (if specified) will be used as the full path to the p4 # executable on the remote machine: # # set :p4, "/usr/local/bin/p4" # # This module accepts another :p4sync_flags configuration # variable, which (if specified) can add extra options. This setting # defaults to the value "-f" which forces resynchronization. # # set :p4sync_flags, "-f" # # This module accepts another :p4client_root configuration # variable to handle mapping adjustments. Perforce doesn't have the # ability to sync to a specific directory (e.g. the release_path); the # location being synced to is defined by the client-spec. As such, we # sync the client-spec (defined by p4client and then copy from # the determined root of the client-spec mappings to the release_path. # In the unlikely event that your client-spec mappings introduces # directory structure above the rails structure, you can override the # may need to specify the directory # # set :p4client_root, "/user/rmcmahon/project1/code" # # Finally, the module accepts a p4diff2_options configuration # variable. This can be used to manipulate the output when running a # diff between what is deployed, and another revision. This option # defaults to "-u". Run 'p4 help diff2' for other options. # class LocalPerforce < Base def latest_revision configuration.logger.debug "querying latest revision..." unless @latest_revision @latest_revision = `#{local_p4} counter change`.strip @latest_revision end # Return the number of the revision currently deployed. def current_revision(actor) latest = actor.releases.last grep = %(grep " #{latest}$" #{configuration.deploy_to}/revisions.log) result = "" actor.run(grep, :once => true) do |ch, str, out| result << out if str == :out raise "could not determine current revision" if str == :err end date, time, user, rev, dir = result.split raise "current revision not found in revisions.log" unless dir == latest rev.to_i end # Return a string containing the diff between the two revisions. +from+ # and +to+ may be in any format that p4 recognizes as a valid revision # identifiers. If +from+ is +nil+, it defaults to the last deployed # revision. If +to+ is +nil+, it defaults to #head. def diff(actor, from=nil, to=nil) from ||= "@#{current_revision(actor)}" to ||= "#head" p4client = configuration[:p4client] p4diff2_options = configuration[:p4diff2_options]||"-u -db" `#{local_p4} diff2 #{p4diff2_options} //#{p4client}/...#{from} //#{p4client}/...#{to}` end # Syncronizes (on all servers associated with the current task) the head # revision of the code. Uses the given actor instance to execute the command. # def checkout(actor) if configuration[:repository_is_not_reachable_from_remote] do_local_checkout_and_send(actor) else p4sync_flags = configuration[:p4sync_flags] || "-f" p4client_root = configuration[:p4client_root] || "`#{remote_p4} client -o | grep ^Root | cut -f2`" command = "#{remote_p4} sync #{p4sync_flags} && cp -rf #{p4client_root} #{actor.release_path};" run_checkout(actor, command, &p4_stream_handler(actor)) end end def update(actor) raise "#{self.class} doesn't support update(actor)" end private def local_p4 add_standard_p4_options('p4') end def remote_p4 p4_cmd = configuration[:p4] || 'p4' add_standard_p4_options(p4_cmd) end def add_standard_p4_options(p4_location) check_settings p4_cmd = p4_location p4_cmd = "#{p4_cmd} -p #{configuration[:p4port]}" if configuration[:p4port] p4_cmd = "#{p4_cmd} -u #{configuration[:p4user]}" if configuration[:p4user] p4_cmd = "#{p4_cmd} -P #{configuration[:p4passwd]}" if configuration[:p4passwd] p4_cmd = "#{p4_cmd} -c #{configuration[:p4client]}" if configuration[:p4client] p4_cmd end def check_settings check_setting(:p4port, "Add set :p4port, to deploy.rb") check_setting(:p4user, "Add set :p4user, to deploy.rb") check_setting(:p4passwd, "Add set :p4passwd, to deploy.rb") check_setting(:p4client, "Add set :p4client, to deploy.rb") end def check_setting(p4setting, message) raise "#{p4setting} is not configured. #{message}" unless configuration[p4setting] end def p4_stream_handler(actor) Proc.new do |ch, stream, out| prefix = "#{stream} :: #{ch[:host]}" actor.logger.info out, prefix if out =~ /\(P4PASSWD\) invalid or unset\./i raise "p4passwd is incorrect or unset" elsif out =~ /Can.t create a new user.*/i raise "p4user is incorrect or unset" elsif out =~ /Perforce client error\:/i raise "p4port is incorrect or unset" elsif out =~ /Client \'[\w\-\_\.]+\' unknown.*/i raise "p4client is incorrect or unset" end end end # Added by Jim Morris for local access def read_local_file(fn) File.read(fn) end # checkout from the local machine # tar up results # send to remote machine(s) # unpack into checkout directory def do_local_checkout_and_send(actor) temp_file= "CAP_TEMP_#{Time.now.to_f}" tmpdir_local= configuration[:tmpdir_local] || "/tmp" temp_dest_local= File.join(tmpdir_local, temp_file) tmpdir_remote= configuration[:tmpdir_remote] || "/tmp" temp_dest_remote= File.join(tmpdir_remote, temp_file) temp_tar_file_local= File.join(tmpdir_local, "#{temp_file}.tar.gz") temp_tar_file_remote= File.join(tmpdir_remote, "#{temp_file}.tar.gz") p4sync_flags = configuration[:p4sync_flags] || "-f" p4client_root = configuration[:p4client_root] || "`#{local_p4} client -o | grep ^Root | cut -f2`" configuration.logger.debug "local executing: #{local_p4} sync #{p4sync_flags}" configuration.logger.debug "local creating tar file: #{temp_tar_file_local}" system( "#{local_p4} sync #{p4sync_flags} && " + "tar -C #{p4client_root} -c -z -f #{temp_tar_file_local} ." ) # send the tar file to the remote machine(s) configuration.logger.debug "sending tar file: #{temp_tar_file_local} to remote #{temp_tar_file_remote}" actor.put(read_local_file(temp_tar_file_local), temp_tar_file_remote) system("rm #{temp_tar_file_local}") # unpack the tar file on the remote machine as if it has been checked out there cmd="mkdir -p #{actor.release_path} && " + "tar -C #{actor.release_path} -x -z -f #{temp_tar_file_remote} && " + "rm -f #{temp_tar_file_remote}" actor.run(cmd) end end end end