capistrano 2.0.0 → 2.15.9

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.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +9 -0
  4. data/CHANGELOG +755 -18
  5. data/Gemfile +13 -0
  6. data/README.md +94 -0
  7. data/Rakefile +11 -0
  8. data/bin/cap +0 -0
  9. data/bin/capify +37 -22
  10. data/capistrano.gemspec +40 -0
  11. data/lib/capistrano/callback.rb +5 -1
  12. data/lib/capistrano/cli/execute.rb +10 -7
  13. data/lib/capistrano/cli/help.rb +39 -16
  14. data/lib/capistrano/cli/help.txt +44 -16
  15. data/lib/capistrano/cli/options.rb +71 -11
  16. data/lib/capistrano/cli/ui.rb +13 -1
  17. data/lib/capistrano/cli.rb +5 -5
  18. data/lib/capistrano/command.rb +215 -58
  19. data/lib/capistrano/configuration/actions/file_transfer.rb +29 -14
  20. data/lib/capistrano/configuration/actions/inspect.rb +3 -3
  21. data/lib/capistrano/configuration/actions/invocation.rb +224 -22
  22. data/lib/capistrano/configuration/alias_task.rb +26 -0
  23. data/lib/capistrano/configuration/callbacks.rb +26 -27
  24. data/lib/capistrano/configuration/connections.rb +130 -52
  25. data/lib/capistrano/configuration/execution.rb +34 -18
  26. data/lib/capistrano/configuration/loading.rb +99 -6
  27. data/lib/capistrano/configuration/log_formatters.rb +75 -0
  28. data/lib/capistrano/configuration/namespaces.rb +45 -12
  29. data/lib/capistrano/configuration/roles.rb +34 -2
  30. data/lib/capistrano/configuration/servers.rb +51 -10
  31. data/lib/capistrano/configuration/variables.rb +3 -3
  32. data/lib/capistrano/configuration.rb +20 -4
  33. data/lib/capistrano/errors.rb +12 -8
  34. data/lib/capistrano/ext/multistage.rb +67 -0
  35. data/lib/capistrano/ext/string.rb +5 -0
  36. data/lib/capistrano/extensions.rb +1 -1
  37. data/lib/capistrano/fix_rake_deprecated_dsl.rb +8 -0
  38. data/lib/capistrano/logger.rb +112 -5
  39. data/lib/capistrano/processable.rb +55 -0
  40. data/lib/capistrano/recipes/compat.rb +2 -2
  41. data/lib/capistrano/recipes/deploy/assets.rb +202 -0
  42. data/lib/capistrano/recipes/deploy/dependencies.rb +2 -2
  43. data/lib/capistrano/recipes/deploy/local_dependency.rb +10 -2
  44. data/lib/capistrano/recipes/deploy/remote_dependency.rb +54 -2
  45. data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
  46. data/lib/capistrano/recipes/deploy/scm/base.rb +31 -11
  47. data/lib/capistrano/recipes/deploy/scm/bzr.rb +14 -14
  48. data/lib/capistrano/recipes/deploy/scm/cvs.rb +11 -9
  49. data/lib/capistrano/recipes/deploy/scm/darcs.rb +12 -1
  50. data/lib/capistrano/recipes/deploy/scm/git.rb +299 -0
  51. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +23 -15
  52. data/lib/capistrano/recipes/deploy/scm/none.rb +55 -0
  53. data/lib/capistrano/recipes/deploy/scm/perforce.rb +54 -28
  54. data/lib/capistrano/recipes/deploy/scm/subversion.rb +36 -18
  55. data/lib/capistrano/recipes/deploy/scm.rb +1 -1
  56. data/lib/capistrano/recipes/deploy/strategy/base.rb +32 -4
  57. data/lib/capistrano/recipes/deploy/strategy/copy.rb +238 -43
  58. data/lib/capistrano/recipes/deploy/strategy/remote.rb +1 -1
  59. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +11 -1
  60. data/lib/capistrano/recipes/deploy/strategy/unshared_remote_cache.rb +21 -0
  61. data/lib/capistrano/recipes/deploy/strategy.rb +4 -3
  62. data/lib/capistrano/recipes/deploy.rb +265 -123
  63. data/lib/capistrano/recipes/standard.rb +1 -1
  64. data/lib/capistrano/role.rb +102 -0
  65. data/lib/capistrano/server_definition.rb +6 -1
  66. data/lib/capistrano/shell.rb +30 -33
  67. data/lib/capistrano/ssh.rb +46 -60
  68. data/lib/capistrano/task_definition.rb +16 -8
  69. data/lib/capistrano/transfer.rb +218 -0
  70. data/lib/capistrano/version.rb +8 -17
  71. data/lib/capistrano.rb +4 -1
  72. data/test/cli/execute_test.rb +3 -3
  73. data/test/cli/help_test.rb +33 -7
  74. data/test/cli/options_test.rb +109 -6
  75. data/test/cli/ui_test.rb +2 -2
  76. data/test/cli_test.rb +3 -3
  77. data/test/command_test.rb +144 -124
  78. data/test/configuration/actions/file_transfer_test.rb +41 -20
  79. data/test/configuration/actions/inspect_test.rb +21 -7
  80. data/test/configuration/actions/invocation_test.rb +141 -30
  81. data/test/configuration/alias_task_test.rb +118 -0
  82. data/test/configuration/callbacks_test.rb +41 -46
  83. data/test/configuration/connections_test.rb +187 -36
  84. data/test/configuration/execution_test.rb +18 -2
  85. data/test/configuration/loading_test.rb +33 -4
  86. data/test/configuration/namespace_dsl_test.rb +54 -5
  87. data/test/configuration/roles_test.rb +114 -4
  88. data/test/configuration/servers_test.rb +97 -4
  89. data/test/configuration/variables_test.rb +12 -2
  90. data/test/configuration_test.rb +9 -13
  91. data/test/deploy/local_dependency_test.rb +76 -0
  92. data/test/deploy/remote_dependency_test.rb +146 -0
  93. data/test/deploy/scm/accurev_test.rb +23 -0
  94. data/test/deploy/scm/base_test.rb +1 -1
  95. data/test/deploy/scm/bzr_test.rb +51 -0
  96. data/test/deploy/scm/darcs_test.rb +37 -0
  97. data/test/deploy/scm/git_test.rb +274 -0
  98. data/test/deploy/scm/mercurial_test.rb +134 -0
  99. data/test/deploy/scm/none_test.rb +35 -0
  100. data/test/deploy/scm/perforce_test.rb +23 -0
  101. data/test/deploy/scm/subversion_test.rb +68 -0
  102. data/test/deploy/strategy/copy_test.rb +240 -26
  103. data/test/extensions_test.rb +2 -2
  104. data/test/logger_formatting_test.rb +149 -0
  105. data/test/logger_test.rb +13 -2
  106. data/test/recipes_test.rb +25 -0
  107. data/test/role_test.rb +11 -0
  108. data/test/server_definition_test.rb +15 -2
  109. data/test/shell_test.rb +33 -1
  110. data/test/ssh_test.rb +40 -24
  111. data/test/task_definition_test.rb +18 -2
  112. data/test/transfer_test.rb +168 -0
  113. data/test/utils.rb +28 -33
  114. data/test/version_test.rb +4 -17
  115. metadata +215 -100
  116. data/MIT-LICENSE +0 -20
  117. data/README +0 -43
  118. data/examples/sample.rb +0 -14
  119. data/lib/capistrano/gateway.rb +0 -131
  120. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +0 -53
  121. data/lib/capistrano/recipes/upgrade.rb +0 -33
  122. data/lib/capistrano/upload.rb +0 -146
  123. data/test/gateway_test.rb +0 -167
  124. data/test/upload_test.rb +0 -131
