capistrano 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|