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
@@ -32,6 +32,12 @@ module Capistrano
32
32
  end
33
33
  private :initialize_with_namespaces
34
34
 
35
+ # Returns the top-level namespace (the one with no parent).
36
+ def top
37
+ return parent.top if parent
38
+ return self
39
+ end
40
+
35
41
  # Returns the fully-qualified name of this namespace, or nil if the
36
42
  # namespace is at the top-level.
37
43
  def fully_qualified_name
@@ -61,7 +67,7 @@ module Capistrano
61
67
  raise ArgumentError, "expected a block" unless block_given?
62
68
 
63
69
  namespace_already_defined = namespaces.key?(name)
64
- if all_methods.include?(name.to_s) && !namespace_already_defined
70
+ if all_methods.any? { |m| m.to_sym == name } && !namespace_already_defined
65
71
  thing = tasks.key?(name) ? "task" : "method"
66
72
  raise ArgumentError, "defining a namespace named `#{name}' would shadow an existing #{thing} with that name"
67
73
  end
@@ -86,17 +92,22 @@ module Capistrano
86
92
  raise ArgumentError, "expected a block" unless block_given?
87
93
 
88
94
  task_already_defined = tasks.key?(name)
89
- if all_methods.include?(name.to_s) && !task_already_defined
95
+ if all_methods.any? { |m| m.to_sym == name } && !task_already_defined
90
96
  thing = namespaces.key?(name) ? "namespace" : "method"
91
97
  raise ArgumentError, "defining a task named `#{name}' would shadow an existing #{thing} with that name"
92
98
  end
93
99
 
94
- tasks[name] = TaskDefinition.new(name, self, {:desc => next_description(:reset)}.merge(options), &block)
95
100
 
96
- if !task_already_defined
97
- metaclass = class << self; self; end
98
- metaclass.send(:define_method, name) { execute_task(tasks[name]) }
99
- end
101
+ task = TaskDefinition.new(name, self, {:desc => next_description(:reset)}.merge(options), &block)
102
+
103
+ define_task(task)
104
+ end
105
+
106
+ def define_task(task)
107
+ tasks[task.name] = task
108
+
109
+ metaclass = class << self; self; end
110
+ metaclass.send(:define_method, task.name) { execute_task(tasks[task.name]) }
100
111
  end
101
112
 
102
113
  # Find the task with the given name, where name is the fully-qualified
@@ -110,7 +121,8 @@ module Capistrano
110
121
 
111
122
  ns = self
112
123
  until parts.empty?
113
- ns = ns.namespaces[parts.shift.to_sym]
124
+ next_part = parts.shift
125
+ ns = next_part.empty? ? nil : ns.namespaces[next_part.to_sym]
114
126
  return nil if ns.nil?
115
127
  end
116
128
 
@@ -144,7 +156,7 @@ module Capistrano
144
156
  return nil if parent.nil?
145
157
  return tasks[DEFAULT_TASK]
146
158
  end
147
-
159
+
148
160
  # Returns the tasks in this namespace as an array of TaskDefinition
149
161
  # objects. If a non-false parameter is given, all tasks in all
150
162
  # namespaces under this namespace will be returned as well.
@@ -170,8 +182,8 @@ module Capistrano
170
182
  raise NotImplementedError, "roles cannot be defined in a namespace"
171
183
  end
172
184
 
173
- def respond_to?(sym)
174
- super || parent.respond_to?(sym)
185
+ def respond_to?(sym, include_priv=false)
186
+ super || parent.respond_to?(sym, include_priv)
175
187
  end
176
188
 
177
189
  def method_missing(sym, *args, &block)
@@ -182,9 +194,30 @@ module Capistrano
182
194
  end
183
195
  end
184
196
 
197
+ include Capistrano::Configuration::AliasTask
185
198
  include Capistrano::Configuration::Namespaces
186
199
  undef :desc, :next_description
187
200
  end
188
201
  end
189
202
  end
190
- end
203
+ end
204
+
205
+ module Kernel
206
+ class << self
207
+ alias_method :method_added_without_capistrano, :method_added
208
+
209
+ # Detect method additions to Kernel and remove them in the Namespace class
210
+ def method_added(name)
211
+ result = method_added_without_capistrano(name)
212
+ return result if self != Kernel
213
+
214
+ namespace = Capistrano::Configuration::Namespaces::Namespace
215
+
216
+ if namespace.method_defined?(name) && namespace.instance_method(name).owner == Kernel
217
+ namespace.send :undef_method, name
218
+ end
219
+
220
+ result
221
+ end
222
+ end
223
+ end
@@ -1,4 +1,5 @@
1
1
  require 'capistrano/server_definition'