@@ -21,29 +21,29 @@ module Capistrano
21
21
  # Returns the command that will check out the given revision to the
22
22
  # given destination.
23
23
  def checkout(revision, destination)
24
- scm :checkout, verbose, authentication, "-r#{revision}", repository, destination
24
+ scm :checkout, arguments, arguments(:checkout), verbose, authentication, "-r#{revision}", repository, destination
25
25
  end
26
26
 
27
27
  # Returns the command that will do an "svn update" to the given
28
28
  # revision, for the working copy at the given destination.
29
29
  def sync(revision, destination)
30
- scm :update, verbose, authentication, "-r#{revision}", destination
30
+ scm :switch, arguments, verbose, authentication, "-r#{revision}", repository, destination
31
31
  end
32
32
 
33
33
  # Returns the command that will do an "svn export" of the given revision
34
34
  # to the given destination.
35
35
  def export(revision, destination)
36
- scm :export, verbose, authentication, "-r#{revision}", repository, destination
36
+ scm :export, arguments, arguments(:export), verbose, authentication, "-r#{revision}", repository, destination
37
37
  end
38
38
 
39
39
  # Returns the command that will do an "svn diff" for the two revisions.
40
40
  def diff(from, to=nil)
41
- scm :diff, repository, authentication, "-r#{from}:#{to || head}"
41
+ scm :diff, repository, arguments(:diff), authentication, "-r#{from}:#{to || head}"
42
42
  end
