capistrano 3.7.0 → 3.17.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 (81) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +113 -0
  3. data/.github/pull_request_template.md +0 -4
  4. data/.github/release-drafter.yml +17 -0
  5. data/.github/workflows/push.yml +12 -0
  6. data/.rubocop.yml +13 -0
  7. data/CHANGELOG.md +1 -484
  8. data/CONTRIBUTING.md +2 -0
  9. data/DEVELOPMENT.md +5 -6
  10. data/Dangerfile +1 -54
  11. data/Gemfile +39 -3
  12. data/LICENSE.txt +1 -1
  13. data/README.md +10 -4
  14. data/RELEASING.md +3 -3
  15. data/Rakefile +13 -5
  16. data/UPGRADING-3.7.md +1 -12
  17. data/capistrano.gemspec +9 -8
  18. data/features/deploy.feature +34 -1
  19. data/features/installation.feature +8 -3
  20. data/features/step_definitions/assertions.rb +31 -3
  21. data/features/step_definitions/cap_commands.rb +10 -0
  22. data/features/step_definitions/setup.rb +37 -1
  23. data/features/subdirectory.feature +9 -0
  24. data/features/support/remote_command_helpers.rb +4 -0
  25. data/features/support/vagrant_helpers.rb +15 -8
  26. data/lib/Capfile +0 -4
  27. data/lib/capistrano/application.rb +11 -3
  28. data/lib/capistrano/configuration/filter.rb +1 -1
  29. data/lib/capistrano/configuration/host_filter.rb +1 -2
  30. data/lib/capistrano/configuration/question.rb +22 -3
  31. data/lib/capistrano/configuration/role_filter.rb +1 -2
  32. data/lib/capistrano/configuration/scm_resolver.rb +8 -3
  33. data/lib/capistrano/configuration/server.rb +1 -0
  34. data/lib/capistrano/configuration/servers.rb +16 -8
  35. data/lib/capistrano/configuration/variables.rb +2 -2
  36. data/lib/capistrano/configuration.rb +7 -3
  37. data/lib/capistrano/defaults.rb +1 -1
  38. data/lib/capistrano/doctor/servers_doctor.rb +1 -1
  39. data/lib/capistrano/doctor/variables_doctor.rb +12 -3
  40. data/lib/capistrano/dsl/paths.rb +3 -16
  41. data/lib/capistrano/dsl.rb +10 -2
  42. data/lib/capistrano/i18n.rb +7 -2
  43. data/lib/capistrano/immutable_task.rb +3 -2
  44. data/lib/capistrano/scm/git.rb +34 -7
  45. data/lib/capistrano/scm/hg.rb +8 -1
  46. data/lib/capistrano/scm/svn.rb +9 -0
  47. data/lib/capistrano/scm/tasks/git.rake +10 -9
  48. data/lib/capistrano/scm/tasks/hg.rake +1 -1
  49. data/lib/capistrano/tasks/deploy.rake +22 -10
  50. data/lib/capistrano/templates/deploy.rb.erb +10 -4
  51. data/lib/capistrano/templates/stage.rb.erb +1 -1
  52. data/lib/capistrano/version.rb +1 -1
  53. data/spec/integration/dsl_spec.rb +5 -3
  54. data/spec/lib/capistrano/application_spec.rb +16 -40
  55. data/spec/lib/capistrano/configuration/filter_spec.rb +1 -1
  56. data/spec/lib/capistrano/configuration/host_filter_spec.rb +10 -5
  57. data/spec/lib/capistrano/configuration/plugin_installer_spec.rb +1 -1
  58. data/spec/lib/capistrano/configuration/question_spec.rb +47 -11
  59. data/spec/lib/capistrano/configuration/role_filter_spec.rb +2 -2
  60. data/spec/lib/capistrano/configuration/scm_resolver_spec.rb +55 -0
  61. data/spec/lib/capistrano/configuration/server_spec.rb +1 -1
  62. data/spec/lib/capistrano/configuration/servers_spec.rb +6 -5
  63. data/spec/lib/capistrano/configuration_spec.rb +2 -2
  64. data/spec/lib/capistrano/doctor/environment_doctor_spec.rb +1 -1
  65. data/spec/lib/capistrano/doctor/gems_doctor_spec.rb +1 -1
  66. data/spec/lib/capistrano/doctor/servers_doctor_spec.rb +1 -1
  67. data/spec/lib/capistrano/doctor/variables_doctor_spec.rb +9 -1
  68. data/spec/lib/capistrano/dsl/paths_spec.rb +30 -0
  69. data/spec/lib/capistrano/dsl/task_enhancements_spec.rb +6 -6
  70. data/spec/lib/capistrano/dsl_spec.rb +48 -7
  71. data/spec/lib/capistrano/immutable_task_spec.rb +1 -1
  72. data/spec/lib/capistrano/plugin_spec.rb +2 -2
  73. data/spec/lib/capistrano/scm/git_spec.rb +54 -1
  74. data/spec/lib/capistrano/scm/hg_spec.rb +6 -1
  75. data/spec/lib/capistrano/scm/svn_spec.rb +21 -0
  76. data/spec/lib/capistrano/version_validator_spec.rb +23 -0
  77. data/spec/spec_helper.rb +13 -0
  78. data/spec/support/Vagrantfile +1 -1
  79. data/spec/support/test_app.rb +28 -14
  80. metadata +20 -80
  81. data/.travis.yml +0 -26