2
+ require 'capistrano/role'
2
3
 
3
4
  module Capistrano
4
5
  class Configuration
@@ -15,7 +16,7 @@ module Capistrano
15
16
 
16
17
  def initialize_with_roles(*args) #:nodoc:
17
18
  initialize_without_roles(*args)
18
- @roles = Hash.new { |h,k| h[k] = [] }
19
+ @roles = Hash.new { |h,k| h[k] = Role.new }
19
20
  end
20
21
 
21
22
  # Define a new role and its associated servers. You must specify at least
@@ -41,11 +42,42 @@ module Capistrano
41
42
  # that call to "role":
42
43
  #
43
44
  # role :web, "web2", "web3", :user => "www", :port => 2345
44
- def role(which, *args)
45
+ def role(which, *args, &block)
45
46
  options = args.last.is_a?(Hash) ? args.pop : {}
46
47
  which = which.to_sym
48
+
49
+ # The roles Hash is defined so that unrecognized keys always auto-initialize
50
+ # to a new Role instance (see the assignment in the initialize_with_roles method,
51
+ # above). However, we explicitly assign here so that role declarations will
52
+ # vivify the role object even if there are no server arguments. (Otherwise,
53
+ # role(:app) won't actually instantiate a Role object for :app.)
54
+ roles[which] ||= Role.new
55
+
56
+ roles[which].push(block, options) if block_given?
47
57
  args.each { |host| roles[which] << ServerDefinition.new(host, options) }
48
58
  end
59
+
60
+ # An alternative way to associate servers with roles. If you have a server
61
+ # that participates in multiple roles, this can be a DRYer way to describe
62
+ # the relationships. Pass the host definition as the first parameter, and
63
+ # the roles as the remaining parameters:
64
+ #
65
+ # server "master.example.com", :web, :app
66
+ #
67
+ # Also, you can specify additional information
68
+ # (in the form of a Hash) which can be used to more uniquely specify the
69
+ # subset of servers specified by this specific definition:
70
+ #
71
+ # server "master.example.com", :db, :primary => true
72
+ def server(host, *roles)
73
+ options = roles.last.is_a?(Hash) ? roles.pop : {}
74
+ raise ArgumentError, "you must associate a server with at least one role" if roles.empty?
75
+ roles.each { |name| role(name, host, options) }
76
+ end
77
+
78
+ def role_names_for_host(host)
79
+ roles.map {|role_name, role| role_name if role.include?(host) }.compact || []
80
+ end
49
81
  end
50
82
  end
51
83
  end
@@ -13,14 +13,22 @@ module Capistrano
13
13
  # The options hash may include a :hosts option (which should specify
14
14
  # an array of host names or ServerDefinition instances), a :roles
15
15
  # option (specifying an array of roles), an :only option (specifying
16
- # a hash of key/value pairs that any matching server must match), and
17
- # an :exception option (like :only, but the inverse).
16
+ # a hash of key/value pairs that any matching server must match),
17
+ # an :except option (like :only, but the inverse), and a
18
+ # :skip_hostfilter option to ignore the HOSTFILTER environment variable
19
+ # described below.
18
20
  #
19
21
  # Additionally, if the HOSTS environment variable is set, it will take
20
22
  # precedence over any other options. Similarly, the ROLES environment
21
23
  # variable will take precedence over other options. If both HOSTS and
22
24
  # ROLES are given, HOSTS wins.
23
25
  #
26
+ # Yet additionally, if the HOSTFILTER environment variable is set, it
27
+ # will limit the result to hosts found in that (comma-separated) list.
28
+ #
29
+ # If the HOSTROLEFILTER environment variable is set, it will limit the
30
+ # result to hosts found in that (comma-separated) list of roles
31
+ #
24
32
  # Usage:
25
33
  #
26
34
  # # return all known servers
@@ -34,23 +42,57 @@ module Capistrano
34
42
  # # returns the given hosts, translated to ServerDefinition objects
35
43
  # servers = find_servers :hosts => "jamis@example.host.com"
36
44
  def find_servers(options={})