43
43
 
44
44
  # Returns an "svn log" command for the two revisions.
45
45
  def log(from, to=nil)
46
- scm :log, repository, authentication, "-r#{from}:#{to || head}"
46
+ scm :log, repository, arguments(:log), authentication, "-r#{from}:#{to || head}"
47
47
  end
48
48
 
49
49
  # Attempts to translate the given revision identifier to a "real"
@@ -52,27 +52,36 @@ module Capistrano
52
52
  # executed (svn info), and will extract the revision from the response.
53
53
  def query_revision(revision)
54
54
  return revision if revision =~ /^\d+$/
55
- result = yield(scm(:info, repository, authentication, "-r#{revision}"))
56
- YAML.load(result)['Revision']
55
+ command = scm(:info, arguments, arguments(:info), repository, authentication, "-r#{revision}")
56
+ result = yield(command)
57
+ yaml = YAML.load(result)
58
+ raise "tried to run `#{command}' and got unexpected result #{result.inspect}" unless Hash === yaml
59
+ [ (yaml['Last Changed Rev'] || 0).to_i, (yaml['Revision'] || 0).to_i ].max
60
+ end
61
+
62
+ # Increments the given revision number and returns it.
63
+ def next_revision(revision)
64
+ revision.to_i + 1
57
65
  end
58
66
 
59
67
  # Determines what the response should be for a particular bit of text
60
68
  # from the SCM. Password prompts, connection requests, passphrases,
61
69
  # etc. are handled here.
62
70
  def handle_data(state, stream, text)
63
- logger.info "[#{stream}] #{text}"
71
+ host = state[:channel][:host]
72
+ logger.info "[#{host} :: #{stream}] #{text}"
64
73
  case text
65
74
  when /\bpassword.*:/i
66
75
  # subversion is prompting for a password
67
- "#{variable(:scm_password) || variable(:password)}\n"
76
+ %("#{scm_password_prompt}"\n)
68
77
  when %r{\(yes/no\)}
69
78
  # subversion is asking whether or not to connect
70
79
  "yes\n"
71
80
  when /passphrase/i
72
81
  # subversion is asking for the passphrase for the user's key
73
- "#{variable(:scm_passphrase)}\n"
82
+ %("#{variable(:scm_passphrase)}"\n)
74
83
  when /The entry \'(.+?)\' is no longer a directory/
75
- raise Capisrano::Error, "subversion can't update because directory '#{$1}' was replaced. Please add it to svn:ignore."
84
+ raise Capistrano::Error, "subversion can't update because directory '#{$1}' was replaced. Please add it to svn:ignore."
76
85
  when /accept \(t\)emporarily/
77
86
  # subversion is asking whether to accept the certificate
78
87
  "t\n"
@@ -81,14 +90,17 @@ module Capistrano
81
90
 
