capistrano 3.7.0 → 3.8.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/CHANGELOG.md +61 -2
  4. data/Dangerfile +1 -54
  5. data/README.md +1 -1
  6. data/UPGRADING-3.7.md +1 -12
  7. data/capistrano.gemspec +0 -1
  8. data/features/deploy.feature +0 -1
  9. data/features/step_definitions/assertions.rb +4 -2
  10. data/features/step_definitions/cap_commands.rb +4 -0
  11. data/features/subdirectory.feature +9 -0
  12. data/lib/Capfile +0 -4
  13. data/lib/capistrano/application.rb +10 -2
  14. data/lib/capistrano/configuration/question.rb +5 -1
  15. data/lib/capistrano/configuration/scm_resolver.rb +5 -1
  16. data/lib/capistrano/configuration/server.rb +1 -0
  17. data/lib/capistrano/configuration/servers.rb +16 -8
  18. data/lib/capistrano/configuration/variables.rb +2 -2
  19. data/lib/capistrano/doctor/variables_doctor.rb +1 -1
  20. data/lib/capistrano/dsl/paths.rb +1 -14
  21. data/lib/capistrano/dsl.rb +2 -0
  22. data/lib/capistrano/i18n.rb +2 -1
  23. data/lib/capistrano/scm/git.rb +25 -4
  24. data/lib/capistrano/scm/hg.rb +8 -1
  25. data/lib/capistrano/scm/svn.rb +9 -0
  26. data/lib/capistrano/scm/tasks/git.rake +2 -2
  27. data/lib/capistrano/scm/tasks/hg.rake +1 -1
  28. data/lib/capistrano/version.rb +1 -1
  29. data/spec/lib/capistrano/configuration/question_spec.rb +11 -4
  30. data/spec/lib/capistrano/configuration/scm_resolver_spec.rb +54 -0
  31. data/spec/lib/capistrano/configuration/servers_spec.rb +2 -1
  32. data/spec/lib/capistrano/doctor/variables_doctor_spec.rb +8 -0
  33. data/spec/lib/capistrano/scm/git_spec.rb +32 -1
  34. data/spec/lib/capistrano/scm/hg_spec.rb +6 -1
  35. data/spec/lib/capistrano/scm/svn_spec.rb +21 -0
  36. data/spec/support/test_app.rb +7 -5
  37. metadata +7 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ce22b5f5826e88b5fd92b3369c02344f20e2191e
4
- data.tar.gz: 491397aab719879777bec4153c3125575f86e62a
3
+ metadata.gz: 0bf003b9169b22ddd324533323c2ccd5a3873aec
4
+ data.tar.gz: 5fd9c7d79004cbc67c515903f280b74f80bd3c87
5
5
  SHA512:
6
- metadata.gz: 37864d78dd2d14478f0e9edcb316ac5e68fe5dc83b02bfd4017b1790350b26d1c929f6b94890fe351101fd05257f4bf8ce427fb8f5be0d1758e36e48fcc70fd5
7
- data.tar.gz: 6eec3f49c1e4867137d3d266d533c44bcba2bc076d1e8d003e51bcc8735a14f613ef8c49a86f69417cd40deca95b2827115885289113e490ea8bf0940639c50b
6
+ metadata.gz: a89eca74965d39609a504a1032356e66a762847d69ff6a45951585a3e1002f3575ab0a9837dc545fe7536f8b38297f83ad93e894ac454bda219e07b847c9797c
7
+ data.tar.gz: 2b0fa5af27a608a391476c31d57640e875b97408e503b279bca9fd0bf05b83aa54ab0dad693f51f29ede1261ed77ba76206d55189810f6d27ee3a62c1792228f
data/.rubocop.yml CHANGED
@@ -3,6 +3,10 @@ AllCops:
3
3
  DisplayStyleGuide: true
4
4
  TargetRubyVersion: 2.0
5
5
 
6
+ Metrics/BlockLength:
7
+ Exclude:
8
+ - "spec/**/*"
9
+ - "lib/**/*.rake"
6
10
  Style/BarePercentLiterals:
7
11
  EnforcedStyle: percent_q
8
12
  Style/ClassAndModuleChildren:
data/CHANGELOG.md CHANGED
@@ -1,13 +1,72 @@
1
1
  # Capistrano 3.x Changelog
2
2
 