@@ -3,8 +3,7 @@ module Capistrano
3
3
  class HostFilter
4
4
  def initialize(values)
5
5
  av = Array(values).dup
6
- av.map! { |v| v.is_a?(String) && v =~ /^(?<name>[-A-Za-z0-9.]+)(,\g<name>)*$/ ? v.split(",") : v }
7
- av.flatten!
6
+ av = av.flat_map { |v| v.is_a?(String) && v =~ /^(?<name>[-A-Za-z0-9.]+)(,\g<name>)*$/ ? v.split(",") : v }
8
7
  @rex = regex_matcher(av)
9
8
  end
10
9
 
@@ -18,6 +18,7 @@ module Capistrano
18
18
 
19
19
  def ask_question
20
20
  $stdout.print question
21
+ $stdout.flush
21
22
  end
22
23
 
23
24
  def value_or_default
@@ -35,10 +36,12 @@ module Capistrano
35
36
  end
36
37
 
37
38
  def gets
39
+ return unless stdin.tty?
40
+
38
41
  if echo?
39
- $stdin.gets
42
+ stdin.gets
40
43
  else
41
- $stdin.noecho(&:gets).tap { $stdout.print "\n" }
44
+ stdin.noecho(&:gets).tap { $stdout.print "\n" }
42
45
  end
43
46
  rescue Errno::EIO
44
47
  # when stdio gets closed
@@ -46,12 +49,28 @@ module Capistrano
46
49
  end
47
50
 
48
51
  def question
49
- I18n.t(:question, key: key, default_value: default, scope: :capistrano)
52
+ if prompt && default.nil?
53
+ I18n.t(:question_prompt, key: prompt, scope: :capistrano)
54
+ elsif prompt
55
+ I18n.t(:question_prompt_default, key: prompt, default_value: default, scope: :capistrano)
56
+ elsif default.nil?
57
+ I18n.t(:question, key: key, scope: :capistrano)
58
+ else
59
+ I18n.t(:question_default, key: key, default_value: default, scope: :capistrano)
60
+ end
50
61
  end
51
62
 
52
63
  def echo?
53
64
  (options || {}).fetch(:echo, true)
54
65
  end
66
+
67
+ def stdin
68
+ (options || {}).fetch(:stdin, $stdin)
69
+ end
70
+
71
+ def prompt
72
+ (options || {}).fetch(:prompt, nil)
73
+ end
55
74
  end
