capistrano 2.4.3 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. data/CHANGELOG.rdoc +39 -0
  2. data/capistrano.gemspec +5 -5
  3. data/lib/capistrano/cli/execute.rb +1 -0
  4. data/lib/capistrano/cli/help.txt +6 -0
  5. data/lib/capistrano/cli/options.rb +26 -0
  6. data/lib/capistrano/cli/ui.rb +0 -1
  7. data/lib/capistrano/command.rb +175 -47
  8. data/lib/capistrano/configuration.rb +2 -1
  9. data/lib/capistrano/configuration/actions/invocation.rb +38 -7
  10. data/lib/capistrano/configuration/connections.rb +12 -6
  11. data/lib/capistrano/configuration/execution.rb +2 -1
  12. data/lib/capistrano/configuration/servers.rb +5 -4
  13. data/lib/capistrano/recipes/deploy.rb +45 -22
  14. data/lib/capistrano/recipes/deploy/local_dependency.rb +7 -3
  15. data/lib/capistrano/recipes/deploy/scm/cvs.rb +2 -1
  16. data/lib/capistrano/recipes/deploy/scm/none.rb +1 -1
  17. data/lib/capistrano/recipes/deploy/scm/subversion.rb +1 -1
  18. data/lib/capistrano/recipes/deploy/strategy/copy.rb +1 -1
  19. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +10 -1
  20. data/lib/capistrano/role.rb +4 -0
  21. data/lib/capistrano/version.rb +2 -2
  22. data/test/cli/execute_test.rb +1 -1
  23. data/test/cli/options_test.rb +84 -0
  24. data/test/command_test.rb +38 -41
  25. data/test/configuration/actions/invocation_test.rb +12 -16
  26. data/test/configuration/connections_test.rb +30 -9
  27. data/test/configuration/execution_test.rb +16 -0
  28. data/test/configuration/servers_test.rb +15 -0
  29. data/test/configuration_test.rb +8 -1
  30. data/test/deploy/local_dependency_test.rb +15 -12
  31. data/test/deploy/scm/none_test.rb +35 -0
  32. data/test/deploy/strategy/copy_test.rb +13 -0
  33. metadata +3 -2
@@ -1,3 +1,42 @@
1
+ == 2.5.0 / August 28, 2008
2
+
3
+ * Allow :gateway to be set to an array, in which case a chain of tunnels is created [Kerry Buckley]
4
+
5
+ * Allow HOSTS spec to override even non-existent roles [Mike Bailey]
6
+
7
+ * Sort releases via "ls -xt" instead of "ls -x" to allow for custom release names [Yan Pritzker]
8
+
9
+ * Convert arguments to -s and -S into integers, booleans, etc. based on whether the arguments appear to be those types [Jamis Buck]
10
+
11
+ * Add descriptions of -n and -d to the verbose help text [Jamis Buck]
12
+
13
+ * Make rollbacks work with processes that need the current directory to be valid in order to restart properly (e.g. mongrel_rails) [Jamis Buck]
14
+
15
+ * Rename deploy:rollback_code to deploy:rollback:code [Jamis Buck]
16
+
17
+ * Added parallel() helper for executing multiple different commands in parallel [Jamis Buck]
18
+
19
+ * Make sure a task only uses the last on_rollback block, once, on rollback [Jamis Buck]
20
+
21
+ * Add :shared_children variable to customize which subdirectories are created by deploy:setup [Jonathan Share]
22
+
23
+ * Allow filename globbing in copy_exclude setting for the copy strategy [Jonathan Share]
24
+
25
+ * Allow remote_cache strategy to use copy_exclude settings (requires rsync) [Lewis Mackenzie]
26
+
27
+ * Make None SCM module work in Windows [Carlos Kozuszko]
28
+
29
+ * Recognize mingw as a Windows platform [Carlos Kozuszko]
30
+
31
+ * Fixed failing tests in Windows [Carlos Kozuszko]
32
+
33
+ * Made :scm_auth_cache control whether password option is emitted in subversion module [Brendan Schwartz]
34
+
35
+ * Fixed timestamp bug in CVS module [Jørgen Fjeld]
36
+
37
+ * Added -n/--dry-run switch, to display but not execute remote tasks [Paul Gross]
38
+
39
+
1
40
  == 2.4.3 / June 28, 2008
2
41
 
3
42
  * Fix gem dependencies so gem actually understands them [Jamis Buck]
@@ -1,19 +1,19 @@
1
1
 
2
- # Gem::Specification for Capistrano-2.4.3
2
+ # Gem::Specification for Capistrano-2.5.0
3
3
  # Originally generated by Echoe
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = %q{capistrano}
7
- s.version = "2.4.3"
7
+ s.version = "2.5.0"
8
8
 