3
- Reverse Chronological Order:
3
+ All notable changes to this project will be documented in this file, in reverse chronological order. This project adheres to [Semantic Versioning](http://semver.org).
4
+
5
+ **Capistrano uses a six-week release cadence.** Every six weeks, give or take, any changes in master will be published as a new rubygems version. If you'd like to use a feature or fix that is in master and you can't wait for the next planned release, put this in your project's Gemfile to use the master branch directly:
6
+
7
+ ```ruby
8
+ gem "capistrano", :github => "capistrano/capistrano"
9
+ ```
4
10
 
5
11
  ## master
6
12
 
7
- https://github.com/capistrano/capistrano/compare/v3.7.0...HEAD
13
+ https://github.com/capistrano/capistrano/compare/v3.7.2...HEAD
8
14
 
9
15
  * Your contribution here!
10
16
 
17
+ ## `3.8.0` (2017-03-10)
18
+
19
+ https://github.com/capistrano/capistrano/compare/v3.7.2...v3.8.0
20
+
21
+ ### Potentially breaking changes:
22
+
23
+ * [#1846](https://github.com/capistrano/capistrano/pull/1846): add_host will add a new host in a case where it used to incorrectly update an existing one (potentially breaking) [(@dbenamy)](https://github.com/dbenamy)
24
+
25
+ ### New features:
26
+
27
+ * [#1860](https://github.com/capistrano/capistrano/pull/1860): Allow cap to be run within subdir and still work - [@mattbrictson](https://github.com/mattbrictson)
28
+
29
+ ### Fixes:
30
+
31
+ * [#1835](https://github.com/capistrano/capistrano/pull/1835): Stopped printing parenthesis in ask prompt if no default or nil was passed as argument [(@chamini2)](https://github.com/chamini2)
32
+ * [#1840](https://github.com/capistrano/capistrano/pull/1840): Git plugin: shellescape git_wrapper_path [(@olleolleolle)](https://github.com/olleolleolle)
33
+ * [#1843](https://github.com/capistrano/capistrano/pull/1843): Properly shell escape git:wrapper steps - [@mattbrictson](https://github.com/mattbrictson)
34
+ * [#1846](https://github.com/capistrano/capistrano/pull/1846): Defining a role is now O(hosts) instead of O(hosts^2) [(@dbenamy)](https://github.com/dbenamy)
35
+ * Run `svn switch` to work with svn branches if repo_url is changed
36
+ * [#1856](https://github.com/capistrano/capistrano/pull/1856): Fix hg repo_tree implementation - [@mattbrictson](https://github.com/mattbrictson)
37
+ * [#1857](https://github.com/capistrano/capistrano/pull/1857): Don't emit doctor warning when repo_tree is set - [@mattbrictson](https://github.com/mattbrictson)
38
+
39
+ ### Other changes:
40
+
41
+ * [capistrano-harrow#4](https://github.com/harrowio/capistrano-harrow/issues/4): Drop dependency on `capistrano-harrow` gem. Gem can still be installed separately [(@leehambley)](https://github.com/leehambley)
42
+ * [#1859](https://github.com/capistrano/capistrano/pull/1859): Move git-specific repo_url logic into git plugin - [@mattbrictson](https://github.com/mattbrictson)
43
+ * [#1858](https://github.com/capistrano/capistrano/pull/1858): Unset the :scm variable when an SCM plugin is used - [@mattbrictson](https://github.com/mattbrictson)
44
+
45
+ ## `3.7.2` (2017-01-27)
46
+
47
+ https://github.com/capistrano/capistrano/compare/v3.7.1...v3.7.2
48
+
49
+ ### Potentially breaking changes:
50
+
51
+ * None
52
+
53
+ ### Other changes:
54
+
55
+ * Suppress log messages of `git ls-remote` by filtering remote refs (@aeroastro)
56
+ * The Git SCM now allows the repo_url to be changed without manually wiping out the mirror on each target host first (@javanthropus)
57
+
58
+ ## `3.7.1` (2016-12-16)
59
+
60
+ https://github.com/capistrano/capistrano/compare/v3.7.0...v3.7.1
61
+
62
+ ### Potentially breaking changes:
63
+
64
+ * None
65
+
66
+ ### Fixes:
67
+
68
+ * Fixed a bug with mercurial deploys failing due to an undefined variable
69
+
11
70
  ## `3.7.0` (2016-12-10)
12
71
 
13
72
  https://github.com/capistrano/capistrano/compare/v3.6.1...v3.7.0
data/Dangerfile CHANGED
@@ -1,54 +1 @@
1
- # Adapted from https://github.com/ruby-grape/danger/blob/master/Dangerfile
2
- # Q: What is a Dangerfile, anyway? A: See http://danger.systems/
3
-
4
- # ------------------------------------------------------------------------------
5
- # Additional pull request data
6
- # ------------------------------------------------------------------------------
7
- project_name = github.pr_json["base"]["repo"]["name"]
8
- pr_number = github.pr_json["number"]
9
- pr_url = github.pr_json["_links"]["html"]["href"]
10
-
11
- # ------------------------------------------------------------------------------
12
- # What changed?
13
- # ------------------------------------------------------------------------------
14
- has_lib_changes = !git.modified_files.grep(/^lib/).empty?
15
- has_test_changes = !git.modified_files.grep(/^(features|spec)/).empty?
16
- has_changelog_changes = git.modified_files.include?("CHANGELOG.md")
17
-
18
- # ------------------------------------------------------------------------------
19
- # You've made changes to lib, but didn't write any tests?
20
- # ------------------------------------------------------------------------------
21
- if has_lib_changes && !has_test_changes
22
- warn("There are code changes, but no corresponding tests. "\
23
- "Please include tests if this PR introduces any modifications in "\
24
- "#{project_name}'s behavior.",
25
- :sticky => false)
26
- end
27
-
28
- # ------------------------------------------------------------------------------
29
- # Have you updated CHANGELOG.md?
30
- # ------------------------------------------------------------------------------
31
- if !has_changelog_changes && has_lib_changes
32
- markdown <<-MARKDOWN
33
- Here's an example of a CHANGELOG.md entry (place it immediately under the `* Your contribution here!` line):
34
-
35
- ```markdown
36
- * [##{pr_number}](#{pr_url}): #{github.pr_title} - [@#{github.pr_author}](https://github.com/#{github.pr_author}).
37
- ```
38
- MARKDOWN
39
- warn("Please update CHANGELOG.md with a description of your changes. "\
40
- "If this PR is not a user-facing change (e.g. just refactoring), "\
41
- "you can disregard this.", :sticky => false)
42
- end
43
-
44
- # ------------------------------------------------------------------------------
45
- # Did you remove the CHANGELOG's "Your contribution here!" line?
46
- # ------------------------------------------------------------------------------
47
- if has_changelog_changes
48
- unless IO.read("CHANGELOG.md") =~ /^\* Your contribution here/i
49
- fail(
50
- "Please put the `* Your contribution here!` line back into CHANGELOG.md.",
51
- :sticky => false
52
- )
53
- end
54
- end
1
+ danger.import_dangerfile(github: "capistrano/danger")
data/README.md CHANGED
@@ -103,7 +103,7 @@ Add Capistrano to your project's Gemfile:
103
103
 
104
104
  ``` ruby
105
105
  group :development do
106
- gem "capistrano", "~> 3.7"
106
+ gem "capistrano", "~> 3.8"
107
107
  end
108
108
  ```
109
109
 
data/UPGRADING-3.7.md CHANGED
@@ -1,16 +1,5 @@
1
1
  # Capistrano 3.7.0 upgrade guide
2
2
 
3
- Capistrano 3.7.0 has not yet been released. This guide serves as a preview of
4
- what is *planned* for 3.7.0, so that you can be prepared to update your
5
- Capistrano deployment if necessary once it becomes available.
6
-
7
- If you wish to try the new 3.7.0 behavior today, you can do so by using the
8
- `master` branch in your Gemfile:
9
-
10
- ```ruby
11
- gem "capistrano", :github => "capistrano/capistrano"
12
- ```
13
-
14
3
  ## The :scm variable is deprecated
15
4
 
16
5
  Up until now, Capistrano's SCM was configured using the `:scm` variable:
@@ -23,7 +12,7 @@ set :scm, :svn
23
12
  To avoid deprecation warnings:
24
13
 
25
14
  1. Remove `set :scm, ...` from your Capistrano configuration.
26
- 2. Add *one* of the following SCM declarations to your `Capfile`:
15
+ 2. Add *one* of the following SCM declarations to your `Capfile` after `require "capistrano/deploy"`:
27
16
 
28
17
  ```ruby
29
18
  # To use Git
data/capistrano.gemspec CHANGED
@@ -24,7 +24,6 @@ Gem::Specification.new do |gem|
24
24
  gem.add_dependency "i18n"
25
25
  gem.add_dependency "rake", ">= 10.0.0"
26
26
  gem.add_dependency "sshkit", ">= 1.9.0"
27
- gem.add_dependency "capistrano-harrow"
28
27
 
29
28
  gem.add_development_dependency "danger"
30
29
  gem.add_development_dependency "mocha"
@@ -7,7 +7,6 @@ Feature: Deploy
7
7
  Scenario: Creating the repo
8
8
  When I run cap "git:check"
9
9
  Then the task is successful
10
- And references in the remote repo are listed
11
10
  And git wrapper permissions are 0700
12
11
 
13
12
  Scenario: Creating the directory structure
@@ -1,10 +1,12 @@
1
+ require "shellwords"
2
+
1
3
  Then(/^references in the remote repo are listed$/) do
2
4
  expect(@output).to include("refs/heads/master")
3
5
  end
4
6
 
5
7
  Then(/^git wrapper permissions are 0700$/) do
6
- permissions_test = %Q([ $(stat -c "%a" #{TestApp.git_wrapper_path}) == "700" ])
7
- expect(vagrant_cli_command("ssh -c '#{permissions_test}'")).to be_success
8
+ permissions_test = %Q([ $(stat -c "%a" #{TestApp.git_wrapper_path.shellescape}) == "700" ])
9
+ expect(vagrant_cli_command("ssh -c #{permissions_test.shellescape}")).to be_success
8
10
  end
9
11
 
10
12
  Then(/^the shared path is created$/) do
@@ -2,6 +2,10 @@ When(/^I run cap "(.*?)"$/) do |task|
2
2
  @success, @output = TestApp.cap(task)
3
3
  end
4
4
 
5
+ When(/^I run cap "(.*?)" within the "(.*?)" directory$/) do |task, directory|
6
+ @success, @output = TestApp.cap(task, directory)
7
+ end
8
+
5
9
  When(/^I run cap "(.*?)" as part of a release$/) do |task|
6
10
  TestApp.cap("deploy:new_release_path #{task}")
7
11
  end
@@ -0,0 +1,9 @@
1
+ Feature: cap can be run from a subdirectory, and will still find the Capfile
2
+
3
+ Background:
4
+ Given a test app with the default configuration
5
+ And servers with the roles app and web
6
+
7
+ Scenario: Running cap from a subdirectory
8
+ When I run cap "git:check" within the "config" directory
9
+ Then the task is successful
data/lib/Capfile CHANGED
@@ -1,7 +1,3 @@
1
1
  #!/usr/bin/env cap
2
2
  include Capistrano::DSL
3
3
  require "capistrano/install"
4
-
5
- require "capistrano/harrow"
6
- require "capistrano/harrow/plugin"
7
- install_plugin Capistrano::Harrow::Plugin
@@ -2,7 +2,7 @@ module Capistrano
2
2
  class Application < Rake::Application
3
3
  def initialize
4
4
  super
5
- @rakefiles = %w{capfile Capfile capfile.rb Capfile.rb} << capfile
5
+ @rakefiles = %w{capfile Capfile capfile.rb Capfile.rb}
6
6
  end
7
7
 
8
8
  def name
@@ -76,6 +76,15 @@ module Capistrano
76
76
  end
77
77
  end
78
78
 
79
+ # allows the `cap install` task to load without a capfile
80
+ def find_rakefile_location
81
+ if (location = super).nil?
82
+ [capfile, Dir.pwd]
83
+ else
84
+ location
85
+ end
86
+ end
87
+
79
88
  private
80
89
 
81
90
  def backtrace_pattern
@@ -96,7 +105,6 @@ module Capistrano
96
105
  super
97
106
  end
98
107
 
99
- # allows the `cap install` task to load without a capfile
100
108
  def capfile
101
109
  File.expand_path(File.join(File.dirname(__FILE__), "..", "Capfile"))
102
110
  end
@@ -46,7 +46,11 @@ module Capistrano
46
46
  end
47
47
 
48
48
  def question
49
- I18n.t(:question, key: key, default_value: default, scope: :capistrano)
49
+ if default.nil?
50
+ I18n.t(:question, key: key, scope: :capistrano)
51
+ else
52
+ I18n.t(:question_default, key: key, default_value: default, scope: :capistrano)
53
+ end
50
54
  end
51
55
 
52
56
  def echo?
@@ -29,8 +29,12 @@ module Capistrano
29
29
  set(:scm, :git) if using_default_scm?
30
30
 
31
31
  print_deprecation_warnings_if_applicable
32
+
32
33
  # Note that `scm_plugin_installed?` comes from Capistrano::DSL
33
- return if scm_plugin_installed?
34
+ if scm_plugin_installed?
35
+ delete(:scm)
36
+ return
37
+ end
34
38
 
35
39
  if built_in_scm_name?
36
40
  load_built_in_scm
@@ -64,6 +64,7 @@ module Capistrano
64
64
  end
65
65
 
66
66
  def matches?(other)
67
+ # This matching logic must stay in sync with `Servers#add_host`.
67
68
  hostname == other.hostname && port == other.port
68
69
  end
69
70
 
@@ -9,22 +9,28 @@ module Capistrano
9
9
 
10
10
  def add_host(host, properties={})
11
11
  new_host = Server[host]
12
- if (server = servers.find { |s| s.matches? new_host })
13
- server.user = new_host.user if new_host.user
14
- server.with(properties)
12
+ new_host.port = properties[:port] if properties.key?(:port)
13
+ # This matching logic must stay in sync with `Server#matches?`.
14
+ key = ServerKey.new(new_host.hostname, new_host.port)
15
+ existing = servers_by_key[key]
16
+ if existing
17
+ existing.user = new_host.user if new_host.user
18
+ existing.with(properties)
15
19
  else
16
- servers << new_host.with(properties)
20
+ servers_by_key[key] = new_host.with(properties)
17
21
  end
18
22
  end
19
23
 
24
+ # rubocop:disable Security/MarshalLoad
20
25
  def add_role(role, hosts, options={})
21
26
  options_deepcopy = Marshal.dump(options.merge(roles: role))
22
27
  Array(hosts).each { |host| add_host(host, Marshal.load(options_deepcopy)) }
23
28
  end
29
+ # rubocop:enable Security/MarshalLoad
24
30
 
25
31
  def roles_for(names)
26
32
  options = extract_options(names)
27
- s = Filter.new(:role, names).filter(servers)
33
+ s = Filter.new(:role, names).filter(servers_by_key.values)
28
34
  s.select { |server| server.select?(options) }
29
35
  end
30
36
 
@@ -51,13 +57,15 @@ module Capistrano
51
57
  end
52
58
 
53
59
  def each
54
- servers.each { |server| yield server }
60
+ servers_by_key.values.each { |server| yield server }
55
61
  end
56
62
 
57
63
  private
58
64
 
59
- def servers
60
- @servers ||= []
65
+ ServerKey = Struct.new(:hostname, :port)
66
+
67
+ def servers_by_key
68
+ @servers_by_key ||= {}
61
69
  end
62
70
 
63
71
  def extract_options(array)
@@ -35,7 +35,7 @@ module Capistrano
35
35
  end
36
36
 
37
37
  def set(key, value=nil, &block)
38
- @trusted_keys << key if trusted?
38
+ @trusted_keys << key if trusted? && !@trusted_keys.include?(key)
39
39
  remember_location(key)
40
40
  values[key] = block || value
41
41
  trace_set(key)
@@ -43,7 +43,7 @@ module Capistrano
43
43
  end
44
44
 
45
45
  def fetch(key, default=nil, &block)
46
- fetched_keys << key
46
+ fetched_keys << key unless fetched_keys.include?(key)
47
47
  peek(key, default, &block)
48
48
  end
49
49
 
@@ -7,7 +7,7 @@ module Capistrano
7
7
  class VariablesDoctor
8
8
  # These are keys that have no default values in Capistrano, but are
9
9
  # nonetheless expected to be set.
10
- WHITELIST = [:application, :repo_url].freeze
10
+ WHITELIST = [:application, :repo_url, :repo_tree].freeze
11
11
  private_constant :WHITELIST
12
12
 
13
13
  include Capistrano::Doctor::OutputHelpers
@@ -36,20 +36,7 @@ module Capistrano
36
36
  end
37
37
 
38
38
  def repo_url
39
- require "cgi"
40
- require "uri"
41
- if fetch(:git_http_username) && fetch(:git_http_password)
42
- URI.parse(fetch(:repo_url)).tap do |repo_uri|
43
- repo_uri.user = fetch(:git_http_username)
44
- repo_uri.password = CGI.escape(fetch(:git_http_password))
45
- end.to_s
46
- elsif fetch(:git_http_username)
47
- URI.parse(fetch(:repo_url)).tap do |repo_uri|
48
- repo_uri.user = fetch(:git_http_username)
49
- end.to_s
50
- else
51
- fetch(:repo_url)
52
- end
39
+ fetch(:repo_url)
53
40
  end
54
41
 
55
42
  def repo_path
@@ -59,10 +59,12 @@ module Capistrano
59
59
  VersionValidator.new(locked_version).verify
60
60
  end
61
61
 
62
+ # rubocop:disable Security/MarshalLoad
62
63
  def on(hosts, options={}, &block)
63
64
  subset_copy = Marshal.dump(Configuration.env.filter(hosts))
64
65
  SSHKit::Coordinator.new(Marshal.load(subset_copy)).each(options, &block)
65
66
  end
67
+ # rubocop:enable Security/MarshalLoad
66
68
 
67
69
  def run_locally(&block)
68
70
  SSHKit::Backend::Local.new(&block).run
@@ -10,7 +10,8 @@ en = {
10
10
  finished: "Finished",
11
11
  stage_not_set: "Stage not set, please call something such as `cap production deploy`, where production is a stage you have defined.",
12
12
  written_file: "create %{file}",
13
- question: "Please enter %{key} (%{default_value}): ",
13
+ question: "Please enter %{key}: ",
14
+ question_default: "Please enter %{key} (%{default_value}): ",
14
15
  keeping_releases: "Keeping %{keep_releases} of %{releases} deployed releases on %{host}",
15
16
  skip_cleanup: "Skipping cleanup of old releases on %{host}; unexpected foldername found (should be timestamp)",
16
17
  no_old_releases: "No old releases (keeping newest %{keep_releases}) on %{host}",
@@ -1,4 +1,7 @@
1
1
  require "capistrano/scm/plugin"
2
+ require "cgi"
3
+ require "shellwords"
4
+ require "uri"
2
5
 
3
6
  class Capistrano::SCM::Git < Capistrano::SCM::Plugin
4
7
  def set_defaults
@@ -6,7 +9,7 @@ class Capistrano::SCM::Git < Capistrano::SCM::Plugin
6
9
  set_if_empty :git_wrapper_path, lambda {
7
10
  # Try to avoid permissions issues when multiple users deploy the same app
8
11
  # by using different file names in the same dir for each deployer and stage.
9
- suffix = [:application, :stage, :local_user].map { |key| fetch(key).to_s }.join("-").gsub(/\s+/, "-")
12
+ suffix = [:application, :stage, :local_user].map { |key| fetch(key).to_s }.join("-")
10
13
  "#{fetch(:tmp_dir)}/git-ssh-#{suffix}.sh"
11
14
  }
12
15
  set_if_empty :git_environmental_variables, lambda {
@@ -32,18 +35,21 @@ class Capistrano::SCM::Git < Capistrano::SCM::Plugin
32
35
  end
33
36
 
34
37
  def check_repo_is_reachable
35
- git :'ls-remote --heads', repo_url
38
+ git :'ls-remote', git_repo_url, "HEAD"
36
39
  end
37
40
 
38
41
  def clone_repo
39
42
  if (depth = fetch(:git_shallow_clone))
40
- git :clone, "--mirror", "--depth", depth, "--no-single-branch", repo_url, repo_path.to_s
43
+ git :clone, "--mirror", "--depth", depth, "--no-single-branch", git_repo_url, repo_path.to_s
41
44
  else
42
- git :clone, "--mirror", repo_url, repo_path.to_s
45
+ git :clone, "--mirror", git_repo_url, repo_path.to_s
43
46
  end
44
47
  end
45
48
 
46
49
  def update_mirror
50
+ # Update the origin URL if necessary.
51
+ git :remote, "set-url", "origin", git_repo_url
52
+
47
53
  # Note: Requires git version 1.9 or greater
48
54
  if (depth = fetch(:git_shallow_clone))
49
55
  git :fetch, "--depth", depth, "origin", fetch(:branch)
@@ -70,4 +76,19 @@ class Capistrano::SCM::Git < Capistrano::SCM::Plugin
70
76
  args.unshift :git
71
77
  backend.execute(*args)
72
78
  end
79
+
80
+ def git_repo_url
81
+ if fetch(:git_http_username) && fetch(:git_http_password)
82
+ URI.parse(repo_url).tap do |repo_uri|
83
+ repo_uri.user = fetch(:git_http_username)
84
+ repo_uri.password = CGI.escape(fetch(:git_http_password))
85
+ end.to_s
86
+ elsif fetch(:git_http_username)
87
+ URI.parse(repo_url).tap do |repo_uri|
88
+ repo_uri.user = fetch(:git_http_username)
89
+ end.to_s
90
+ else
91
+ repo_url
92
+ end
93
+ end
73
94
  end
@@ -1,4 +1,5 @@
1
1
  require "capistrano/scm/plugin"
2
+ require "securerandom"
2
3
 
3
4
  class Capistrano::SCM::Hg < Capistrano::SCM::Plugin
4
5
  def register_hooks
@@ -36,7 +37,13 @@ class Capistrano::SCM::Hg < Capistrano::SCM::Plugin
36
37
  if (tree = fetch(:repo_tree))
37
38
  tree = tree.slice %r#^/?(.*?)/?$#, 1
38
39
  components = tree.split("/").size
39
- hg "archive --type tgz -p . -I", tree, "--rev", fetch(:branch), "| tar -x --strip-components #{components} -f - -C", release_path
40
+ temp_tar = "#{fetch(:tmp_dir)}/#{SecureRandom.hex(10)}.tar"
41
+
42
+ hg "archive -p . -I", tree, "--rev", fetch(:branch), temp_tar
43
+
44
+ backend.execute :mkdir, "-p", release_path
45
+ backend.execute :tar, "-x --strip-components #{components} -f", temp_tar, "-C", release_path
46
+ backend.execute :rm, temp_tar
40
47
  else
41
48
  hg "archive", release_path, "--rev", fetch(:branch)
42
49
  end
@@ -34,6 +34,9 @@ class Capistrano::SCM::Svn < Capistrano::SCM::Plugin
34
34
  end
35
35
 
36
36
  def update_mirror
37
+ # Switch the repository URL if necessary.
38
+ repo_mirror_url = fetch_repo_mirror_url
39
+ svn :switch, repo_url unless repo_mirror_url == repo_url
37
40
  svn :update
38
41
  end
39
42
 
@@ -44,4 +47,10 @@ class Capistrano::SCM::Svn < Capistrano::SCM::Plugin
44
47
  def fetch_revision
45
48
  backend.capture(:svnversion, repo_path.to_s)
46
49
  end
50
+
51
+ def fetch_repo_mirror_url
52
+ backend.capture(:svn, :info, repo_path.to_s).each_line do |line|
53
+ return $1 if /\AURL: (.*)\n\z/ =~ line
54
+ end
55
+ end
47
56
  end
@@ -5,9 +5,9 @@ namespace :git do
5
5
  desc "Upload the git wrapper script, this script guarantees that we can script git without getting an interactive prompt"
6
6
  task :wrapper do
7
7
  on release_roles :all do
8
- execute :mkdir, "-p", File.dirname(fetch(:git_wrapper_path))
8
+ execute :mkdir, "-p", File.dirname(fetch(:git_wrapper_path)).shellescape
9
9
  upload! StringIO.new("#!/bin/sh -e\nexec /usr/bin/ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no \"$@\"\n"), fetch(:git_wrapper_path)
10
- execute :chmod, "700", fetch(:git_wrapper_path)
10
+ execute :chmod, "700", fetch(:git_wrapper_path).shellescape
11
11
  end
12
12
  end
13
13
 
@@ -28,7 +28,7 @@ namespace :hg do
28
28
  task update: :'hg:clone' do
29
29
  on release_roles :all do
30
30
  within repo_path do
31
- hr.update_mirror
31
+ hg_plugin.update_mirror
32
32
  end
33
33
  end
34
34
  end
@@ -1,3 +1,3 @@
1
1
  module Capistrano
2
- VERSION = "3.7.0".freeze
2
+ VERSION = "3.8.0".freeze
3
3
  end
@@ -5,6 +5,7 @@ module Capistrano
5
5
  describe Question do
6
6
  let(:question) { Question.new(key, default, options) }
7
7
  let(:question_without_echo) { Question.new(key, default, echo: false) }
8
+ let(:question_without_default) { Question.new(key, nil) }
8
9
  let(:default) { :default }
9
10
  let(:key) { :branch }
10
11
  let(:options) { nil }
@@ -19,11 +20,8 @@ module Capistrano
19
20
  context "value is entered" do
20
21
  let(:branch) { "branch" }
21
22
 
22
- before do
23
- $stdout.expects(:print).with("Please enter branch (default): ")
24
- end
25
-
26
23
  it "returns the echoed value" do
24
+ $stdout.expects(:print).with("Please enter branch (default): ")
27
25
  $stdin.expects(:gets).returns(branch)
28
26
  $stdin.expects(:noecho).never
29
27
 
@@ -31,11 +29,20 @@ module Capistrano
31
29
  end
32
30
 
33
31
  it "returns the value but does not echo it" do
32
+ $stdout.expects(:print).with("Please enter branch (default): ")
34
33
  $stdin.expects(:noecho).returns(branch)
35
34
  $stdout.expects(:print).with("\n")
36
35
 
37
36
  expect(question_without_echo.call).to eq(branch)
38
37
  end
38
+
39
+ it "returns the value but has no default between parenthesis" do
40
+ $stdout.expects(:print).with("Please enter branch: ")
41
+ $stdin.expects(:gets).returns(branch)
42
+ $stdin.expects(:noecho).never
43
+
44
+ expect(question_without_default.call).to eq(branch)
45
+ end
39
46
  end
40
47
 
41
48
  context "value is not entered" do
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ module Capistrano
4
+ class Configuration
5
+ describe SCMResolver do
6
+ include Capistrano::DSL
7
+
8
+ let(:resolver) { SCMResolver.new }
9
+
10
+ before do
11
+ Rake::Task.define_task("deploy:check")
12
+ Rake::Task.define_task("deploy:new_release_path")
13
+ Rake::Task.define_task("deploy:set_current_revision")
14
+ set :scm, SCMResolver::DEFAULT_GIT
15
+ end
16
+
17
+ after do
18
+ Rake::Task.clear
19
+ Capistrano::Configuration.reset!
20
+ end
21
+
22
+ context "default scm, no plugin installed" do
23
+ it "emits a warning" do
24
+ expect { resolver.resolve }.to output(/will not load the git scm/i).to_stderr
25
+ end
26
+
27
+ it "activates the git scm" do
28
+ resolver.resolve
29
+ expect(Rake::Task["git:wrapper"]).not_to be_nil
30
+ end
31
+
32
+ it "sets :scm to :git" do
33
+ resolver.resolve
34
+ expect(fetch(:scm)).to eq(:git)
35
+ end
36
+ end
37
+
38
+ context "default scm, git plugin installed" do
39
+ before do
40
+ install_plugin Capistrano::SCM::Git
41
+ end
42
+
43
+ it "emits no warning" do
44
+ expect { resolver.resolve }.not_to output.to_stderr
45
+ end
46
+
47
+ it "deletes :scm" do
48
+ resolver.resolve
49
+ expect(fetch(:scm)).to be_nil
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -130,13 +130,14 @@ module Capistrano
130
130
 
131
131
  it "can accept multiple servers with the same hostname but different ports or users" do
132
132
  servers.add_host("1", roles: [:app, "web"], test: :value, port: 12)
133
+ expect(servers.count).to eq(2)
133
134
  servers.add_host("1", roles: [:app, "web"], test: :value, port: 34)
134
135
  servers.add_host("1", roles: [:app, "web"], test: :value, user: "root")
135
136
  servers.add_host("1", roles: [:app, "web"], test: :value, user: "deployer")
136
137
  servers.add_host("1", roles: [:app, "web"], test: :value, user: "root", port: 34)
137
138
  servers.add_host("1", roles: [:app, "web"], test: :value, user: "deployer", port: 34)
138
139
  servers.add_host("1", roles: [:app, "web"], test: :value, user: "deployer", port: 56)
139
- expect(servers.count).to eq(5)
140
+ expect(servers.count).to eq(4)
140
141
  end
141
142
 
142
143
  describe "with a :user property" do
@@ -14,6 +14,7 @@ module Capistrano
14
14
 
15
15
  env.variables.untrusted! do
16
16
  set :application, "my_app"
17
+ set :repo_tree, "public"
17
18
  set :repo_url, ".git"
18
19
  set :copy_strategy, :scp
19
20
  set :custom_setting, "hello"
@@ -35,6 +36,7 @@ module Capistrano
35
36
  expect { doc.call }.to output(/:pty\s+false$/).to_stdout
36
37
  expect { doc.call }.to output(/:application\s+"my_app"$/).to_stdout
37
38
  expect { doc.call }.to output(/:repo_url\s+".git"$/).to_stdout
39
+ expect { doc.call }.to output(/:repo_tree\s+"public"$/).to_stdout
38
40
  expect { doc.call }.to output(/:copy_strategy\s+:scp$/).to_stdout
39
41
  expect { doc.call }.to output(/:custom_setting\s+"hello"$/).to_stdout
40
42
  expect { doc.call }.to output(/"string_setting"\s+"hello"$/).to_stdout
@@ -56,6 +58,12 @@ module Capistrano
56
58
  .to_stdout
57
59
  end
58
60
 
61
+ it "does not print warning for whitelisted variable" do
62
+ expect { doc.call }.not_to \
63
+ output(/:repo_tree is not a recognized Capistrano setting/)\
64
+ .to_stdout
65
+ end
66
+
59
67
  describe "Rake" do
60
68
  before do
61
69
  load File.expand_path("../../../../../lib/capistrano/doctor.rb",
@@ -27,6 +27,17 @@ module Capistrano
27
27
  Capistrano::Configuration.reset!
28
28
  end
29
29
 
30
+ describe "#set_defaults" do
31
+ it "makes git_wrapper_path using application, stage, and local_user" do
32
+ env.set(:tmp_dir, "/tmp")
33
+ env.set(:application, "my_app")
34
+ env.set(:stage, "staging")
35
+ env.set(:local_user, "(Git Web User) via ShipIt")
36
+ subject.set_defaults
37
+ expect(env.fetch(:git_wrapper_path)).to eq("/tmp/git-ssh-my_app-staging-(Git Web User) via ShipIt.sh")
38
+ end
39
+ end
40
+
30
41
  describe "#git" do
31
42
  it "should call execute git in the context, with arguments" do
32
43
  backend.expects(:execute).with(:git, :init)
@@ -46,7 +57,7 @@ module Capistrano
46
57
  describe "#check_repo_is_reachable" do
47
58
  it "should test the repo url" do
48
59
  env.set(:repo_url, "url")
49
- backend.expects(:execute).with(:git, :'ls-remote --heads', "url").returns(true)
60
+ backend.expects(:execute).with(:git, :'ls-remote', "url", "HEAD").returns(true)
50
61
 
51
62
  subject.check_repo_is_reachable
52
63
  end
@@ -70,10 +81,27 @@ module Capistrano
70
81
 
71
82
  subject.clone_repo
72
83
  end
84
+
85
+ context "with username and password specified" do
86
+ before do
87
+ env.set(:git_http_username, "hello")
88
+ env.set(:git_http_password, "topsecret")
89
+ env.set(:repo_url, "https://example.com/repo.git")
90
+ env.set(:repo_path, "path")
91
+ end
92
+
93
+ it "should include the credentials in the url" do
94
+ backend.expects(:execute).with(:git, :clone, "--mirror", "https://hello:topsecret@example.com/repo.git", "path")
95
+ subject.clone_repo
96
+ end
97
+ end
73
98
  end
74
99
 
75
100
  describe "#update_mirror" do
76
101
  it "should run git update" do
102
+ env.set(:repo_url, "url")
103
+
104
+ backend.expects(:execute).with(:git, :remote, "set-url", "origin", "url")
77
105
  backend.expects(:execute).with(:git, :remote, :update, "--prune")
78
106
 
79
107
  subject.update_mirror
@@ -82,6 +110,9 @@ module Capistrano
82
110
  it "should run git update in shallow mode" do
83
111
  env.set(:git_shallow_clone, "1")
84
112
  env.set(:branch, "branch")
113
+ env.set(:repo_url, "url")
114
+
115
+ backend.expects(:execute).with(:git, :remote, "set-url", "origin", "url")
85
116
  backend.expects(:execute).with(:git, :fetch, "--depth", "1", "origin", "branch")
86
117
 
87
118
  subject.update_mirror
@@ -85,8 +85,13 @@ module Capistrano
85
85
  env.set(:repo_tree, "tree")
86
86
  env.set(:branch, :branch)
87
87
  env.set(:release_path, "path")
88
+ env.set(:tmp_dir, "/tmp")
88
89
 
89
- backend.expects(:execute).with(:hg, "archive --type tgz -p . -I", "tree", "--rev", :branch, "| tar -x --strip-components 1 -f - -C", "path")
90
+ SecureRandom.stubs(:hex).with(10).returns("random")
91
+ backend.expects(:execute).with(:hg, "archive -p . -I", "tree", "--rev", :branch, "/tmp/random.tar")
92
+ backend.expects(:execute).with(:mkdir, "-p", "path")
93
+ backend.expects(:execute).with(:tar, "-x --strip-components 1 -f", "/tmp/random.tar", "-C", "path")
94
+ backend.expects(:execute).with(:rm, "/tmp/random.tar")
90
95
 
91
96
  subject.archive_to_release_path
92
97
  end
@@ -71,6 +71,10 @@ module Capistrano
71
71
 
72
72
  describe "#update_mirror" do
73
73
  it "should run svn update" do
74
+ env.set(:repo_url, "url")
75
+ env.set(:repo_path, "path")
76
+ backend.expects(:capture).with(:svn, :info, "path").returns("URL: url\n")
77
+
74
78
  env.set(:svn_username, "someuser")
75
79
  env.set(:svn_password, "somepassword")
76
80
  backend.expects(:execute).with(:svn, :update, "--username someuser", "--password somepassword")
@@ -80,6 +84,10 @@ module Capistrano
80
84
 
81
85
  context "for specific revision" do
82
86
  it "should run svn update" do
87
+ env.set(:repo_url, "url")
88
+ env.set(:repo_path, "path")
89
+ backend.expects(:capture).with(:svn, :info, "path").returns("URL: url\n")
90
+
83
91
  env.set(:svn_username, "someuser")
84
92
  env.set(:svn_password, "somepassword")
85
93
  env.set(:svn_revision, "12345")
@@ -88,6 +96,19 @@ module Capistrano
88
96
  subject.update_mirror
89
97
  end
90
98
  end
99
+
100
+ it "should run svn switch if repo_url is changed" do
101
+ env.set(:repo_url, "url")
102
+ env.set(:repo_path, "path")
103
+ backend.expects(:capture).with(:svn, :info, "path").returns("URL: old_url\n")
104
+
105
+ env.set(:svn_username, "someuser")
106
+ env.set(:svn_password, "somepassword")
107
+ backend.expects(:execute).with(:svn, :switch, "url", "--username someuser", "--password somepassword")
108
+ backend.expects(:execute).with(:svn, :update, "--username someuser", "--password somepassword")
109
+
110
+ subject.update_mirror
111
+ end
91
112
  end
92
113
 
93
114
  describe "#archive_to_release_path" do
@@ -19,6 +19,7 @@ module TestApp
19
19
  set :linked_files, #{linked_files}
20
20
  set :linked_dirs, #{linked_dirs}
21
21
  set :format_options, log_file: nil
22
+ set :local_user, #{current_user.inspect}
22
23
  CONFIG
23
24
  end
24
25
 
@@ -89,13 +90,14 @@ module TestApp
89
90
  File.open(shared_path.join(path), "w")
90
91
  end
91
92
 
92
- def cap(task)
93
- run "bundle exec cap #{stage} #{task}"
93
+ def cap(task, subdirectory=nil)
94
+ run "bundle exec cap #{stage} #{task}", subdirectory
94
95
  end
95
96
 
96
- def run(command)
97
+ def run(command, subdirectory=nil)
97
98
  output = nil
98
- Dir.chdir(test_app_path) do
99
+ dir = subdirectory ? test_app_path.join(subdirectory) : test_app_path
100
+ Dir.chdir(dir) do
99
101
  output = `#{command}`
100
102
  end
101
103
  [$CHILD_STATUS.success?, output]
@@ -154,7 +156,7 @@ module TestApp
154
156
  end
155
157
 
156
158
  def current_user
157
- `whoami`.chomp
159
+ "(GitHub Web Flow) via ShipIt"
158
160
  end
159
161
 
160
162
  def task_dir
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capistrano
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.7.0
4
+ version: 3.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Clements
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-12-10 00:00:00.000000000 Z
12
+ date: 2017-03-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: airbrussh
@@ -67,20 +67,6 @@ dependencies:
67
67
  - - ">="
68
68
  - !ruby/object:Gem::Version
69
69
  version: 1.9.0
70
- - !ruby/object:Gem::Dependency
71
- name: capistrano-harrow
72
- requirement: !ruby/object:Gem::Requirement
73
- requirements:
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- version: '0'
77
- type: :runtime
78
- prerelease: false
79
- version_requirements: !ruby/object:Gem::Requirement
80
- requirements:
81
- - - ">="
82
- - !ruby/object:Gem::Version
83
- version: '0'
84
70
  - !ruby/object:Gem::Dependency
85
71
  name: danger
86
72
  requirement: !ruby/object:Gem::Requirement
@@ -176,6 +162,7 @@ files:
176
162
  - features/step_definitions/assertions.rb
177
163
  - features/step_definitions/cap_commands.rb
178
164
  - features/step_definitions/setup.rb
165
+ - features/subdirectory.feature
179
166
  - features/support/env.rb
180
167
  - features/support/remote_command_helpers.rb
181
168
  - features/support/vagrant_helpers.rb
@@ -247,6 +234,7 @@ files:
247
234
  - spec/lib/capistrano/configuration/plugin_installer_spec.rb
248
235
  - spec/lib/capistrano/configuration/question_spec.rb
249
236
  - spec/lib/capistrano/configuration/role_filter_spec.rb
237
+ - spec/lib/capistrano/configuration/scm_resolver_spec.rb
250
238
  - spec/lib/capistrano/configuration/server_spec.rb
251
239
  - spec/lib/capistrano/configuration/servers_spec.rb
252
240
  - spec/lib/capistrano/configuration_spec.rb
@@ -297,7 +285,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
297
285
  version: '0'
298
286
  requirements: []
299
287
  rubyforge_project:
300
- rubygems_version: 2.6.8
288
+ rubygems_version: 2.6.10
301
289
  signing_key:
302
290
  specification_version: 4
303
291
  summary: Capistrano - Welcome to easy deployment with Ruby over SSH
@@ -312,6 +300,7 @@ test_files:
312
300
  - features/step_definitions/assertions.rb
313
301
  - features/step_definitions/cap_commands.rb
314
302
  - features/step_definitions/setup.rb
303
+ - features/subdirectory.feature
315
304
  - features/support/env.rb
316
305
  - features/support/remote_command_helpers.rb
317
306
  - features/support/vagrant_helpers.rb
@@ -325,6 +314,7 @@ test_files:
325
314
  - spec/lib/capistrano/configuration/plugin_installer_spec.rb
326
315
  - spec/lib/capistrano/configuration/question_spec.rb
327
316
  - spec/lib/capistrano/configuration/role_filter_spec.rb
317
+ - spec/lib/capistrano/configuration/scm_resolver_spec.rb
328
318
  - spec/lib/capistrano/configuration/server_spec.rb
329
319
  - spec/lib/capistrano/configuration/servers_spec.rb
330
320
  - spec/lib/capistrano/configuration_spec.rb