56
75
  end
57
76
  end
@@ -3,8 +3,7 @@ module Capistrano
3
3
  class RoleFilter
4
4
  def initialize(values)
5
5
  av = Array(values).dup
6
- av.map! { |v| v.is_a?(String) ? v.split(",") : v }
7
- av.flatten!
6
+ av = av.flat_map { |v| v.is_a?(String) ? v.split(",") : v }
8
7
  @rex = regex_matcher(av)
9
8
  end
10
9
 
@@ -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
@@ -108,7 +112,8 @@ module Capistrano
108
112
  $stderr.puts(<<-MESSAGE)
109
113
  [Deprecation Notice] `set :scm, #{scm_name.inspect}` is deprecated.
110
114
  To ensure your project is compatible with future versions of Capistrano,
111
- remove the :scm setting and instead add these lines to your Capfile:
115
+ remove the :scm setting and instead add these lines to your Capfile after
116
+ `require "capistrano/deploy"`:
112
117
 
113
118
  require "capistrano/scm/#{scm_name}"
114
119
  install_plugin #{built_in_scm_plugin_class_name}
@@ -120,7 +125,7 @@ MESSAGE
120
125
  $stderr.puts(<<-MESSAGE)
121
126
  [Deprecation Notice] Future versions of Capistrano will not load the Git SCM
122
127
  plugin by default. To silence this deprecation warning, add the following to
123
- your Capfile:
128
+ your Capfile after `require "capistrano/deploy"`:
124
129
 
125
130
  require "capistrano/scm/git"
126
131
  install_plugin Capistrano::SCM::Git
@@ -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
 
@@ -47,10 +47,14 @@ module Capistrano
47
47
  def any?(key)
48
48
  value = fetch(key)
49
49
  if value && value.respond_to?(:any?)
50
- value.any?
51
- else
52
- !fetch(key).nil?
50
+ begin
51
+ return value.any?
52
+ rescue ArgumentError # rubocop:disable Lint/HandleExceptions
53
+ # Gracefully ignore values whose `any?` method doesn't accept 0 args
54
+ end
53
55
  end
56
+
57
+ !value.nil?
54
58
  end
55
59
 
56
60
  def is_question?(key)
@@ -8,7 +8,7 @@ validate :application do |_key, value|
8
8
  end
9
9
  end
10
10
 
11
- [:git_strategy, :hg_strategy, :svn_strategy].each do |strategy|
11
+ %i(git_strategy hg_strategy svn_strategy).each do |strategy|
12
12
  validate(strategy) do |key, _value|
