capistrano 2.0.0 → 2.1.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.
@@ -114,6 +114,18 @@ module Capistrano
114
114
  raise NotImplementedError, "`query_revision' is not implemented by #{self.class.name}"
115
115
  end
116
116
 
117
+ # Returns the revision number immediately following revision, if at
118
+ # all possible. A block should always be passed to this method, which
119
+ # accepts a command to invoke and returns the result, although a
120
+ # particular SCM's implementation is not required to invoke the block.
121
+ #
122
+ # By default, this method simply returns the revision itself. If a
123
+ # particular SCM is able to determine a subsequent revision given a
124
+ # revision identifier, it should override this method.
125
+ def next_revision(revision)
126
+ revision
127
+ end
128
+
117
129
  # Should analyze the given text and determine whether or not a
118
130
  # response is expected, and if so, return the appropriate response.
119
131
  # If no response is expected, return nil. The +state+ parameter is a
@@ -0,0 +1,191 @@
1
+ require 'capistrano/recipes/deploy/scm/base'
2
+
3
+ module Capistrano
4
+ module Deploy
5
+ module SCM
6
+
7
+ # An SCM module for using Git as your source control tool with Capistrano
8
+ # 2.0. If you are using Capistrano 1.x, use this plugin instead:
9
+ #
10
+ # http://scie.nti.st/2007/3/16/capistrano-with-git-shared-repository
11
+ #
12
+ # Assumes you are using a shared Git repository.
13
+ #
14
+ # Parts of this plugin borrowed from Scott Chacon's version, which I
15
+ # found on the Capistrano mailing list but failed to be able to get
16
+ # working.
17
+ #
18
+ # FEATURES:
19
+ #
20
+ # * Very simple, only requiring 2 lines in your deploy.rb.
21
+ # * Can deploy different branches, tags, or any SHA1 easily.
22
+ # * Supports prompting for password / passphrase upon checkout.
23
+ # (I am amazed at how some plugins don't do this)
24
+ # * Supports :scm_command, :scm_password, :scm_passphrase Capistrano
25
+ # directives.
26
+ #
27
+ # REQUIREMENTS
28
+ # ------------
29
+ #
30
+ # Git is required to be installed on your remote machine(s), because a
31
+ # clone and checkout is done to get the code up there. This is the way
32
+ # I prefer to deploy; there is no alternative to this, so :deploy_via
33
+ # is ignored.
34
+ #
35
+ # CONFIGURATION
36
+ # -------------
37
+ #
38
+ # Use this plugin by adding the following line in your config/deploy.rb:
39
+ #
40
+ # set :scm, :git
41
+ #
42
+ # Set <tt>:repository</tt> to the path of your Git repo:
43
+ #
44
+ # set :repository, "someuser@somehost:/home/myproject"
45
+ #
46
+ # The above two options are required to be set, the ones below are
47
+ # optional.
48
+ #
49
+ # You may set <tt>:branch</tt>, which is the reference to the branch, tag,
50
+ # or any SHA1 you are deploying, for example:
51
+ #
52
+ # set :branch, "origin/master"
53
+ #
54
+ # Otherwise, HEAD is assumed. I strongly suggest you set this. HEAD is
55
+ # not always the best assumption.
56
+ #
57
+ # The <tt>:scm_command</tt> configuration variable, if specified, will
58
+ # be used as the full path to the git executable on the *remote* machine:
59
+ #
60
+ # set :scm_command, "/opt/local/bin/git"
61
+ #
62
+ # For compatibility with deploy scripts that may have used the 1.x
63
+ # version of this plugin before upgrading, <tt>:git</tt> is still
64
+ # recognized as an alias for :scm_command.
65
+ #
66
+ # Set <tt>:scm_password</tt> to the password needed to clone your repo
67
+ # if you don't have password-less (public key) entry:
68
+ #
69
+ # set :scm_password, "my_secret'
70
+ #
71
+ # Otherwise, you will be prompted for a password.
72
+ #
73
+ # <tt>:scm_passphrase</tt> is also supported.
74
+ #
75
+ # The remote cache strategy is also supported.
76
+ #
77
+ # set :repository_cache, "git_master"
78
+ # set :deploy_via, :remote_cache
79
+ #
80
+ # For faster clone, you can also use shallow cloning. This will set the
81
+ # '--depth' flag using the depth specified. This *cannot* be used
82
+ # together with the :remote_cache strategy
83
+ #
84
+ # set :git_shallow_clone, 1
85
+ #
86
+ # AUTHORS
87
+ # -------
88
+ #
89
+ # Garry Dolley http://scie.nti.st
90
+ # Contributions by Geoffrey Grosenbach http://topfunky.com
91
+ # and Scott Chacon http://jointheconversation.org
92
+
93
+ class Git < Base
94
+ # Sets the default command name for this SCM on your *local* machine.
95
+ # Users may override this by setting the :scm_command variable.
96
+ default_command "git"
97
+
98
+ # When referencing "head", use the branch we want to deploy or, by
99
+ # default, Git's reference of HEAD (the latest changeset in the default
100
+ # branch, usually called "master").
101
+ def head
102
+ configuration[:branch] || 'HEAD'
103
+ end
104
+
105
+ # Performs a clone on the remote machine, then checkout on the branch
106
+ # you want to deploy.
107
+ 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
113
+
114
+ if depth = configuration[:git_shallow_clone]
115
+ execute = "#{git} clone --depth #{depth} #{configuration[:repository]} #{destination} && "
116
+ else
117
+ execute = "#{git} clone #{configuration[:repository]} #{destination} && "
118
+ end
119
+
120
+ execute += "cd #{destination} && #{git} checkout -b deploy #{branch}"
121
+
122
+ execute
123
+ end
124
+
125
+ # Merges the changes to 'head' since the last fetch, for remote_cache
126
+ # deployment strategy
127
+ def sync(revision, destination)
128
+ execute = "cd #{destination} && git fetch origin && "
129
+
130
+ if head == 'HEAD'
131
+ execute += "git merge origin/HEAD"
132
+ else
133
+ execute += "git merge #{head}"
134
+ end
135
+
136
+ execute
137
+ end
138
+
139
+ # Returns a string of diffs between two revisions
140
+ def diff(from, to=nil)
141
+ from << "..#{to}" if to
142
+ scm :diff, from
143
+ end
144
+
145
+ # Returns a log of changes between the two revisions (inclusive).
146
+ def log(from, to=nil)
147
+ from << "..#{to}" if to
148
+ scm :log, from
149
+ end
150
+
151
+ # Getting the actual commit id, in case we were passed a tag
152
+ # or partial sha or something - it will return the sha if you pass a sha, too
153
+ def query_revision(revision)
154
+ yield(scm('rev-parse', revision)).chomp
155
+ end
156
+
157
+ def command
158
+ # For backwards compatibility with 1.x version of this module
159
+ configuration[:git] || super
160
+ end
161
+
162
+ # Determines what the response should be for a particular bit of text
163
+ # from the SCM. Password prompts, connection requests, passphrases,
164
+ # etc. are handled here.
165
+ def handle_data(state, stream, text)
166
+ logger.info "[#{stream}] #{text}"
167
+ case text
168
+ when /\bpassword.*:/i
169
+ # git is prompting for a password
170
+ unless pass = configuration[:scm_password]
171
+ pass = Capistrano::CLI.password_prompt
172
+ end
173
+ "#{pass}\n"
174
+ when %r{\(yes/no\)}
175
+ # git is asking whether or not to connect
176
+ "yes\n"
177
+ when /passphrase/i
178
+ # git is asking for the passphrase for the user's key
179
+ unless pass = configuration[:scm_passphrase]
180
+ pass = Capistrano::CLI.password_prompt
181
+ end
182
+ "#{pass}\n"
183
+ when /accept \(t\)emporarily/
184
+ # git is asking whether to accept the certificate
185
+ "t\n"
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
@@ -52,8 +52,16 @@ module Capistrano
52
52
  # executed (svn info), and will extract the revision from the response.
