capistrano 2.1.0 → 2.2.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 CHANGED
@@ -1,3 +1,50 @@
1
+ *2.2.0* February 27, 2008
2
+
3
+ * Fix git submodule support to init on sync [halorgium]
4
+
5
+ * Add alternative server-centric role definition method [James Duncan Davidson]
6
+
7
+ * Add support for password prompts from the Mercurial SCM [ches]
8
+
9
+ * Add support for :max_hosts option in task definition or run() [Rob Holland <rob@inversepath.com>]
10
+
11
+ * Distributed git support for better operability with remote_cache strategy [voidlock]
12
+
13
+ * Use a default line length in help text if line length is otherwise too small [Jamis Buck]
14
+
15
+ * Fix incorrect reference to the 'setup' task in task documentation [rajeshduggal]
16
+
17
+ * Don't try to kill the spawner process on deploy:stop if no spawner process exists [Jamis Buck]
18
+
19
+ * Dynamic roles (e.g. role(:app) { "host.name" }) [dmasover]
20
+
21
+ * Implement Bzr#next_revision so that pending changes can be reported correctly [casret]
22
+
23
+ * Use a proper export command for bzr SCM [drudru]
24
+
25
+ * Use checkout instead of merge for git SCM [nuttycom]
26
+
27
+ * Fix typo in Subversion SCM module, encountered when an update fails [kemiller]
28
+
29
+ * Fix documentation typo in upload.rb [evolving_jerk]
30
+
31
+ * Added test case to show that the :scm_command is honored by the git SCM module [grempe]
32
+
33
+ * Fail gracefully when double-colons are used to delimit namespaces [richie]
34
+
35
+ * Add support for :git_enable_submodules variable, to enable submodules with the git SCM [halorgium]
36
+
37
+ * If subversion asks for a password, prompt as a last resort [Jamis Buck]
38
+
39
+ * Use checkout --lightweight for bzr checkout, instead of branch [michiels]
40
+
41
+ * Make sure bzr SCM works when revision is head (or unspecified) [michiels]
42
+
43
+ * Support p4sync_flags and p4client_root variables for Perforce [gseidman]
44
+
45
+ * Prepare for Net::SSH v2 by making sure Capistrano only tries to load Net::SSH versions less than 1.99.0 [Jamis Buck]
46
+
47
+
1
48
  *2.1.0* October 14, 2007
2
49
 
3
50
  * Default to 0664 instead of 0660 on upload [Jamis Buck]
@@ -84,7 +84,9 @@ module Capistrano
84
84
  text.each_line do |line|
85
85
  indentation = line[/^\s+/] || ""