13
13
  warn(
14
14
  "[Deprecation Warning] #{key} is deprecated and will be removed in "\
@@ -56,7 +56,7 @@ module Capistrano
56
56
  private
57
57
 
58
58
  def find_whitespace_roles
59
- servers.map(&:roles).map(&:to_a).flatten.uniq
59
+ servers.map(&:roles).flat_map(&:to_a).uniq
60
60
  .select { |role| include_whitespace?(role) }
61
61
  end
62
62
  end
@@ -5,9 +5,18 @@ module Capistrano
5
5
  # Prints a table of all Capistrano variables and their current values. If
6
6
  # there are unrecognized variables, print warnings for them.
7
7
  class VariablesDoctor
8
- # These are keys that have no default values in Capistrano, but are
9
- # nonetheless expected to be set.
10
- WHITELIST = [:application, :repo_url].freeze
8
+ # These are keys that are recognized by Capistrano, but do not have values
9
+ # set by default.
10
+ WHITELIST = %i(
11
+ application
12
+ current_directory
13
+ linked_dirs
14
+ linked_files
15
+ releases_directory
16
+ repo_url
17
+ repo_tree
18
+ shared_directory
19
+ ).freeze
11
20
  private_constant :WHITELIST
12
21
 
13
22
  include Capistrano::Doctor::OutputHelpers
@@ -15,7 +15,7 @@ module Capistrano
15
15
  end
16
16
 
17
17
  def releases_path
18
- deploy_path.join("releases")
18
+ deploy_path.join(fetch(:releases_directory, "releases"))
19
19
  end
20
20
 
21
21
  def release_path
@@ -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
@@ -57,7 +44,7 @@ module Capistrano
57
44
  end
58
45
 
59
46
  def shared_path
60
- deploy_path.join("shared")
47
+ deploy_path.join(fetch(:shared_directory, "shared"))
61
48
  end
62
49
 
63
50
  def revision_log
@@ -19,15 +19,21 @@ module Capistrano
19
19
  colors = SSHKit::Color.new($stderr)
20
20
  $stderr.puts colors.colorize("Skipping task `#{task_name}'.", :yellow)
21
21
  $stderr.puts "Capistrano tasks may only be invoked once. Since task `#{task}' was previously invoked, invoke(\"#{task_name}\") at #{file}:#{line} will be skipped."
22
- $stderr.puts "If you really meant to run this task again, first call Rake::Task[\"#{task_name}\"].reenable"
22
+ $stderr.puts "If you really meant to run this task again, use invoke!(\"#{task_name}\")"
23
23
  $stderr.puts colors.colorize("THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF CAPISTRANO. Please join the conversation here if this affects you.", :red)
24
24
  $stderr.puts colors.colorize("https://github.com/capistrano/capistrano/issues/1686", :red)
25
25
  end
26
26
  task.invoke(*args)
27
27
  end
28
28
 
29
+ def invoke!(task_name, *args)
30
+ task = Rake::Task[task_name]
31
+ task.reenable
32
+ task.invoke(*args)
33
+ end
34
+
29
35
  def t(key, options={})
30
- I18n.t(key, options.merge(scope: :capistrano))
36
+ I18n.t(key, **options.merge(scope: :capistrano))
31
37
  end
32
38
 
33
39
  def scm
@@ -59,10 +65,12 @@ module Capistrano
59
65
  VersionValidator.new(locked_version).verify
60
66
  end
61
67
 
68
+ # rubocop:disable Security/MarshalLoad
62
69
  def on(hosts, options={}, &block)
63
70
  subset_copy = Marshal.dump(Configuration.env.filter(hosts))
64
71
  SSHKit::Coordinator.new(Marshal.load(subset_copy)).each(options, &block)
65
72
  end
73
+ # rubocop:enable Security/MarshalLoad
66
74
 
67
75
  def run_locally(&block)
68
76
  SSHKit::Backend::Local.new(&block).run
@@ -10,9 +10,14 @@ 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}): ",
15
+ question_prompt: "%{key}: ",
16
+ question_prompt_default: "%{key} (%{default_value}): ",
14
17
  keeping_releases: "Keeping %{keep_releases} of %{releases} deployed releases on %{host}",
15
- skip_cleanup: "Skipping cleanup of old releases on %{host}; unexpected foldername found (should be timestamp)",
18
+ skip_cleanup: "Skipping cleanup of invalid releases on %{host}; unexpected foldername found (should be timestamp)",
19
+ wont_delete_current_release: "Current release was marked for being removed but it's going to be skipped on %{host}",
20
+ no_current_release: "There is no current release present on %{host}",
16
21
  no_old_releases: "No old releases (keeping newest %{keep_releases}) on %{host}",
17
22
  linked_file_does_not_exist: "linked file %{file} does not exist on %{host}",
18
23
  cannot_rollback: "There are no older releases to rollback to",
@@ -17,8 +17,9 @@ module Capistrano
17
17
 
18
18
  def enhance(*args, &block)
19
19
  $stderr.puts <<-MESSAGE
20
- WARNING: #{name} has already been invoked and can no longer be modified.
21
- Check that you haven't loaded a Capistrano plugin in deploy.rb by mistake.
20
+ ERROR: #{name} has already been invoked and can no longer be modified.
21
+ Check that you haven't loaded a Capistrano plugin in deploy.rb or a stage
22
+ (e.g. deploy/production.rb) by mistake.
22
23
  Plugins must be loaded in the Capfile to initialize properly.