82
91
  private
83
92
 
84
- # If a username or password is configured for the SCM, return the
85
- # command-line switches for those values.
93
+ # If a username is configured for the SCM, return the command-line
94
+ # switches for that. Note that we don't need to return the password
95
+ # switch, since Capistrano will check for that prompt in the output
96
+ # and will respond appropriately.
86
97
  def authentication
87
- auth = ""
88
- auth << "--username #{variable(:scm_username)} " if variable(:scm_username)
89
- auth << "--password #{variable(:scm_password)} " if variable(:scm_password)
90
- auth << "--no-auth-cache" if !auth.empty?
91
- auth
98
+ username = variable(:scm_username)
99
+ return "" unless username
100
+ result = %(--username "#{variable(:scm_username)}")
101
+ result << %( --password "#{variable(:scm_password)}") unless variable(:scm_auth_cache) || variable(:scm_prefer_prompt)
102
+ result << " --no-auth-cache " unless variable(:scm_auth_cache)
103
+ result
92
104
  end
93
105
 
94
106
  # If verbose output is requested, return nil, otherwise return the
@@ -96,6 +108,12 @@ module Capistrano
96
108
  def verbose
97
109
  variable(:scm_verbose) ? nil : "-q"
98
110
  end
111
+
112
+ def scm_password_prompt
113
+ @scm_password_prompt ||= variable(:scm_password) ||
114
+ variable(:password) ||
115
+ Capistrano::CLI.password_prompt("Subversion password: ")
116
+ end
99
117
  end
100
118
 
101
119
  end
@@ -16,4 +16,4 @@ module Capistrano
16
16
  end
17
17
  end
18
18
  end
19
- end
19
+ end
@@ -1,3 +1,4 @@
1
+ require 'benchmark'
1
2
  require 'capistrano/recipes/deploy/dependencies'
2
3
 
3
4
  module Capistrano
@@ -28,12 +29,16 @@ module Capistrano
28
29
  # is setup such that a deploy could succeed.
29
30
  def check!
30
31
  Dependencies.new(configuration) do |d|
31
- d.remote.directory(configuration[:releases_path]).or("`#{configuration[:releases_path]}' does not exist. Please run `cap deploy:setup'.")
32
+ if exists?(:stage)
33
+ d.remote.directory(configuration[:releases_path]).or("`#{configuration[:releases_path]}' does not exist. Please run `cap #{configuration[:stage]} deploy:setup'.")
34
+ else
35
+ d.remote.directory(configuration[:releases_path]).or("`#{configuration[:releases_path]}' does not exist. Please run `cap deploy:setup'.")
36
+ end
32
37
  d.remote.writable(configuration[:deploy_to]).or("You do not have permissions to write to `#{configuration[:deploy_to]}'.")
33
38
  d.remote.writable(configuration[:releases_path]).or("You do not have permissions to write to `#{configuration[:releases_path]}'.")
34
39
  end
35
40
  end
36
-
41
+
37
42
  protected
38
43
 
39
44
  # This is to allow helper methods like "run" and "put" to be more
@@ -46,10 +51,33 @@ module Capistrano
46
51
  end
47
52
  end
48
53
 