53
53
  def query_revision(revision)
54
54
  return revision if revision =~ /^\d+$/
55
- result = yield(scm(:info, repository, authentication, "-r#{revision}"))
56
- YAML.load(result)['Revision']
55
+ command = scm(:info, repository, authentication, "-r#{revision}")
56
+ result = yield(command)
57
+ yaml = YAML.load(result)
58
+ raise "tried to run `#{command}' and got unexpected result #{result.inspect}" unless Hash === yaml
59
+ yaml['Last Changed Rev'] || yaml['Revision']
60
+ end
61
+
62
+ # Increments the given revision number and returns it.
63
+ def next_revision(revision)
64
+ revision.to_i + 1
57
65
  end
58
66
 
59
67
  # Determines what the response should be for a particular bit of text
@@ -81,14 +89,17 @@ module Capistrano
81
89
 
82
90
  private
83
91
 
84
- # If a username or password is configured for the SCM, return the
85
- # command-line switches for those values.
92
+ # If a username is configured for the SCM, return the command-line
93
+ # switches for that. Note that we don't need to return the password
94
+ # switch, since Capistrano will check for that prompt in the output
95
+ # and will respond appropriately.
86
96
  def authentication
87
- auth = ""
88
- auth << "--username #{variable(:scm_username)} " if variable(:scm_username)
89
- auth << "--password #{variable(:scm_password)} " if variable(:scm_password)
90
- auth << "--no-auth-cache" if !auth.empty?
91
- auth
97
+ username = variable(:scm_username)
98
+ return "" unless username
99
+ result = "--username #{variable(:scm_username)} "
100
+ result << "--password #{variable(:scm_password)} " unless variable(:scm_prefer_prompt)
101
+ result << "--no-auth-cache " unless variable(:scm_auth_cache)
102
+ result
92
103
  end