86
86
  indentation_size = indentation.split(//).inject(0) { |c,s| c + (s[0] == ?\t ? 8 : 1) }
87
- lines = line.strip.gsub(/(.{1,#{output_columns - indentation_size}})(?:\s+|\Z)/, "\\1\n").split(/\n/)
87
+ line_length = output_columns - indentation_size
88
+ line_length = MIN_MAX_LEN if line_length < MIN_MAX_LEN
89
+ lines = line.strip.gsub(/(.{1,#{line_length}})(?:\s+|\Z)/, "\\1\n").split(/\n/)
88
90
  if lines.empty?
89
91
  formatted << "\n"
90
92
  else
@@ -1,3 +1,5 @@
1
+
2
+ require 'enumerator'
1
3
  require 'capistrano/gateway'
2
4
  require 'capistrano/ssh'
3
5
 
@@ -95,6 +97,14 @@ module Capistrano
95
97
  end
96
98
  end
97
99
 
100
+ # Destroys sessions for each server in the list.
101
+ def teardown_connections_to(servers)
102
+ servers.each do |server|
103
+ @sessions[server].close
104
+ @sessions.delete(server)
105
+ end
106
+ end
107
+
98
108
  # Determines the set of servers within the current task's scope and
99
109
  # establishes connections to them, and then yields that list of
100
110
  # servers.
@@ -120,22 +130,32 @@ module Capistrano
120
130
  servers = [servers.first] if options[:once]
121
131
  logger.trace "servers: #{servers.map { |s| s.host }.inspect}"
122
132
 
123
- # establish connections to those servers, as necessary
124
- begin
125
- establish_connections_to(servers)
126
- rescue ConnectionError => error
127
- raise error unless task && task.continue_on_error?
128
- error.hosts.each do |h|
129
- servers.delete(h)
130
- failed!(h)
133
+ max_hosts = (options[:max_hosts] || (task && task.max_hosts) || servers.size).to_i
134
+ is_subset = max_hosts < servers.size
135
+
136
+ # establish connections to those servers in groups of max_hosts, as necessary
137
+ servers.each_slice(max_hosts) do |servers_slice|
138
+ begin
139
+ establish_connections_to(servers_slice)
140
+ rescue ConnectionError => error
141
+ raise error unless task && task.continue_on_error?
142
+ error.hosts.each do |h|
143
+ servers_slice.delete(h)
144
+ failed!(h)
145
+ end
146
+ end
147
+
148
+ begin
149
+ yield servers_slice
150
+ rescue RemoteError => error
151
+ raise error unless task && task.continue_on_error?
152
+ error.hosts.each { |h| failed!(h) }
131
153
  end
132
- end
133
154
 
134
- begin
135
- yield servers
136
- rescue RemoteError => error
137
- raise error unless task && task.continue_on_error?
138
- error.hosts.each { |h| failed!(h) }
155
+ # if dealing with a subset (e.g., :max_hosts is less than the
156
+ # number of servers available) teardown the subset of connections
157
+ # that were just made, so that we can make room for the next subset.
158
+ teardown_connections_to(servers_slice) if is_subset
139
159
  end
140
160
  end
141
161
 
@@ -116,7 +116,8 @@ module Capistrano
116
116
 
117
117
  ns = self
118
118
  until parts.empty?
119
- ns = ns.namespaces[parts.shift.to_sym]
119
+ next_part = parts.shift
120
+ ns = next_part.empty? ? nil : ns.namespaces[next_part.to_sym]
120
121
  return nil if ns.nil?
121
122
  end
122
123
 
@@ -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,24 @@ 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
+ roles[which].push(block, options) if block_given?
47
49
  args.each { |host| roles[which] << ServerDefinition.new(host, options) }
48
50
  end
51
+
52
+ # An alternative way to associate servers with roles. If you have a server
53
+ # that participates in multiple roles, this can be a DRYer way to describe
54
+ # the relationships. Pass the host definition as the first parameter, and
55
+ # the roles as the remaining parameters:
56
+ #
57
+ # server "master.example.com", :web, :app
58
+ def server(host, *roles)
59
+ options = roles.last.is_a?(Hash) ? roles.pop : {}
60
+ raise ArgumentError, "you must associate a server with at least one role" if roles.empty?
61
+ roles.each { |name| role(name, host, options) }
62
+ end
49
63
  end
50
64
  end
51
65
  end
@@ -110,11 +110,11 @@ namespace :deploy do
110
110
  desc <<-DESC
111
111
  Prepares one or more servers for deployment. Before you can use any \
112
112
  of the Capistrano deployment tasks with your project, you will need to \
113
- make sure all of your servers have been prepared with `cap setup'. When \
113
+ make sure all of your servers have been prepared with `cap deploy:setup'. When \
114
114
  you add a new server to your cluster, you can easily run the setup task \
115
115
  on just that server by specifying the HOSTS environment variable:
116
116
 
117
- $ cap HOSTS=new.server.com setup
117
+ $ cap HOSTS=new.server.com deploy:setup
118
118
 
119
119
  It is safe to run this task on servers that have already been set up; it \
120
120
  will not destroy any deployed revisions or data.
@@ -426,7 +426,7 @@ namespace :deploy do
426
426
  as = fetch(:runner, "app")
427
427
  via = fetch(:run_method, :sudo)
428
428
 
429
- invoke_command "#{current_path}/script/process/reaper -a kill -r dispatch.spawner.pid", :via => via, :as => as
429
+ invoke_command "if [ -f #{current_path}/tmp/pids/dispatch.spawner.pid ]; then #{current_path}/script/process/reaper -a kill -r dispatch.spawner.pid; fi", :via => via, :as => as
430
430
  invoke_command "#{current_path}/script/process/reaper -a kill", :via => via, :as => as
431
431
  end
432
432
 
@@ -21,7 +21,7 @@ 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 :branch, revswitch(revision), repository, destination
24
+ scm :checkout, "--lightweight", revswitch(revision), repository, destination
25
25
  end
26
26
 
27
27
  # The bzr 'update' command does not support updating to a specific
@@ -33,13 +33,9 @@ module Capistrano
33
33
  commands.join(" && ")
34
34
  end
35
35
 
36
- # The bzr 'export' command would work fine, except it doesn't let you
37
- # specify the repository itself, so it only works if there is a working
38
- # tree handy, locally. Since this needs to work even on a remote host
39
- # where there is no working tree, we'll just do a checkout, followed
40
- # by a deletion of the ".bzr" metadata directory.
36
+ # The bzr 'export' does an export similar to other SCM systems
41
37
  def export(revision, destination)
42
- [checkout(revision, destination) && "rm -rf #{destination}/.bzr"].join(" && ")
38
+ scm :export, revswitch(revision), destination, repository
43
39
  end
44
40
 
45
41
  # The bzr "diff" command doesn't accept a repository argument, so it
@@ -63,17 +59,18 @@ module Capistrano
63
59
  # If the 'revision' argument, on the other hand, is not :head, it is
64
60
  # simply returned.
65
61
  def query_revision(revision)
66
- if revision == head
67
- yield(scm(:revno, repository)).chomp
68
- else
69
- revision
70
- end
62
+ revision
63
+ end
64
+
65
+ # Increments the given revision number and returns it.
66
+ def next_revision(revision)
67
+ revision.to_i + 1
71
68
  end
72
69
 
73
70
  private
74
71
 
75
72
  def revswitch(revision)
76
- if revision == :head
73
+ if revision == :head || revision.nil?
77
74
  nil
78
75
  else
79
76
  "-r #{revision}"
@@ -49,11 +49,28 @@ module Capistrano
49
49
  # You may set <tt>:branch</tt>, which is the reference to the branch, tag,
50
50
  # or any SHA1 you are deploying, for example:
51
51
  #
52
- # set :branch, "origin/master"
52
+ # set :branch, "master"
53
53
  #
54
54
  # Otherwise, HEAD is assumed. I strongly suggest you set this. HEAD is
55
55
  # not always the best assumption.
56
56
  #
57
+ # You may also set <tt>:remote</tt>, which will be used as a name for remote
58
+ # tracking of repositories. This option is intended for use with the
59
+ # <tt>:remote_cache</tt> strategy in a distributed git environment.
60
+ #
61
+ # For example in the projects <tt>config/deploy.rb</tt>:
62
+ #
63
+ # set :repository, "#{scm_user}@somehost:~/projects/project.git"
64
+ # set :remote, "#{scm_user}"
65
+ #
66
+ # Then each person with deploy priveledges can add the following to their
67
+ # local <tt>~/.caprc</tt> file:
68
+ #
69
+ # set :scm_user, 'someuser'
70
+ #
71
+ # Now any time a person deploys the project, their repository will be
72
+ # setup as a remote git repository within the cached repository.
73
+ #
57
74
  # The <tt>:scm_command</tt> configuration variable, if specified, will
58
75
  # be used as the full path to the git executable on the *remote* machine:
59
76
  #
@@ -78,7 +95,7 @@ module Capistrano
78
95
  # set :deploy_via, :remote_cache
79
96
  #
80
97
  # For faster clone, you can also use shallow cloning. This will set the
81
- # '--depth' flag using the depth specified. This *cannot* be used
98
+ # '--depth' flag using the depth specified. This *cannot* be used
82
99
  # together with the :remote_cache strategy
83
100
  #
84
101
  # set :git_shallow_clone, 1
@@ -88,8 +105,9 @@ module Capistrano
88
105
  #
89
106
  # Garry Dolley http://scie.nti.st
90
107
  # Contributions by Geoffrey Grosenbach http://topfunky.com
91
- # and Scott Chacon http://jointheconversation.org
92
-
108
+ # Scott Chacon http://jointheconversation.org
109
+ # and Alex Arnell http://twologic.com
110
+
93
111
  class Git < Base
94
112
  # Sets the default command name for this SCM on your *local* machine.
95
113
  # Users may override this by setting the :scm_command variable.
@@ -102,38 +120,69 @@ module Capistrano
102
120
  configuration[:branch] || 'HEAD'
103
121
  end
104
122
 
123
+ def origin
124
+ configuration[:remote] || 'origin'
125
+ end
126
+
105
127
  # Performs a clone on the remote machine, then checkout on the branch
106
128
  # you want to deploy.
107
129
  def checkout(revision, destination)
108
- git = command
109
-
110
- branch = head
111
-
112
- fail "No branch specified, use for example 'set :branch, \"origin/master\"' in your deploy.rb" unless branch
130
+ git = command
131
+ remote = origin
113
132
 
133
+ args = []
134
+ args << "-o #{remote}" unless remote == 'origin'
114
135
  if depth = configuration[:git_shallow_clone]
115
- execute = "#{git} clone --depth #{depth} #{configuration[:repository]} #{destination} && "
136
+ args << "--depth #{depth}"
137
+ end
138
+
139
+ execute = []
140
+ if args.empty?
141
+ execute << "#{git} clone #{configuration[:repository]} #{destination}"
116
142
  else
117
- execute = "#{git} clone #{configuration[:repository]} #{destination} && "
143
+ execute << "#{git} clone #{args.join(' ')} #{configuration[:repository]} #{destination}"
118
144
  end
119
145
 
120
- execute += "cd #{destination} && #{git} checkout -b deploy #{branch}"
146
+ # checkout into a local branch rather than a detached HEAD
147
+ execute << "cd #{destination} && #{git} checkout -b deploy #{revision}"
148
+
149
+ if configuration[:git_enable_submodules]
150
+ execute << "#{git} submodule init"
151
+ execute << "#{git} submodule update"
152
+ end
121
153
 
122
- execute
154
+ execute.join(" && ")
123
155
  end
124
156
 
125
157
  # Merges the changes to 'head' since the last fetch, for remote_cache
126
158
  # deployment strategy
127
159
  def sync(revision, destination)
128
- execute = "cd #{destination} && git fetch origin && "
160
+ git = command
161
+ remote = origin
162
+
163
+ execute = []
164
+ execute << "cd #{destination}"
165
+
166
+ # Use git-config to setup a remote tracking branches. Could use
167
+ # git-remote but it complains when a remote of the same name already
168
+ # exists, git-config will just silenty overwrite the setting every
169
+ # time. This could cause wierd-ness in the remote cache if the url
170
+ # changes between calls, but as long as the repositories are all
171
+ # based from each other it should still work fine.
172
+ if remote != 'origin'
173
+ execute << "#{git} config remote.#{remote}.url #{configuration[:repository]}"
174
+ execute << "#{git} config remote.#{remote}.fetch +refs/heads/*:refs/remotes/#{remote}/*"
175
+ end
129
176
 
130
- if head == 'HEAD'
131
- execute += "git merge origin/HEAD"
132
- else
133
- execute += "git merge #{head}"
177
+ # 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}"
179
+
180
+ if configuration[:git_enable_submodules]
181
+ execute << "#{git} submodule init"
182
+ execute << "#{git} submodule update"
134
183
  end
135
184
 
136
- execute
185
+ execute.join(" && ")
137
186
  end
138
187
 
139
188
  # Returns a string of diffs between two revisions
@@ -70,24 +70,25 @@ module Capistrano
70
70
  logger.info "[#{stream}] #{text}"
71
71
  case text
72
72
  when /^user:/mi
73
- if variable(:scm_user)
74
- "#{variable(:scm_user)}\n"
73
+ # support :scm_user for backwards compatibility of this module
74
+ if user = variable(:scm_username) || variable(:scm_user)
75
+ "#{user}\n"
75
76
  else
76
- raise "No variable :scm_user specified and Mercurial asked!\n" +
77
+ raise "No variable :scm_username specified and Mercurial asked!\n" +
77
78
  "Prompt was: #{text}"
78
79
  end
79
- when /^password:/mi
80
- if variable(:scm_password)
81
- "#{variable(:scm_password)}\n"
82
- else
80
+ when /\bpassword:/mi
81
+ unless pass = scm_password_or_prompt
82
+ # fall back on old behavior of erroring out with msg
83
83
  raise "No variable :scm_password specified and Mercurial asked!\n" +
84
84
  "Prompt was: #{text}"
85
85
  end
86
+ "#{pass}\n"
86
87
  when /yes\/no/i
87
88
  "yes\n"
88
89
  end
89
90
  end
90
-
91
+
91
92
  private
92
93
 
93
94
  # Fine grained mercurial commands
@@ -122,6 +123,12 @@ module Capistrano
122
123
  else "--verbose"
123
124
  end
124
125
  end
126
+
127
+ # honor Cap 2.1+'s :scm_prefer_prompt if present
128
+ def scm_password_or_prompt
129
+ @scm_password_or_prompt ||= variable(:scm_password) ||
130
+ (Capistrano::CLI.password_prompt("hg password: ") if variable(:scm_prefer_prompt))
131
+ end
125
132
 
126
133
  end
127
134
  end
@@ -25,21 +25,21 @@ module Capistrano
25
25
  # destination directory. The perforce client has a fixed destination so
26
26
  # the files must be copied from there to their intended resting place.
27
27
  def checkout(revision, destination)
28
- p4_sync(revision, destination, "-f")
28
+ p4_sync(revision, destination, p4sync_flags)
29
29
  end
30
30
 
31
31
  # Returns the command that will sync the given revision to the given
32
32
  # destination directory. The perforce client has a fixed destination so
33
33
  # the files must be copied from there to their intended resting place.
34
34
  def sync(revision, destination)
35
- p4_sync(revision, destination, "-f")
35
+ p4_sync(revision, destination, p4sync_flags)
36
36
  end
37
37
 
38
38
  # Returns the command that will sync the given revision to the given
39
39
  # destination directory. The perforce client has a fixed destination so
40
40
  # the files must be copied from there to their intended resting place.
41
41
  def export(revision, destination)
42
- p4_sync(revision, destination, "-f")
42
+ p4_sync(revision, destination, p4sync_flags)
43
43
  end
44
44
 
45
45
  # Returns the command that will do an "p4 diff2" for the two revisions.
@@ -89,7 +89,6 @@ module Capistrano
89
89
  # a fixed destination so the files must be copied from there to their
90
90
  # intended resting place.
91
91
  def p4_sync(revision, destination, options="")
92
- p4client_root = "`#{command} #{authentication} client -o | grep ^Root | cut -f2`"
93
92
  scm authentication, :sync, options, "#{rev_no(revision)}", "&& cp -rf #{p4client_root} #{destination}"
94
93
  end
95
94
 
@@ -108,6 +107,14 @@ module Capistrano
108
107
  def p4passwd
109
108
  variable(:p4passwd) || variable(:scm_password)
110
109
  end
110
+
111
+ def p4sync_flags
112
+ variable(:p4sync_flags) || "-f"
113
+ end
114
+
115
+ def p4client_root
116
+ variable(:p4client_root) || "`#{command} #{authentication} client -o | grep ^Root | cut -f2`"
117
+ end
111
118
 
112
119
  def rev_no(revision)
113
120
  case revision.to_s
@@ -72,7 +72,7 @@ module Capistrano
72
72
  case text
73
73
  when /\bpassword.*:/i
74
74
  # subversion is prompting for a password
75
- "#{variable(:scm_password) || variable(:password)}\n"
75
+ "#{scm_password_prompt}\n"
76
76
  when %r{\(yes/no\)}
77
77
  # subversion is asking whether or not to connect
78
78
  "yes\n"
@@ -80,7 +80,7 @@ module Capistrano
80
80
  # subversion is asking for the passphrase for the user's key
81
81
  "#{variable(:scm_passphrase)}\n"
82
82
  when /The entry \'(.+?)\' is no longer a directory/
83
- raise Capisrano::Error, "subversion can't update because directory '#{$1}' was replaced. Please add it to svn:ignore."
83
+ raise Capistrano::Error, "subversion can't update because directory '#{$1}' was replaced. Please add it to svn:ignore."
84
84
  when /accept \(t\)emporarily/
85
85
  # subversion is asking whether to accept the certificate
86
86
  "t\n"
@@ -107,6 +107,12 @@ module Capistrano
107
107
  def verbose
108
108
  variable(:scm_verbose) ? nil : "-q"
109
109
  end
110
+
111
+ def scm_password_prompt
112
+ @scm_password_prompt ||= variable(:scm_password) ||
113
+ variable(:password) ||
114
+ Capistrano::CLI.password_prompt("Subversion password: ")
115
+ end
110
116
  end
111
117
 
112
118
  end
@@ -0,0 +1,112 @@
1
+
2
+ module Capistrano
3
+ class Role
4
+ include Enumerable
5
+
6
+ def initialize(*list)
7
+ @static_servers = []
8
+ @dynamic_servers = []
9
+ push(*list)
10
+ end
11
+
12
+ def each(&block)
13
+ servers.each &block
14
+ end
15
+
16
+ def push(*list)
17
+ options = list.last.is_a?(Hash) ? list.pop : {}
18
+ list.each do |item|
19
+ if item.respond_to?(:call)
20
+ @dynamic_servers << DynamicServerList.new(item, options)
21
+ else
22
+ @static_servers << self.class.wrap_server(item, options)
23
+ end
24
+ end
25
+ end
26
+ alias_method :<<, :push
27
+
28
+ def servers
29
+ @static_servers + dynamic_servers
30
+ end
31
+ alias_method :to_ary, :servers
32
+
33
+ def empty?
34
+ servers.empty?
35
+ end
36
+
37
+ # Resets the cache, so that proc values may be recalculated.
38
+ # There should be a command in Configuration::Roles to do this,
39
+ # but I haven't needed it yet, and I'm not sure yet
40
+ # what to call that command. Suggestions?
41
+ def reset!
42
+ @dynamic_servers.each { |item| item.reset! }
43
+ end
44
+
45
+ # Clears everything. I still thing this should be 'clear!', but that's not
46
+ # the way Array does it.
47
+ def clear
48
+ @dynamic_servers.clear
49
+ @static_servers.clear
50
+ end
51
+
52
+ # Mostly for documentation purposes. Doesn't seem to do anything.
53
+ protected
54
+
55
+ # This is the combination of a block, a hash of options, and a cached value.
56
+ # It is protected because it is an implementation detail -- the original
57
+ # implementation was two lists (blocks and cached results of calling them).
58
+ class DynamicServerList
59
+ def initialize (block, options)
60
+ @block = block
61
+ @options = options
62
+ @cached = []
63
+ @is_cached = false
64
+ end
65
+
66
+ # Convert to a list of ServerDefinitions
67
+ def to_ary
68
+ unless @is_cached
69
+ @cached = Role::wrap_list(@block.call(@options), @options)
70
+ @is_cached = true
71
+ end
72
+ @cached
73
+ end
74
+
75
+ # Clear the cached value
76
+ def reset!
77
+ @cached.clear
78
+ @is_cached = false
79
+ end
80
+ end
81
+
82
+ # Attribute reader for the cached results of executing the blocks in turn
83
+ def dynamic_servers
84
+ @dynamic_servers.inject([]) { |list, item| list.concat item }
85
+ end
86
+
87
+ # Wraps a string in a ServerDefinition, if it isn't already.
88
+ # This and wrap_list should probably go in ServerDefinition in some form.
89
+ def self.wrap_server (item, options)
90
+ item.is_a?(ServerDefinition) ? item : ServerDefinition.new(item, options)
91
+ end
92
+
93
+ # Turns a list, or something resembling a list, into a properly-formatted
94
+ # ServerDefinition list. Keep an eye on this one -- it's entirely too
95
+ # magical for its own good. In particular, if ServerDefinition ever inherits
96
+ # from Array, this will break.
97
+ def self.wrap_list (*list)
98
+ options = list.last.is_a?(Hash) ? list.pop : {}
99
+ if list.length == 1
100
+ if list.first.nil?
101
+ return []
102
+ elsif list.first.is_a?(Array)
103
+ list = list.first
104
+ end
105
+ end
106
+ options.merge! list.pop if list.last.is_a?(Hash)
107
+ list.map do |item|
108
+ self.wrap_server item, options
109
+ end
110
+ end
111
+ end
112
+ end
@@ -1,3 +1,9 @@
1
+ begin
2
+ require 'rubygems'
3
+ gem 'net-ssh', "< 1.99.0"
4
+ rescue LoadError, NameError
5
+ end
6
+
1
7
  require 'net/ssh'
2
8
 
3
9
  module Capistrano
@@ -3,12 +3,13 @@ require 'capistrano/server_definition'
3
3
  module Capistrano
4
4
  # Represents the definition of a single task.
5
5
  class TaskDefinition
6
- attr_reader :name, :namespace, :options, :body, :desc, :on_error
6
+ attr_reader :name, :namespace, :options, :body, :desc, :on_error, :max_hosts
7
7
 
8
8
  def initialize(name, namespace, options={}, &block)
9
9
  @name, @namespace, @options = name, namespace, options
10
10
  @desc = @options.delete(:desc)
11
11
  @on_error = options.delete(:on_error)
12
+ @max_hosts = options[:max_hosts] && options[:max_hosts].to_i
12
13
  @body = block or raise ArgumentError, "a task requires a block"
13
14
  @servers = nil
14
15
  end
@@ -1,3 +1,9 @@
1
+ begin
2
+ require 'rubygems'
3
+ gem 'net-sftp', "< 1.99.0"
4
+ rescue LoadError, NameError
5
+ end
6
+
1
7
  require 'net/sftp'
2
8
  require 'net/sftp/operations/errors'
3
9
  require 'capistrano/errors'
@@ -42,7 +48,7 @@ module Capistrano
42
48
  #
43
49
  # * data: required. Should refer to a String containing the contents of
44
50
  # the file to upload.
45
- # * mode: optional. The "mode" of the destination file. Defaults to 0660.
51
+ # * mode: optional. The "mode" of the destination file. Defaults to 0664.
46
52
  # * logger: optional. Should point to a Capistrano::Logger instance, if
47
53
  # given.
48
54
  def initialize(sessions, filename, options)
@@ -11,7 +11,7 @@ module Capistrano
11
11
  end
12
12
 
13
13
  MAJOR = 2
14
- MINOR = 1
14
+ MINOR = 2
15
15
  TINY = 0
16
16
 
17
17
  STRING = [MAJOR, MINOR, TINY].join(".")
@@ -217,7 +217,6 @@ class ConfigurationConnectionsTest < Test::Unit::TestCase
217
217
  @config.current_task = mock_task(:on_error => :continue)
218
218
  @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns(list)
219
219
  Capistrano::SSH.expects(:connect).times(2).raises(Exception).then.returns(:success)
220
- @config.expects(:failed!).with(server("cap1"))
221
220
  @config.execute_on_servers do |servers|
222
221
  assert_equal %w(cap2), servers.map { |s| s.host }
223
222
  end
@@ -260,7 +259,7 @@ class ConfigurationConnectionsTest < Test::Unit::TestCase
260
259
  assert_equal %w(cap2), servers.map { |s| s.host }
261
260
  end
262
261
  end
263
-
262
+
264
263
  def test_connect_should_establish_connections_to_all_servers_in_scope
265
264
  assert @config.sessions.empty?
266
265
  @config.current_task = mock_task
@@ -269,7 +268,43 @@ class ConfigurationConnectionsTest < Test::Unit::TestCase
269
268
  @config.connect!
270
269
  assert_equal %w(cap1 cap2 cap3), @config.sessions.keys.sort.map { |s| s.host }
271
270
  end
272
-
271
+
272
+ def test_execute_on_servers_should_only_run_on_tasks_max_hosts_hosts_at_once
273
+ cap1 = server("cap1")
274
+ cap2 = server("cap2")
275
+ connection1 = mock()
276
+ connection2 = mock()
277
+ connection1.expects(:close)
278
+ connection2.expects(:close)
279
+ @config.current_task = mock_task(:max_hosts => 1)
280
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([cap1, cap2])
281
+ Capistrano::SSH.expects(:connect).times(2).returns(connection1).then.returns(connection2)
282
+ block_called = 0
283
+ @config.execute_on_servers do |servers|
284
+ block_called += 1
285
+ assert_equal 1, servers.size
286
+ end
287
+ assert_equal 2, block_called
288
+ end
289
+
290
+ def test_execute_on_servers_should_only_run_on_max_hosts_hosts_at_once
291
+ cap1 = server("cap1")
292
+ cap2 = server("cap2")
293
+ connection1 = mock()
294
+ connection2 = mock()
295
+ connection1.expects(:close)
296
+ connection2.expects(:close)
297
+ @config.current_task = mock_task(:max_hosts => 1)
298
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([cap1, cap2])
299
+ Capistrano::SSH.expects(:connect).times(2).returns(connection1).then.returns(connection2)
300
+ block_called = 0
301
+ @config.execute_on_servers do |servers|
302
+ block_called += 1
303
+ assert_equal 1, servers.size
304
+ end
305
+ assert_equal 2, block_called
306
+ end
307
+
273
308
  def test_connect_should_honor_once_option
274
309
  assert @config.sessions.empty?
275
310
  @config.current_task = mock_task
@@ -283,6 +318,11 @@ class ConfigurationConnectionsTest < Test::Unit::TestCase
283
318
 
284
319
  def mock_task(options={})
285
320
  continue_on_error = options[:on_error] == :continue
286
- stub("task", :fully_qualified_name => "name", :options => options, :continue_on_error? => continue_on_error)
321
+ stub("task",
322
+ :fully_qualified_name => "name",
323
+ :options => options,
324
+ :continue_on_error? => continue_on_error,
325
+ :max_hosts => options[:max_hosts]
326
+ )
287
327
  end
288
328
  end
@@ -294,4 +294,12 @@ class ConfigurationNamespacesDSLTest < Test::Unit::TestCase
294
294
  @config.namespace(:outer) { namespace(:middle) { namespace(:inner) {} } }
295
295
  assert_equal @config, @config.namespaces[:outer].namespaces[:middle].namespaces[:inner].top
296
296
  end
297
+
298
+ def test_find_task_should_return_nil_when_empty_inner_task
299
+ @config.namespace :outer do
300
+ namespace :inner do
301
+ end
302
+ end
303
+ assert_nil @config.find_task("outer::inner")
304
+ end
297
305
  end
@@ -1,5 +1,6 @@
1
1
  require "#{File.dirname(__FILE__)}/../utils"
2
2
  require 'capistrano/configuration/roles'
3
+ require 'capistrano/server_definition'
3
4
 
4
5
  class ConfigurationRolesTest < Test::Unit::TestCase
5
6
  class MockConfig
@@ -29,13 +30,34 @@ class ConfigurationRolesTest < Test::Unit::TestCase
29
30
  def test_role_with_one_argument_should_add_to_roles_collection
30
31
  @config.role :app, "app1.capistrano.test"
31
32
  assert_equal [:app], @config.roles.keys
32
- assert_equal %w(app1.capistrano.test), @config.roles[:app].map { |s| s.host }
33
+ assert_role_equals %w(app1.capistrano.test)
34
+ end
35
+
36
+ def test_role_block_returning_single_string_is_added_to_roles_collection
37
+ @config.role :app do
38
+ 'app1.capistrano.test'
39
+ end
40
+ assert_role_equals %w(app1.capistrano.test)
33
41
  end
34
42
 
35
43
  def test_role_with_multiple_arguments_should_add_each_to_roles_collection
36
44
  @config.role :app, "app1.capistrano.test", "app2.capistrano.test"
37
45
  assert_equal [:app], @config.roles.keys
38
- assert_equal %w(app1.capistrano.test app2.capistrano.test), @config.roles[:app].map { |s| s.host }
46
+ assert_role_equals %w(app1.capistrano.test app2.capistrano.test)
47
+ end
48
+
49
+ def test_role_with_block_and_strings_should_add_both_to_roles_collection
50
+ @config.role :app, 'app1.capistrano.test' do
51
+ 'app2.capistrano.test'
52
+ end
53
+ assert_role_equals %w(app1.capistrano.test app2.capistrano.test)
54
+ end
55
+
56
+ def test_role_block_returning_array_should_add_each_to_roles_collection
57
+ @config.role :app do
58
+ ['app1.capistrano.test', 'app2.capistrano.test']
59
+ end
60
+ assert_role_equals %w(app1.capistrano.test app2.capistrano.test)
39
61
  end
40
62
 
41
63
  def test_role_with_options_should_apply_options_to_each_argument
@@ -44,4 +66,78 @@ class ConfigurationRolesTest < Test::Unit::TestCase
44
66
  assert_equal({:extra => :value}, server.options)
45
67
  end
46
68
  end
47
- end
69
+
70
+ def test_role_with_options_should_apply_options_to_block_results
71
+ @config.role :app, :extra => :value do
72
+ ['app1.capistrano.test', 'app2.capistrano.test']
73
+ end
74
+ @config.roles[:app].each do |server|
75
+ assert_equal({:extra => :value}, server.options)
76
+ end
77
+ end
78
+
79
+ def test_options_should_apply_only_to_this_argument_set
80
+ @config.role :app, 'app1.capistrano.test', 'app2.capistrano.test' do
81
+ ['app3.capistrano.test', 'app4.capistrano.test']
82
+ end
83
+ @config.role :app, 'app5.capistrano.test', 'app6.capistrano.test', :extra => :value do
84
+ ['app7.capistrano.test', 'app8.capistrano.test']
85
+ end
86
+ @config.role :app, 'app9.capistrano.test'
87
+
88
+ option_hosts = ['app5.capistrano.test', 'app6.capistrano.test', 'app7.capistrano.test', 'app8.capistrano.test']
89
+ @config.roles[:app].each do |server|
90
+ if (option_hosts.include? server.host)
91
+ assert_equal({:extra => :value}, server.options)
92
+ else
93
+ assert_not_equal({:extra => :value}, server.options)
94
+ end
95
+ end
96
+ end
97
+
98
+ # Here, the source should be more readable than the method name
99
+ def test_role_block_returns_options_hash_is_merged_with_role_options_argument
100
+ @config.role :app, :first => :one, :second => :two do
101
+ ['app1.capistrano.test', 'app2.capistrano.test', {:second => :please, :third => :three}]
102
+ end
103
+ @config.roles[:app].each do |server|
104
+ assert_equal({:first => :one, :second => :please, :third => :three}, server.options)
105
+ end
106
+ end
107
+
108
+ def test_role_block_can_override_role_options_argument
109
+ @config.role :app, :value => :wrong do
110
+ Capistrano::ServerDefinition.new('app.capistrano.test')
111
+ end
112
+ @config.roles[:app].servers
113
+ @config.roles[:app].servers.each do |server|
114
+ assert_not_equal({:value => :wrong}, server.options)
115
+ end
116
+ end
117
+
118
+ def test_role_block_can_return_nil
119
+ @config.role :app do
120
+ nil
121
+ end
122
+ assert_role_equals ([])
123
+ end
124
+
125
+ def test_role_block_can_return_empty_array
126
+ @config.role :app do
127
+ []
128
+ end
129
+ assert_role_equals ([])
130
+ end
131
+
132
+ def test_role_definitions_via_server_should_associate_server_with_roles
133
+ @config.server "www.capistrano.test", :web, :app
134
+ assert_equal %w(www.capistrano.test), @config.roles[:app].map { |s| s.host }
135
+ assert_equal %w(www.capistrano.test), @config.roles[:web].map { |s| s.host }
136
+ end
137
+
138
+ private
139
+
140
+ def assert_role_equals(list)
141
+ assert_equal list, @config.roles[:app].map { |s| s.host }
142
+ end
143
+ end
@@ -15,18 +15,28 @@ class DeploySCMGitTest < Test::Unit::TestCase
15
15
 
16
16
  def test_head
17
17
  assert_equal "HEAD", @source.head
18
+
19
+ # With :branch
18
20
  @config[:branch] = "master"
19
21
  assert_equal "master", @source.head
20
22
  end
21
23
 
24
+ def test_origin
25
+ assert_equal "origin", @source.origin
26
+ @config[:remote] = "username"
27
+ assert_equal "username", @source.origin
28
+ end
29
+
22
30
  def test_checkout
23
31
  @config[:repository] = "git@somehost.com:project.git"
24
32
  dest = "/var/www"
25
- assert_equal "git clone git@somehost.com:project.git /var/www && cd /var/www && git checkout -b deploy HEAD", @source.checkout('Not used', dest)
33
+ rev = 'c2d9e79'
34
+ assert_equal "git clone git@somehost.com:project.git /var/www && cd /var/www && git checkout -b deploy #{rev}", @source.checkout(rev, dest)
26
35
 
27
- # With branch
28
- @config[:branch] = "origin/foo"
29
- assert_equal "git clone git@somehost.com:project.git /var/www && cd /var/www && git checkout -b deploy origin/foo", @source.checkout('Not used', dest)
36
+ # With :scm_command
37
+ git = "/opt/local/bin/git"
38
+ @config[:scm_command] = git
39
+ assert_equal "#{git} clone git@somehost.com:project.git /var/www && cd /var/www && #{git} checkout -b deploy #{rev}", @source.checkout(rev, dest)
30
40
  end
31
41
 
32
42
  def test_diff
@@ -51,22 +61,41 @@ class DeploySCMGitTest < Test::Unit::TestCase
51
61
 
52
62
  def test_sync
53
63
  dest = "/var/www"
54
- assert_equal "cd #{dest} && git fetch origin && git merge origin/HEAD", @source.sync('Not used', dest)
64
+ rev = 'c2d9e79'
65
+ assert_equal "cd #{dest} && git fetch origin && git reset --hard #{rev}", @source.sync(rev, dest)
66
+
67
+ # With :scm_command
68
+ git = "/opt/local/bin/git"
69
+ @config[:scm_command] = git
70
+ assert_equal "cd #{dest} && #{git} fetch origin && #{git} reset --hard #{rev}", @source.sync(rev, dest)
71
+ end
72
+
73
+ def test_sync_with_remote
74
+ dest = "/var/www"
75
+ rev = 'c2d9e79'
76
+ remote = "username"
77
+ repository = "git@somehost.com:project.git"
78
+
79
+ @config[:repository] = repository
80
+ @config[:remote] = remote
55
81
 
56
- # With branch
57
- @config[:branch] = "origin/foo"
58
- assert_equal "cd #{dest} && git fetch origin && git merge origin/foo", @source.sync('Not used', dest)
82
+ assert_equal "cd #{dest} && git config remote.#{remote}.url #{repository} && git config remote.#{remote}.fetch +refs/heads/*:refs/remotes/#{remote}/* && git fetch #{remote} && git reset --hard #{rev}", @source.sync(rev, dest)
59
83
  end
60
84
 
61
85
  def test_shallow_clone
62
86
  @config[:repository] = "git@somehost.com:project.git"
63
87
  @config[:git_shallow_clone] = 1
64
88
  dest = "/var/www"
65
- assert_equal "git clone --depth 1 git@somehost.com:project.git /var/www && cd /var/www && git checkout -b deploy HEAD", @source.checkout('Not used', dest)
89
+ rev = 'c2d9e79'
90
+ assert_equal "git clone --depth 1 git@somehost.com:project.git /var/www && cd /var/www && git checkout -b deploy #{rev}", @source.checkout(rev, dest)
91
+ end
66
92
 
67
- # With branch
68
- @config[:branch] = "origin/foo"
69
- assert_equal "git clone --depth 1 git@somehost.com:project.git /var/www && cd /var/www && git checkout -b deploy origin/foo", @source.checkout('Not used', dest)
93
+ def test_remote_clone
94
+ @config[:repository] = "git@somehost.com:project.git"
95
+ @config[:remote] = "username"
96
+ dest = "/var/www"
97
+ rev = 'c2d9e79'
98
+ assert_equal "git clone -o username git@somehost.com:project.git /var/www && cd /var/www && git checkout -b deploy #{rev}", @source.checkout(rev, dest)
70
99
  end
71
100
 
72
101
  # Tests from base_test.rb, makin' sure we didn't break anything up there!
@@ -0,0 +1,123 @@
1
+ require "#{File.dirname(__FILE__)}/../../utils"
2
+ require 'capistrano/recipes/deploy/scm/mercurial'
3
+
4
+ class DeploySCMMercurialTest < Test::Unit::TestCase
5
+ class TestSCM < Capistrano::Deploy::SCM::Mercurial
6
+ default_command "hg"
7
+ end
8
+
9
+ def setup
10
+ @config = { }
11
+ def @config.exists?(name); key?(name); end
12
+
13
+ @source = TestSCM.new(@config)
14
+ end
15
+
16
+ def test_head
17
+ assert_equal "tip", @source.head
18
+ end
19
+
20
+ def test_checkout
21
+ @config[:repository] = "http://example.com/project-hg"
22
+ dest = "/var/www"
23
+ assert_equal "hg clone --noupdate http://example.com/project-hg /var/www && hg update --repository /var/www --clean 8a8e00b8f11b", @source.checkout('8a8e00b8f11b', dest)
24
+ end
25
+
26
+ def test_diff
27
+ assert_equal "hg diff --rev tip", @source.diff('tip')
28
+ assert_equal "hg diff --rev 1 --rev 2", @source.diff('1', '2')
29
+ end
30
+
31
+ def test_log
32
+ assert_equal "hg log --rev 8a8e00b8f11b", @source.log('8a8e00b8f11b')
33
+ assert_equal "hg log --rev 0:3", @source.log('0', '3')
34
+ end
35
+
36
+ def test_query_revision
37
+ assert_equal "hg log -r 8a8e00b8f11b --template '{node|short}'", @source.query_revision('8a8e00b8f11b') { |o| o }
38
+ end
39
+
40
+ def test_username_should_be_backwards_compatible
41
+ # older versions of this module required :scm_user var instead
42
+ # of the currently preferred :scm_username
43
+ require 'capistrano/logger'
44
+ @config[:scm_user] = "fred"
45
+ text = "user:"
46
+ assert_equal "fred\n", @source.handle_data(:test_state, :test_stream, text)
47
+ # :scm_username takes priority
48
+ @config[:scm_username] = "wilma"
49
+ assert_equal "wilma\n", @source.handle_data(:test_state, :test_stream, text)
50
+ end
51
+
52
+ def test_sync
53
+ dest = "/var/www"
54
+ assert_equal "hg pull --repository /var/www && hg update --repository /var/www --clean 8a8e00b8f11b", @source.sync('8a8e00b8f11b', dest)
55
+
56
+ # With :scm_command
57
+ @config[:scm_command] = "/opt/local/bin/hg"
58
+ assert_equal "/opt/local/bin/hg pull --repository /var/www && /opt/local/bin/hg update --repository /var/www --clean 8a8e00b8f11b", @source.sync('8a8e00b8f11b', dest)
59
+ end
60
+
61
+ def test_export
62
+ dest = "/var/www"
63
+ assert_raise(NotImplementedError) { @source.export('8a8e00b8f11b', dest) }
64
+ end
65
+
66
+ def test_sends_password_if_set
67
+ require 'capistrano/logger'
68
+ text = "password:"
69
+ @config[:scm_password] = "opensesame"
70
+ assert_equal "opensesame\n", @source.handle_data(:test_state, :test_stream, text)
71
+ end
72
+
73
+ def test_prompts_for_password_if_preferred
74
+ require 'capistrano/logger'
75
+ require 'capistrano/cli'
76
+ Capistrano::CLI.stubs(:password_prompt).with("hg password: ").returns("opensesame")
77
+ @config[:scm_prefer_prompt] = true
78
+ text = "password:"
79
+ assert_equal "opensesame\n", @source.handle_data(:test_state, :test_stream, text)
80
+ end
81
+
82
+
83
+ # Tests from base_test.rb, makin' sure we didn't break anything up there!
84
+ def test_command_should_default_to_default_command
85
+ assert_equal "hg", @source.command
86
+ @source.local { assert_equal "hg", @source.command }
87
+ end
88
+
89
+ def test_command_should_use_scm_command_if_available
90
+ @config[:scm_command] = "/opt/local/bin/hg"
91
+ assert_equal "/opt/local/bin/hg", @source.command
92
+ end
93
+
94
+ def test_command_should_use_scm_command_in_local_mode_if_local_scm_command_not_set
95
+ @config[:scm_command] = "/opt/local/bin/hg"
96
+ @source.local { assert_equal "/opt/local/bin/hg", @source.command }
97
+ end
98
+
99
+ def test_command_should_use_local_scm_command_in_local_mode_if_local_scm_command_is_set
100
+ @config[:scm_command] = "/opt/local/bin/hg"
101
+ @config[:local_scm_command] = "/usr/local/bin/hg"
102
+ assert_equal "/opt/local/bin/hg", @source.command
103
+ @source.local { assert_equal "/usr/local/bin/hg", @source.command }
104
+ end
105
+
106
+ def test_command_should_use_default_if_scm_command_is_default
107
+ @config[:scm_command] = :default
108
+ assert_equal "hg", @source.command
109
+ end
110
+
111
+ def test_command_should_use_default_in_local_mode_if_local_scm_command_is_default
112
+ @config[:scm_command] = "/foo/bar/hg"
113
+ @config[:local_scm_command] = :default
114
+ @source.local { assert_equal "hg", @source.command }
115
+ end
116
+
117
+ def test_local_mode_proxy_should_treat_messages_as_being_in_local_mode
118
+ @config[:scm_command] = "/foo/bar/hg"
119
+ @config[:local_scm_command] = :default
120
+ assert_equal "hg", @source.local.command
121
+ assert_equal "/foo/bar/hg", @source.command
122
+ end
123
+ end
metadata CHANGED
@@ -1,33 +1,59 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.4
3
- specification_version: 1
4
2
  name: capistrano
5
3
  version: !ruby/object:Gem::Version
6
- version: 2.1.0
7
- date: 2007-10-14 00:00:00 -06:00
8
- summary: Capistrano is a utility and framework for executing commands in parallel on multiple remote machines, via SSH.
9
- require_paths:
10
- - lib
11
- email: jamis@37signals.com
12
- homepage: http://www.capify.org
13
- rubyforge_project:
14
- description:
15
- autorequire: capistrano
16
- default_executable:
17
- bindir: bin
18
- has_rdoc: false
19
- required_ruby_version: !ruby/object:Gem::Version::Requirement
20
- requirements:
21
- - - ">"
22
- - !ruby/object:Gem::Version
23
- version: 0.0.0
24
- version:
4
+ version: 2.2.0
25
5
  platform: ruby
26
- signing_key:
27
- cert_chain:
28
- post_install_message:
29
6
  authors:
30
7
  - Jamis Buck
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-02-27 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: net-ssh
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.0.10
23
+ - - <
24
+ - !ruby/object:Gem::Version
25
+ version: 1.99.0
26
+ version:
27
+ - !ruby/object:Gem::Dependency
28
+ name: net-sftp
29
+ version_requirement:
30
+ version_requirements: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 1.1.0
35
+ - - <
36
+ - !ruby/object:Gem::Version
37
+ version: 1.99.0
38
+ version:
39
+ - !ruby/object:Gem::Dependency
40
+ name: highline
41
+ version_requirement:
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ version:
48
+ description:
49
+ email: jamis@37signals.com
50
+ executables:
51
+ - cap
52
+ - capify
53
+ extensions: []
54
+
55
+ extra_rdoc_files: []
56
+
31
57
  files:
32
58
  - bin/cap
33
59
  - bin/capify
@@ -91,6 +117,7 @@ files:
91
117
  - lib/capistrano/recipes/templates
92
118
  - lib/capistrano/recipes/templates/maintenance.rhtml
93
119
  - lib/capistrano/recipes/upgrade.rb
120
+ - lib/capistrano/role.rb
94
121
  - lib/capistrano/server_definition.rb
95
122
  - lib/capistrano/shell.rb
96
123
  - lib/capistrano/ssh.rb
@@ -125,6 +152,7 @@ files:
125
152
  - test/deploy/scm/accurev_test.rb
126
153
  - test/deploy/scm/base_test.rb
127
154
  - test/deploy/scm/git_test.rb
155
+ - test/deploy/scm/mercurial_test.rb
128
156
  - test/deploy/strategy
129
157
  - test/deploy/strategy/copy_test.rb
130
158
  - test/extensions_test.rb
@@ -144,44 +172,31 @@ files:
144
172
  - README
145
173
  - MIT-LICENSE
146
174
  - CHANGELOG
147
- test_files: []
148
-
175
+ has_rdoc: true
176
+ homepage: http://www.capify.org
177
+ post_install_message:
149
178
  rdoc_options: []
150
179
 
151
- extra_rdoc_files: []
152
-
153
- executables:
154
- - cap
155
- - capify
156
- extensions: []
157
-
180
+ require_paths:
181
+ - lib
182
+ required_ruby_version: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: "0"
187
+ version:
188
+ required_rubygems_version: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: "0"
193
+ version:
158
194
  requirements: []
159
195
 
160
- dependencies:
161
- - !ruby/object:Gem::Dependency
162
- name: net-ssh
163
- version_requirement:
164
- version_requirements: !ruby/object:Gem::Version::Requirement
165
- requirements:
166
- - - ">="
167
- - !ruby/object:Gem::Version
168
- version: 1.0.10
169
- version:
170
- - !ruby/object:Gem::Dependency
171
- name: net-sftp
172
- version_requirement:
173
- version_requirements: !ruby/object:Gem::Version::Requirement
174
- requirements:
175
- - - ">="
176
- - !ruby/object:Gem::Version
177
- version: 1.1.0
178
- version:
179
- - !ruby/object:Gem::Dependency
180
- name: highline
181
- version_requirement:
182
- version_requirements: !ruby/object:Gem::Version::Requirement
183
- requirements:
184
- - - ">"
185
- - !ruby/object:Gem::Version
186
- version: 0.0.0
187
- version:
196
+ rubyforge_project: capistrano
197
+ rubygems_version: 1.0.0
198
+ signing_key:
199
+ specification_version: 2
200
+ summary: Capistrano is a utility and framework for executing commands in parallel on multiple remote machines, via SSH.
201
+ test_files: []
202
+