45
+ return [] if options.key?(:hosts) && (options[:hosts].nil? || [] == options[:hosts])
46
+ return [] if options.key?(:roles) && (options[:roles].nil? || [] == options[:roles])
47
+
37
48
  hosts = server_list_from(ENV['HOSTS'] || options[:hosts])
38
- roles = role_list_from(ENV['ROLES'] || options[:roles] || self.roles.keys)
39
- only = options[:only] || {}
40
- except = options[:except] || {}
41
49
 
42
50
  if hosts.any?
43
- hosts.uniq
51
+ if options[:skip_hostfilter]
52
+ hosts.uniq
53
+ else
54
+ filter_server_list(hosts.uniq)
55
+ end
44
56
  else
45
- servers = roles.inject([]) { |list, role| list.concat(self.roles[role]) }
57
+ roles = role_list_from(ENV['ROLES'] || options[:roles] || self.roles.keys)
58
+ roles = roles & Array(options[:roles]) if preserve_roles && !options[:roles].nil?
59
+
60
+ only = options[:only] || {}
61
+ except = options[:except] || {}
62
+
63
+ # If we don't have a def for a role it means its bogus, skip it so higher level can handle
64
+ servers = roles.inject([]) { |list, role| list.concat(self.roles[role] || []) }
46
65
  servers = servers.select { |server| only.all? { |key,value| server.options[key] == value } }
47
66
  servers = servers.reject { |server| except.any? { |key,value| server.options[key] == value } }
48
- servers.uniq
67
+
68
+ if options[:skip_hostfilter]
69
+ servers.uniq
70
+ else
71
+ filter_server_list(servers.uniq)
72
+ end
49
73
  end
50
74
  end
51
75
 
52
76
  protected
53
77
 
78
+ def filter_server_list(servers)
79
+ return servers unless ENV['HOSTFILTER'] or ENV['HOSTROLEFILTER']
80
+ if ENV['HOSTFILTER']
81
+ filters = ENV['HOSTFILTER'].split(/,/)
82
+ servers.select { |server| filters.include?(server.host) }
83
+ elsif ENV['HOSTROLEFILTER']
84
+ filters = ENV['HOSTROLEFILTER'].split(/,/).map do |role|
85
+ local_roles = roles[role.to_sym]
86
+ if local_roles.is_a? Array
87
+ roles[role.to_sym]
88
+ else
89
+ roles[role.to_sym].servers
90
+ end
91
+ end.flatten
92
+ servers.select { |server| filters.include?(server) }
93
+ end
94
+ end
95
+
54
96
  def server_list_from(hosts)
55
97
  hosts = hosts.split(/,/) if String === hosts
56
98
  hosts = build_list(hosts)
@@ -62,7 +104,6 @@ module Capistrano
62
104
  roles = build_list(roles)
63
105
  roles.map do |role|
64
106
  role = String === role ? role.strip.to_sym : role
65
- raise ArgumentError, "unknown role `#{role}'" unless self.roles.key?(role)
66
107
  role
67
108
  end
68
109
  end
@@ -72,4 +113,4 @@ module Capistrano
72
113
  end
73
114
  end
74
115
  end
75
- end
116
+ end
@@ -111,8 +111,8 @@ module Capistrano
111
111
  end
112
112
  private :protect
113
113
 
114
- def respond_to_with_variables?(sym) #:nodoc:
115
- @variables.has_key?(sym) || respond_to_without_variables?(sym)
114
+ def respond_to_with_variables?(sym, include_priv=false) #:nodoc:
115
+ @variables.has_key?(sym.to_sym) || respond_to_without_variables?(sym, include_priv)
116
116
  end
117
117
 
118
118
  def method_missing_with_variables(sym, *args, &block) #:nodoc:
@@ -124,4 +124,4 @@ module Capistrano
124
124
  end
125
125
  end
126
126
  end
127
- end
127
+ end
@@ -1,9 +1,11 @@
1
1
  require 'capistrano/logger'
2
2
 
3
+ require 'capistrano/configuration/alias_task'
3
4
  require 'capistrano/configuration/callbacks'
4
5
  require 'capistrano/configuration/connections'
5
6
  require 'capistrano/configuration/execution'
6
7
  require 'capistrano/configuration/loading'
8
+ require 'capistrano/configuration/log_formatters'
7
9
  require 'capistrano/configuration/namespaces'
8
10
  require 'capistrano/configuration/roles'
9
11
  require 'capistrano/configuration/servers'
