capistrano 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +29 -0
- data/README +4 -7
- data/bin/cap +0 -0
- data/bin/capify +0 -0
- data/lib/capistrano/command.rb +32 -38
- data/lib/capistrano/configuration/actions/file_transfer.rb +21 -13
- data/lib/capistrano/configuration/actions/invocation.rb +1 -1
- data/lib/capistrano/configuration/connections.rb +30 -20
- data/lib/capistrano/errors.rb +1 -1
- data/lib/capistrano/processable.rb +53 -0
- data/lib/capistrano/recipes/deploy/remote_dependency.rb +6 -0
- data/lib/capistrano/recipes/deploy/scm/git.rb +26 -11
- data/lib/capistrano/recipes/deploy/scm/none.rb +44 -0
- data/lib/capistrano/recipes/deploy/strategy/base.rb +6 -0
- data/lib/capistrano/recipes/deploy/strategy/copy.rb +74 -3
- data/lib/capistrano/recipes/deploy.rb +15 -13
- data/lib/capistrano/role.rb +0 -14
- data/lib/capistrano/server_definition.rb +5 -0
- data/lib/capistrano/shell.rb +21 -17
- data/lib/capistrano/ssh.rb +24 -58
- data/lib/capistrano/transfer.rb +216 -0
- data/lib/capistrano/version.rb +1 -1
- data/test/cli/execute_test.rb +1 -1
- data/test/cli/help_test.rb +1 -1
- data/test/cli/options_test.rb +1 -1
- data/test/cli/ui_test.rb +1 -1
- data/test/cli_test.rb +1 -1
- data/test/command_test.rb +31 -51
- data/test/configuration/actions/file_transfer_test.rb +21 -19
- data/test/configuration/actions/inspect_test.rb +1 -1
- data/test/configuration/actions/invocation_test.rb +6 -6
- data/test/configuration/callbacks_test.rb +1 -1
- data/test/configuration/connections_test.rb +11 -12
- data/test/configuration/execution_test.rb +1 -1
- data/test/configuration/loading_test.rb +1 -1
- data/test/configuration/namespace_dsl_test.rb +1 -1
- data/test/configuration/roles_test.rb +1 -1
- data/test/configuration/servers_test.rb +1 -1
- data/test/configuration/variables_test.rb +1 -1
- data/test/configuration_test.rb +1 -1
- data/test/deploy/scm/accurev_test.rb +1 -1
- data/test/deploy/scm/base_test.rb +1 -1
- data/test/deploy/scm/git_test.rb +10 -6
- data/test/deploy/scm/mercurial_test.rb +1 -1
- data/test/deploy/strategy/copy_test.rb +120 -27
- data/test/extensions_test.rb +1 -1
- data/test/logger_test.rb +1 -1
- data/test/server_definition_test.rb +1 -1
- data/test/shell_test.rb +27 -1
- data/test/ssh_test.rb +27 -21
- data/test/task_definition_test.rb +1 -1
- data/test/transfer_test.rb +160 -0
- data/test/utils.rb +30 -34
- data/test/version_test.rb +1 -1
- metadata +26 -14
- data/lib/capistrano/gateway.rb +0 -131
- data/lib/capistrano/upload.rb +0 -152
- data/test/gateway_test.rb +0 -167
- data/test/upload_test.rb +0 -131
data/CHANGELOG
CHANGED
@@ -1,3 +1,32 @@
|
|
1
|
+
*2.3.0* May 2, 2008
|
2
|
+
|
3
|
+
* Make sure git fetches include tags [Alex Arnell]
|
4
|
+
|
5
|
+
* Make deploy:setup obey the :use_sudo and :runner directives, and generalize the :use_sudo and :runner options into a try_sudo() helper method [Jamis Buck]
|
6
|
+
|
7
|
+
* Make sudo helper play nicely with complex command chains [Jamis Buck]
|
8
|
+
|
9
|
+
* Expand file-transfer options with new upload() and download() helpers. [Jamis Buck]
|
10
|
+
|
11
|
+
* Allow SCP transfers in addition to SFTP. [Jamis Buck]
|
12
|
+
|
13
|
+
* Use Net::SSH v2 and Net::SSH::Gateway. [Jamis Buck]
|
14
|
+
|
15
|
+
* Added #export method for git SCM [Phillip Goldenburg]
|
16
|
+
|
17
|
+
* For query_revision, git SCM used git-rev-parse on the repo hosting the Capfile, which may NOT be the same tree as the actual source reposistory. Use git-ls-remote instead to resolve the revision for checkout. [Robin H. Johnson]
|
18
|
+
|
19
|
+
* Allow :ssh_options hash to be specified per server [Jesse Newland]
|
20
|
+
|
21
|
+
* Added support for depend :remote, :file to test for existence of a specific file [Andrew Carter]
|
22
|
+
|
23
|
+
* Ensure that the default run options are mixed into the command options when executing a command from the cap shell [Ken Collins]
|
24
|
+
|
25
|
+
* Added :none SCM module for deploying a specific directory's contents [Jamis Buck]
|
26
|
+
|
27
|
+
* Improved "copy" strategy supports local caching and pattern exclusion (via :copy_cache and :copy_exclude variables) [Jamis Buck]
|
28
|
+
|
29
|
+
|
1
30
|
*2.2.0* February 27, 2008
|
2
31
|
|
3
32
|
* Fix git submodule support to init on sync [halorgium]
|
data/README
CHANGED
@@ -7,8 +7,10 @@ Capistrano was originally designed to simplify and automate deployment of web ap
|
|
7
7
|
|
8
8
|
== Dependencies
|
9
9
|
|
10
|
-
* Net::SSH
|
11
|
-
*
|
10
|
+
* Net::SSH v2 (http://net-ssh.rubyforge.org)
|
11
|
+
* Net::SFTP v2 (http://net-ssh.rubyforge.org)
|
12
|
+
* Net::SCP v1 (http://net-ssh.rubyforge.org)
|
13
|
+
* Net::SSH::Gateway v1 (http://net-ssh.rubyforge.org)
|
12
14
|
* HighLine (http://highline.rubyforge.org)
|
13
15
|
|
14
16
|
If you want to run the tests, you'll also need to have the following dependencies installed:
|
@@ -36,8 +38,3 @@ Use the +cap+ script as follows:
|
|
36
38
|
cap sometask
|
37
39
|
|
38
40
|
By default, the script will look for a file called one of +capfile+ or +Capfile+. The +someaction+ text indicates which task to execute. You can do "cap -h" to see all the available options and "cap -T" to see all the available tasks.
|
39
|
-
|
40
|
-
== KNOWN ISSUES
|
41
|
-
|
42
|
-
* Using "put" to upload a file to two or more hosts when a gateway is in effect has a good chance of crashing with a "corrupt mac detected" error. This is due to a bug in Net::SSH.
|
43
|
-
* Running commands may rarely hang inexplicably. This appears to be specific only to certain platforms. Most people will never see this behavior.
|
data/bin/cap
CHANGED
File without changes
|
data/bin/capify
CHANGED
File without changes
|
data/lib/capistrano/command.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
require 'capistrano/errors'
|
2
|
+
require 'capistrano/processable'
|
2
3
|
|
3
4
|
module Capistrano
|
4
5
|
|
5
6
|
# This class encapsulates a single command to be executed on a set of remote
|
6
7
|
# machines, in parallel.
|
7
8
|
class Command
|
9
|
+
include Processable
|
10
|
+
|
8
11
|
attr_reader :command, :sessions, :options
|
9
12
|
|
10
13
|
def self.process(command, sessions, options={}, &block)
|
@@ -32,21 +35,8 @@ module Capistrano
|
|
32
35
|
# fails (non-zero return code) on any of the hosts, this will raise a
|
33
36
|
# Capistrano::CommandError.
|
34
37
|
def process!
|
35
|
-
since = Time.now
|
36
38
|
loop do
|
37
|
-
|
38
|
-
@channels.each do |ch|
|
39
|
-
next if ch[:closed]
|
40
|
-
active += 1
|
41
|
-
ch.connection.process(true)
|
42
|
-
end
|
43
|
-
|
44
|
-
break if active == 0
|
45
|
-
if Time.now - since >= 1
|
46
|
-
since = Time.now
|
47
|
-
@channels.each { |ch| ch.connection.ping! }
|
48
|
-
end
|
49
|
-
sleep 0.01 # a brief respite, to keep the CPU from going crazy
|
39
|
+
break unless process_iteration { @channels.any? { |ch| !ch[:closed] } }
|
50
40
|
end
|
51
41
|
|
52
42
|
logger.trace "command finished" if logger
|
@@ -84,38 +74,32 @@ module Capistrano
|
|
84
74
|
channel[:host] = server.host
|
85
75
|
channel[:options] = options
|
86
76
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
if options[:shell] == false
|
92
|
-
shell = nil
|
93
|
-
else
|
94
|
-
shell = "#{options[:shell] || "sh"} -c"
|
95
|
-
cmd = cmd.gsub(/[$\\`"]/) { |m| "\\#{m}" }
|
96
|
-
cmd = "\"#{cmd}\""
|
97
|
-
end
|
77
|
+
request_pty_if_necessary(channel) do |ch, success|
|
78
|
+
if success
|
79
|
+
logger.trace "executing command", ch[:server] if logger
|
80
|
+
cmd = replace_placeholders(command, ch)
|
98
81
|
|
99
|
-
|
82
|
+
if options[:shell] == false
|
83
|
+
shell = nil
|
84
|
+
else
|
85
|
+
shell = [options.fetch(:shell, "sh"), "-c"].join(" ")
|
86
|
+
cmd = cmd.gsub(/[$\\`"]/) { |m| "\\#{m}" }
|
87
|
+
cmd = "\"#{cmd}\""
|
88
|
+
end
|
100
89
|
|
101
|
-
|
102
|
-
ch.send_data(options[:data]) if options[:data]
|
103
|
-
end
|
90
|
+
command_line = [environment, options[:command_prefix], shell, cmd].compact.join(" ")
|
104
91
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
channel.on_failure do |ch|
|
92
|
+
ch.exec(command_line)
|
93
|
+
ch.send_data(options[:data]) if options[:data]
|
94
|
+
else
|
109
95
|
# just log it, don't actually raise an exception, since the
|
110
96
|
# process method will see that the status is not zero and will
|
111
97
|
# raise an exception then.
|
112
98
|
logger.important "could not open channel", ch[:server] if logger
|
113
99
|
ch.close
|
114
100
|
end
|
115
|
-
else
|
116
|
-
execute_command.call(channel)
|
117
101
|
end
|
118
|
-
|
102
|
+
|
119
103
|
channel.on_data do |ch, data|
|
120
104
|
@callback[ch, :out, data] if @callback
|
121
105
|
end
|
@@ -124,8 +108,8 @@ module Capistrano
|
|
124
108
|
@callback[ch, :err, data] if @callback
|
125
109
|
end
|
126
110
|
|
127
|
-
channel.on_request do |ch,
|
128
|
-
ch[:status] = data.read_long
|
111
|
+
channel.on_request("exit-status") do |ch, data|
|
112
|
+
ch[:status] = data.read_long
|
129
113
|
end
|
130
114
|
|
131
115
|
channel.on_close do |ch|
|
@@ -135,6 +119,16 @@ module Capistrano
|
|
135
119
|
end
|
136
120
|
end
|
137
121
|
|
122
|
+
def request_pty_if_necessary(channel)
|
123
|
+
if options[:pty]
|
124
|
+
channel.request_pty do |ch, success|
|
125
|
+
yield ch, success
|
126
|
+
end
|
127
|
+
else
|
128
|
+
yield channel, true
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
138
132
|
def replace_placeholders(command, channel)
|
139
133
|
command.gsub(/\$CAPISTRANO:HOST\$/, channel[:host])
|
140
134
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'capistrano/
|
1
|
+
require 'capistrano/transfer'
|
2
2
|
|
3
3
|
module Capistrano
|
4
4
|
class Configuration
|
@@ -9,23 +9,31 @@ module Capistrano
|
|
9
9
|
# by the current task. If <tt>:mode</tt> is specified it is used to
|
10
10
|
# set the mode on the file.
|
11
11
|
def put(data, path, options={})
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
12
|
+
opts = options.dup
|
13
|
+
opts[:permissions] = opts.delete(:mode)
|
14
|
+
upload(StringIO.new(data), path, opts)
|
16
15
|
end
|
17
16
|
|
18
|
-
# Get file remote_path from FIRST server
|
17
|
+
# Get file remote_path from FIRST server targeted by
|
19
18
|
# the current task and transfer it to local machine as path.
|
20
19
|
#
|
21
20
|
# get "#{deploy_to}/current/log/production.log", "log/production.log.web"
|
22
|
-
def get(remote_path, path, options
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
21
|
+
def get(remote_path, path, options={}, &block)
|
22
|
+
download(remote_path, path, options.merge(:once => true), &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def upload(from, to, options={}, &block)
|
26
|
+
transfer(:up, from, to, options, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def download(from, to, options={}, &block)
|
30
|
+
transfer(:down, from, to, options, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def transfer(direction, from, to, options={}, &block)
|
34
|
+
execute_on_servers(options) do |servers|
|
35
|
+
targets = servers.map { |s| sessions[s] }
|
36
|
+
Transfer.process(direction, from, to, targets, options.merge(:logger => logger), &block)
|
29
37
|
end
|
30
38
|
end
|
31
39
|
|
@@ -70,7 +70,7 @@ module Capistrano
|
|
70
70
|
as = options.delete(:as)
|
71
71
|
|
72
72
|
user = as && "-u #{as}"
|
73
|
-
|
73
|
+
options[:command_prefix] = [fetch(:sudo, "sudo"), "-p '#{sudo_prompt}'", user].compact.join(" ")
|
74
74
|
|
75
75
|
run(command, options, &sudo_behavior_callback(block))
|
76
76
|
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
|
2
1
|
require 'enumerator'
|
3
|
-
require '
|
2
|
+
require 'net/ssh/gateway'
|
4
3
|
require 'capistrano/ssh'
|
4
|
+
require 'capistrano/errors'
|
5
5
|
|
6
6
|
module Capistrano
|
7
7
|
class Configuration
|
@@ -11,8 +11,6 @@ module Capistrano
|
|
11
11
|
base.send :alias_method, :initialize, :initialize_with_connections
|
12
12
|
end
|
13
13
|
|
14
|
-
# An adaptor for making the SSH interface look and act like that of the
|
15
|
-
# Gateway class.
|
16
14
|
class DefaultConnectionFactory #:nodoc:
|
17
15
|
def initialize(options)
|
18
16
|
@options = options
|
@@ -23,6 +21,27 @@ module Capistrano
|
|
23
21
|
end
|
24
22
|
end
|
25
23
|
|
24
|
+
class GatewayConnectionFactory #:nodoc:
|
25
|
+
def initialize(gateway, options)
|
26
|
+
Thread.abort_on_exception = true
|
27
|
+
server = ServerDefinition.new(gateway)
|
28
|
+
|
29
|
+
@options = options
|
30
|
+
@gateway = SSH.connection_strategy(server, options) do |host, user, connect_options|
|
31
|
+
Net::SSH::Gateway.new(host, user, connect_options)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def connect_to(server)
|
36
|
+
@options[:logger].debug "establishing connection to `#{server}' via gateway" if @options[:logger]
|
37
|
+
local_host = ServerDefinition.new("127.0.0.1", :user => server.user, :port => @gateway.open(server.host, server.port || 22))
|
38
|
+
session = SSH.connect(local_host, @options)
|
39
|
+
session.xserver = server
|
40
|
+
@options[:logger].trace "connected: `#{server}' (via gateway)" if @options[:logger]
|
41
|
+
session
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
26
45
|
# A hash of the SSH sessions that are currently open and available.
|
27
46
|
# Because sessions are constructed lazily, this will only contain
|
28
47
|
# connections to those servers that have been the targets of one or more
|
@@ -61,7 +80,7 @@ module Capistrano
|
|
61
80
|
@connection_factory ||= begin
|
62
81
|
if exists?(:gateway)
|
63
82
|
logger.debug "establishing connection to gateway `#{fetch(:gateway)}'"
|
64
|
-
|
83
|
+
GatewayConnectionFactory.new(fetch(:gateway), self)
|
65
84
|
else
|
66
85
|
DefaultConnectionFactory.new(self)
|
67
86
|
end
|
@@ -72,22 +91,13 @@ module Capistrano
|
|
72
91
|
def establish_connections_to(servers)
|
73
92
|
failed_servers = []
|
74
93
|
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
|
79
|
-
if fetch(:synchronous_connect, false)
|
80
|
-
logger.trace "synchronous_connect: true"
|
81
|
-
Array(servers).each { |server| safely_establish_connection_to(server, failed_servers) }
|
82
|
-
else
|
83
|
-
# force the connection factory to be instantiated synchronously,
|
84
|
-
# otherwise we wind up with multiple gateway instances, because
|
85
|
-
# each connection is done in parallel.
|
86
|
-
connection_factory
|
94
|
+
# force the connection factory to be instantiated synchronously,
|
95
|
+
# otherwise we wind up with multiple gateway instances, because
|
96
|
+
# each connection is done in parallel.
|
97
|
+
connection_factory
|
87
98
|
|
88
|
-
|
89
|
-
|
90
|
-
end
|
99
|
+
threads = Array(servers).map { |server| establish_connection_to(server, failed_servers) }
|
100
|
+
threads.each { |t| t.join }
|
91
101
|
|
92
102
|
if failed_servers.any?
|
93
103
|
errors = failed_servers.map { |h| "#{h[:server]} (#{h[:error].class}: #{h[:error].message})" }
|
data/lib/capistrano/errors.rb
CHANGED
@@ -0,0 +1,53 @@
|
|
1
|
+
module Capistrano
|
2
|
+
module Processable
|
3
|
+
module SessionAssociation
|
4
|
+
def self.on(exception, session)
|
5
|
+
unless exception.respond_to?(:session)
|
6
|
+
exception.extend(self)
|
7
|
+
exception.session = session
|
8
|
+
end
|
9
|
+
|
10
|
+
return exception
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :session
|
14
|
+
end
|
15
|
+
|
16
|
+
def process_iteration(wait=nil, &block)
|
17
|
+
ensure_each_session { |session| session.preprocess }
|
18
|
+
|
19
|
+
return false if block && !block.call(self)
|
20
|
+
|
21
|
+
readers = sessions.map { |session| session.listeners.keys }.flatten.reject { |io| io.closed? }
|
22
|
+
writers = readers.select { |io| io.respond_to?(:pending_write?) && io.pending_write? }
|
23
|
+
|
24
|
+
if readers.any? || writers.any?
|
25
|
+
readers, writers, = IO.select(readers, writers, nil, wait)
|
26
|
+
end
|
27
|
+
|
28
|
+
if readers
|
29
|
+
ensure_each_session do |session|
|
30
|
+
ios = session.listeners.keys
|
31
|
+
session.postprocess(ios & readers, ios & writers)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def ensure_each_session
|
39
|
+
errors = []
|
40
|
+
|
41
|
+
sessions.each do |session|
|
42
|
+
begin
|
43
|
+
yield session
|
44
|
+
rescue Exception => error
|
45
|
+
errors << SessionAssociation.on(error, session)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
raise errors.first if errors.any?
|
50
|
+
sessions
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -15,6 +15,12 @@ module Capistrano
|
|
15
15
|
self
|
16
16
|
end
|
17
17
|
|
18
|
+
def file(path, options={})
|
19
|
+
@message ||= "`#{path}' is not a file"
|
20
|
+
try("test -f #{path}", options)
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
18
24
|
def writable(path, options={})
|
19
25
|
@message ||= "`#{path}' is not writable"
|
20
26
|
try("test -w #{path}", options)
|
@@ -24,14 +24,6 @@ module Capistrano
|
|
24
24
|
# * Supports :scm_command, :scm_password, :scm_passphrase Capistrano
|
25
25
|
# directives.
|
26
26
|
#
|
27
|
-
# REQUIREMENTS
|
28
|
-
# ------------
|
29
|
-
#
|
30
|
-
# Git is required to be installed on your remote machine(s), because a
|
31
|
-
# clone and checkout is done to get the code up there. This is the way
|
32
|
-
# I prefer to deploy; there is no alternative to this, so :deploy_via
|
33
|
-
# is ignored.
|
34
|
-
#
|
35
27
|
# CONFIGURATION
|
36
28
|
# -------------
|
37
29
|
#
|
@@ -100,13 +92,24 @@ module Capistrano
|
|
100
92
|
#
|
101
93
|
# set :git_shallow_clone, 1
|
102
94
|
#
|
95
|
+
# For those that don't like to leave your entire repository on
|
96
|
+
# your production server you can:
|
97
|
+
#
|
98
|
+
# set :deploy_via, :export
|
99
|
+
#
|
100
|
+
# To deploy from a local repository:
|
101
|
+
#
|
102
|
+
# set :repository, "file://."
|
103
|
+
# set :deploy_via, :copy
|
104
|
+
#
|
103
105
|
# AUTHORS
|
104
106
|
# -------
|
105
107
|
#
|
106
108
|
# Garry Dolley http://scie.nti.st
|
107
109
|
# Contributions by Geoffrey Grosenbach http://topfunky.com
|
108
110
|
# Scott Chacon http://jointheconversation.org
|
109
|
-
#
|
111
|
+
# Alex Arnell http://twologic.com
|
112
|
+
# and Phillip Goldenburg
|
110
113
|
|
111
114
|
class Git < Base
|
112
115
|
# Sets the default command name for this SCM on your *local* machine.
|
@@ -153,6 +156,12 @@ module Capistrano
|
|
153
156
|
|
154
157
|
execute.join(" && ")
|
155
158
|
end
|
159
|
+
|
160
|
+
# An expensive export. Performs a checkout as above, then
|
161
|
+
# removes the repo.
|
162
|
+
def export(revision, destination)
|
163
|
+
checkout(revision, destination) << " && rm -Rf #{destination}/.git"
|
164
|
+
end
|
156
165
|
|
157
166
|
# Merges the changes to 'head' since the last fetch, for remote_cache
|
158
167
|
# deployment strategy
|
@@ -175,7 +184,7 @@ module Capistrano
|
|
175
184
|
end
|
176
185
|
|
177
186
|
# since we're in a local branch already, just reset to specified revision rather than merge
|
178
|
-
execute << "#{git} fetch #{remote} && #{git} reset --hard #{revision}"
|
187
|
+
execute << "#{git} fetch --tags #{remote} && #{git} reset --hard #{revision}"
|
179
188
|
|
180
189
|
if configuration[:git_enable_submodules]
|
181
190
|
execute << "#{git} submodule init"
|
@@ -200,7 +209,13 @@ module Capistrano
|
|
200
209
|
# Getting the actual commit id, in case we were passed a tag
|
201
210
|
# or partial sha or something - it will return the sha if you pass a sha, too
|
202
211
|
def query_revision(revision)
|
203
|
-
|
212
|
+
return revision if revision =~ /^[0-9a-f]{40}$/
|
213
|
+
command = scm('ls-remote', repository, revision)
|
214
|
+
result = yield(command)
|
215
|
+
revdata = result.split("\t")
|
216
|
+
newrev = revdata[0]
|
217
|
+
raise "Unable to resolve revision for #{revision}" unless newrev =~ /^[0-9a-f]{40}$/
|
218
|
+
return newrev
|
204
219
|
end
|
205
220
|
|
206
221
|
def command
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'capistrano/recipes/deploy/scm/base'
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Deploy
|
5
|
+
module SCM
|
6
|
+
|
7
|
+
# A trivial SCM wrapper for representing the current working directory
|
8
|
+
# as a repository. Obviously, not all operations are available for this
|
9
|
+
# SCM, but it works sufficiently for use with the "copy" deployment
|
10
|
+
# strategy.
|
11
|
+
#
|
12
|
+
# Use of this module is _not_ recommended; in general, it is good
|
13
|
+
# practice to use some kind of source code management even for anything
|
14
|
+
# you are wanting to deploy. However, this module is provided in
|
15
|
+
# acknowledgement of the cases where trivial deployment of your current
|
16
|
+
# working directory is desired.
|
17
|
+
#
|
18
|
+
# set :repository, "."
|
19
|
+
# set :scm, :none
|
20
|
+
# set :deploy_via, :copy
|
21
|
+
class None < Base
|
22
|
+
# No versioning, thus, no head. Returns the empty string.
|
23
|
+
def head
|
24
|
+
""
|
25
|
+
end
|
26
|
+
|
27
|
+
# Simply does a copy from the :repository directory to the
|
28
|
+
# :destination directory.
|
29
|
+
def checkout(revision, destination)
|
30
|
+
"cp -R #{repository} #{destination}"
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :export, :checkout
|
34
|
+
|
35
|
+
# No versioning, so this just returns the argument, with no
|
36
|
+
# modification.
|
37
|
+
def query_revision(revision)
|
38
|
+
revision
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -15,7 +15,26 @@ module Capistrano
|
|
15
15
|
# of the source code. If you would rather use the export operation,
|
16
16
|
# you can set the :copy_strategy variable to :export.
|
17
17
|
#
|
18
|
-
#
|
18
|
+
# set :copy_strategy, :export
|
19
|
+
#
|
20
|
+
# For even faster deployments, you can set the :copy_cache variable to
|
21
|
+
# true. This will cause deployments to do a new checkout of your
|
22
|
+
# repository to a new directory, and then copy that checkout. Subsequent
|
23
|
+
# deploys will just resync that copy, rather than doing an entirely new
|
24
|
+
# checkout. Additionally, you can specify file patterns to exclude from
|
25
|
+
# the copy when using :copy_cache; just set the :copy_exclude variable
|
26
|
+
# to a file glob (or an array of globs).
|
27
|
+
#
|
28
|
+
# set :copy_cache, true
|
29
|
+
# set :copy_exclude, ".git/*"
|
30
|
+
#
|
31
|
+
# Note that :copy_strategy is ignored when :copy_cache is set. Also, if
|
32
|
+
# you want the copy cache put somewhere specific, you can set the variable
|
33
|
+
# to the path you want, instead of merely 'true':
|
34
|
+
#
|
35
|
+
# set :copy_cache, "/tmp/caches/myapp"
|
36
|
+
#
|
37
|
+
# This deployment strategy also supports a special variable,
|
19
38
|
# :copy_compression, which must be one of :gzip, :bz2, or
|
20
39
|
# :zip, and which specifies how the source should be compressed for
|
21
40
|
# transmission to each host.
|
@@ -25,8 +44,44 @@ module Capistrano
|
|
25
44
|
# servers, and uncompresses it on each of them into the deployment
|
26
45
|
# directory.
|
27
46
|
def deploy!
|
28
|
-
|
29
|
-
|
47
|
+
if copy_cache
|
48
|
+
if File.exists?(copy_cache)
|
49
|
+
logger.debug "refreshing local cache to revision #{revision} at #{copy_cache}"
|
50
|
+
system(source.sync(revision, copy_cache))
|
51
|
+
else
|
52
|
+
logger.debug "preparing local cache at #{copy_cache}"
|
53
|
+
system(source.checkout(revision, copy_cache))
|
54
|
+
end
|
55
|
+
|
56
|
+
logger.debug "copying cache to deployment staging area #{destination}"
|
57
|
+
Dir.chdir(copy_cache) do
|
58
|
+
FileUtils.mkdir_p(destination)
|
59
|
+
queue = Dir.glob("*", File::FNM_DOTMATCH)
|
60
|
+
while queue.any?
|
61
|
+
item = queue.shift
|
62
|
+
name = File.basename(item)
|
63
|
+
|
64
|
+
next if name == "." || name == ".."
|
65
|
+
next if copy_exclude.any? { |pattern| File.fnmatch(pattern, item) }
|
66
|
+
|
67
|
+
if File.directory?(item)
|
68
|
+
queue += Dir.glob("#{item}/*", File::FNM_DOTMATCH)
|
69
|
+
FileUtils.mkdir(File.join(destination, item))
|
70
|
+
else
|
71
|
+
FileUtils.ln(File.join(copy_cache, item), File.join(destination, item))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
else
|
76
|
+
logger.debug "getting (via #{copy_strategy}) revision #{revision} to #{destination}"
|
77
|
+
system(command)
|
78
|
+
|
79
|
+
if copy_exclude.any?
|
80
|
+
logger.debug "processing exclusions..."
|
81
|
+
copy_exclude.each { |pattern| FileUtils.rm_rf(File.join(destination, pattern)) }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
30
85
|
File.open(File.join(destination, "REVISION"), "w") { |f| f.puts(revision) }
|
31
86
|
|
32
87
|
logger.trace "compressing #{destination} to #{filename}"
|
@@ -48,8 +103,24 @@ module Capistrano
|
|
48
103
|
end
|
49
104
|
end
|
50
105
|
|
106
|
+
# Returns the location of the local copy cache, if the strategy should
|
107
|
+
# use a local cache + copy instead of a new checkout/export every
|
108
|
+
# time. Returns +nil+ unless :copy_cache has been set. If :copy_cache
|
109
|
+
# is +true+, a default cache location will be returned.
|
110
|
+
def copy_cache
|
111
|
+
@copy_cache ||= configuration[:copy_cache] == true ?
|
112
|
+
File.join(Dir.tmpdir, configuration[:application]) :
|
113
|
+
configuration[:copy_cache]
|
114
|
+
end
|
115
|
+
|
51
116
|
private
|
52
117
|
|
118
|
+
# Specify patterns to exclude from the copy. This is only valid
|
119
|
+
# when using a local cache.
|
120
|
+
def copy_exclude
|
121
|
+
@copy_exclude ||= Array(configuration.fetch(:copy_exclude, []))
|
122
|
+
end
|
123
|
+
|
53
124
|
# Returns the basename of the release_path, which will be used to
|
54
125
|
# name the local copy and archive file.
|
55
126
|
def destination
|