9
9
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
10
  s.authors = ["Jamis Buck"]
11
- s.date = %q{2008-06-28}
11
+ s.date = %q{2008-08-28}
12
12
  s.description = %q{Capistrano is a utility and framework for executing commands in parallel on multiple remote machines, via SSH.}
13
13
  s.email = %q{jamis@jamisbuck.org}
14
14
  s.executables = ["cap", "capify"]
15
15
  s.extra_rdoc_files = ["CHANGELOG.rdoc", "lib/capistrano/callback.rb", "lib/capistrano/cli/execute.rb", "lib/capistrano/cli/help.rb", "lib/capistrano/cli/help.txt", "lib/capistrano/cli/options.rb", "lib/capistrano/cli/ui.rb", "lib/capistrano/cli.rb", "lib/capistrano/command.rb", "lib/capistrano/configuration/actions/file_transfer.rb", "lib/capistrano/configuration/actions/inspect.rb", "lib/capistrano/configuration/actions/invocation.rb", "lib/capistrano/configuration/callbacks.rb", "lib/capistrano/configuration/connections.rb", "lib/capistrano/configuration/execution.rb", "lib/capistrano/configuration/loading.rb", "lib/capistrano/configuration/namespaces.rb", "lib/capistrano/configuration/roles.rb", "lib/capistrano/configuration/servers.rb", "lib/capistrano/configuration/variables.rb", "lib/capistrano/configuration.rb", "lib/capistrano/errors.rb", "lib/capistrano/extensions.rb", "lib/capistrano/logger.rb", "lib/capistrano/processable.rb", "lib/capistrano/recipes/compat.rb", "lib/capistrano/recipes/deploy/dependencies.rb", "lib/capistrano/recipes/deploy/local_dependency.rb", "lib/capistrano/recipes/deploy/remote_dependency.rb", "lib/capistrano/recipes/deploy/scm/accurev.rb", "lib/capistrano/recipes/deploy/scm/base.rb", "lib/capistrano/recipes/deploy/scm/bzr.rb", "lib/capistrano/recipes/deploy/scm/cvs.rb", "lib/capistrano/recipes/deploy/scm/darcs.rb", "lib/capistrano/recipes/deploy/scm/git.rb", "lib/capistrano/recipes/deploy/scm/mercurial.rb", "lib/capistrano/recipes/deploy/scm/none.rb", "lib/capistrano/recipes/deploy/scm/perforce.rb", "lib/capistrano/recipes/deploy/scm/subversion.rb", "lib/capistrano/recipes/deploy/scm.rb", "lib/capistrano/recipes/deploy/strategy/base.rb", "lib/capistrano/recipes/deploy/strategy/checkout.rb", "lib/capistrano/recipes/deploy/strategy/copy.rb", "lib/capistrano/recipes/deploy/strategy/export.rb", "lib/capistrano/recipes/deploy/strategy/remote.rb", "lib/capistrano/recipes/deploy/strategy/remote_cache.rb", "lib/capistrano/recipes/deploy/strategy.rb", "lib/capistrano/recipes/deploy/templates/maintenance.rhtml", "lib/capistrano/recipes/deploy.rb", "lib/capistrano/recipes/standard.rb", "lib/capistrano/recipes/templates/maintenance.rhtml", "lib/capistrano/recipes/upgrade.rb", "lib/capistrano/role.rb", "lib/capistrano/server_definition.rb", "lib/capistrano/shell.rb", "lib/capistrano/ssh.rb", "lib/capistrano/task_definition.rb", "lib/capistrano/transfer.rb", "lib/capistrano/version.rb", "lib/capistrano.rb", "README.rdoc"]