@@ -19,10 +21,13 @@ module Capistrano
19
21
  # define roles, and set configuration variables.
20
22
  class Configuration
21
23
  # The logger instance defined for this configuration.
22
- attr_accessor :logger
24
+ attr_accessor :debug, :logger, :dry_run, :preserve_roles
23
25
 
24
- def initialize #:nodoc:
25
- @logger = Logger.new
26
+ def initialize(options={}) #:nodoc:
27
+ @debug = false
28
+ @dry_run = false
29
+ @preserve_roles = false
30
+ @logger = Logger.new(options)
26
31
  end
27
32
 
28
33
  # make the DSL easier to read when using lazy evaluation via lambdas
@@ -30,12 +35,23 @@ module Capistrano
30
35
 
31
36
  # The includes must come at the bottom, since they may redefine methods
32
37
  # defined in the base class.
33
- include Connections, Execution, Loading, Namespaces, Roles, Servers, Variables
38
+ include AliasTask, Connections, Execution, Loading, LogFormatters, Namespaces, Roles, Servers, Variables
34
39
 
35
40
  # Mix in the actions
36
41
  include Actions::FileTransfer, Actions::Inspect, Actions::Invocation
37
42
 
38
43
  # Must mix last, because it hooks into previously defined methods
39
44
  include Callbacks
45
+
46
+ (self.instance_methods & Kernel.methods).select do |name|
47
+ # Select the instance methods owned by the Configuration class.
48
+ self.instance_method(name).owner.to_s.start_with?("Capistrano::Configuration")
49
+ end.select do |name|
50
+ # Of those, select methods that are being shadowed by the Kernel module in the Namespace class.
51
+ Namespaces::Namespace.method_defined?(name) && Namespaces::Namespace.instance_method(name).owner == Kernel
52
+ end.each do |name|
53
+ # Undefine the shadowed methods, since we want Namespace objects to defer handling to the Configuration object.
54
+ Namespaces::Namespace.send(:undef_method, name)
55
+ end
40
56
  end
41
57
  end
@@ -1,15 +1,19 @@
1
1
  module Capistrano
2
- class Error < RuntimeError; end
3
2
 
4
- class CaptureError < Error; end
5
- class NoSuchTaskError < Error; end
6
- class NoMatchingServersError < Error; end
7
-
3
+ Error = Class.new(RuntimeError)
4
+
5
+ CaptureError = Class.new(Capistrano::Error)
6
+ NoSuchTaskError = Class.new(Capistrano::Error)
7
+ NoMatchingServersError = Class.new(Capistrano::Error)
8
+
8
9
  class RemoteError < Error
9
10
  attr_accessor :hosts
10
11
  end
11
12
 
12
- class ConnectionError < RemoteError; end
13
- class UploadError < RemoteError; end
14
- class CommandError < RemoteError; end
13
+ ConnectionError = Class.new(Capistrano::RemoteError)
14
+ TransferError = Class.new(Capistrano::RemoteError)
15
+ CommandError = Class.new(Capistrano::RemoteError)
16
+
17
+ LocalArgumentError = Class.new(Capistrano::Error)
18
+
15
19
  end