23
24
  MESSAGE
24
25
 
@@ -1,13 +1,16 @@
1
1
  require "capistrano/scm/plugin"
2
+ require "cgi"
3
+ require "securerandom"
4
+ require "shellwords"
5
+ require "uri"
2
6
 
3
7
  class Capistrano::SCM::Git < Capistrano::SCM::Plugin
4
8
  def set_defaults
5
9
  set_if_empty :git_shallow_clone, false
6
10
  set_if_empty :git_wrapper_path, lambda {
7
- # Try to avoid permissions issues when multiple users deploy the same app
8
- # 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+/, "-")
10
- "#{fetch(:tmp_dir)}/git-ssh-#{suffix}.sh"
11
+ # Use a unique name that won't collide with other deployments, and
12
+ # that cannot be guessed by other processes that have access to /tmp.
13
+ "#{fetch(:tmp_dir)}/git-ssh-#{SecureRandom.hex(10)}.sh"
11
14
  }
12
15
  set_if_empty :git_environmental_variables, lambda {
13
16
  {
@@ -15,6 +18,8 @@ class Capistrano::SCM::Git < Capistrano::SCM::Plugin
15
18
  git_ssh: fetch(:git_wrapper_path)
16
19
  }
17
20
  }
21
+ set_if_empty :git_max_concurrent_connections, 10
22
+ set_if_empty :git_wait_interval, 0
18
23
  end
19
24
 
20
25
  def register_hooks
@@ -32,18 +37,21 @@ class Capistrano::SCM::Git < Capistrano::SCM::Plugin
32
37
  end
33
38
 
34
39
  def check_repo_is_reachable
35
- git :'ls-remote --heads', repo_url
40
+ git :'ls-remote', git_repo_url, "HEAD"
36
41
  end
37
42
 
38
43
  def clone_repo
39
44
  if (depth = fetch(:git_shallow_clone))
40
- git :clone, "--mirror", "--depth", depth, "--no-single-branch", repo_url, repo_path.to_s
45
+ git :clone, "--mirror", "--depth", depth, "--no-single-branch", git_repo_url, repo_path.to_s
41
46
  else
42
- git :clone, "--mirror", repo_url, repo_path.to_s
47
+ git :clone, "--mirror", git_repo_url, repo_path.to_s
43
48
  end
44
49
  end
45
50
 
46
51
  def update_mirror
52
+ # Update the origin URL if necessary.
53
+ git :remote, "set-url", "origin", git_repo_url
54
+
47
55
  # Note: Requires git version 1.9 or greater
48
56
  if (depth = fetch(:git_shallow_clone))
49
57
  git :fetch, "--depth", depth, "origin", fetch(:branch)
@@ -52,6 +60,10 @@ class Capistrano::SCM::Git < Capistrano::SCM::Plugin
52
60
  end
53
61
  end
54
62
 
63
+ def verify_commit
64
+ git :"verify-commit", fetch_revision
65
+ end
66
+
55
67
  def archive_to_release_path
56
68
  if (tree = fetch(:repo_tree))
57
69
  tree = tree.slice %r#^/?(.*?)/?$#, 1
@@ -70,4 +82,19 @@ class Capistrano::SCM::Git < Capistrano::SCM::Plugin
70
82
  args.unshift :git
71
83
  backend.execute(*args)
72
84
  end
85
+
86
+ def git_repo_url
87
+ if fetch(:git_http_username) && fetch(:git_http_password)
88
+ URI.parse(repo_url).tap do |repo_uri|
89
+ repo_uri.user = fetch(:git_http_username)
90
+ repo_uri.password = CGI.escape(fetch(:git_http_password))
91
+ end.to_s
92
+ elsif fetch(:git_http_username)
93
+ URI.parse(repo_url).tap do |repo_uri|
94
+ repo_uri.user = fetch(:git_http_username)
95
+ end.to_s
96
+ else
97
+ repo_url
98
+ end
99
+ end
73
100
  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