54
+ # A wrapper for Kernel#system that logs the command being executed.
55
+ def system(*args)
56
+ cmd = args.join(' ')
57
+ result = nil
58
+ if RUBY_PLATFORM =~ /win32/
59
+ cmd = cmd.split(/\s+/).collect {|w| w.match(/^[\w+]+:\/\//) ? w : w.gsub('/', '\\') }.join(' ') # Split command by spaces, change / by \\ unless element is a some+thing://
60
+ cmd.gsub!(/^cd /,'cd /D ') # Replace cd with cd /D
61
+ cmd.gsub!(/&& cd /,'&& cd /D ') # Replace cd with cd /D
62
+ logger.trace "executing locally: #{cmd}"
63
+ elapsed = Benchmark.realtime do
64
+ result = super(cmd)
65
+ end
66
+ else
67
+ logger.trace "executing locally: #{cmd}"
68
+ elapsed = Benchmark.realtime do
69
+ result = super
70
+ end
71
+ end
72
+
73
+ logger.trace "command finished in #{(elapsed * 1000).round}ms"
74
+ result
75
+ end
76
+
49
77
  private
50
78
 
51
79
  def logger
52
- @logger ||= configuration[:logger] || Capistrano::Logger.new(:output => STDOUT)
80
+ @logger ||= configuration.logger || Capistrano::Logger.new(:output => STDOUT)
53
81
  end
54
82
 
55
83
  # The revision to deploy. Must return a real revision identifier,
@@ -61,4 +89,4 @@ module Capistrano
61
89
 
62
90
  end
63
91
  end
64
- end
92
+ end
@@ -15,44 +15,236 @@ 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
- # This deployment strategy supports a special variable,
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.
41
+ #
42
+ # By default, files will be transferred across to the remote machines via 'sftp'. If you prefer
43
+ # to use 'scp' you can set the :copy_via variable to :scp.
44
+ #
45
+ # set :copy_via, :scp
46
+ #
47
+ # There is a possibility to pass a build command that will get
48
+ # executed if your code needs to be compiled or something needs to be
49
+ # done before the code is ready to run.
50
+ #
51
+ # set :build_script, "make all"
52
+ #
53
+ # Note that if you use :copy_cache, the :build_script is used on the
54
+ # cache and thus you get faster compilation if your script does not
55
+ # recompile everything.
22
56
  class Copy < Base
23
57
  # Obtains a copy of the source code locally (via the #command method),
24
58
  # compresses it to a single file, copies that file to all target
25
59
  # servers, and uncompresses it on each of them into the deployment
26
60
  # directory.
27
61
  def deploy!
28
- logger.debug "getting (via #{copy_strategy}) revision #{revision} to #{destination}"
29
- system(command)
30
- File.open(File.join(destination, "REVISION"), "w") { |f| f.puts(revision) }
31
-
32
- logger.trace "compressing #{destination} to #{filename}"
33
- Dir.chdir(tmpdir) { system(compress(File.basename(destination), File.basename(filename)).join(" ")) }
62
+ copy_cache ? run_copy_cache_strategy : run_copy_strategy
34
63
 
35
- put File.read(filename), remote_filename
36
- run "cd #{configuration[:releases_path]} && #{decompress(remote_filename).join(" ")} && rm #{remote_filename}"
64
+ create_revision_file
65
+ compress_repository
66
+ distribute!
37
67
  ensure
38
- FileUtils.rm filename rescue nil
39
- FileUtils.rm_rf destination rescue nil
68
+ rollback_changes
69
+ end
70
+
71
+ def build directory
72
+ execute "running build script on #{directory}" do
73
+ Dir.chdir(directory) { system(build_script) }
74
+ end if build_script
40
75
  end
41
76
 
42
77
  def check!
43
78
  super.check do |d|
44
- d.local.command(source.local.command)
79
+ d.local.command(source.local.command) if source.local.command
45
80
  d.local.command(compress(nil, nil).first)
46
81
  d.remote.command(decompress(nil).first)
47
82
  end
48
83
  end
49
84
 
85
+ # Returns the location of the local copy cache, if the strategy should
86
+ # use a local cache + copy instead of a new checkout/export every
87
+ # time. Returns +nil+ unless :copy_cache has been set. If :copy_cache
88
+ # is +true+, a default cache location will be returned.
89
+ def copy_cache
90
+ @copy_cache ||= configuration[:copy_cache] == true ?
91
+ File.expand_path(configuration[:application], Dir.tmpdir) :
92
+ File.expand_path(configuration[:copy_cache], Dir.pwd) rescue nil
93
+ end
94
+
50
95
  private
51
96
 
97
+ def run_copy_cache_strategy
98
+ copy_repository_to_local_cache
99
+ build copy_cache
100
+ copy_cache_to_staging_area
101
+ end
102
+
103
+ def run_copy_strategy
104
+ copy_repository_to_server
105
+ build destination
106
+ remove_excluded_files if copy_exclude.any?
107
+ end
108
+
109
+ def execute description, &block
110
+ logger.debug description
111
+ handle_system_errors &block
112
+ end
113
+
114
+ def handle_system_errors &block
115
+ block.call
116
+ raise_command_failed if last_command_failed?
117
+ end
118
+
119
+ def refresh_local_cache
120
+ execute "refreshing local cache to revision #{revision} at #{copy_cache}" do
121
+ system(source.sync(revision, copy_cache))
122
+ end
123
+ end
124
+
125
+ def create_local_cache
126
+ execute "preparing local cache at #{copy_cache}" do
127
+ system(source.checkout(revision, copy_cache))
128
+ end
129
+ end
130
+
131
+ def raise_command_failed
132
+ raise Capistrano::Error, "shell command failed with return code #{$?}"
133
+ end
134
+
135
+ def last_command_failed?
136
+ $? != 0
137
+ end
138
+
139
+ def copy_cache_to_staging_area
140
+ execute "copying cache to deployment staging area #{destination}" do
141
+ create_destination
142
+ Dir.chdir(copy_cache) { copy_files(queue_files) }
143
+ end
144
+ end
145
+
146
+ def create_destination
147
+ FileUtils.mkdir_p(destination)
148
+ end
149
+
150
+ def copy_files files
151
+ files.each { |name| process_file(name) }
152
+ end
153
+
154
+ def process_file name
155
+ send "copy_#{filetype(name)}", name
156
+ end
157
+
158
+ def filetype name
159
+ filetype = File.ftype name
160
+ filetype = "file" unless ["link", "directory"].include? filetype
161
+ filetype
162
+ end
163
+
164
+ def copy_link name
165
+ FileUtils.ln_s(File.readlink(name), File.join(destination, name))
166
+ end
167
+
168
+ def copy_directory name
169
+ FileUtils.mkdir(File.join(destination, name))
170
+ copy_files(queue_files(name))
171
+ end
172
+
173
+ def copy_file name
174
+ FileUtils.ln(name, File.join(destination, name))
175
+ end
176
+
177
+ def queue_files directory=nil
178
+ Dir.glob(pattern_for(directory), File::FNM_DOTMATCH).reject! { |file| excluded_files_contain? file }
179
+ end
180
+
181
+ def pattern_for directory
182
+ !directory.nil? ? "#{escape_globs(directory)}/*" : "*"
183
+ end
184
+
185
+ def escape_globs path
186
+ path.gsub(/[*?{}\[\]]/, '\\\\\\&')
187
+ end
188
+
189
+ def excluded_files_contain? file
190
+ copy_exclude.any? { |p| File.fnmatch(p, file) } or [ ".", ".."].include? File.basename(file)
191
+ end
192
+
193
+ def copy_repository_to_server
194
+ execute "getting (via #{copy_strategy}) revision #{revision} to #{destination}" do
195
+ copy_repository_via_strategy
196
+ end
197
+ end
198
+
199
+ def copy_repository_via_strategy
200
+ system(command)
201
+ end
202
+
203
+ def remove_excluded_files
204
+ logger.debug "processing exclusions..."
205
+
206
+ copy_exclude.each do |pattern|
207
+ delete_list = Dir.glob(File.join(destination, pattern), File::FNM_DOTMATCH)
208
+ # avoid the /.. trap that deletes the parent directories
209
+ delete_list.delete_if { |dir| dir =~ /\/\.\.$/ }
210
+ FileUtils.rm_rf(delete_list.compact)
211
+ end
212
+ end
213
+
214
+ def create_revision_file
215
+ File.open(File.join(destination, "REVISION"), "w") { |f| f.puts(revision) }
216
+ end
217
+
218
+ def compress_repository
219
+ execute "Compressing #{destination} to #{filename}" do
220
+ Dir.chdir(copy_dir) { system(compress(File.basename(destination), File.basename(filename)).join(" ")) }
221
+ end
222
+ end
223
+
224
+ def rollback_changes
225
+ FileUtils.rm filename rescue nil
226
+ FileUtils.rm_rf destination rescue nil
227
+ end
228
+
229
+ def copy_repository_to_local_cache
230
+ return refresh_local_cache if File.exists?(copy_cache)
231
+ create_local_cache
232
+ end
233
+
234
+ def build_script
235
+ configuration[:build_script]
236
+ end
237
+
238
+ # Specify patterns to exclude from the copy. This is only valid
239
+ # when using a local cache.
240
+ def copy_exclude
241
+ @copy_exclude ||= Array(configuration.fetch(:copy_exclude, []))
242
+ end
243
+
52
244
  # Returns the basename of the release_path, which will be used to
53
245
  # name the local copy and archive file.
54
246
  def destination
55
- @destination ||= File.join(tmpdir, File.basename(configuration[:release_path]))
247
+ @destination ||= File.join(copy_dir, File.basename(configuration[:release_path]))
56
248
  end
57
249
 
58
250
  # Returns the value of the :copy_strategy variable, defaulting to
@@ -75,12 +267,12 @@ module Capistrano
75
267
  # Returns the name of the file that the source code will be
76
268
  # compressed to.
77
269
  def filename
78
- @filename ||= File.join(tmpdir, "#{File.basename(destination)}.#{compression_extension}")
270
+ @filename ||= File.join(copy_dir, "#{File.basename(destination)}.#{compression.extension}")
79
271
  end
80
272
 
81
273
  # The directory to which the copy should be checked out
82
- def tmpdir
83
- @tmpdir ||= configuration[:copy_dir] || Dir.tmpdir
274
+ def copy_dir
275
+ @copy_dir ||= File.expand_path(configuration[:copy_dir] || Dir.tmpdir, Dir.pwd)
84
276
  end
85
277
 
86
278
  # The directory on the remote server to which the archive should be
@@ -95,46 +287,49 @@ module Capistrano
95
287
  @remote_filename ||= File.join(remote_dir, File.basename(filename))
96
288
  end
97
289
 
290
+ # A struct for representing the specifics of a compression type.
291
+ # Commands are arrays, where the first element is the utility to be
292
+ # used to perform the compression or decompression.
293
+ Compression = Struct.new(:extension, :compress_command, :decompress_command)
294
+
98
295
  # The compression method to use, defaults to :gzip.
99
296
  def compression
100
- configuration[:copy_compression] || :gzip
101
- end
297
+ remote_tar = configuration[:copy_remote_tar] || 'tar'
298
+ local_tar = configuration[:copy_local_tar] || 'tar'
102
299
 
103
- # Returns the file extension used for the compression method in
104
- # question.
105
- def compression_extension
106
- case compression
107
- when :gzip, :gz then "tar.gz"
108
- when :bzip2, :bz2 then "tar.bz2"
109
- when :zip then "zip"
110
- else raise ArgumentError, "invalid compression type #{compression.inspect}"
300
+ type = configuration[:copy_compression] || :gzip
301
+ case type
302
+ when :gzip, :gz then Compression.new("tar.gz", [local_tar, 'czf'], [remote_tar, 'xzf'])
303
+ when :bzip2, :bz2 then Compression.new("tar.bz2", [local_tar, 'cjf'], [remote_tar, 'xjf'])
304
+ when :zip then Compression.new("zip", %w(zip -qyr), %w(unzip -q))
305
+ else raise ArgumentError, "invalid compression type #{type.inspect}"
111
306
  end
112
307
  end
113
308
 
114
309
  # Returns the command necessary to compress the given directory
115
- # into the given file. The command is returned as an array, where
116
- # the first element is the utility to be used to perform the compression.
310
+ # into the given file.
117
311
  def compress(directory, file)
118
- case compression
119
- when :gzip, :gz then ["tar", "czf", file, directory]
120
- when :bzip2, :bz2 then ["tar", "cjf", file, directory]
121
- when :zip then ["zip", "-qr", file, directory]
122
- else raise ArgumentError, "invalid compression type #{compression.inspect}"
123
- end
312
+ compression.compress_command + [file, directory]
124
313
  end
125
314
 
126
315
  # Returns the command necessary to decompress the given file,
127
316
  # relative to the current working directory. It must also
128
- # preserve the directory structure in the file. The command is returned
129
- # as an array, where the first element is the utility to be used to
130
- # perform the decompression.
317
+ # preserve the directory structure in the file.
131
318
  def decompress(file)
132
- case compression
133
- when :gzip, :gz then ["tar", "xzf", file]
134
- when :bzip2, :bz2 then ["tar", "xjf", file]
135
- when :zip then ["unzip", "-q", file]
136
- else raise ArgumentError, "invalid compression type #{compression.inspect}"
137
- end
319
+ compression.decompress_command + [file]
320
+ end
321
+
322
+ def decompress_remote_file
323
+ run "cd #{configuration[:releases_path]} && #{decompress(remote_filename).join(" ")} && rm #{remote_filename}"
324
+ end
325
+
326
+ # Distributes the file to the remote servers
327
+ def distribute!
328
+ args = [filename, remote_filename]
329
+ args << { :via => configuration[:copy_via] } if configuration[:copy_via]
330
+
331
+ upload(*args)
332
+ decompress_remote_file
138
333
  end
139
334
  end
140
335
 
@@ -27,7 +27,7 @@ module Capistrano
27
27
  # #handle_data filter of the SCM implementation.
28
28
  def scm_run(command)
29
29
  run(command) do |ch,stream,text|
30
- ch[:state] ||= {}
30
+ ch[:state] ||= { :channel => ch }
31
31
  output = source.handle_data(ch[:state], stream, text)
32
32
  ch.send_data(output) if output
33
33
  end
@@ -18,6 +18,7 @@ module Capistrano
18
18
 
19
19
  def check!
20
20
  super.check do |d|
21
+ d.remote.command("rsync") unless copy_exclude.empty?
21
22
  d.remote.writable(shared_path)
22
23
  end
23
24
  end
@@ -38,7 +39,16 @@ module Capistrano
38
39
 
39
40
  def copy_repository_cache
40
41
  logger.trace "copying the cached version to #{configuration[:release_path]}"
41
- run "cp -RPp #{repository_cache} #{configuration[:release_path]} && #{mark}"
42
+ if copy_exclude.empty?
43
+ run "cp -RPp #{repository_cache} #{configuration[:release_path]} && #{mark}"
44
+ else
45
+ exclusions = copy_exclude.map { |e| "--exclude=\"#{e}\"" }.join(' ')
46
+ run "rsync -lrpt #{exclusions} #{repository_cache}/ #{configuration[:release_path]} && #{mark}"
47
+ end
48
+ end
49
+
50
+ def copy_exclude
51
+ @copy_exclude ||= Array(configuration.fetch(:copy_exclude, []))
42
52
  end
43
53
  end
44
54
 
@@ -0,0 +1,21 @@
1
+ require 'capistrano/recipes/deploy/strategy/remote_cache'
2
+
3
+ module Capistrano
4
+ module Deploy
5
+ module Strategy
6
+ class UnsharedRemoteCache < RemoteCache
7
+ def check!
8
+ super.check do |d|
9
+ d.remote.writable(repository_cache)
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def repository_cache
16
+ configuration[:repository_cache]
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -3,9 +3,10 @@ module Capistrano
3
3
  module Strategy
4
4
  def self.new(strategy, config={})
5
5
  strategy_file = "capistrano/recipes/deploy/strategy/#{strategy}"
6
- require(strategy_file)
7
-
8
6
  strategy_const = strategy.to_s.capitalize.gsub(/_(.)/) { $1.upcase }
7
+
8
+ require(strategy_file) unless const_defined?(strategy_const)
9
+
9
10
  if const_defined?(strategy_const)
10
11
  const_get(strategy_const).new(config)
11
12
  else
@@ -16,4 +17,4 @@ module Capistrano
16
17
  end
17
18
  end
18
19
  end
19
- end
20
+ end