@@ -0,0 +1,67 @@
1
+ require 'fileutils'
2
+
3
+ unless Capistrano::Configuration.respond_to?(:instance)
4
+ abort "capistrano/ext/multistage requires Capistrano 2"
5
+ end
6
+
7
+ Capistrano::Configuration.instance.load do
8
+ location = fetch(:stage_dir, "config/deploy")
9
+
10
+ unless exists?(:stages)
11
+ set :stages, Dir["#{location}/*.rb"].map { |f| File.basename(f, ".rb") }
12
+ end
13
+
14
+ stages.each do |name|
15
+ desc "Set the target stage to `#{name}'."
16
+ task(name) do
17
+ set :stage, name.to_sym
18
+
19
+ file = "#{location}/#{stage}"
20
+ load file if file_in_load_path?(file)
21
+ end
22
+ end
23
+
24
+ on :load do
25
+ # The first non option argument
26
+ env = ARGV.detect { |a| a.to_s !~ /\A-/ && a.to_s !~ /=/ }
27
+
28
+ if stages.include?(env)
29
+ # Execute the specified stage so that recipes required in stage can contribute to task list
30
+ find_and_execute_task(env) if ARGV.any?{ |option| option =~ /-T|--tasks|-e|--explain/ }
31
+ else
32
+ # Execute the default stage so that recipes required in stage can contribute tasks
33
+ find_and_execute_task(default_stage) if exists?(:default_stage)
34
+ end
35
+ end
36
+
37
+ namespace :multistage do
38
+ desc "[internal] Ensure that a stage has been selected."
39
+ task :ensure do
40
+ if !exists?(:stage)
41
+ if exists?(:default_stage)
42
+ logger.important "Defaulting to `#{default_stage}'"
43
+ find_and_execute_task(default_stage)
44
+ else
45
+ abort "No stage specified. Please specify one of: #{stages.join(', ')} (e.g. `cap #{stages.first} #{ARGV.last}')"
46
+ end
47
+ end
48
+ end
49
+
50
+ desc "Stub out the staging config files."
51
+ task :prepare do
52
+ FileUtils.mkdir_p(location)
53
+ stages.each do |name|
54
+ file = File.join(location, name + ".rb")
55
+ unless File.exists?(file)
56
+ File.open(file, "w") do |f|
57
+ f.puts "# #{name.upcase}-specific deployment configuration"
58
+ f.puts "# please put general deployment config in config/deploy.rb"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ on :start, "multistage:ensure", :except => stages + ['multistage:prepare']
66
+
67
+ end
@@ -0,0 +1,5 @@
1
+ class String
2
+ def compact
3
+ self.gsub(/\s+/, ' ')
4
+ end
5
+ end
@@ -25,7 +25,7 @@ module Capistrano
25
25
  Capistrano::Configuration.protected_instance_methods +
26
26
  Capistrano::Configuration.private_instance_methods
27
27
 
28
- if methods.include?(name.to_s)
28
+ if methods.any? { |m| m.to_sym == name }
29
29
  raise Capistrano::Error, "registering a plugin named `#{name}' would shadow a method on Capistrano::Configuration with the same name"
30
30
  end
31
31
 
@@ -0,0 +1,8 @@
1
+ #
2
+ # See https://github.com/jimweirich/rake/issues/81
3
+ #
4
+ if defined?(Rake::DeprecatedObjectDSL)
5
+ Rake::DeprecatedObjectDSL.private_instance_methods.each do |m|
6
+ Rake::DeprecatedObjectDSL.send("undef_method", m)
7
+ end
8
+ end
@@ -1,15 +1,80 @@
1
1
  module Capistrano
2
2
  class Logger #:nodoc:
3
- attr_accessor :level
4
- attr_reader :device
3
+ attr_accessor :level, :device, :disable_formatters
5
4
 
6
5
  IMPORTANT = 0
7
6
  INFO = 1
8
7
  DEBUG = 2
9
8
  TRACE = 3
10
-
9
+
11
10
  MAX_LEVEL = 3
12
11
 