16
- s.files = ["bin/cap", "bin/capify", "CHANGELOG.rdoc", "examples/sample.rb", "lib/capistrano/callback.rb", "lib/capistrano/cli/execute.rb", "lib/capistrano/cli/help.rb", "lib/capistrano/cli/help.txt", "lib/capistrano/cli/options.rb", "lib/capistrano/cli/ui.rb", "lib/capistrano/cli.rb", "lib/capistrano/command.rb", "lib/capistrano/configuration/actions/file_transfer.rb", "lib/capistrano/configuration/actions/inspect.rb", "lib/capistrano/configuration/actions/invocation.rb", "lib/capistrano/configuration/callbacks.rb", "lib/capistrano/configuration/connections.rb", "lib/capistrano/configuration/execution.rb", "lib/capistrano/configuration/loading.rb", "lib/capistrano/configuration/namespaces.rb", "lib/capistrano/configuration/roles.rb", "lib/capistrano/configuration/servers.rb", "lib/capistrano/configuration/variables.rb", "lib/capistrano/configuration.rb", "lib/capistrano/errors.rb", "lib/capistrano/extensions.rb", "lib/capistrano/logger.rb", "lib/capistrano/processable.rb", "lib/capistrano/recipes/compat.rb", "lib/capistrano/recipes/deploy/dependencies.rb", "lib/capistrano/recipes/deploy/local_dependency.rb", "lib/capistrano/recipes/deploy/remote_dependency.rb", "lib/capistrano/recipes/deploy/scm/accurev.rb", "lib/capistrano/recipes/deploy/scm/base.rb", "lib/capistrano/recipes/deploy/scm/bzr.rb", "lib/capistrano/recipes/deploy/scm/cvs.rb", "lib/capistrano/recipes/deploy/scm/darcs.rb", "lib/capistrano/recipes/deploy/scm/git.rb", "lib/capistrano/recipes/deploy/scm/mercurial.rb", "lib/capistrano/recipes/deploy/scm/none.rb", "lib/capistrano/recipes/deploy/scm/perforce.rb", "lib/capistrano/recipes/deploy/scm/subversion.rb", "lib/capistrano/recipes/deploy/scm.rb", "lib/capistrano/recipes/deploy/strategy/base.rb", "lib/capistrano/recipes/deploy/strategy/checkout.rb", "lib/capistrano/recipes/deploy/strategy/copy.rb", "lib/capistrano/recipes/deploy/strategy/export.rb", "lib/capistrano/recipes/deploy/strategy/remote.rb", "lib/capistrano/recipes/deploy/strategy/remote_cache.rb", "lib/capistrano/recipes/deploy/strategy.rb", "lib/capistrano/recipes/deploy/templates/maintenance.rhtml", "lib/capistrano/recipes/deploy.rb", "lib/capistrano/recipes/standard.rb", "lib/capistrano/recipes/templates/maintenance.rhtml", "lib/capistrano/recipes/upgrade.rb", "lib/capistrano/role.rb", "lib/capistrano/server_definition.rb", "lib/capistrano/shell.rb", "lib/capistrano/ssh.rb", "lib/capistrano/task_definition.rb", "lib/capistrano/transfer.rb", "lib/capistrano/version.rb", "lib/capistrano.rb", "Rakefile", "README.rdoc", "setup.rb", "test/cli/execute_test.rb", "test/cli/help_test.rb", "test/cli/options_test.rb", "test/cli/ui_test.rb", "test/cli_test.rb", "test/command_test.rb", "test/configuration/actions/file_transfer_test.rb", "test/configuration/actions/inspect_test.rb", "test/configuration/actions/invocation_test.rb", "test/configuration/callbacks_test.rb", "test/configuration/connections_test.rb", "test/configuration/execution_test.rb", "test/configuration/loading_test.rb", "test/configuration/namespace_dsl_test.rb", "test/configuration/roles_test.rb", "test/configuration/servers_test.rb", "test/configuration/variables_test.rb", "test/configuration_test.rb", "test/deploy/local_dependency_test.rb", "test/deploy/remote_dependency_test.rb", "test/deploy/scm/accurev_test.rb", "test/deploy/scm/base_test.rb", "test/deploy/scm/git_test.rb", "test/deploy/scm/mercurial_test.rb", "test/deploy/strategy/copy_test.rb", "test/extensions_test.rb", "test/fixtures/cli_integration.rb", "test/fixtures/config.rb", "test/fixtures/custom.rb", "test/logger_test.rb", "test/role_test.rb", "test/server_definition_test.rb", "test/shell_test.rb", "test/ssh_test.rb", "test/task_definition_test.rb", "test/transfer_test.rb", "test/utils.rb", "Manifest", "capistrano.gemspec"]
16
+ s.files = ["bin/cap", "bin/capify", "CHANGELOG.rdoc", "examples/sample.rb", "lib/capistrano/callback.rb", "lib/capistrano/cli/execute.rb", "lib/capistrano/cli/help.rb", "lib/capistrano/cli/help.txt", "lib/capistrano/cli/options.rb", "lib/capistrano/cli/ui.rb", "lib/capistrano/cli.rb", "lib/capistrano/command.rb", "lib/capistrano/configuration/actions/file_transfer.rb", "lib/capistrano/configuration/actions/inspect.rb", "lib/capistrano/configuration/actions/invocation.rb", "lib/capistrano/configuration/callbacks.rb", "lib/capistrano/configuration/connections.rb", "lib/capistrano/configuration/execution.rb", "lib/capistrano/configuration/loading.rb", "lib/capistrano/configuration/namespaces.rb", "lib/capistrano/configuration/roles.rb", "lib/capistrano/configuration/servers.rb", "lib/capistrano/configuration/variables.rb", "lib/capistrano/configuration.rb", "lib/capistrano/errors.rb", "lib/capistrano/extensions.rb", "lib/capistrano/logger.rb", "lib/capistrano/processable.rb", "lib/capistrano/recipes/compat.rb", "lib/capistrano/recipes/deploy/dependencies.rb", "lib/capistrano/recipes/deploy/local_dependency.rb", "lib/capistrano/recipes/deploy/remote_dependency.rb", "lib/capistrano/recipes/deploy/scm/accurev.rb", "lib/capistrano/recipes/deploy/scm/base.rb", "lib/capistrano/recipes/deploy/scm/bzr.rb", "lib/capistrano/recipes/deploy/scm/cvs.rb", "lib/capistrano/recipes/deploy/scm/darcs.rb", "lib/capistrano/recipes/deploy/scm/git.rb", "lib/capistrano/recipes/deploy/scm/mercurial.rb", "lib/capistrano/recipes/deploy/scm/none.rb", "lib/capistrano/recipes/deploy/scm/perforce.rb", "lib/capistrano/recipes/deploy/scm/subversion.rb", "lib/capistrano/recipes/deploy/scm.rb", "lib/capistrano/recipes/deploy/strategy/base.rb", "lib/capistrano/recipes/deploy/strategy/checkout.rb", "lib/capistrano/recipes/deploy/strategy/copy.rb", "lib/capistrano/recipes/deploy/strategy/export.rb", "lib/capistrano/recipes/deploy/strategy/remote.rb", "lib/capistrano/recipes/deploy/strategy/remote_cache.rb", "lib/capistrano/recipes/deploy/strategy.rb", "lib/capistrano/recipes/deploy/templates/maintenance.rhtml", "lib/capistrano/recipes/deploy.rb", "lib/capistrano/recipes/standard.rb", "lib/capistrano/recipes/templates/maintenance.rhtml", "lib/capistrano/recipes/upgrade.rb", "lib/capistrano/role.rb", "lib/capistrano/server_definition.rb", "lib/capistrano/shell.rb", "lib/capistrano/ssh.rb", "lib/capistrano/task_definition.rb", "lib/capistrano/transfer.rb", "lib/capistrano/version.rb", "lib/capistrano.rb", "Rakefile", "README.rdoc", "setup.rb", "test/cli/execute_test.rb", "test/cli/help_test.rb", "test/cli/options_test.rb", "test/cli/ui_test.rb", "test/cli_test.rb", "test/command_test.rb", "test/configuration/actions/file_transfer_test.rb", "test/configuration/actions/inspect_test.rb", "test/configuration/actions/invocation_test.rb", "test/configuration/callbacks_test.rb", "test/configuration/connections_test.rb", "test/configuration/execution_test.rb", "test/configuration/loading_test.rb", "test/configuration/namespace_dsl_test.rb", "test/configuration/roles_test.rb", "test/configuration/servers_test.rb", "test/configuration/variables_test.rb", "test/configuration_test.rb", "test/deploy/local_dependency_test.rb", "test/deploy/remote_dependency_test.rb", "test/deploy/scm/accurev_test.rb", "test/deploy/scm/base_test.rb", "test/deploy/scm/git_test.rb", "test/deploy/scm/mercurial_test.rb", "test/deploy/strategy/copy_test.rb", "test/extensions_test.rb", "test/fixtures/cli_integration.rb", "test/fixtures/config.rb", "test/fixtures/custom.rb", "test/logger_test.rb", "test/role_test.rb", "test/server_definition_test.rb", "test/shell_test.rb", "test/ssh_test.rb", "test/task_definition_test.rb", "test/transfer_test.rb", "test/utils.rb", "Manifest", "capistrano.gemspec", "test/deploy/scm/none_test.rb"]
17
17
  s.has_rdoc = true