@@ -4,17 +4,17 @@ git_plugin = self
4
4
  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
- on release_roles :all do
8
- execute :mkdir, "-p", File.dirname(fetch(:git_wrapper_path))
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)
7
+ on release_roles(:all), in: :groups, limit: fetch(:git_max_concurrent_connections), wait: fetch(:git_wait_interval) do
8
+ execute :mkdir, "-p", File.dirname(fetch(:git_wrapper_path)).shellescape
9
+ upload! StringIO.new("#!/bin/sh -e\nexec /usr/bin/env ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no \"$@\"\n"), fetch(:git_wrapper_path)
10
+ execute :chmod, "700", fetch(:git_wrapper_path).shellescape
11
11
  end
12
12
  end
13
13
 
14
14
  desc "Check that the repository is reachable"
15
15
  task check: :'git:wrapper' do
16
16
  fetch(:branch)
17
- on release_roles :all do
17
+ on release_roles(:all), in: :groups, limit: fetch(:git_max_concurrent_connections), wait: fetch(:git_wait_interval) do
18
18
  with fetch(:git_environmental_variables) do
19
19
  git_plugin.check_repo_is_reachable
20
20
  end
@@ -23,7 +23,7 @@ namespace :git do
23
23
 
24
24
  desc "Clone the repo to the cache"
25
25
  task clone: :'git:wrapper' do
26
- on release_roles :all do
26
+ on release_roles(:all), in: :groups, limit: fetch(:git_max_concurrent_connections), wait: fetch(:git_wait_interval) do
27
27
  if git_plugin.repo_mirror_exists?
28
28
  info t(:mirror_exists, at: repo_path)
29
29
  else
@@ -38,10 +38,11 @@ namespace :git do
38
38
 
39
39
  desc "Update the repo mirror to reflect the origin state"
40
40
  task update: :'git:clone' do
41
- on release_roles :all do
41
+ on release_roles(:all), in: :groups, limit: fetch(:git_max_concurrent_connections), wait: fetch(:git_wait_interval) do
42
42
  within repo_path do
43
43
  with fetch(:git_environmental_variables) do
44
44
  git_plugin.update_mirror
45
+ git_plugin.verify_commit if fetch(:git_verify_commit)
45
46
  end
46
47
  end
47
48
  end
@@ -49,7 +50,7 @@ namespace :git do
49
50
 
50
51
  desc "Copy repo to releases"
51
52
  task create_release: :'git:update' do
52
- on release_roles :all do
53
+ on release_roles(:all), in: :groups, limit: fetch(:git_max_concurrent_connections), wait: fetch(:git_wait_interval) do
53
54
  with fetch(:git_environmental_variables) do
54
55
  within repo_path do
55
56
  execute :mkdir, "-p", release_path
@@ -61,7 +62,7 @@ namespace :git do
61
62
 
62
63
  desc "Determine the revision that will be deployed"
63
64
  task :set_current_revision do
64
- on release_roles :all do
65
+ on release_roles(:all), in: :groups, limit: fetch(:git_max_concurrent_connections), wait: fetch(:git_wait_interval) do
65
66
  within repo_path do
66
67
  with fetch(:git_environmental_variables) do
67
68
  set :current_revision, git_plugin.fetch_revision
@@ -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
@@ -149,16 +149,28 @@ namespace :deploy do
149
149
  task :cleanup do
150
150
  on release_roles :all do |host|
151
151
  releases = capture(:ls, "-x", releases_path).split