93
104
 
94
105
  # If verbose output is requested, return nil, otherwise return the
@@ -32,7 +32,8 @@ module Capistrano
32
32
  logger.trace "compressing #{destination} to #{filename}"
33
33
  Dir.chdir(tmpdir) { system(compress(File.basename(destination), File.basename(filename)).join(" ")) }
34
34
 
35
- put File.read(filename), remote_filename
35
+ content = File.open(filename, "rb") { |f| f.read }
36
+ put content, remote_filename
36
37
  run "cd #{configuration[:releases_path]} && #{decompress(remote_filename).join(" ")} && rm #{remote_filename}"
37
38
  ensure
38
39
  FileUtils.rm filename rescue nil
@@ -10,7 +10,7 @@ namespace :upgrade do
10
10
  tag file used in Capistrano 2.x. It is non-destructive and may be safely \
11
11
  run any number of times.
12
12
  DESC
13
- task :revisions do
13
+ task :revisions, :except => { :no_release => true } do
14
14
  revisions = capture("cat #{deploy_to}/revisions.log")
15
15
 
16
16
  mapping = {}
@@ -166,24 +166,12 @@ HELP
166
166
 
167
167
  # Execute a command on the given list of servers.
168
168
  def exec_command(command, servers)
169
- processor = Proc.new do |ch, stream, out|
170
- # TODO: more robust prompt detection
171
- out.each do |line|
172
- if stream == :out
173
- if out =~ /Password:\s*/i
174
- ch.send_data "#{configuration[:password]}\n"
175
- else
176
- puts "[#{ch[:server]}] #{line.chomp}"
177
- end
178
- elsif stream == :err
179
- puts "[#{ch[:server]} ERR] #{line.chomp}"
180
- end
181
- end
182
- end
183
-
184
- previous = trap("INT") { cmd.stop! }
169
+ command = command.gsub(/\bsudo\b/, "sudo -p '#{configuration.sudo_prompt}'")
170
+ processor = configuration.sudo_behavior_callback(Configuration.default_io_proc)
185
171
  sessions = servers.map { |server| configuration.sessions[server] }
186
- Command.process(command, sessions, :logger => configuration.logger, &Capistrano::Configuration.default_io_proc)
172
+ cmd = Command.new(command, sessions, :logger => configuration.logger, &processor)
173
+ previous = trap("INT") { cmd.stop! }
174
+ cmd.process!
187
175
  rescue Capistrano::Error => error
188
176
  warn "error: #{error.message}"
189
177
  ensure
@@ -101,7 +101,7 @@ module Capistrano
101
101
  sftp.channel[:failed] = false
102
102
 
103
103
  real_filename = filename.gsub(/\$CAPISTRANO:HOST\$/, server.host)
104
- sftp.open(real_filename, IO::WRONLY | IO::CREAT | IO::TRUNC, options[:mode] || 0660) do |status, handle|
104
+ sftp.open(real_filename, IO::WRONLY | IO::CREAT | IO::TRUNC, options[:mode] || 0664) do |status, handle|
105
105
  break unless check_status(sftp, "open #{real_filename}", server, status)
106
106
 
107
107
  logger.info "uploading data to #{server}:#{real_filename}" if logger
@@ -11,7 +11,7 @@ module Capistrano
11
11
  end
12
12
 