18
18
  s.homepage = %q{http://www.capify.org}
19
19
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Capistrano", "--main", "README.rdoc"]
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.rubyforge_project = %q{capistrano}
22
22
  s.rubygems_version = %q{1.2.0}
23
23
  s.summary = %q{Capistrano is a utility and framework for executing commands in parallel on multiple remote machines, via SSH.}
24
- s.test_files = ["test/cli/execute_test.rb", "test/cli/help_test.rb", "test/cli/options_test.rb", "test/cli/ui_test.rb", "test/cli_test.rb", "test/command_test.rb", "test/configuration/actions/file_transfer_test.rb", "test/configuration/actions/inspect_test.rb", "test/configuration/actions/invocation_test.rb", "test/configuration/callbacks_test.rb", "test/configuration/connections_test.rb", "test/configuration/execution_test.rb", "test/configuration/loading_test.rb", "test/configuration/namespace_dsl_test.rb", "test/configuration/roles_test.rb", "test/configuration/servers_test.rb", "test/configuration/variables_test.rb", "test/configuration_test.rb", "test/deploy/local_dependency_test.rb", "test/deploy/remote_dependency_test.rb", "test/deploy/scm/accurev_test.rb", "test/deploy/scm/base_test.rb", "test/deploy/scm/git_test.rb", "test/deploy/scm/mercurial_test.rb", "test/deploy/strategy/copy_test.rb", "test/extensions_test.rb", "test/logger_test.rb", "test/role_test.rb", "test/server_definition_test.rb", "test/shell_test.rb", "test/ssh_test.rb", "test/task_definition_test.rb", "test/transfer_test.rb"]
24
+ s.test_files = ["test/cli/execute_test.rb", "test/cli/help_test.rb", "test/cli/options_test.rb", "test/cli/ui_test.rb", "test/cli_test.rb", "test/command_test.rb", "test/configuration/actions/file_transfer_test.rb", "test/configuration/actions/inspect_test.rb", "test/configuration/actions/invocation_test.rb", "test/configuration/callbacks_test.rb", "test/configuration/connections_test.rb", "test/configuration/execution_test.rb", "test/configuration/loading_test.rb", "test/configuration/namespace_dsl_test.rb", "test/configuration/roles_test.rb", "test/configuration/servers_test.rb", "test/configuration/variables_test.rb", "test/configuration_test.rb", "test/deploy/local_dependency_test.rb", "test/deploy/remote_dependency_test.rb", "test/deploy/scm/accurev_test.rb", "test/deploy/scm/base_test.rb", "test/deploy/scm/git_test.rb", "test/deploy/scm/mercurial_test.rb", "test/deploy/scm/none_test.rb", "test/deploy/strategy/copy_test.rb", "test/extensions_test.rb", "test/logger_test.rb", "test/role_test.rb", "test/server_definition_test.rb", "test/shell_test.rb", "test/ssh_test.rb", "test/task_definition_test.rb", "test/transfer_test.rb"]
25
25
 
26
26
  if s.respond_to? :specification_version then
27
27
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
@@ -23,6 +23,7 @@ module Capistrano
23
23
  def execute!
24
24
  config = instantiate_configuration
25
25
  config.debug = options[:debug]
26
+ config.dry_run = options[:dry_run]
26
27
  config.logger.level = options[:verbose]
27
28
 
28
29
  set_pre_vars(config)
@@ -10,6 +10,9 @@ The command-line interface to Capistrano is via the `cap' command.
10
10
 
11
11
  The following options are understood:
12
12
 
13
+ <%= color '-d, --debug', :bold %>
14
+ Causes Capistrano to pause and prompt before executing any remote command, giving the user the option to either execute the command, skip the command, or abort execution entirely. This makes it a great way to troubleshoot tasks, or test custom tasks, by executing commands one at a time and checking the server to make sure they worked as expected before moving on to the next command. (Compare this to the --dry-run command.)
15
+
13
16
  <%= color '-e, --explain TASK', :bold %>
14
17
  Displays the extended description of the given task. Not all tasks will have an extended description, but for those that do, this can provide a wealth of additional usage information, such as describing environment variables or settings that can affect the execution of the task.
15
18
 
@@ -25,6 +28,9 @@ The following options are understood:
25
28
  <%= color '-h, --help', :bold %>
26
29
  Shows a brief summary of these options and exits.
27
30
 
31
+ <%= color '-n, --dry-run', :bold %>
32
+ Causes Capistrano to simply display each remote command, without executing it. In this sense it is similar to --debug, but without the prompt. Note that commands executed locally are still run--only remote commands are skipped.
33
+
28
34
  <%= color '-p, --password', :bold %>
29
35
  Normally, cap will prompt for the password on-demand, the first time it is needed. This can make it hard to walk away from Capistrano, since you might not know if it will prompt for a password down the road. In such cases, you can use the -p option to force cap to prompt for the password immediately.
30
36
 
@@ -53,6 +53,10 @@ module Capistrano
53
53
  exit
54
54
  end
55
55
 
56
+ opts.on("-n", "--dry-run",
57
+ "Prints out commands without running them."
58
+ ) { |value| options[:dry_run] = true }
59
+
56
60
  opts.on("-p", "--password",
57
61
  "Immediately prompt for the password."
58
62
  ) { options[:password] = nil }
@@ -120,6 +124,8 @@ module Capistrano
120
124
 
121
125
  option_parser.parse!(args)
122
126
 
127
+ coerce_variable_types!
128
+
123
129
  # if no verbosity has been specified, be verbose
124
130
  options[:verbose] = 3 if !options.has_key?(:verbose)
125
131
 
@@ -182,6 +188,26 @@ module Capistrano
182
188
  "/"
183
189
  end
184
190
 
191
+ def coerce_variable_types!
192
+ [:pre_vars, :vars].each do |collection|
193
+ options[collection].keys.each do |key|
194
+ options[collection][key] = coerce_variable(options[collection][key])
195
+ end
196
+ end
197
+ end
198
+
199
+ def coerce_variable(value)
200
+ case value
201
+ when /^"(.*)"$/ then $1
202
+ when /^'(.*)'$/ then $1
203
+ when /^\d+$/ then value.to_i
204
+ when /^\d+\.\d*$/ then value.to_f
205
+ when "true" then true
206
+ when "false" then false
207
+ when "nil" then nil
208
+ else value
209
+ end
210
+ end
185
211
  end
186
212
  end
187
213
  end
@@ -29,7 +29,6 @@ module Capistrano
29
29
  prompt = "Execute ([Yes], No, Abort) "
30
30
  ui.ask("#{prompt}? ") do |q|
31
31
  q.overwrite = false
32
- q.character = true
33
32
  q.default = 'y'
34
33
  q.validate = /(y(es)?)|(no?)|(a(bort)?|\n)/i
35
34
  q.responses[:not_valid] = prompt
@@ -8,10 +8,129 @@ module Capistrano
8
8
  class Command
9
9
  include Processable
10
10
 
11
- attr_reader :command, :sessions, :options
11
+ class Tree
12
+ attr_reader :configuration
13
+ attr_reader :branches
14
+ attr_reader :fallback
12
15
 
13
- def self.process(command, sessions, options={}, &block)
14
- new(command, sessions, options, &block).process!
16
+ include Enumerable
17
+
18
+ class Branch
19
+ attr_accessor :command, :callback
20
+ attr_reader :options
21
+
22
+ def initialize(command, options, callback)
23
+ @command = command.strip.gsub(/\r?\n/, "\\\n")
24
+ @callback = callback || Capistrano::Configuration.default_io_proc
25
+ @options = options
26
+ @skip = false
27
+ end
28
+
29
+ def last?
30
+ options[:last]
31
+ end
32
+
33
+ def skip?
34
+ @skip
35
+ end
36
+
37
+ def skip!
38
+ @skip = true
39
+ end
40
+
41
+ def match(server)
42
+ true
43
+ end
44
+
45
+ def to_s
46
+ command.inspect
47
+ end
48
+ end
49
+
50
+ class ConditionBranch < Branch
51
+ attr_accessor :configuration
52
+ attr_accessor :condition
53
+
54
+ class Evaluator
55
+ attr_reader :configuration, :condition, :server
56
+
57
+ def initialize(config, condition, server)
58
+ @configuration = config
59
+ @condition = condition
60
+ @server = server
61
+ end
62
+
63
+ def in?(role)
64
+ configuration.roles[role].include?(server)
65
+ end
66
+
67
+ def result
68
+ eval(condition, binding)
69
+ end
70
+
71
+ def method_missing(sym, *args, &block)
72
+ if server.respond_to?(sym)
73
+ server.send(sym, *args, &block)
74
+ elsif configuration.respond_to?(sym)
75
+ configuration.send(sym, *args, &block)
76
+ else
77
+ super
78
+ end
79
+ end
80
+ end
81
+
82
+ def initialize(configuration, condition, command, options, callback)
83
+ @configuration = configuration
84
+ @condition = condition
85
+ super(command, options, callback)
86
+ end
87
+
88
+ def match(server)
89
+ Evaluator.new(configuration, condition, server).result
90
+ end
91
+
92
+ def to_s
93
+ "#{condition.inspect} :: #{command.inspect}"
94
+ end
95
+ end
96
+
97
+ def initialize(config)
98
+ @configuration = config
99
+ @branches = []
100
+ yield self if block_given?
101
+ end
102
+
103
+ def when(condition, command, options={}, &block)
104
+ branches << ConditionBranch.new(configuration, condition, command, options, block)
105
+ end
106
+
107
+ def else(command, &block)
108
+ @fallback = Branch.new(command, {}, block)
109
+ end
110
+
111
+ def branches_for(server)
112
+ seen_last = false
113
+ matches = branches.select do |branch|
114
+ success = !seen_last && !branch.skip? && branch.match(server)
115
+ seen_last = success && branch.last?
116
+ success
117
+ end
118
+
119
+ matches << fallback if matches.empty? && fallback
120
+ return matches
121
+ end
122
+
123
+ def each
124
+ branches.each { |branch| yield branch }
125
+ yield fallback if fallback
126
+ return self
127
+ end
128
+ end
129
+
130
+ attr_reader :tree, :sessions, :options
131
+
132
+ def self.process(tree, sessions, options={})
133
+ new(tree, sessions, options).process!
15
134
  end
16
135
 
17
136
  # Instantiates a new command object. The +command+ must be a string
@@ -23,11 +142,16 @@ module Capistrano
23
142
  # * +data+: (optional), a string to be sent to the command via it's stdin
24
143
  # * +env+: (optional), a string or hash to be interpreted as environment
25
144
  # variables that should be defined for this command invocation.
26
- def initialize(command, sessions, options={}, &block)
27
- @command = command.strip.gsub(/\r?\n/, "\\\n")
145
+ def initialize(tree, sessions, options={}, &block)
146
+ if String === tree
147
+ tree = Tree.new(nil) { |t| t.else(tree, &block) }
148
+ elsif block
149
+ raise ArgumentError, "block given with tree argument"
150
+ end
151
+
152
+ @tree = tree
28
153
  @sessions = sessions
29
154
  @options = options
30
- @callback = block
31
155
  @channels = open_channels
32
156
  end
33
157
 
@@ -42,9 +166,10 @@ module Capistrano
42
166
  logger.trace "command finished" if logger
43
167
 
44
168
  if (failed = @channels.select { |ch| ch[:status] != 0 }).any?
45
- hosts = failed.map { |ch| ch[:server] }
46
- error = CommandError.new("command #{command.inspect} failed on #{hosts.join(',')}")
47
- error.hosts = hosts
169
+ commands = failed.inject({}) { |map, ch| (map[ch[:command]] ||= []) << ch[:server]; map }
170
+ message = commands.map { |command, list| "#{command.inspect} on #{list.join(',')}" }.join("; ")
171
+ error = CommandError.new("failed: #{message}")
172
+ error.hosts = commands.values.flatten
48
173
  raise error
49
174
  end
50
175
 
@@ -67,56 +192,59 @@ module Capistrano
67
192
 
68
193
  def open_channels
69
194
  sessions.map do |session|
70
- session.open_channel do |channel|
71
- server = session.xserver
195
+ server = session.xserver
196
+ tree.branches_for(server).map do |branch|
197
+ session.open_channel do |channel|
198
+ channel[:server] = server
199
+ channel[:host] = server.host
200
+ channel[:options] = options
201
+ channel[:branch] = branch
202
+
203
+ request_pty_if_necessary(channel) do |ch, success|
204
+ if success
205
+ logger.trace "executing command", ch[:server] if logger
206
+ cmd = replace_placeholders(channel[:branch].command, ch)
72
207
 
73
- channel[:server] = server
74
- channel[:host] = server.host
75
- channel[:options] = options
208
+ if options[:shell] == false
209
+ shell = nil
210
+ else
211
+ shell = "#{options[:shell] || "sh"} -c"
212
+ cmd = cmd.gsub(/[$\\`"]/) { |m| "\\#{m}" }
213
+ cmd = "\"#{cmd}\""
214
+ end
76
215
 
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)
216
+ command_line = [environment, shell, cmd].compact.join(" ")
217
+ ch[:command] = command_line
81
218
 