152
- if !(releases.all? { |e| /^\d{14}$/ =~ e })
153
- warn t(:skip_cleanup, host: host.to_s)
154
- elsif releases.count >= fetch(:keep_releases)
155
- info t(:keeping_releases, host: host.to_s, keep_releases: fetch(:keep_releases), releases: releases.count)
156
- directories = (releases - releases.last(fetch(:keep_releases)))
152
+ valid, invalid = releases.partition { |e| /^\d{14}$/ =~ e }
153
+
154
+ warn t(:skip_cleanup, host: host.to_s) if invalid.any?
155
+
156
+ if valid.count >= fetch(:keep_releases)
157
+ info t(:keeping_releases, host: host.to_s, keep_releases: fetch(:keep_releases), releases: valid.count)
158
+ directories = (valid - valid.last(fetch(:keep_releases))).map do |release|
159
+ releases_path.join(release).to_s
160
+ end
161
+ if test("[ -d #{current_path} ]")
162
+ current_release = capture(:readlink, current_path).to_s
163
+ if directories.include?(current_release)
164
+ warn t(:wont_delete_current_release, host: host.to_s)
165
+ directories.delete(current_release)
166
+ end
167
+ else
168
+ debug t(:no_current_release, host: host.to_s)
169
+ end
157
170
  if directories.any?
158
- directories_str = directories.map do |release|
159
- releases_path.join(release)
160
- end.join(" ")
161
- execute :rm, "-rf", directories_str
171
+ directories.each_slice(100) do |directories_batch|
172
+ execute :rm, "-rf", *directories_batch
173
+ end
162
174
  else
163
175
  info t(:no_old_releases, host: host.to_s, keep_releases: fetch(:keep_releases))
164
176
  end
@@ -227,7 +239,7 @@ namespace :deploy do
227
239
  task :set_current_revision do
228
240
  on release_roles(:all) do
229
241
  within release_path do
230
- execute :echo, "\"#{fetch(:current_revision)}\" >> REVISION"
242
+ execute :echo, "\"#{fetch(:current_revision)}\" > REVISION"
231
243
  end
232
244
  end
233
245
  end
@@ -1,5 +1,5 @@
1
- # config valid only for current version of Capistrano
2
- lock "<%= Capistrano::VERSION %>"
1
+ # config valid for current version and patch releases of Capistrano
2
+ lock "~> <%= Capistrano::VERSION %>"
3
3
 
4
4
  set :application, "my_app_name"
5
5
  set :repo_url, "git@example.com:me/my_repo.git"
@@ -21,13 +21,19 @@ set :repo_url, "git@example.com:me/my_repo.git"
21
21
  # set :pty, true
22
22
 
23
23
  # Default value for :linked_files is []
24
- # append :linked_files, "config/database.yml", "config/secrets.yml"
24
+ # append :linked_files, "config/database.yml", 'config/master.key'
25
25
 
26
26
  # Default value for linked_dirs is []
27
- # append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"
27
+ # append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "tmp/webpacker", "public/system", "vendor", "storage"
28
28
 
29
29
  # Default value for default_env is {}
30
30
  # set :default_env, { path: "/opt/ruby/bin:$PATH" }
31
31
 
32
+ # Default value for local_user is ENV['USER']
33
+ # set :local_user, -> { `git config user.name`.chomp }
34
+
32
35
  # Default value for keep_releases is 5
33
36
  # set :keep_releases, 5
37
+
38
+ # Uncomment the following to require manually verifying the host key before first deploy.
39
+ # set :ssh_options, verify_host_key: :secure
@@ -42,7 +42,7 @@
42
42
  # Global options
43
43
  # --------------
44
44
  # set :ssh_options, {
45
- # keys: %w(/home/rlisowski/.ssh/id_rsa),
45
+ # keys: %w(/home/user_name/.ssh/id_rsa),
46
46
  # forward_agent: false,
47
47
  # auth_methods: %w(password)
48
48
  # }
@@ -1,3 +1,3 @@
1
1
  module Capistrano
2
- VERSION = "3.7.0".freeze
2
+ VERSION = "3.17.0".freeze
3
3
  end