13
13
  MAJOR = 2
14
- MINOR = 0
14
+ MINOR = 1
15
15
  TINY = 0
16
16
 
17
17
  STRING = [MAJOR, MINOR, TINY].join(".")
@@ -19,43 +19,52 @@ class CommandTest < Test::Unit::TestCase
19
19
  assert_equal "ls\\\necho", cmd.command
20
20
  end
21
21
 
22
- def test_command_with_env_key_should_have_environment_constructed_and_prepended
22
+ def test_command_with_pty_should_request_pty_and_register_success_callback
23
23
  session = setup_for_extracting_channel_action(:on_success) do |ch|
24
+ ch.expects(:request_pty).with(:want_reply => true)
25
+ ch.expects(:exec).with(%(sh -c "ls"))
26
+ end
27
+ Capistrano::Command.new("ls", [session], :pty => true)
28
+ end
29
+
30
+ def test_command_with_env_key_should_have_environment_constructed_and_prepended
31
+ session = setup_for_extracting_channel_action do |ch|
32
+ ch.expects(:request_pty).never
24
33
  ch.expects(:exec).with(%(env FOO=bar sh -c "ls"))
25
34
  end
26
35
  Capistrano::Command.new("ls", [session], :env => { "FOO" => "bar" })
27
36
  end
28
37
 
29
38
  def test_env_with_symbolic_key_should_be_accepted_as_a_string
30
- session = setup_for_extracting_channel_action(:on_success) do |ch|
39
+ session = setup_for_extracting_channel_action do |ch|
31
40
  ch.expects(:exec).with(%(env FOO=bar sh -c "ls"))
32
41
  end
33
42
  Capistrano::Command.new("ls", [session], :env => { :FOO => "bar" })
34
43
  end
35
44
 
36
45
  def test_env_as_string_should_be_substituted_in_directly
37
- session = setup_for_extracting_channel_action(:on_success) do |ch|
46
+ session = setup_for_extracting_channel_action do |ch|
38
47
  ch.expects(:exec).with(%(env HOWDY=there sh -c "ls"))
39
48
  end
40
49
  Capistrano::Command.new("ls", [session], :env => "HOWDY=there")
41
50
  end
42
51
 
43
52
  def test_env_with_symbolic_value_should_be_accepted_as_string
44
- session = setup_for_extracting_channel_action(:on_success) do |ch|
53
+ session = setup_for_extracting_channel_action do |ch|
45
54
  ch.expects(:exec).with(%(env FOO=bar sh -c "ls"))
46
55
  end
47
56
  Capistrano::Command.new("ls", [session], :env => { "FOO" => :bar })
48
57
  end
49
58
 
50
59
  def test_env_value_should_be_escaped