12
+ COLORS = {
13
+ :none => "0",
14
+ :black => "30",
15
+ :red => "31",
16
+ :green => "32",
17
+ :yellow => "33",
18
+ :blue => "34",
19
+ :magenta => "35",
20
+ :cyan => "36",
21
+ :white => "37"
22
+ }
23
+
24
+ STYLES = {
25
+ :bright => 1,
26
+ :dim => 2,
27
+ :underscore => 4,
28
+ :blink => 5,
29
+ :reverse => 7,
30
+ :hidden => 8
31
+ }
32
+
33
+ # Set up default formatters
34
+ @default_formatters = [
35
+ # TRACE
36
+ { :match => /command finished/, :color => :white, :style => :dim, :level => 3, :priority => -10 },
37
+ { :match => /executing locally/, :color => :yellow, :level => 3, :priority => -20 },
38
+
39
+ # DEBUG
40
+ { :match => /executing `.*/, :color => :green, :level => 2, :priority => -10, :timestamp => true },
41
+ { :match => /.*/, :color => :yellow, :level => 2, :priority => -30 },
42
+
43
+ # INFO
44
+ { :match => /.*out\] (fatal:|ERROR:).*/, :color => :red, :level => 1, :priority => -10 },
45
+ { :match => /Permission denied/, :color => :red, :level => 1, :priority => -20 },
46
+ { :match => /sh: .+: command not found/, :color => :magenta, :level => 1, :priority => -30 },
47
+
48
+ # IMPORTANT
49
+ { :match => /^err ::/, :color => :red, :level => 0, :priority => -10 },
50
+ { :match => /.*/, :color => :blue, :level => 0, :priority => -20 }
51
+ ]
52
+ @formatters = @default_formatters
53
+
54
+ class << self
55
+ def default_formatters
56
+ @default_formatters
57
+ end
58
+
59
+ def default_formatters=(defaults=nil)
60
+ @default_formatters = [defaults].flatten
61
+
62
+ # reset the formatters
63
+ @formatters = @default_formatters
64
+ @sorted_formatters = nil
65
+ end
66
+
67
+ def add_formatter(options) #:nodoc:
68
+ @formatters.push(options)
69
+ @sorted_formatters = nil
70
+ end
71
+
72
+ def sorted_formatters
73
+ # Sort matchers in reverse order so we can break if we found a match.
74
+ @sorted_formatters ||= @formatters.sort_by { |i| -(i[:priority] || i[:prio] || 0) }
75
+ end
76
+ end
77
+
13
78
  def initialize(options={})
14
79
  output = options[:output] || $stderr
15
80
  if output.respond_to?(:puts)
@@ -20,7 +85,8 @@ module Capistrano
20
85
  end
21
86
 
22
87
  @options = options
23
- @level = 0
88
+ @level = options[:level] || 0
89
+ @disable_formatters = options[:disable_formatters]
24
90
  end
25
91
 
26
92
  def close
@@ -29,8 +95,44 @@ module Capistrano
29
95
 
30
96
  def log(level, message, line_prefix=nil)
31
97
  if level <= self.level
98
+ # Only format output if device is a TTY and formatters are not disabled
99
+ if device.tty? && !@disable_formatters
100
+ color = :none
101
+ style = nil
102
+
103
+ Logger.sorted_formatters.each do |formatter|
104
+ if (formatter[:level] == level || formatter[:level].nil?)
105
+ if message =~ formatter[:match] || line_prefix =~ formatter[:match]
106
+ color = formatter[:color] if formatter[:color]
107
+ style = formatter[:style] || formatter[:attribute] # (support original cap colors)
108
+ message.gsub!(formatter[:match], formatter[:replace]) if formatter[:replace]
109
+ message = formatter[:prepend] + message unless formatter[:prepend].nil?
110
+ message = message + formatter[:append] unless formatter[:append].nil?
111
+ message = Time.now.strftime('%Y-%m-%d %T') + ' ' + message if formatter[:timestamp]
112
+ break unless formatter[:replace]
113
+ end
114
+ end
115
+ end
116
+
117
+ if color == :hide
118
+ # Don't do anything if color is set to :hide
119
+ return false
120
+ end
121
+
122
+ term_color = COLORS[color]
123
+ term_style = STYLES[style]
124
+
125
+ # Don't format message if no color or style
126
+ unless color == :none and style.nil?
127
+ unless line_prefix.nil?
128
+ line_prefix = format(line_prefix, term_color, term_style, nil)
129
+ end
130
+ message = format(message, term_color, term_style)
131
+ end
132
+ end
133
+
32
134
  indent = "%*s" % [MAX_LEVEL, "*" * (MAX_LEVEL - level)]
33
- message.each do |line|
135
+ (RUBY_VERSION >= "1.9" ? message.lines : message).each do |line|
34
136
  if line_prefix
35
137
  device.puts "#{indent} [#{line_prefix}] #{line.strip}\n"
36
138
  else
@@ -55,5 +157,10 @@ module Capistrano
55
157
  def trace(message, line_prefix=nil)
56
158
  log(TRACE, message, line_prefix)
57
159
  end
160
+
161
+ def format(message, color, style, nl = "\n")
162
+ style = "#{style};" if style
163
+ "\e[#{style}#{color}m" + message.to_s.strip + "\e[0m#{nl}"
164
+ end
58
165
  end
59
166
  end
@@ -0,0 +1,55 @@
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
+ else
27
+ return false
28
+ end
29
+
30
+ if readers
31
+ ensure_each_session do |session|
32
+ ios = session.listeners.keys
33
+ session.postprocess(ios & readers, ios & writers)
34
+ end
35
+ end
36
+
37
+ true
38
+ end
39
+
40
+ def ensure_each_session
41
+ errors = []
42
+
43
+ sessions.each do |session|
44
+ begin
45
+ yield session
46
+ rescue Exception => error
47
+ errors << SessionAssociation.on(error, session)
48
+ end
49
+ end
50
+
51
+ raise errors.first if errors.any?
52
+ sessions
53
+ end
54
+ end
55
+ end