82
- if options[:shell] == false
83
- shell = nil
219
+ ch.exec(command_line)
220
+ ch.send_data(options[:data]) if options[:data]
84
221
  else
85
- shell = "#{options[:shell] || "sh"} -c"
86
- cmd = cmd.gsub(/[$\\`"]/) { |m| "\\#{m}" }
87
- cmd = "\"#{cmd}\""
222
+ # just log it, don't actually raise an exception, since the
223
+ # process method will see that the status is not zero and will
224
+ # raise an exception then.
225
+ logger.important "could not open channel", ch[:server] if logger
226
+ ch.close
88
227
  end
89
-
90
- command_line = [environment, shell, cmd].compact.join(" ")
91
-
92
- ch.exec(command_line)
93
- ch.send_data(options[:data]) if options[:data]
94
- else
95
- # just log it, don't actually raise an exception, since the
96
- # process method will see that the status is not zero and will
97
- # raise an exception then.
98
- logger.important "could not open channel", ch[:server] if logger
99
- ch.close
100
228
  end
101
- end
102
229
 
103
- channel.on_data do |ch, data|
104
- @callback[ch, :out, data] if @callback
105
- end
230
+ channel.on_data do |ch, data|
231
+ ch[:branch].callback[ch, :out, data]
232
+ end
106
233
 
107
- channel.on_extended_data do |ch, type, data|
108
- @callback[ch, :err, data] if @callback
109
- end
234
+ channel.on_extended_data do |ch, type, data|
235
+ ch[:branch].callback[ch, :err, data]
236
+ end
110
237
 
111
- channel.on_request("exit-status") do |ch, data|
112
- ch[:status] = data.read_long
113
- end
238
+ channel.on_request("exit-status") do |ch, data|
239
+ ch[:status] = data.read_long
240
+ end
114
241
 
115
- channel.on_close do |ch|
116
- ch[:closed] = true
242
+ channel.on_close do |ch|
243
+ ch[:closed] = true
244
+ end
117
245
  end
118
246
  end
119
- end
247
+ end.flatten
120
248
  end
121
249
 
122
250
  def request_pty_if_necessary(channel)
@@ -19,10 +19,11 @@ module Capistrano
19
19
  # define roles, and set configuration variables.
20
20
  class Configuration
21
21
  # The logger instance defined for this configuration.
22
- attr_accessor :debug, :logger
22
+ attr_accessor :debug, :logger, :dry_run
23
23
 
24
24
  def initialize #:nodoc:
25
25
  @debug = false
26
+ @dry_run = false
26
27
  @logger = Logger.new
27
28
  end
28
29
 
@@ -26,6 +26,12 @@ module Capistrano
26
26
  set :default_run_options, {}
27
27
  end
28
28
 
29
+ def parallel(options={})
30
+ raise ArgumentError, "parallel() requires a block" unless block_given?
31
+ tree = Command::Tree.new(self) { |t| yield t }
32
+ run_tree(tree)
33
+ end
34
+
29
35
  # Invokes the given command. If a +via+ key is given, it will be used
30
36
  # to determine what method to use to invoke the command. It defaults
31
37
  # to :run, but may be :sudo, or any other method that conforms to the
@@ -44,19 +50,35 @@ module Capistrano
44
50
  # stdout), and the data that was received.
45
51
  def run(cmd, options={}, &block)
46
52
  block ||= self.class.default_io_proc
47
- logger.debug "executing #{cmd.strip.inspect}"
53
+ tree = Command::Tree.new(self) { |t| t.else(cmd, &block) }
54
+ run_tree(tree, options)
55
+ end
56
+
57
+ def run_tree(tree, options={})
58
+ if tree.branches.empty? && tree.fallback
59
+ logger.debug "executing #{tree.fallback}"
60
+ elsif tree.branches.any?
61
+ logger.debug "executing multiple commands in parallel"
62
+ tree.each do |branch|
63
+ logger.trace "-> #{branch}"
64
+ end
65
+ else
66
+ raise ArgumentError, "attempt to execute without specifying a command"
67
+ end
48
68
 
49
- return if debug && continue_execution(cmd) == false
69
+ return if dry_run || (debug && continue_execution(tree) == false)
50
70
 
51
71
  options = add_default_command_options(options)
52
72
 
53
- if cmd.include?(sudo)
54
- block = sudo_behavior_callback(block)
73
+ tree.each do |branch|
74
+ if branch.command.include?(sudo)
75
+ branch.callback = sudo_behavior_callback(branch.callback)
76
+ end
55
77
  end
56
78
 
57
79
  execute_on_servers(options) do |servers|
58
80
  targets = servers.map { |s| sessions[s] }
59
- Command.process(cmd, targets, options.merge(:logger => logger), &block)
81
+ Command.process(tree, targets, options.merge(:logger => logger))
60
82
  end
61
83
  end
62
84
 
@@ -145,8 +167,17 @@ module Capistrano
145
167
  fetch(:sudo_prompt, "sudo password: ")
146
168
  end
147
169
 
148
- def continue_execution(cmd)
149
- case Capistrano::CLI.debug_prompt(cmd)
170
+ def continue_execution(tree)
171
+ if tree.branches.length == 1
172
+ continue_execution_for_branch(tree.branches.first)
173
+ else
174
+ tree.each { |branch| branch.skip! unless continue_execution_for_branch(branch) }
175
+ tree.any? { |branch| !branch.skip? }
176
+ end
177
+ end
178
+
179
+ def continue_execution_for_branch(branch)
180
+ case Capistrano::CLI.debug_prompt(branch)
150
181
  when "y"
151
182
  true
152
183
  when "n"