51
- session = setup_for_extracting_channel_action(:on_success) do |ch|
60
+ session = setup_for_extracting_channel_action do |ch|
52
61
  ch.expects(:exec).with(%(env FOO=(\\ \\\"bar\\\"\\ ) sh -c "ls"))
53
62
  end
54
63
  Capistrano::Command.new("ls", [session], :env => { "FOO" => '( "bar" )' })
55
64
  end
56
65
 
57
66
  def test_env_with_multiple_keys_should_chain_the_entries_together
58
- session = setup_for_extracting_channel_action(:on_success) do |ch|
67
+ session = setup_for_extracting_channel_action do |ch|
59
68
  ch.expects(:exec).with do |command|
60
69
  command =~ /^env / &&
61
70
  command =~ /\ba=b\b/ &&
@@ -73,6 +82,7 @@ class CommandTest < Test::Unit::TestCase
73
82
 
74
83
  session.expects(:open_channel).yields(channel)
75
84
  channel.expects(:[]=).with(:host, "capistrano")
85
+ channel.stubs(:[]).with(:host).returns("capistrano")
76
86
 
77
87
  Capistrano::Command.new("ls", [session])
78
88
  end
@@ -83,47 +93,45 @@ class CommandTest < Test::Unit::TestCase
83
93
 
84
94
  session.expects(:open_channel).yields(channel)
85
95
  channel.expects(:[]=).with(:options, {:data => "here we go"})
96
+ channel.stubs(:[]).with(:host).returns("capistrano")
86
97
 
87
98
  Capistrano::Command.new("ls", [session], :data => "here we go")
88
99
  end
89
100
 
90
- def test_open_channel_should_request_pty
91
- session = mock(:xserver => server("capistrano"))
92
- channel = stub_everything
93
-
94
- session.expects(:open_channel).yields(channel)
95
- channel.expects(:request_pty).with(:want_reply => true)
96
-
97
- Capistrano::Command.new("ls", [session])
98
- end
99
-
100
101
  def test_successful_channel_should_send_command
101
- session = setup_for_extracting_channel_action(:on_success) do |ch|
102
+ session = setup_for_extracting_channel_action do |ch|
102
103
  ch.expects(:exec).with(%(sh -c "ls"))
103
104
  end
104
105
  Capistrano::Command.new("ls", [session])
105
106
  end
106
107
 
107
108
  def test_successful_channel_with_shell_option_should_send_command_via_specified_shell
108
- session = setup_for_extracting_channel_action(:on_success) do |ch|
109
+ session = setup_for_extracting_channel_action do |ch|
109
110
  ch.expects(:exec).with(%(/bin/bash -c "ls"))
110
111
  end
111
112
  Capistrano::Command.new("ls", [session], :shell => "/bin/bash")
112
113
  end
113
114
 
115
+ def test_successful_channel_with_shell_false_should_send_command_without_shell
116
+ session = setup_for_extracting_channel_action do |ch|
117
+ ch.expects(:exec).with(%(echo `hostname`))
118
+ end
119
+ Capistrano::Command.new("echo `hostname`", [session], :shell => false)
120
+ end
121
+
114
122
  def test_successful_channel_should_send_data_if_data_key_is_present
115
- session = setup_for_extracting_channel_action(:on_success) do |ch|
123
+ session = setup_for_extracting_channel_action do |ch|
116
124
  ch.expects(:exec).with(%(sh -c "ls"))
117
125
  ch.expects(:send_data).with("here we go")
118
126
  end
119
127
  Capistrano::Command.new("ls", [session], :data => "here we go")
120
128
  end
121
129
 
122
- def test_unsuccessful_channel_should_close_channel
130
+ def test_unsuccessful_pty_request_should_close_channel
123
131
  session = setup_for_extracting_channel_action(:on_failure) do |ch|
124
132
  ch.expects(:close)
125
133
  end
126
- Capistrano::Command.new("ls", [session])
134
+ Capistrano::Command.new("ls", [session], :pty => true)
127
135
  end
128
136
 
129
137
  def test_on_data_should_invoke_callback_as_stdout
@@ -258,14 +266,14 @@ class CommandTest < Test::Unit::TestCase
258
266
  end
259
267
 
260
268
  def test_process_with_host_placeholder_should_substitute_placeholder_with_each_host
261
- session = setup_for_extracting_channel_action(:on_success) do |ch|
269
+ session = setup_for_extracting_channel_action do |ch|
262
270
  ch.expects(:exec).with(%(sh -c "echo capistrano"))
263
271
  end
264
272
  Capistrano::Command.new("echo $CAPISTRANO:HOST$", [session])
265
273
  end
266
274
 
267
275
  def test_process_with_unknown_placeholder_should_not_replace_placeholder
268
- session = setup_for_extracting_channel_action(:on_success) do |ch|
276
+ session = setup_for_extracting_channel_action do |ch|
269
277
  ch.expects(:exec).with(%(sh -c "echo \\$CAPISTRANO:OTHER\\$"))
270
278
  end
271
279
  Capistrano::Command.new("echo $CAPISTRANO:OTHER$", [session])
@@ -283,19 +291,18 @@ class CommandTest < Test::Unit::TestCase
283
291
  ch
284
292
  end
285
293
 
286
- def setup_for_extracting_channel_action(action, *args)
294
+ def setup_for_extracting_channel_action(action=nil, *args)
287
295
  s = server("capistrano")
288
296
  session = mock("session", :xserver => s)
289
297
 
290
298
  channel = stub_everything
291
299
  session.expects(:open_channel).yields(channel)
292
300
 
293
- ch = mock
294
- ch.stubs(:[]).with(:server).returns(s)
295
- ch.stubs(:[]).with(:host).returns(s.host)
296
- channel.expects(action).yields(ch, *args)
301
+ channel.stubs(:[]).with(:server).returns(s)
302
+ channel.stubs(:[]).with(:host).returns(s.host)
303
+ channel.expects(action).yields(channel, *args) if action
297
304
 
298
- yield ch if block_given?
305
+ yield channel if block_given?
299
306
 
300
307
  session
301
308
  end