[DRE-commits] [capistrano] 01/04: New upstream version 3.6.1

Micah Anderson micah at moszumanska.debian.org
Tue Nov 15 13:57:06 UTC 2016


This is an automated email from the git hooks/post-receive script.

micah pushed a commit to branch master
in repository capistrano.

commit bdaa3c72f789546c41f20db789ab420ff281a4c8
Author: Micah Anderson <micah at riseup.net>
Date:   Tue Nov 15 07:48:47 2016 -0500

    New upstream version 3.6.1
---
 .gitignore                                         |  12 +-
 .rubocop.yml                                       |  49 ++
 .travis.yml                                        |  15 +-
 CHANGELOG.md                                       | 129 ++++-
 CONTRIBUTING.md                                    | 154 +++---
 DEVELOPMENT.md                                     | 122 +++++
 Gemfile                                            |   7 +-
 LICENSE.txt                                        |   2 +-
 README.md                                          | 164 +++++--
 RELEASING.md                                       |  17 +
 Rakefile                                           |   5 +-
 UPGRADING-3.7.md                                   |  97 ++++
 bin/cap                                            |   2 +-
 capistrano.gemspec                                 |  37 +-
 features/deploy.feature                            |   1 +
 features/doctor.feature                            |  11 +
 features/stage_failure.feature                     |   9 +
 features/step_definitions/assertions.rb            |  39 +-
 features/step_definitions/cap_commands.rb          |   1 -
 features/step_definitions/setup.rb                 |  24 +-
 features/support/env.rb                            |  10 +-
 features/support/remote_command_helpers.rb         |  14 +-
 features/support/vagrant_helpers.rb                |   9 +-
 issue_template.md                                  |  21 +
 lib/Capfile                                        |   6 +-
 lib/capistrano/all.rb                              |  19 +-
 lib/capistrano/application.rb                      |  67 +--
 lib/capistrano/configuration.rb                    | 102 ++--
 lib/capistrano/configuration/empty_filter.rb       |   9 +
 lib/capistrano/configuration/filter.rb             |  64 +--
 lib/capistrano/configuration/host_filter.rb        |  30 ++
 lib/capistrano/configuration/null_filter.rb        |   9 +
 lib/capistrano/configuration/plugin_installer.rb   |  33 ++
 lib/capistrano/configuration/question.rb           |  17 +-
 lib/capistrano/configuration/role_filter.rb        |  30 ++
 lib/capistrano/configuration/server.rb             |  51 +-
 lib/capistrano/configuration/servers.rb            |  13 +-
 .../configuration/validated_variables.rb           | 111 +++++
 lib/capistrano/configuration/variables.rb          | 112 +++++
 lib/capistrano/defaults.rb                         |  26 +-
 lib/capistrano/deploy.rb                           |   2 +-
 lib/capistrano/doctor.rb                           |   6 +
 lib/capistrano/doctor/environment_doctor.rb        |  19 +
 lib/capistrano/doctor/gems_doctor.rb               |  45 ++
 lib/capistrano/doctor/output_helpers.rb            |  79 ++++
 lib/capistrano/doctor/servers_doctor.rb            | 105 +++++
 lib/capistrano/doctor/variables_doctor.rb          |  65 +++
 lib/capistrano/dotfile.rb                          |   3 +-
 lib/capistrano/dsl.rb                              |  53 ++-
 lib/capistrano/dsl/env.rb                          |  53 +--
 lib/capistrano/dsl/paths.rb                        |  25 +-
 lib/capistrano/dsl/stages.rb                       |  16 +-
 lib/capistrano/dsl/task_enhancements.rb            |  17 +-
 lib/capistrano/framework.rb                        |   2 +-
 lib/capistrano/git.rb                              |  26 +-
 lib/capistrano/hg.rb                               |   8 +-
 lib/capistrano/i18n.rb                             |  50 +-
 lib/capistrano/immutable_task.rb                   |  29 ++
 lib/capistrano/install.rb                          |   2 +-
 lib/capistrano/plugin.rb                           |  95 ++++
 lib/capistrano/proc_helpers.rb                     |  13 +
 lib/capistrano/scm.rb                              |  27 +-
 lib/capistrano/setup.rb                            |  24 +-
 lib/capistrano/svn.rb                              |  14 +-
 lib/capistrano/tasks/console.rake                  |  12 +-
 lib/capistrano/tasks/deploy.rake                   | 147 +++---
 lib/capistrano/tasks/doctor.rake                   |  24 +
 lib/capistrano/tasks/framework.rake                |  27 +-
 lib/capistrano/tasks/git.rake                      |  32 +-
 lib/capistrano/tasks/hg.rake                       |  14 +-
 lib/capistrano/tasks/install.rake                  |  29 +-
 lib/capistrano/tasks/svn.rake                      |  14 +-
 lib/capistrano/templates/Capfile                   |   6 +-
 lib/capistrano/templates/deploy.rb.erb             |  26 +-
 lib/capistrano/upload_task.rb                      |   2 +-
 lib/capistrano/version.rb                          |   2 +-
 lib/capistrano/version_validator.rb                |  10 +-
 metadata.yml                                       | 252 ----------
 spec/integration/dsl_spec.rb                       | 525 +++++++++++----------
 spec/integration_spec_helper.rb                    |   8 +-
 spec/lib/capistrano/application_spec.rb            |  38 +-
 .../capistrano/configuration/empty_filter_spec.rb  |  17 +
 spec/lib/capistrano/configuration/filter_spec.rb   | 168 ++++---
 .../capistrano/configuration/host_filter_spec.rb   |  61 +++
 .../capistrano/configuration/null_filter_spec.rb   |  17 +
 spec/lib/capistrano/configuration/question_spec.rb |  30 +-
 .../capistrano/configuration/role_filter_spec.rb   |  64 +++
 spec/lib/capistrano/configuration/server_spec.rb   | 217 +++++----
 spec/lib/capistrano/configuration/servers_spec.rb  | 265 +++++------
 spec/lib/capistrano/configuration_spec.rb          | 234 ++++++---
 .../capistrano/doctor/environment_doctor_spec.rb   |  44 ++
 spec/lib/capistrano/doctor/gems_doctor_spec.rb     |  67 +++
 spec/lib/capistrano/doctor/output_helpers_spec.rb  |  47 ++
 spec/lib/capistrano/doctor/servers_doctor_spec.rb  |  86 ++++
 .../lib/capistrano/doctor/variables_doctor_spec.rb |  88 ++++
 spec/lib/capistrano/dsl/paths_spec.rb              | 126 ++---
 spec/lib/capistrano/dsl/task_enhancements_spec.rb  |  99 ++--
 spec/lib/capistrano/dsl_spec.rb                    |  54 ++-
 spec/lib/capistrano/git_spec.rb                    |  42 +-
 spec/lib/capistrano/hg_spec.rb                     |  19 +-
 spec/lib/capistrano/immutable_task_spec.rb         |  31 ++
 spec/lib/capistrano/plugin_spec.rb                 |  84 ++++
 spec/lib/capistrano/scm_spec.rb                    |  13 +-
 spec/lib/capistrano/svn_spec.rb                    |  54 ++-
 spec/lib/capistrano/upload_task_spec.rb            |  14 +-
 spec/lib/capistrano/version_validator_spec.rb      |  86 ++--
 spec/lib/capistrano_spec.rb                        |   5 +-
 spec/spec_helper.rb                                |  16 +-
 spec/support/Vagrantfile                           |  19 +-
 spec/support/tasks/database.rake                   |   6 +-
 spec/support/tasks/fail.rake                       |   7 +-
 spec/support/tasks/failed.rake                     |   4 +-
 spec/support/tasks/plugin.rake                     |   6 +
 spec/support/tasks/root.rake                       |   8 +-
 spec/support/test_app.rb                           |  69 +--
 115 files changed, 3889 insertions(+), 1874 deletions(-)

diff --git a/.gitignore b/.gitignore
index 58c3cfc..b524226 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,15 @@
 *.gem
 *.rbc
+*.swp
 .bundle
 .config
+.rspec
+.rspec-local
+.ruby-version
 .yardoc
 Gemfile.lock
 InstalledFiles
+_site
 _yardoc
 coverage
 doc/
@@ -12,11 +17,8 @@ lib/bundler/man
 pkg
 rdoc
 spec/reports
+tags
 test/tmp
 test/version_tmp
 tmp
-.rspec-local
-.ruby-version
-_site
-.rspec
-*.swp
+vendor/
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 0000000..d977021
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,49 @@
+AllCops:
+  DisplayCopNames: true
+  DisplayStyleGuide: true
+  TargetRubyVersion: 2.0
+
+Style/BarePercentLiterals:
+  EnforcedStyle: percent_q
+Style/ClassAndModuleChildren:
+  Enabled: false
+Style/DoubleNegation:
+  Enabled: false
+Style/SpaceAroundEqualsInParameterDefault:
+  EnforcedStyle: no_space
+Style/StringLiterals:
+  EnforcedStyle: double_quotes
+Style/TrivialAccessors:
+  AllowPredicates: true
+Style/PercentLiteralDelimiters:
+  Enabled: false
+Style/SingleLineBlockParams:
+  Enabled: false
+Style/ModuleFunction:
+  Enabled: false
+
+# Enable someday
+Style/Documentation:
+  Enabled: false
+
+# Needs refactors
+Metrics/PerceivedComplexity:
+  Enabled: false
+Metrics/CyclomaticComplexity:
+  Enabled: false
+Metrics/MethodLength:
+  Enabled: false
+Style/PredicateName:
+  Enabled: false
+Metrics/LineLength:
+  Enabled: false
+Metrics/AbcSize:
+  Enabled: false
+Style/PerlBackrefs:
+  Enabled: false
+Metrics/ClassLength:
+  Enabled: false
+Metrics/ModuleLength:
+  Enabled: false
+Style/AccessorMethodName:
+  Enabled: false
diff --git a/.travis.yml b/.travis.yml
index b80bed5..a082ea3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,15 @@
 language: ruby
 rvm:
-  - 2.1.0
-  - 2.0.0
-  - 1.9.3
-  - rbx-2
-script: bundle exec rake spec
+  - 2.3.0
+  - 2.2
+  - 2.1
+  - 2.0
+matrix:
+  include:
+    # Run specs on Rubinius, but not RuboCop, since it seg faults
+    - rvm: rbx-2
+      script: bundle exec rake spec
+script: bundle exec rake spec rubocop
 install: bundle install --jobs=1
 cache: bundler
 branches:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ec2da8b..900434a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,132 @@ Reverse Chronological Order:
 
 ## master
 
-https://github.com/capistrano/capistrano/compare/v3.4.0...HEAD
+https://github.com/capistrano/capistrano/compare/v3.6.1...HEAD
+
+* Your contribution here!
+
+## `3.6.1` (2016-08-23)
+
+https://github.com/capistrano/capistrano/compare/v3.6.0...v3.6.1
+
+### Fixes:
+
+* Restore compatibility with older versions of Rake (< 11.0.0) (@troelskn)
+* Fix `NoMethodError: undefined method gsub` when setting `:application` to a Proc. The original fix released in 3.6.0 worked for values specified with blocks, but not for those specified with procs or lambdas (the latter syntax is much more common). [#1681](https://github.com/capistrano/capistrano/issues/1681)
+* Fix a bug where deploy would fail if `:local_user` contained a space; spaces are now replaced with dashes when computing the git-ssh suffix. (@will_in_wi)
+
+## `3.6.0` (2016-07-26)
+
+https://github.com/capistrano/capistrano/compare/v3.5.0...v3.6.0
+
+Thank you to the many first-time contributors from the Capistrano community who
+helped with this release!
+
+### Deprecations:
+
+  * Deprecate `remote_file` feature (will be removed in Capistrano 3.7.0) (@lebedev-yury)
+  * Deprecate `:git_strategy`, `:hg_strategy`, and `:svn_strategy` variables.
+    These will be completely removed in 3.7.0.
+  * Added warning about future deprecation of reinvocation behaviour (@troelskn)
+
+Refer to the [Capistrano 3.7.0 upgrade document](UPGRADING-3.7.md) if you are
+affected by these deprecations.
+
+### New features:
+
+  * Added a `doctor:servers` subtask that outputs a summary of servers, roles & properties (@irvingwashington)
+  * Make path to git wrapper script configurable (@thickpaddy)
+  * Make name of current directory configurable via configuration variable `:current_directory` (@websi)
+  * It is now possible to rollback to a specific release using the
+    `ROLLBACK_RELEASE` environment variable.
+    [#1155](https://github.com/capistrano/capistrano/issues/1155) (@lanrion)
+
+### Fixes:
+
+  * `doctor` no longer erroneously warns that `:git_strategy` and other SCM options are "unrecognized" (@shanesaww)
+  * Fix `NoMethodError: undefined method gsub` when setting `:application` to a
+    Proc. [#1681](https://github.com/capistrano/capistrano/issues/1681)
+    (@mattbrictson)
+
+### Other changes:
+
+  * Raise a better error when an ‘after’ hook isn’t found (@jdelStrother)
+  * Change git wrapper path to work better with multiple users (@thickpaddy)
+  * Restrict the uploaded git wrapper script permissions to 700 (@irvingwashington)
+  * Add `net-ssh` gem version to `doctor:gems` output (@lebedev-yury)
+
+## `3.5.0`
+
+https://github.com/capistrano/capistrano/compare/v3.4.1...v3.5.0
+
+**You'll notice a big cosmetic change in this release: the default logging
+format has been changed to
+[Airbrussh](https://github.com/mattbrictson/airbrussh).** For more details on
+what Airbrussh does
+and how to configure it, visit the
+[Airbrussh README](https://github.com/mattbrictson/airbrussh#readme).
+
+* To opt out of the new format, simply add `set :format, :pretty` to switch to
+  the old default of Capistrano 3.4.0 and earlier.
+* If you are already an Airbrussh user, note that the default configuration has
+  changed, and the syntax for configuring Airbrussh has changed as well.
+  [This simple upgrade guide](https://github.com/mattbrictson/airbrussh/blob/master/UPGRADING-CAP-3.5.md)
+  will walk you through it.
+
+### Potentially breaking changes:
+
+* Drop support for Ruby 1.9.3 (Capistrano does no longer work with 1.9.3)
+* Git version 1.6.3 or greater is now required
+* Remove 'vendor/bundle' from default :linked_dirs (@ojab)
+* Old versions of SSHKit (before 1.9.0) are no longer supported
+* SHA1 hash of current git revision written to REVISION file is no longer abbreviated
+* Ensure task invocation within after hooks is namespace aware, which may require
+  you to change how your `after` hooks are declared in some cases; see
+  [#1652](https://github.com/capistrano/capistrano/issues/1652) for an example
+  and how to correct it (@thickpaddy)
+* Validation of the `:application` variable forbids special characters such as slash,
+  this may be a breaking change in case that you rely on using a `/` in your application
+  name to deploy from a sub directory.
+
+### New features:
+
+* Added a `doctor` task that outputs helpful troubleshooting information. Try it like this: `cap production doctor`. (@mattbrictson)
+* Added a `dry_run?` helper method
+* `remove` DSL method for removing values like from arrays like `linked_dirs`
+* `append` DSL method for pushing values like `linked_dirs`
+  [#1447](https://github.com/capistrano/capistrano/pull/1447),
+  [#1586](https://github.com/capistrano/capistrano/pull/1586)
+* Added support for git shallow clone
+* Added new runtime option `--print-config-variables` that inspect all defined config variables in order to assist development of new capistrano tasks (@gerardo-navarro)
+* Prune dead tracking branches from git repositories while updating
+* Added options to set username and password when using Subversion as SCM (@dsthode)
+* Allow after() to refer to tasks that have not been loaded yet (@jcoglan)
+* Allow use "all" as string for server filtering (@theist)
+* Print a warning and abort if "load:defaults" is erroneously invoked after
+  capistrano is already loaded, e.g. when a plugin is loaded in `deploy.rb`
+  instead of `Capfile`. (@mattbrictson)
+* Added option to set specific revision when using Subversion as SCM (@marcovtwout)
+* Deduplicate list of linked directories
+* Integration with Harrow.io (See http://capistranorb.com/documentation/harrow/) when running `cap install`
+* Added validate method to DSL to allow validation of certain values (@Kriechi)
+    * validate values before assignment inside of `set(:key, value)`
+    * should raise a `Capistrano::ValidationError` if invalid
+* Added default validation for Capistrano-specific variables (@Kriechi)
+
+### Fixes:
+
+* Capistrano is now fully-compatible with Rake 11.0. (@mattbrictson)
+* Fix filtering behaviour when using literal hostnames in on() block (@townsen)
+* Allow dot in :application name (@marcovtwout)
+* Fixed git-ssh permission error (@spight)
+
+### Other changes:
+
+* Internal Rubocop cleanups.
+* Removed the post-install message (@Kriechi)
+* Refactor `Configuration::Filter` to use filtering strategies instead
+  of case statements (@cshaffer)
+* Clean up rubocop lint warnings (@cshaffer)
 
 ## `3.4.0`
 
@@ -25,7 +150,7 @@ https://github.com/capistrano/capistrano/compare/v3.3.5...v3.4.0
 * Breaking Changes
   * Hosts with the same name are now consolidated into one irrespective of the
     user and port. This allows multiple declarations of a server to be made safely.
-    The last declared properties will win. See capistrnorb.com Properties documentation
+    The last declared properties will win. See capistranorb.com Properties documentation
     for details.
   * Inside the on() block the host variable is now a copy of the host, so changes can be
     made within the block (such as dynamically overriding the user) that will not persist.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c619e4a..74a5544 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,93 +1,61 @@
-## CONTRIBUTING
-
-**The issue tracker is intended exclusively for things that are genuine bugs,
-or improvements to the code.**
-
-If you have a user support query, or you suspect that you might just be holding
-it wrong, drop us a line at [the mailing list](https://groups.google.com/forum/#!forum/capistrano), [StackOverflow](http://stackoverflow.com/questions/tagged/capistrano) or at [CodersClan](http://codersclan.net/?repo_id=325&source=contributing). The
-mailing list is moderated to cut down on spam, so please be patient, if you use
-StackOverflow, make sure to tag your post with "Capistrano". (Not forgetting
-any other tags which might relate, rvm, rbenv, Ubuntu, etc.)
-
-If you have an urgent problem you can use [CodersClan](http://codersclan.net/?repo_id=325&source=contributing) to solve your problem quickly. CodersClan has a community of Capistrano experts dedicated to solve code problems for bounties.
-
-Wherever you post please be sure to include the version of Capistrano you are
-using, which versions of any plugins (*capistrano-rvm*, *capistrano-bundler*,
-etc.). Proper logs are vital, if you need to redact them, go ahead, but be
-careful not to remove anything important. Please take care to format logs and
-code correctly, ideally wrapped to a sane line length, and in a mono spaced
-font. This all helps us to gather a clear understanding of what is going wrong.
-
-**If you really think that you found a bug, or want to enquire about a feature,
-or send us a patch to add a feature, or fix a bug, please keep a few things in
-mind:**
-
-## When Submitting An Issue:
-
-If you think there's a bug, please make sure it's really a bug in Capistrano.
-As Capistrano sits on the (sometimes rough) edges between SSH, Git, the
-network, Ruby, RVM, rbenv, chruby, Bundler, your Linux distribution, countless
-shell configuration files on your end, and the server… there's a good chance
-the problem lies somewhere else.
-
-Please make sure you have reviewed the FAQs at http://www.capistranorb.com/.
-
-It's really important to include as much information as possible, versions of
-everything involved, anything weird you might be doing that might be having
-side effects, include as much as you can in a [GitHub
-Gist](https://gist.github.com/) and link that from the issue, with tools such
-as Gist, we can link to individual lines and help work out what is going wrong.
-
-If you are an experienced Ruby programmer, take a few minutes to get our test
-suite running, and do what you can to get a test case written that fails, from
-there we can understand exactly what it takes to reproduce the issue (as it's
-documented with code)
-
-## When Requesting a Feature:
-
-We can't make everyone happy all of the time, and we've been around the block
-well enough to know when something doesn't work well, or when your proposed fix
-might impact other things.
-
-We prefer to [start with
-"no"](https://gettingreal.37signals.com/ch05_Start_With_No.php), and help you
-find a better way to solve your problem, sometimes the solution is to [build
-faster
-horses](http://blog.cauvin.org/2010/07/henry-fords-faster-horse-quote.html),
-sometimes the solution is to work around it in a neat way that you didn't know
-existed.
-
-Please don't be offended if we say no, and don't be afraid to fight your
-corner, try and avoid being one of the [poisonous
-people](https://www.youtube.com/watch?v=Q52kFL8zVoM)
-
-## Submitting A Pull Request:
-
-Pull requests are awesome, and if they arrive with decent tests, and conform to
-the guidelines below, we'll merge them in as soon as possible, we'll let you
-know which release we're planning them for (we adhere to
-[semver](http://semver.org/) so please don't be upset if we plan your changes
-for a later release)
-
- * The code is MIT licenced, your code will fall under the same license if we merge it.
- * We can't merge it without a [good commit
-   message](http://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message).
-   If you do this right, Github will use the commit message as the body of your
-   pull request, double win.
- * If you are referencing an improvement to an existing issue (if we have not
-   yet merged it )
- * Add an entry to the `CHANGELOG` under the `### master` section, but please
-   don't mess with the version.
- * If you add a new feature, please make sure to document it, open a
-   corresponding pull request in [the
-   documentation](https://github.com/capistrano/documentation) and mention the
-   code change pull request over there, and Github will link everything up. If
-   it's a simple feature, or a new variable, or something changed, it may be
-   appropriate simply to document it in the generated `Capfile` or `deploy.rb`, or
-   in the `README`
- * Take care to squash your commit into one single commit with a good message, it
-   saves us a lot of work in maintaining the CHANGELOG if we can generate it from
-   the commit messages between the release tags!
- * Tests! It's tricky to test some parts of Capistrano, but do your best, it
-   might just serve as a starting point for us to build a reliable test on top of,
-   and help us understand where you are coming from.
+**Hello and welcome!** Please look over this document before opening an issue or submitting a pull request to Capistrano.
+
+* [If you’re looking for help or have a question](#if-youre-looking-for-help-or-have-a-question)
+* [Reporting bugs](#reporting-bugs)
+* [Requesting new features or improvements](#requesting-new-features-or-improvements)
+* [Contributing code or documentation](#contributing-code-or-documentation)
+
+## If you’re looking for help or have a question
+
+**Check [Stack Overflow](http://stackoverflow.com/questions/tagged/capistrano) first if you need help using Capistrano.** You are more likely to get a quick response at Stack Overflow for common Capistrano topics. Make sure to tag your post with `capistrano` and/or `capistrano3` (not forgetting any other tags which might relate: rvm, rbenv, Ubuntu, etc.)
+
+If you have an urgent problem you can also try [CodersClan](http://codersclan.net/?repo_id=325&source=contributing), which has a community of Capistrano experts dedicated to solve code problems for bounties.
+
+When posting to Stack Overflow or CodersClan, be sure to include relevant information:
+
+* Capistrano version
+* Plugins and versions (capistrano-rvm, capistrano-bundler, etc.)
+* Logs and backtraces
+
+If you think you’ve found a bug in Capistrano itself, then…
+
+## Reporting bugs
+
+As much the Capistrano community tries to write good, well-tested code, bugs still happen. Sorry about that!
+
+**In case you’ve run across an already-known issue, check the FAQs first on the [official Capistrano site](http://capistranorb.com).**
+
+When opening a bug report, please include the output of the `cap <stage> doctor` task, e.g.:
+
+```
+cap production doctor
+```
+
+Also include in your report:
+
+* Versions of Ruby, Capistrano, and any plugins you’re using (if `doctor` didn't already do this for you)
+* A description of the troubleshooting steps you’ve taken
+* Logs and backtraces
+* Sections of your `deploy.rb` that may be relevant
+* Any other unique aspects of your environment
+
+If you are an experienced Ruby programmer, take a few minutes to get the Capistrano test suite running (see [DEVELOPMENT.md][]), and do what you can to get a test case written that fails. *This will be a huge help!*
+
+## Requesting new features or improvements
+
+Capistrano continues to improve thanks to people like you! Feel free to open a GitHub issue for any or all of these ideas:
+
+* New features that would make Capistrano even better
+* Areas where documentation could be improved
+* Ways to improve developer happiness
+
+Generally speaking the maintainers are very conservative about adding new features, and we can’t guarantee that the community will agree with or implement your idea. Please don’t be offended if we say no! The Capistrano team will do our best to review all suggestions and at least weigh in with a comment or suggest a workaround, if applicable.
+
+**Your idea will have a much better chance of becoming reality if you contribute code for it (even if the code is incomplete!).**
+
+## Contributing code or documentation
+
+So you want to contribute to Capistrano? Awesome! We have a whole separate document just you. It explains our pull request workflow and walks you through setting up the development environment: [DEVELOPMENT.md][].
+
+
+[DEVELOPMENT.md]: https://github.com/capistrano/capistrano/blob/master/DEVELOPMENT.md
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
new file mode 100644
index 0000000..115a1cd
--- /dev/null
+++ b/DEVELOPMENT.md
@@ -0,0 +1,122 @@
+Thanks for helping build Capistrano! Here are the development practices followed by our community.
+
+* [Who can help](#who-can-help)
+* [Setting up your development environment](#setting-up-your-development-environment)
+* [Coding guidelines](#coding-guidelines)
+* [Submitting a pull request](#submitting-a-pull-request)
+* [Managing GitHub issues](#managing-github-issues)
+* [Reviewing and merging pull requests](#reviewing-and-merging-pull-requests)
+
+## Who can help
+
+Everyone can help improve Capistrano. There are ways to contribute even if you aren’t a Ruby programmer. Here’s what you can do to help the project:
+
+* adding to or fixing typos/quality in documentation
+* adding failing tests for reported bugs
+* writing code (no contribution is too small!)
+* reviewing pull requests and suggesting improvements
+* reporting bugs or suggesting new features (see [CONTRIBUTING.md][])
+
+## Setting up your development environment
+
+Capistrano is a Ruby project, so we expect you to have a functioning Ruby environment. To hack on Capistrano you will further need some specialized tools to run its test suite.
+
+Make sure to install:
+
+* [Bundler](https://bundler.io/) 1.10.5 (see note)
+* [Vagrant](https://www.vagrantup.com/)
+* [VirtualBox](https://www.virtualbox.org/wiki/Downloads) (or another [Vagrant-supported](https://docs.vagrantup.com/v2/getting-started/providers.html) VM host)
+
+*Note: As of this writing (December 2015), Vagrant does not work with Bundler > 1.10.5. If you have multiple versions of Bundler installed, use the special underscore command-line argument to force a compatible version, like this: `bundle _1.10.5_ exec rake features`.*
+
+### Running tests
+
+Capistrano has two test suites: an RSpec suite and a Cucumber suite. The RSpec suite handles quick feedback unit specs. The Cucumber suite is an integration suite that uses Vagrant to deploy to a real virtual server.
+
+```
+# Ensure all dependencies are installed
+$ bundle install
+
+# Run the RSpec suite
+$ bundle exec rake spec
+
+# Run the Cucumber suite (requires Bundler 1.10.5)
+$ bundle exec rake features
+
+# Run the Cucumber suite and leave the VM running (faster for subsequent runs)
+$ bundle exec rake features KEEP_RUNNING=1
+```
+
+### Report failing Cucumber features!
+
+Currently, the Capistrano Travis build does *not* run the Cucumber suite. This means it is possible for a failing Cucumber feature to sneak in without being noticed by our continuous integration checks.
+
+**If you come across a failing Cucumber feature, this is a bug.** Please report it by opening a GitHub issue. Or even better: do your best to fix the feature and submit a pull request!
+
+## Coding guidelines
+
+This project uses [RuboCop](https://github.com/bbatsov/rubocop) to enforce standard Ruby coding guidelines.
+
+* Test that your contributions pass with `rake rubocop`
+* Rubocop is also run as part of the full test suite with `rake`
+* Note the Travis build will fail and your PR cannot be merged if Rubocop finds errors
+
+## Submitting a pull request
+
+Pull requests are awesome, and if they arrive with decent tests, and conform to the guidelines below, we'll merge them in as soon as possible, we'll let you know which release we're planning them for (we adhere to [semver](http://semver.org/) so please don't be upset if we plan your changes for a later release).
+
+Your code should conform to these guidelines:
+
+ * The code is MIT licenced, your code will fall under the same license if we merge it.
+ * We can't merge it without a [good commit message](http://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message). If you do this right, Github will use the commit message as the body of your pull request, double win.
+ * If you are making an improvement/fix for an existing issue, make sure to mention the issue number (if we have not yet merged it )
+ * Add an entry to the `CHANGELOG` under the `### master` section, but please don't mess with the version.
+ * If you add a new feature, please make sure to document it, open a corresponding pull request in [the documentation](https://github.com/capistrano/documentation) and mention the code change pull request over there, and Github will link everything up. If it's a simple feature, or a new variable, or something changed, it may be appropriate simply to document it in the generated `Capfile` or `deploy.rb`, or in the `README`
+ * Take care to squash your commit into one single commit with a good message, it saves us a lot of work in maintaining the CHANGELOG if we can generate it from the commit messages between the release tags!
+ * Tests! It's tricky to test some parts of Capistrano, but do your best, it might just serve as a starting point for us to build a reliable test on top of, and help us understand where you are coming from.
+
+## Managing GitHub issues
+
+The Capistrano maintainers will do our best to review all GitHub issues. Here’s how we manage issues:
+
+1. Issues will be acknowledged with an appropriate label (see below).
+2. Issues that are duplicates, spam, or irrelevant (e.g. wrong project), or are questions better suited for Stack Overflow (see [CONTRIBUTING.md][]) will be closed.
+3. Once an issue is fixed or resolved in an upcoming Capistrano release, it will be closed and assigned to a GitHub milestone for that upcoming version number.
+
+The maintainers do not have time to fix every issue ourselves, but we will gladly accept pull requests, especially for issues labeled as "you can help" (see below).
+
+### Issue labels
+
+Capistrano uses these GitHub labels to categorize issues:
+
+* **bug?** – Could be a bug (not reproducible or might be user error)
+* **confirmed bug** – Definitely a bug
+* **new feature** – A request for Capistrano to add a feature or work differently
+* **chore** – A TODO that is neither a bug nor a feature (e.g. improve docs, CI infrastructure, etc.)
+
+Also, the Capistrano team will sometimes add these labels to facilitate communication and encourage community feedback:
+
+* **discuss!** – The Capistrano team wants more feedback from the community on this issue; e.g. how a new feature should work, or whether a bug is serious or not.
+* **you can help!** – We want the community to help by submitting a pull request to solve a bug or add a feature. If you are looking for ways to contribute to Capistrano, start here!
+* **needs more info** – We need more info from the person who opened the issue; e.g. steps to reproduce a bug, clarification on a desired feature, etc.
+
+*These labels were inspired by Daniel Doubrovkine’s [2014 Golden Gate Ruby Conference talk](http://confreaks.tv/videos/gogaruco2014-taking-over-someone-else-s-open-source-projects).*
+
+## Reviewing and merging pull requests
+
+Pull requests and issues follow similar workflows. Before merging a pull request, the Capistrano maintainers will check that these requirements are met:
+
+* All CI checks pass
+* Significant changes in behavior or fixes mentioned in the CHANGELOG
+* Clean commit history
+* New features are documented
+* Code changes/additions are tested
+
+If any of these are missing, the **needs more info** label is assigned to the pull request to indicate the PR is incomplete.
+
+Merging a pull request is a decision entrusted to the maintainers of the Capistrano project. Any maintainer is free to merge a pull request if they feel the PR meets the above requirements and is in the best interest of the Capistrano community.
+
+After a pull request is merged, it is assigned to a GitHub milestone for the upcoming version number.
+
+
+[CONTRIBUTING.md]: https://github.com/capistrano/capistrano/blob/master/CONTRIBUTING.md
diff --git a/Gemfile b/Gemfile
index d41a5ae..e01eac6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,9 +1,10 @@
-source 'https://rubygems.org'
+source "https://rubygems.org"
 
 # Specify your gem's dependencies in capistrano.gemspec
 gemspec
 
 group :cucumber do
-  gem 'cucumber'
-  gem 'rspec', '~> 3.0.0'
+  gem "cucumber"
+  gem "rspec"
+  gem "rspec-core", "~> 3.4.4"
 end
diff --git a/LICENSE.txt b/LICENSE.txt
index baf6fd6..24b6d22 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,6 +1,6 @@
 MIT License (MIT)
 
-Copyright (c) 2012-2013 Tom Clements, Lee Hambley
+Copyright (c) 2012-2016 Tom Clements, Lee Hambley
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 99e1160..c0cbdcc 100644
--- a/README.md
+++ b/README.md
@@ -1,48 +1,127 @@
-# Capistrano [![Build Status](https://travis-ci.org/capistrano/capistrano.svg?branch=master)](https://travis-ci.org/capistrano/capistrano) [![Code Climate](http://img.shields.io/codeclimate/github/capistrano/capistrano.svg)](https://codeclimate.com/github/capistrano/capistrano) <a href="http://codersclan.net/?repo_id=325&source=small"><img src="http://img.shields.io/badge/get-support-blue.svg"></a>
 
-## Documentation
+# Capistrano: A deployment automation tool built on Ruby, Rake, and SSH.
 
-Check out the [online documentation](http://capistranorb.com) of Capistrano 3 hosted via this [repository](https://github.com/capistrano/capistrano.github.io).
+[![Gem Version](https://badge.fury.io/rb/capistrano.svg)](http://badge.fury.io/rb/capistrano) [![Build Status](https://travis-ci.org/capistrano/capistrano.svg?branch=master)](https://travis-ci.org/capistrano/capistrano) [![Code Climate](https://codeclimate.com/github/capistrano/capistrano/badges/gpa.svg)](https://codeclimate.com/github/capistrano/capistrano) [![CodersClan](https://img.shields.io/badge/get-support-blue.svg)](http://codersclan.net/?repo_id=325&source=small)
 
-## Support
+Capistrano is a framework for building automated deployment scripts. Although Capistrano itself is written in Ruby, it can easily be used to deploy projects of any language or framework, be it Rails, Java, or PHP.
 
-Need help with getting Capistrano up and running? Got a code problem you want to get solved quickly?
+Once installed, Capistrano gives you a `cap` tool to perform your deployments from the comfort of your command line.
 
-Get <a href="http://codersclan.net/?repo_id=325&source=link">Capistrano support on CodersClan.</a> <a href="http://codersclan.net/?repo_id=325&source=big"><img src="http://www.codersclan.net/gs_button/?repo_id=325" width="150"></a>
+```
+$ cd my-capistrano-enabled-project
+$ cap production deploy
+```
+
+When you run `cap`, Capistrano dutifully connects to your server(s) via SSH and executes the steps necessary to deploy your project. You can define those steps yourself by writing [Rake](https://github.com/ruby/rake) tasks, or by using pre-built task libraries provided by the Capistrano community.
+
+Tasks are simple to make. Here's an example:
+
+```ruby
+task :restart_sidekiq do
+  on roles(:worker) do
+    execute :service, "sidekiq restart"
+  end
+end
+after "deploy:published", "restart_sidekiq"
+```
+
+*Note: This documentation is for the current version of Capistrano (3.x). If you are looking for Capistrano 2.x documentation, you can find it in [this archive](https://github.com/capistrano/capistrano-2.x-docs).*
+
+---
+
+## Contents
+
+* [Features](#features)
+* [Gotchas](#gotchas)
+* [Quick start](#quick-start)
+* [Finding help and documentation](#finding-help-and-documentation)
+* [How to contribute](#how-to-contribute)
+* [License](#license)
+
+## Features
+
+There are many ways to automate deployments, from simple rsync bash scripts to complex containerized toolchains. Capistrano sits somewhere in the middle: it automates what you already know how to do manually with SSH, but in a repeatable, scalable fashion. There is no magic here!
+
+Here's what makes Capistrano great:
+
+#### Strong conventions
+
+Capistrano defines a standard deployment process that all Capistrano-enabled projects follow by default. You don't have to decide how to structure your scripts, where deployed files should be placed on the server, or how to perform common tasks: Capistrano has done this work for you.
+
+#### Multiple stages
+
+Define your deployment once, and then easily parameterize it for multiple *stages* (environments), e.g. `qa`, `staging`, and `production`. No copy-and-paste necessary: you only need to specify what is different for each stage, like IP addresses.
+
+#### Parallel execution
+
+Deploying to a fleet of app servers? Capistrano can run each deployment task concurrently across those servers and uses connection pooling for speed.
+
+#### Server roles
 
-## Requirements
+Your application may need many different types of servers: a database server, an app server, two app servers, and a job queue work server, for example. Capistrano lets you tag each server with one or more roles, so you can control what tasks are executed where.
 
-* Ruby >= 1.9.3 (JRuby and C-Ruby/YARV are supported)
+#### Community driven
 
-Capistrano support these source code version control systems out of the box:
+Capistrano is easily extensible using the rubygems package manager. Deploying a Rails app? Wordpress? Laravel? Chances are, someone has already written Capistrano tasks for your framework of choice and has distributed it as a gem. Many Ruby projects also come with Capistrano tasks built-in.
 
-* Git 1.8 or higher
-* Mercurial
-* SVN
+#### It's just SSH
 
-Binaries for these VCS might be required on the local and/or the remote systems.
+Everything in Capistrano comes down to running SSH commands on remote servers. On the one hand, that makes Capistrano simple. On the other hand, if you aren't comfortable SSH-ing into a Linux box and doing stuff on the command-line, then Capistrano is probably not for you.
 
-## Installation
+## Gotchas
 
-Add this line to your application's Gemfile:
+While Capistrano ships with a strong set of conventions that are common for all types of deployments, it needs help understanding the specifics of your project, and there are some things Capistrano is not suited to do.
+
+#### Project specifics
+
+Out of the box, Capistrano can deploy your code to server(s), but it does not know how to *execute* your code. Does `foreman` need to be run? Does Apache need to be restarted? You'll need to tell Capistrano how to do this part by writing these deployment steps yourself, or by finding a gem in the Capistrano community that does it for you.
+
+#### Key-based SSH
+
+Capistrano depends on connecting to your server(s) with SSH using key-based (i.e. password-less) authentication. You'll need this working before you can use Capistrano.
+
+#### Provisioning
+
+Likewise, your server(s) will likely need supporting software installed before you can perform a deployment. Capistrano itself has no requirements other than SSH, but your application probably needs database software, a web server like Apache or Nginx, and a language runtime like Java, Ruby, or PHP. These *server provisioning* steps are not done by Capistrano.
+
+#### `sudo`, etc.
+
+Capistrano is designed to deploy using a single, non-privileged SSH user, using a *non-interactive* SSH session. If your deployment requires `sudo`, interactive prompts, authenticating as one user but running commands as another, you can probably accomplish this with Capistrano, but it may be difficult. Your automated deployments will be much smoother if you can avoid such requirements.
+
+## Quick start
+
+### Requirements
+
+* Ruby version 2.0 or higher on your local machine (MRI or Rubinius)
+* A project that uses source control (Git, Mercurial, and Subversion support is built-in)
+* The SCM binaries (e.g. `git`, `hg`) needed to check out your project must be installed on the server(s) you are deploying to
+* [Bundler](http://bundler.io), along with a Gemfile for your project, are recommended
+
+### Install the Capistrano gem
+
+Add Capistrano to your project's Gemfile:
 
 ``` ruby
-gem 'capistrano', '~> 3.3.0'
+group :development do
+  gem "capistrano", "~> 3.6"
+end
 ```
 
-And then execute:
+Then run Bundler to ensure Capistrano is downloaded and installed:
 
 ``` sh
 $ bundle install
 ```
 
-Capify:
-*make sure there's no "Capfile" or "capfile" present*
+### "Capify" your project
+
+Make sure your project doesn't already have a "Capfile" or "capfile" present. Then run:
+
 ``` sh
 $ bundle exec cap install
 ```
 
-This creates the following files:
+This creates all the necessary configuration files and directory structure for a Capistrano-enabled project with two stages, `staging` and `production`:
 
 ```
 ├── Capfile
@@ -56,13 +135,15 @@ This creates the following files:
             └── tasks
 ```
 
-To create different stages:
+To customize the stages that are created, use:
 
 ``` sh
 $ bundle exec cap install STAGES=local,sandbox,qa,production
 ```
 
-## Usage
+Note that the files that Capistrano creates are simply templates to get you started. Make sure to edit the `deploy.rb` and stage files so they contain values appropriate for your project and your target servers.
+
+### Command-line usage
 
 ``` sh
 # list all available tasks
@@ -83,35 +164,32 @@ $ bundle exec cap production deploy --prereqs
 
 # trace through task invocations
 $ bundle exec cap production deploy --trace
+
+# lists all config variable before deployment tasks
+$ bundle exec cap production deploy --print-config-variables
 ```
 
-## Testing
+## Finding help and documentation
 
-Capistrano has two test suites: an RSpec suite and a Cucumber suite. The
-RSpec suite handles quick feedback unit specs. The Cucumber features are
-an integration suite that uses Vagrant to deploy to a real virtual
-server. In order to run the Cucumber suite you will need to install
-[Vagrant](http://www.vagrantup.com/) and Vagrant supported
-virtualization software like
-[VirtualBox](https://www.virtualbox.org/wiki/Downloads).
+Capistrano is a large project encompassing multiple GitHub repositories and a community of plugins, and it can be overwhelming when you are just getting started. Here are resources that can help:
 
-```
-# To run the RSpec suite
-$ rake spec
+* **[The Capistrano website](http://capistranorb.com) is the best place for official documentation**
+* [Stack Overflow](http://stackoverflow.com/questions/tagged/capistrano) has a Capistrano tag with lots of activity
+* [The Capistrano mailing list](https://groups.google.com/forum/#!forum/capistrano) is low-traffic but is monitored by Capistrano contributors
+* [CodersClan](http://codersclan.net/?repo_id=325&source=link) has Capistrano experts available to solve problems for bounties
 
-# To run the Cucumber suite
-$ rake features
+Related GitHub repositories:
 
-# To run the Cucumber suite and leave the VM running (faster for subsequent runs)
-$ rake features KEEP_RUNNING=1
-```
+* [capistrano/sshkit](https://github.com/capistrano/sshkit) provides the SSH behavior that underlies Capistrano (when you use `execute` in a Capistrano task, you are using SSHKit)
+* [capistrano/documentation](https://github.com/capistrano/documentation) is what generates the official Capistrano website
+* [capistrano/rails](https://github.com/capistrano/rails) is a very popular gem that adds Ruby on Rails deployment tasks
+* [mattbrictson/airbrussh](https://github.com/mattbrictson/airbrussh) provides Capistrano's default log formatting
+
+GitHub issues are for bug reports and feature requests. Please refer to the [CONTRIBUTING](CONTRIBUTING.md) document for guidelines on submitting GitHub issues.
 
-## SSHKit
+## How to contribute
 
-[SSHKit](https://github.com/leehambley/sshkit) is the driver for SSH
-connections behind the scenes in Capistrano. Depending on how deep you dig, you
-might run into interfaces that come directly from SSHKit (the configuration is
-a good example).
+Contributions to Capistrano, in the form of code, documentation or idea, are gladly accepted. Read the [DEVELOPMENT](DEVELOPMENT.md) document to learn how to hack on Capistrano's code, run the tests, and contribute your first pull request.
 
 ## License
 
diff --git a/RELEASING.md b/RELEASING.md
new file mode 100644
index 0000000..b110ddc
--- /dev/null
+++ b/RELEASING.md
@@ -0,0 +1,17 @@
+# Releasing
+
+## Prerequisites
+
+* You must have commit rights to the Capistrano repository.
+* You must have push rights for the capistrano gem on rubygems.org.
+
+## How to release
+
+1. Run `bundle install` to make sure that you have all the gems necessary for testing and releasing.
+2.  **Ensure all tests are passing by running `rake spec` and `rake features`.**
+3. Determine which would be the correct next version number according to [semver](http://semver.org/).
+4. Update the version in `./lib/capistrano/version.rb`.
+4. Update the version in the `./README.md` Gemfile example (`gem "capistrano", "~> X.Y"`).
+5. Update the `CHANGELOG`.
+6. Commit the changelog and version in a single commit, the message should be "Preparing vX.Y.Z"
+7. Run `rake release`; this will tag, push to GitHub, and publish to rubygems.org.
diff --git a/Rakefile b/Rakefile
index fc943b1..7c57938 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,9 +1,12 @@
 require "bundler/gem_tasks"
 require "cucumber/rake/task"
 require "rspec/core/rake_task"
+require "rubocop/rake_task"
 
-task :default => :spec
+task default: [:spec, :rubocop]
 RSpec::Core::RakeTask.new
 
 Cucumber::Rake::Task.new(:features)
 
+desc "Run RuboCop checks"
+RuboCop::RakeTask.new
diff --git a/UPGRADING-3.7.md b/UPGRADING-3.7.md
new file mode 100644
index 0000000..75da210
--- /dev/null
+++ b/UPGRADING-3.7.md
@@ -0,0 +1,97 @@
+# Capistrano 3.7.0 upgrade guide
+
+Capistrano 3.7.0 has not yet been released. This guide serves as a preview of
+what is *planned* for 3.7.0, so that you can be prepared to update your
+Capistrano deployment if necessary once it becomes available.
+
+If you wish to try the new 3.7.0 behavior today, you can do so by using the
+`master` branch in your Gemfile:
+
+```ruby
+gem "capistrano", :github => "capistrano/capistrano"
+```
+
+## The :scm variable is deprecated
+
+Up until now, Capistrano's SCM was configured using the `:scm` variable:
+
+```ruby
+# This is now deprecated
+set :scm, :svn
+```
+
+To avoid deprecation warnings:
+
+1. Remove `set :scm, ...` from your Capistrano configuration.
+2. Add *one* of the following SCM declarations to your `Capfile`:
+
+```ruby
+# To use Git
+require "capistrano/scm/git"
+install_plugin Capistrano::SCM::Git
+
+# To use Mercurial
+require "capistrano/scm/hg"
+install_plugin Capistrano::SCM::Hg
+
+# To use Subversion
+require "capistrano/scm/svn"
+install_plugin Capistrano::SCM::Svn
+```
+
+## This is the last release where Git is the automatic default
+
+If you do not specify an SCM, Capistrano assumes Git. However this behavior is
+now deprecated. Add this to your Capfile to avoid deprecation warnings:
+
+```ruby
+require "capistrano/scm/git"
+install_plugin Capistrano::SCM::Git
+```
+
+## :git_strategy, :hg_strategy, and :svn_strategy are removed
+
+Capistrano 3.7.0 has a rewritten SCM system that relies on "plugins". This
+system is more flexible than the old "strategy" system that only allowed certain
+parts of SCM tasks to be customized.
+
+If your deployment relies on a custom SCM strategy, you will need to rewrite
+that strategy to be a full-fledged SCM plugin instead. There is a fairly
+straightforward migration path: write your plugin to be a subclass of the
+built-in SCM that you want to customize. For example:
+
+```ruby
+require "capistrano/scm/git"
+
+class MyCustomGit < Capistrano::SCM::Git
+  # Override the methods you wish to customize, e.g.:
+  def clone_repo
+    # ...
+  end
+end
+```
+
+Then use your plugin in by loading it in the Capfile:
+
+```ruby
+require_relative "path/to/my_custom_git.rb"
+install_plugin MyCustomGit
+```
+
+## Existing third-party SCMs are deprecated
+
+If you are using a third-party SCM, you can continue using it without
+changes, but you will see deprecation warnings. Contact the maintainer of the
+third-party SCM gem and ask them about modifying the gem to work with the new
+Capistrano 3.7.0 SCM plugin system.
+
+## remote_file is removed
+
+The `remote_file` method is no longer in Capistrano 3.7.0. You can read the
+discussion that led to its removal here:
+[issue 762](https://github.com/capistrano/capistrano/issues/762).
+
+There is no direct replacement. To migrate to 3.7.0, you will need to rewrite
+any parts of your deployment that use `remote_file` to use a different
+mechanism for uploading files. Consider using the `upload!` method directly in
+a procedural fashion instead.
diff --git a/bin/cap b/bin/cap
index 5f4773b..d97ca4b 100755
--- a/bin/cap
+++ b/bin/cap
@@ -1,3 +1,3 @@
 #!/usr/bin/env ruby
-require 'capistrano/all'
+require "capistrano/all"
 Capistrano::Application.new.run
diff --git a/capistrano.gemspec b/capistrano.gemspec
index 40e3c48..3d66af3 100644
--- a/capistrano.gemspec
+++ b/capistrano.gemspec
@@ -1,37 +1,32 @@
 # -*- encoding: utf-8 -*-
-lib = File.expand_path('../lib', __FILE__)
+lib = File.expand_path("../lib", __FILE__)
 $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
-require 'capistrano/version'
+require "capistrano/version"
 
 Gem::Specification.new do |gem|
   gem.name          = "capistrano"
   gem.version       = Capistrano::VERSION
   gem.authors       = ["Tom Clements", "Lee Hambley"]
   gem.email         = ["seenmyfate at gmail.com", "lee.hambley at gmail.com"]
-  gem.description   = %q{Capistrano is a utility and framework for executing commands in parallel on multiple remote machines, via SSH.}
-  gem.summary       = %q{Capistrano - Welcome to easy deployment with Ruby over SSH}
+  gem.description   = "Capistrano is a utility and framework for executing commands in parallel on multiple remote machines, via SSH."
+  gem.summary       = "Capistrano - Welcome to easy deployment with Ruby over SSH"
   gem.homepage      = "http://capistranorb.com/"
 
-  gem.files         = `git ls-files`.split($/)
-  gem.executables   = ['cap', 'capify']
+  gem.files         = `git ls-files -z`.split("\x0")
+  gem.executables   = %w(cap capify)
   gem.test_files    = gem.files.grep(%r{^(test|spec|features)/})
   gem.require_paths = ["lib"]
 
-  gem.licenses      = ['MIT']
+  gem.licenses      = ["MIT"]
 
-  gem.post_install_message = <<eos
-Capistrano 3.1 has some breaking changes. Please check the CHANGELOG: http://goo.gl/SxB0lr
+  gem.required_ruby_version = ">= 2.0"
+  gem.add_dependency "airbrussh", ">= 1.0.0"
+  gem.add_dependency "i18n"
+  gem.add_dependency "rake", ">= 10.0.0"
+  gem.add_dependency "sshkit", ">= 1.9.0"
+  gem.add_dependency "capistrano-harrow"
 
-If you're upgrading Capistrano from 2.x, we recommend to read the upgrade guide: http://goo.gl/4536kB
-
-The `deploy:restart` hook for passenger applications is now in a separate gem called capistrano-passenger.  Just add it to your Gemfile and require it in your Capfile.
-eos
-
-  gem.required_ruby_version = '>= 1.9.3'
-  gem.add_dependency 'sshkit', '~> 1.3'
-  gem.add_dependency 'rake', '>= 10.0.0'
-  gem.add_dependency 'i18n'
-
-  gem.add_development_dependency 'rspec'
-  gem.add_development_dependency 'mocha'
+  gem.add_development_dependency "rspec"
+  gem.add_development_dependency "mocha"
+  gem.add_development_dependency "rubocop"
 end
diff --git a/features/deploy.feature b/features/deploy.feature
index b9fea56..4425e1f 100644
--- a/features/deploy.feature
+++ b/features/deploy.feature
@@ -8,6 +8,7 @@ Feature: Deploy
     When I run cap "git:check"
     Then the task is successful
     And references in the remote repo are listed
+    And git wrapper permissions are 0700
 
   Scenario: Creating the directory structure
     When I run cap "deploy:check:directories"
diff --git a/features/doctor.feature b/features/doctor.feature
new file mode 100644
index 0000000..692fffd
--- /dev/null
+++ b/features/doctor.feature
@@ -0,0 +1,11 @@
+Feature: Doctor
+
+  Background:
+    Given a test app with the default configuration
+
+  Scenario: Running the doctor task
+    When I run cap "doctor"
+    Then the task is successful
+    And contains "Environment" in the output
+    And contains "Gems" in the output
+    And contains "Variables" in the output
diff --git a/features/stage_failure.feature b/features/stage_failure.feature
new file mode 100644
index 0000000..9201f2b
--- /dev/null
+++ b/features/stage_failure.feature
@@ -0,0 +1,9 @@
+Feature: Stage failure
+
+  Background:
+    Given a test app with the default configuration
+    And a stage file named deploy.rb
+
+  Scenario: Running a task
+    When I run cap "doctor"
+    Then the task fails
diff --git a/features/step_definitions/assertions.rb b/features/step_definitions/assertions.rb
index 3e54ddd..f5a8db2 100644
--- a/features/step_definitions/assertions.rb
+++ b/features/step_definitions/assertions.rb
@@ -1,5 +1,10 @@
 Then(/^references in the remote repo are listed$/) do
-  expect(@output).to include('refs/heads/master')
+  expect(@output).to include("refs/heads/master")
+end
+
+Then(/^git wrapper permissions are 0700$/) do
+  permissions_test = %Q([ $(stat -c "%a" #{TestApp.git_wrapper_path}) == "700" ])
+  expect(vagrant_cli_command("ssh -c '#{permissions_test}'")).to be_success
 end
 
 Then(/^the shared path is created$/) do
@@ -18,7 +23,7 @@ end
 
 Then(/^directories referenced in :linked_files are created in shared$/) do
   dirs = TestApp.linked_files.map { |path| TestApp.shared_path.join(path).dirname }
-  dirs.each do | dir|
+  dirs.each do |dir|
     run_vagrant_command(test_dir_exists(dir))
   end
 end
@@ -49,27 +54,27 @@ Then(/^the current directory will be a symlink to the release$/) do
 end
 
 Then(/^the deploy\.rb file is created$/) do
-  file = TestApp.test_app_path.join('config/deploy.rb')
-  expect(File.exists?(file)).to be true
+  file = TestApp.test_app_path.join("config/deploy.rb")
+  expect(File.exist?(file)).to be true
 end
 
 Then(/^the default stage files are created$/) do
-  staging = TestApp.test_app_path.join('config/deploy/staging.rb')
-  production = TestApp.test_app_path.join('config/deploy/production.rb')
-  expect(File.exists?(staging)).to be true
-  expect(File.exists?(production)).to be true
+  staging = TestApp.test_app_path.join("config/deploy/staging.rb")
+  production = TestApp.test_app_path.join("config/deploy/production.rb")
+  expect(File.exist?(staging)).to be true
+  expect(File.exist?(production)).to be true
 end
 
 Then(/^the tasks folder is created$/) do
-  path = TestApp.test_app_path.join('lib/capistrano/tasks')
-  expect(Dir.exists?(path)).to be true
+  path = TestApp.test_app_path.join("lib/capistrano/tasks")
+  expect(Dir.exist?(path)).to be true
 end
 
 Then(/^the specified stage files are created$/) do
-  qa = TestApp.test_app_path.join('config/deploy/qa.rb')
-  production = TestApp.test_app_path.join('config/deploy/production.rb')
-  expect(File.exists?(qa)).to be true
-  expect(File.exists?(production)).to be true
+  qa = TestApp.test_app_path.join("config/deploy/qa.rb")
+  production = TestApp.test_app_path.join("config/deploy/production.rb")
+  expect(File.exist?(qa)).to be true
+  expect(File.exist?(production)).to be true
 end
 
 Then(/^it creates the file with the remote_task prerequisite$/) do
@@ -91,18 +96,18 @@ Then(/^the task fails$/) do
 end
 
 Then(/^the failure task will run$/) do
-  failed = TestApp.shared_path.join('failed')
+  failed = TestApp.shared_path.join("failed")
   run_vagrant_command(test_file_exists(failed))
 end
 
 Then(/^the failure task will not run$/) do
-  failed = TestApp.shared_path.join('failed')
+  failed = TestApp.shared_path.join("failed")
   expect { run_vagrant_command(test_file_exists(failed)) }
     .to raise_error(VagrantHelpers::VagrantSSHCommandError)
 end
 
 When(/^an error is raised$/) do
-  error = TestApp.shared_path.join('fail')
+  error = TestApp.shared_path.join("fail")
   run_vagrant_command(test_file_exists(error))
 end
 
diff --git a/features/step_definitions/cap_commands.rb b/features/step_definitions/cap_commands.rb
index 2d6f30c..10c93f5 100644
--- a/features/step_definitions/cap_commands.rb
+++ b/features/step_definitions/cap_commands.rb
@@ -9,4 +9,3 @@ end
 When(/^I run "(.*?)"$/) do |command|
   @success, @output = TestApp.run(command)
 end
-
diff --git a/features/step_definitions/setup.rb b/features/step_definitions/setup.rb
index 8fc1e1c..e1b7e5a 100644
--- a/features/step_definitions/setup.rb
+++ b/features/step_definitions/setup.rb
@@ -3,7 +3,11 @@ Given(/^a test app with the default configuration$/) do
 end
 
 Given(/^servers with the roles app and web$/) do
-  vagrant_cli_command('up') rescue nil
+  begin
+    vagrant_cli_command("up")
+  rescue
+    nil
+  end
 end
 
 Given(/^a linked file "(.*?)"$/) do |file|
@@ -24,11 +28,11 @@ Given(/^file "(.*?)" does not exist in shared path$/) do |file|
 end
 
 Given(/^a custom task to generate a file$/) do
-  TestApp.copy_task_to_test_app('spec/support/tasks/database.rake')
+  TestApp.copy_task_to_test_app("spec/support/tasks/database.rake")
 end
 
 Given(/^a task which executes as root$/) do
-  TestApp.copy_task_to_test_app('spec/support/tasks/root.rake')
+  TestApp.copy_task_to_test_app("spec/support/tasks/root.rake")
 end
 
 Given(/config stage file has line "(.*?)"/) do |line|
@@ -36,15 +40,19 @@ Given(/config stage file has line "(.*?)"/) do |line|
 end
 
 Given(/^the configuration is in a custom location$/) do
-  TestApp.move_configuration_to_custom_location('app')
+  TestApp.move_configuration_to_custom_location("app")
 end
 
 Given(/^a custom task that will simulate a failure$/) do
-  safely_remove_file(TestApp.shared_path.join('failed'))
-  TestApp.copy_task_to_test_app('spec/support/tasks/fail.rake')
+  safely_remove_file(TestApp.shared_path.join("failed"))
+  TestApp.copy_task_to_test_app("spec/support/tasks/fail.rake")
 end
 
 Given(/^a custom task to run in the event of a failure$/) do
-  safely_remove_file(TestApp.shared_path.join('failed'))
-  TestApp.copy_task_to_test_app('spec/support/tasks/failed.rake')
+  safely_remove_file(TestApp.shared_path.join("failed"))
+  TestApp.copy_task_to_test_app("spec/support/tasks/failed.rake")
+end
+
+Given(/^a stage file named (.+)$/) do |filename|
+  TestApp.write_local_stage_file(filename)
 end
diff --git a/features/support/env.rb b/features/support/env.rb
index 853b6f1..8fcb849 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -1,11 +1,11 @@
-PROJECT_ROOT = File.expand_path('../../../', __FILE__)
-VAGRANT_ROOT = File.join(PROJECT_ROOT, 'spec/support')
-VAGRANT_BIN = ENV['VAGRANT_BIN'] || "vagrant"
+PROJECT_ROOT = File.expand_path("../../../", __FILE__)
+VAGRANT_ROOT = File.join(PROJECT_ROOT, "spec/support")
+VAGRANT_BIN = ENV["VAGRANT_BIN"] || "vagrant"
 
 at_exit do
-  if ENV['KEEP_RUNNING']
+  if ENV["KEEP_RUNNING"]
     VagrantHelpers.run_vagrant_command("rm -rf /home/vagrant/var")
   end
 end
 
-require_relative '../../spec/support/test_app'
+require_relative "../../spec/support/test_app"
diff --git a/features/support/remote_command_helpers.rb b/features/support/remote_command_helpers.rb
index 9b5232b..91420c1 100644
--- a/features/support/remote_command_helpers.rb
+++ b/features/support/remote_command_helpers.rb
@@ -1,22 +1,24 @@
 module RemoteCommandHelpers
   def test_dir_exists(path)
-    exists?('d', path)
+    exists?("d", path)
   end
 
   def test_symlink_exists(path)
-    exists?('L', path)
+    exists?("L", path)
   end
 
   def test_file_exists(path)
-    exists?('f', path)
+    exists?("f", path)
   end
 
   def exists?(type, path)
-    %{[ -#{type} "#{path}" ]}
+    %Q{[ -#{type} "#{path}" ]}
   end
 
-  def safely_remove_file(path)
-    run_vagrant_command("rm #{test_file}") rescue VagrantHelpers::VagrantSSHCommandError
+  def safely_remove_file(_path)
+    run_vagrant_command("rm #{test_file}")
+  rescue
+    VagrantHelpers::VagrantSSHCommandError
   end
 end
 
diff --git a/features/support/vagrant_helpers.rb b/features/support/vagrant_helpers.rb
index 1a1822a..a42fdb2 100644
--- a/features/support/vagrant_helpers.rb
+++ b/features/support/vagrant_helpers.rb
@@ -1,10 +1,12 @@
+require "English"
+
 module VagrantHelpers
   extend self
 
   class VagrantSSHCommandError < RuntimeError; end
 
   at_exit do
-    if ENV['KEEP_RUNNING']
+    if ENV["KEEP_RUNNING"]
       puts "Vagrant vm will be left up because KEEP_RUNNING is set."
       puts "Rerun without KEEP_RUNNING set to cleanup the vm."
     else
@@ -19,17 +21,16 @@ module VagrantHelpers
         puts "[vagrant] #{line}"
       end
     end
-    $?
+    $CHILD_STATUS
   end
 
   def run_vagrant_command(command)
     if (status = vagrant_cli_command("ssh -c #{command.inspect}")).success?
       true
     else
-      fail VagrantSSHCommandError, status
+      raise VagrantSSHCommandError, status
     end
   end
-
 end
 
 World(VagrantHelpers)
diff --git a/issue_template.md b/issue_template.md
new file mode 100644
index 0000000..c7d5e32
--- /dev/null
+++ b/issue_template.md
@@ -0,0 +1,21 @@
+**Important:** GitHub issues are for feature requests or bug reports. The Capistrano team recommends you use [Stack Overflow](http://stackoverflow.com/questions/tagged/capistrano) for general questions. For more details, please see our [contribution policy](https://github.com/capistrano/capistrano/blob/master/CONTRIBUTING.md).
+
+---
+
+#### Steps to reproduce
+
+1. Lorem.
+2. Ipsum..
+3. Dolor...
+
+#### Expected behaviour
+
+Tell us what should happen
+
+#### Actual behaviour
+
+Tell us what happens instead
+
+#### Your configuration
+
+Paste Capistrano's `doctor` output here (`cap <stage> doctor`):
diff --git a/lib/Capfile b/lib/Capfile
index 8211354..ee875f2 100644
--- a/lib/Capfile
+++ b/lib/Capfile
@@ -1,3 +1,7 @@
 #!/usr/bin/env cap
 include Capistrano::DSL
-require 'capistrano/install'
+require "capistrano/install"
+
+require "capistrano/harrow"
+require "capistrano/harrow/plugin"
+install_plugin Capistrano::Harrow::Plugin
diff --git a/lib/capistrano/all.rb b/lib/capistrano/all.rb
index 334d2a7..80c966b 100644
--- a/lib/capistrano/all.rb
+++ b/lib/capistrano/all.rb
@@ -1,17 +1,16 @@
-require 'rake'
-require 'sshkit'
+require "rake"
+require "sshkit"
 
-require 'io/console'
+require "io/console"
 
 Rake.application.options.trace = true
 
-require 'capistrano/version'
-require 'capistrano/version_validator'
-require 'capistrano/i18n'
-require 'capistrano/dsl'
-require 'capistrano/application'
-require 'capistrano/configuration'
+require "capistrano/version"
+require "capistrano/version_validator"
+require "capistrano/i18n"
+require "capistrano/dsl"
+require "capistrano/application"
+require "capistrano/configuration"
 
 module Capistrano
-
 end
diff --git a/lib/capistrano/application.rb b/lib/capistrano/application.rb
index caefa57..f3105a4 100644
--- a/lib/capistrano/application.rb
+++ b/lib/capistrano/application.rb
@@ -1,6 +1,5 @@
 module Capistrano
   class Application < Rake::Application
-
     def initialize
       super
       @rakefiles = %w{capfile Capfile capfile.rb Capfile.rb} << capfile
@@ -21,11 +20,11 @@ module Capistrano
         switch =~ /--#{Regexp.union(not_applicable_to_capistrano)}/
       end
 
-      super.push(version, dry_run, roles, hostfilter)
+      super.push(version, dry_run, roles, hostfilter, print_config_variables)
     end
 
     def handle_options
-      options.rakelib = ['rakelib']
+      options.rakelib = ["rakelib"]
       options.trace_output = $stderr
 
       OptionParser.new do |opts|
@@ -48,11 +47,10 @@ module Capistrano
         end
 
         standard_rake_options.each { |args| opts.on(*args) }
-        opts.environment('RAKEOPT')
+        opts.environment("RAKEOPT")
       end.parse!
     end
 
-
     def top_level_tasks
       if tasks_without_stage_dependency.include?(@top_level_tasks.first)
         @top_level_tasks
@@ -63,13 +61,10 @@ module Capistrano
 
     def display_error_message(ex)
       unless options.backtrace
-        if loc = Rake.application.find_rakefile_location
-          whitelist = (@imported.dup << loc[0]).map{|f| File.absolute_path(f, loc[1])}
-          pattern = %r@^(?!#{whitelist.map{|p| Regexp.quote(p)}.join('|')})@
-          Rake.application.options.suppress_backtrace_pattern = pattern
-        end
+        Rake.application.options.suppress_backtrace_pattern = backtrace_pattern if backtrace_pattern
         trace "(Backtrace restricted to imported tasks)"
       end
+
       super
     end
 
@@ -83,10 +78,18 @@ module Capistrano
 
     private
 
+    def backtrace_pattern
+      loc = Rake.application.find_rakefile_location
+      return unless loc
+
+      whitelist = (@imported.dup << loc[0]).map { |f| File.absolute_path(f, loc[1]) }
+      /^(?!#{whitelist.map { |p| Regexp.quote(p) }.join('|')})/
+    end
+
     def load_imports
       if options.show_tasks
-        invoke 'load:defaults'
-        set(:stage, '')
+        invoke "load:defaults"
+        set(:stage, "")
         Dir[deploy_config_path].each { |f| add_import f }
       end
 
@@ -95,46 +98,48 @@ module Capistrano
 
     # allows the `cap install` task to load without a capfile
     def capfile
-      File.expand_path(File.join(File.dirname(__FILE__),'..','Capfile'))
+      File.expand_path(File.join(File.dirname(__FILE__), "..", "Capfile"))
     end
 
     def version
-      ['--version', '-V',
+      ["--version", "-V",
        "Display the program version.",
-       lambda { |value|
-         puts "Capistrano Version: #{Capistrano::VERSION} (Rake Version: #{RAKEVERSION})"
+       lambda do |_value|
+         puts "Capistrano Version: #{Capistrano::VERSION} (Rake Version: #{Rake::VERSION})"
          exit
-       }
-      ]
+       end]
     end
 
     def dry_run
-      ['--dry-run', '-n',
+      ["--dry-run", "-n",
        "Do a dry run without executing actions",
-       lambda { |value|
+       lambda do |_value|
          Configuration.env.set(:sshkit_backend, SSHKit::Backend::Printer)
-       }
-      ]
+       end]
     end
 
     def roles
-      ['--roles ROLES', '-r',
+      ["--roles ROLES", "-r",
        "Run SSH commands only on hosts matching these roles",
-       lambda { |value|
+       lambda do |value|
          Configuration.env.add_cmdline_filter(:role, value)
-       }
-      ]
+       end]
     end
 
     def hostfilter
-      ['--hosts HOSTS', '-z',
+      ["--hosts HOSTS", "-z",
        "Run SSH commands only on matching hosts",
-       lambda { |value|
+       lambda do |value|
          Configuration.env.add_cmdline_filter(:host, value)
-       }
-      ]
+       end]
     end
 
+    def print_config_variables
+      ["--print-config-variables", "-p",
+       "Display the defined config variables before starting the deployment tasks.",
+       lambda do |_value|
+         Configuration.env.set(:print_config_variables, true)
+       end]
+    end
   end
-
 end
diff --git a/lib/capistrano/configuration.rb b/lib/capistrano/configuration.rb
index e025869..20d8ddf 100644
--- a/lib/capistrano/configuration.rb
+++ b/lib/capistrano/configuration.rb
@@ -1,15 +1,15 @@
-require_relative 'configuration/filter'
-require_relative 'configuration/question'
-require_relative 'configuration/server'
-require_relative 'configuration/servers'
+require_relative "configuration/filter"
+require_relative "configuration/question"
+require_relative "configuration/plugin_installer"
+require_relative "configuration/server"
+require_relative "configuration/servers"
+require_relative "configuration/validated_variables"
+require_relative "configuration/variables"
 
 module Capistrano
-  class Configuration
-
-    def initialize(config = nil)
-      @config ||= config
-    end
+  class ValidationError < RuntimeError; end
 
+  class Configuration
     def self.env
       @env ||= new
     end
@@ -18,38 +18,49 @@ module Capistrano
       @env = new
     end
 
+    extend Forwardable
+    attr_reader :variables
+    def_delegators :variables,
+                   :set, :fetch, :fetch_for, :delete, :keys, :validate
+
+    def initialize(values={})
+      @variables = ValidatedVariables.new(Variables.new(values))
+    end
+
     def ask(key, default=nil, options={})
       question = Question.new(key, default, options)
       set(key, question)
     end
 
-    def set(key, value)
-      config[key] = value
+    def set_if_empty(key, value=nil, &block)
+      set(key, value, &block) unless keys.include?(key)
     end
 
-    def set_if_empty(key, value)
-      config[key] = value unless config.has_key? key
+    def append(key, *values)
+      set(key, Array(fetch(key)).concat(values))
     end
 
-    def delete(key)
-      config.delete(key)
+    def remove(key, *values)
+      set(key, Array(fetch(key)) - values)
     end
 
-    def fetch(key, default=nil, &block)
-      value = fetch_for(key, default, &block)
-      while callable_without_parameters?(value)
-        value = set(key, value.call)
+    def any?(key)
+      value = fetch(key)
+      if value && value.respond_to?(:any?)
+        value.any?
+      else
+        !fetch(key).nil?
       end
-      return value
     end
 
-    def keys
-      config.keys
+    def is_question?(key)
+      value = fetch_for(key, nil)
+      !value.nil? && value.is_a?(Question)
     end
 
     def role(name, hosts, options={})
       if name == :all
-        raise ArgumentError.new("#{name} reserved name for role. Please choose another name")
+        raise ArgumentError, "#{name} reserved name for role. Please choose another name"
       end
 
       servers.add_role(name, hosts, options)
@@ -79,14 +90,14 @@ module Capistrano
 
     def configure_backend
       backend.configure do |sshkit|
-        sshkit.format           = fetch(:format)
+        configure_sshkit_output(sshkit)
         sshkit.output_verbosity = fetch(:log_level)
         sshkit.default_env      = fetch(:default_env)
         sshkit.backend          = fetch(:sshkit_backend, SSHKit::Backend::Netssh)
         sshkit.backend.configure do |backend|
           backend.pty                = fetch(:pty)
           backend.connection_timeout = fetch(:connection_timeout)
-          backend.ssh_options        = (backend.ssh_options || {}).merge(fetch(:ssh_options,{}))
+          backend.ssh_options        = (backend.ssh_options || {}).merge(fetch(:ssh_options, {}))
         end
       end
     end
@@ -97,9 +108,11 @@ module Capistrano
 
     def setup_filters
       @filters = cmdline_filters.clone
-      @filters << Filter.new(:role, ENV['ROLES']) if ENV['ROLES']
-      @filters << Filter.new(:host, ENV['HOSTS']) if ENV['HOSTS']
-      fh = fetch_for(:filter,{})
+      @filters << Filter.new(:role, ENV["ROLES"]) if ENV["ROLES"]
+      @filters << Filter.new(:host, ENV["HOSTS"]) if ENV["HOSTS"]
+      fh = fetch_for(:filter, {}) || {}
+      @filters << Filter.new(:host, fh[:hosts]) if fh[:hosts]
+      @filters << Filter.new(:role, fh[:roles]) if fh[:roles]
       @filters << Filter.new(:host, fh[:host]) if fh[:host]
       @filters << Filter.new(:role, fh[:role]) if fh[:role]
     end
@@ -108,35 +121,38 @@ module Capistrano
       cmdline_filters << Filter.new(type, values)
     end
 
-    def filter list
+    def filter(list)
       setup_filters if @filters.nil?
-      @filters.reduce(list) { |l,f| f.filter l }
+      @filters.reduce(list) { |l, f| f.filter l }
     end
 
-    private
+    def dry_run?
+      fetch(:sshkit_backend) == SSHKit::Backend::Printer
+    end
 
-    def cmdline_filters
-      @cmdline_filters ||= []
+    def install_plugin(plugin, load_hooks: true)
+      installer.install(plugin, load_hooks: load_hooks)
     end
 
     def servers
       @servers ||= Servers.new
     end
 
-    def config
-      @config ||= Hash.new
+    private
+
+    def cmdline_filters
+      @cmdline_filters ||= []
     end
 
-    def fetch_for(key, default, &block)
-      if block_given?
-        config.fetch(key, &block)
-      else
-        config.fetch(key, default)
-      end
+    def installer
+      @installer ||= PluginInstaller.new
     end
 
-    def callable_without_parameters?(x)
-      x.respond_to?(:call) && ( !x.respond_to?(:arity) || x.arity == 0)
+    def configure_sshkit_output(sshkit)
+      format_args = [fetch(:format)]
+      format_args.push(fetch(:format_options)) if any?(:format_options)
+
+      sshkit.use_format(*format_args)
     end
   end
 end
diff --git a/lib/capistrano/configuration/empty_filter.rb b/lib/capistrano/configuration/empty_filter.rb
new file mode 100644
index 0000000..7ed8eed
--- /dev/null
+++ b/lib/capistrano/configuration/empty_filter.rb
@@ -0,0 +1,9 @@
+module Capistrano
+  class Configuration
+    class EmptyFilter
+      def filter(_servers)
+        []
+      end
+    end
+  end
+end
diff --git a/lib/capistrano/configuration/filter.rb b/lib/capistrano/configuration/filter.rb
index 2e22a19..5974d03 100644
--- a/lib/capistrano/configuration/filter.rb
+++ b/lib/capistrano/configuration/filter.rb
@@ -1,55 +1,25 @@
-require 'capistrano/configuration'
+require "capistrano/configuration"
+require "capistrano/configuration/empty_filter"
+require "capistrano/configuration/host_filter"
+require "capistrano/configuration/null_filter"
+require "capistrano/configuration/role_filter"
 
 module Capistrano
   class Configuration
     class Filter
-      def initialize type, values = nil
-        raise "Invalid filter type #{type}" unless [:host,:role].include? type
-        av = Array(values).dup
-        @mode = case
-                when av.size == 0 then :none
-                when av.include?(:all) then :all
-                else type
-                end
-        @rex = case @mode
-          when :host
-            av.map!{|v| (v.is_a?(String) && v =~ /^(?<name>[-A-Za-z0-9.]+)(,\g<name>)*$/) ? v.split(',') : v }
-            av.flatten!
-            av.map! do |v|
-              case v
-              when Regexp then v
-              else
-                vs = v.to_s
-                vs =~ /^[-A-Za-z0-9.]+$/ ? vs : Regexp.new(vs)
-              end
-            end
-            Regexp.union av
-          when :role
-            av.map!{|v| v.is_a?(String) ? v.split(',') : v }
-            av.flatten!
-            av.map! do |v|
-              case v
-              when Regexp then v
-              else
-                vs = v.to_s
-                vs =~ %r{^/(.+)/$} ? Regexp.new($1) : %r{^#{vs}$}
-              end
-            end
-            Regexp.union av
-          else
-            nil
-          end
+      def initialize(type, values=nil)
+        raise "Invalid filter type #{type}" unless [:host, :role].include? type
+        av = Array(values)
+        @strategy = if av.empty? then EmptyFilter.new
+                    elsif av.include?(:all) || av.include?("all") then NullFilter.new
+                    elsif type == :host then HostFilter.new(values)
+                    elsif type == :role then RoleFilter.new(values)
+                    else NullFilter.new
+                    end
       end
-      def filter servers
-        as = Array(servers)
-        case @mode
-        when :none then return []
-        when :all  then return servers
-        when :host
-          as.select {|s| @rex.match s.hostname}
-        when :role
-          as.select {|s| s.roles.any? {|r| @rex.match r} }
-        end
+
+      def filter(servers)
+        @strategy.filter servers
       end
     end
   end
diff --git a/lib/capistrano/configuration/host_filter.rb b/lib/capistrano/configuration/host_filter.rb
new file mode 100644
index 0000000..dd16835
--- /dev/null
+++ b/lib/capistrano/configuration/host_filter.rb
@@ -0,0 +1,30 @@
+module Capistrano
+  class Configuration
+    class HostFilter
+      def initialize(values)
+        av = Array(values).dup
+        av.map! { |v| v.is_a?(String) && v =~ /^(?<name>[-A-Za-z0-9.]+)(,\g<name>)*$/ ? v.split(",") : v }
+        av.flatten!
+        @rex = regex_matcher(av)
+      end
+
+      def filter(servers)
+        Array(servers).select { |s| @rex.match s.to_s }
+      end
+
+      private
+
+      def regex_matcher(values)
+        values.map! do |v|
+          case v
+          when Regexp then v
+          else
+            vs = v.to_s
+            vs =~ /^[-A-Za-z0-9.]+$/ ? vs : Regexp.new(vs)
+          end
+        end
+        Regexp.union values
+      end
+    end
+  end
+end
diff --git a/lib/capistrano/configuration/null_filter.rb b/lib/capistrano/configuration/null_filter.rb
new file mode 100644
index 0000000..9d5089d
--- /dev/null
+++ b/lib/capistrano/configuration/null_filter.rb
@@ -0,0 +1,9 @@
+module Capistrano
+  class Configuration
+    class NullFilter
+      def filter(servers)
+        servers
+      end
+    end
+  end
+end
diff --git a/lib/capistrano/configuration/plugin_installer.rb b/lib/capistrano/configuration/plugin_installer.rb
new file mode 100644
index 0000000..221e8b9
--- /dev/null
+++ b/lib/capistrano/configuration/plugin_installer.rb
@@ -0,0 +1,33 @@
+# Encapsulates the logic for installing plugins into Capistrano. Plugins must
+# simply conform to a basic API; the PluginInstaller takes care of invoking the
+# API at appropriate times.
+#
+# This class is not used directly; instead it is typically accessed via the
+# `install_plugin` method of the Capistrano DSL.
+#
+module Capistrano
+  class Configuration
+    class PluginInstaller
+      # "Installs" a Plugin into Capistrano by loading its tasks, hooks, and
+      # defaults at the appropriate time. The hooks in particular can be
+      # skipped, if you want full control over when and how the plugin's tasks
+      # are executed. Simply pass `load_hooks:false` to opt out.
+      #
+      # The plugin class or instance may be provided. These are equivalent:
+      #
+      # install(Capistrano::SCM::Git)
+      # install(Capistrano::SCM::Git.new)
+      #
+      def install(plugin, load_hooks: true)
+        plugin = plugin.is_a?(Class) ? plugin.new : plugin
+
+        plugin.define_tasks
+        plugin.register_hooks if load_hooks
+
+        Rake::Task.define_task("load:defaults") do
+          plugin.set_defaults
+        end
+      end
+    end
+  end
+end
diff --git a/lib/capistrano/configuration/question.rb b/lib/capistrano/configuration/question.rb
index 4447011..0226efd 100644
--- a/lib/capistrano/configuration/question.rb
+++ b/lib/capistrano/configuration/question.rb
@@ -1,9 +1,10 @@
 module Capistrano
   class Configuration
     class Question
-
-      def initialize(key, default, options = {})
-        @key, @default, @options = key, default, options
+      def initialize(key, default, options={})
+        @key = key
+        @default = default
+        @options = options
       end
 
       def call
@@ -12,6 +13,7 @@ module Capistrano
       end
 
       private
+
       attr_reader :key, :default, :options
 
       def ask_question
@@ -28,20 +30,21 @@ module Capistrano
 
       def response
         return @response if defined? @response
-        
+
         @response = (gets || "").chomp
       end
-      
+
       def gets
         if echo?
           $stdin.gets
         else
-          $stdin.noecho(&:gets).tap{ $stdout.print "\n" }
+          $stdin.noecho(&:gets).tap { $stdout.print "\n" }
         end
       rescue Errno::EIO
         # when stdio gets closed
+        return
       end
-        
+
       def question
         I18n.t(:question, key: key, default_value: default, scope: :capistrano)
       end
diff --git a/lib/capistrano/configuration/role_filter.rb b/lib/capistrano/configuration/role_filter.rb
new file mode 100644
index 0000000..5157e32
--- /dev/null
+++ b/lib/capistrano/configuration/role_filter.rb
@@ -0,0 +1,30 @@
+module Capistrano
+  class Configuration
+    class RoleFilter
+      def initialize(values)
+        av = Array(values).dup
+        av.map! { |v| v.is_a?(String) ? v.split(",") : v }
+        av.flatten!
+        @rex = regex_matcher(av)
+      end
+
+      def filter(servers)
+        Array(servers).select { |s| s.is_a?(String) ? false : s.roles.any? { |r| @rex.match r } }
+      end
+
+      private
+
+      def regex_matcher(values)
+        values.map! do |v|
+          case v
+          when Regexp then v
+          else
+            vs = v.to_s
+            vs =~ %r{^/(.+)/$} ? Regexp.new($1) : /^#{vs}$/
+          end
+        end
+        Regexp.union values
+      end
+    end
+  end
+end
diff --git a/lib/capistrano/configuration/server.rb b/lib/capistrano/configuration/server.rb
index 35c55d3..bd0158f 100644
--- a/lib/capistrano/configuration/server.rb
+++ b/lib/capistrano/configuration/server.rb
@@ -1,4 +1,4 @@
-require 'set'
+require "set"
 module Capistrano
   class Configuration
     class Server < SSHKit::Host
@@ -25,19 +25,21 @@ module Capistrano
       end
 
       def select?(options)
-        options.each do |k,v|
-          callable = v.respond_to?(:call) ? v: ->(server){server.fetch(v)}
-          result = case k
-          when :filter, :select
-            callable.call(self)
-          when :exclude
-            !callable.call(self)
-          else
-            self.fetch(k) == v
-          end
+        options.each do |k, v|
+          callable = v.respond_to?(:call) ? v : ->(server) { server.fetch(v) }
+          result = \
+            case k
+            when :filter, :select
+              callable.call(self)
+            when :exclude
+              !callable.call(self)
+            else
+              fetch(k) == v
+            end
           return false unless result
         end
-        return true
+
+        true
       end
 
       def primary
@@ -54,7 +56,7 @@ module Capistrano
       end
 
       def netssh_options
-        @netssh_options ||= super.merge( fetch(:ssh_options) || {} )
+        @netssh_options ||= super.merge(fetch(:ssh_options) || {})
       end
 
       def roles_array
@@ -62,7 +64,7 @@ module Capistrano
       end
 
       def matches?(other)
-        hostname == other.hostname
+        hostname == other.hostname && port == other.port
       end
 
       private
@@ -76,18 +78,17 @@ module Capistrano
       end
 
       class Properties
-
         def initialize
           @properties = {}
         end
 
         def set(key, value)
           pval = @properties[key]
-          if pval.is_a? Hash and value.is_a? Hash
+          if pval.is_a?(Hash) && value.is_a?(Hash)
             pval.merge!(value)
-          elsif pval.is_a? Set and value.is_a? Set
+          elsif pval.is_a?(Set) && value.is_a?(Set)
             pval.merge(value)
-          elsif pval.is_a? Array and value.is_a? Array
+          elsif pval.is_a?(Array) && value.is_a?(Array)
             pval.concat value
           else
             @properties[key] = value
@@ -98,8 +99,8 @@ module Capistrano
           @properties[key]
         end
 
-        def respond_to?(method, include_all=false)
-          @properties.has_key?(method)
+        def respond_to_missing?(method, _include_all=false)
+          @properties.key?(method) || super
         end
 
         def roles
@@ -110,6 +111,7 @@ module Capistrano
           @properties.keys
         end
 
+        # rubocop:disable Style/MethodMissing
         def method_missing(key, value=nil)
           if value
             set(lvalue(key), value)
@@ -117,15 +119,18 @@ module Capistrano
             fetch(key)
           end
         end
+        # rubocop:enable Style/MethodMissing
+
+        def to_h
+          @properties
+        end
 
         private
 
         def lvalue(key)
-          key.to_s.chomp('=').to_sym
+          key.to_s.chomp("=").to_sym
         end
-
       end
-
     end
   end
 end
diff --git a/lib/capistrano/configuration/servers.rb b/lib/capistrano/configuration/servers.rb
index 7bc2fdd..36d14d4 100644
--- a/lib/capistrano/configuration/servers.rb
+++ b/lib/capistrano/configuration/servers.rb
@@ -1,6 +1,6 @@
-require 'set'
-require 'capistrano/configuration'
-require 'capistrano/configuration/filter'
+require "set"
+require "capistrano/configuration"
+require "capistrano/configuration/filter"
 
 module Capistrano
   class Configuration
@@ -9,9 +9,8 @@ module Capistrano
 
       def add_host(host, properties={})
         new_host = Server[host]
-        if server = servers.find { |s| s.matches? new_host }
+        if (server = servers.find { |s| s.matches? new_host })
           server.user = new_host.user if new_host.user
-          server.port = new_host.port if new_host.port
           server.with(properties)
         else
           servers << new_host.with(properties)
@@ -38,12 +37,12 @@ module Capistrano
               if block_given?
                 yield host, role, props
               else
-                rps << (props || {}).merge( role: role, hostname: host.hostname )
+                rps << (props || {}).merge(role: role, hostname: host.hostname)
               end
             end
           end
         end
-        block_given? ? nil: rps
+        block_given? ? nil : rps
       end
 
       def fetch_primary(role)
diff --git a/lib/capistrano/configuration/validated_variables.rb b/lib/capistrano/configuration/validated_variables.rb
new file mode 100644
index 0000000..7798fa3
--- /dev/null
+++ b/lib/capistrano/configuration/validated_variables.rb
@@ -0,0 +1,111 @@
+require "capistrano/proc_helpers"
+require "delegate"
+
+module Capistrano
+  class Configuration
+    # Decorates a Variables object to additionally perform an optional set of
+    # user-supplied validation rules. Each rule for a given key is invoked
+    # immediately whenever `set` is called with a value for that key.
+    #
+    # If `set` is called with a callable value or a block, validation is not
+    # performed immediately. Instead, the validation rules are invoked the first
+    # time `fetch` is used to access the value.
+    #
+    # A rule is simply a block that accepts two arguments: key and value. It is
+    # up to the rule to raise an exception when it deems the value is invalid
+    # (or just print a warning).
+    #
+    # Rules can be registered using the DSL like this:
+    #
+    #   validate(:my_key) do |key, value|
+    #     # rule goes here
+    #   end
+    #
+    class ValidatedVariables < SimpleDelegator
+      include Capistrano::ProcHelpers
+
+      def initialize(variables)
+        super(variables)
+        @validators = {}
+      end
+
+      # Decorate Variables#set to add validation behavior.
+      def set(key, value=nil, &block)
+        assert_value_or_block_not_both(value, block)
+
+        # Skip validation behavior if no validators are registered for this key
+        return super unless validators.key?(key)
+
+        value_to_evaluate = block || value
+
+        if callable_without_parameters?(value_to_evaluate)
+          super(key, assert_valid_later(key, value_to_evaluate), &nil)
+        else
+          assert_valid_now(key, value_to_evaluate)
+          super
+        end
+      end
+
+      # Register a validation rule for the given key.
+      def validate(key, &validator)
+        vs = (validators[key] || [])
+        vs << validator
+        validators[key] = vs
+      end
+
+      private
+
+      attr_reader :validators
+
+      # Given a callable that provides a value, wrap the callable with another
+      # object that responds to `call`. This new object will perform validation
+      # and then return the original callable's value.
+      #
+      # If the callable is a `Question`, the object returned by this method will
+      # also be a `Question` (a `ValidatedQuestion`, to be precise). This
+      # ensures that `is_a?(Question)` remains true even after the validation
+      # wrapper is applied. This is needed so that `Configuration#is_question?`
+      # works as expected.
+      #
+      def assert_valid_later(key, callable)
+        validation_callback = lambda do
+          value = callable.call
+          assert_valid_now(key, value)
+          value
+        end
+
+        if callable.is_a?(Question)
+          ValidatedQuestion.new(validation_callback)
+        else
+          validation_callback
+        end
+      end
+
+      # Runs all validation rules registered for the given key against the
+      # user-supplied value for that variable. If no validator raises an
+      # exception, the value is assumed to be valid.
+      def assert_valid_now(key, value)
+        validators[key].each do |validator|
+          validator.call(key, value)
+        end
+      end
+
+      def assert_value_or_block_not_both(value, block)
+        unless value.nil? || block.nil?
+          raise Capistrano::ValidationError,
+                "Value and block both passed to Configuration#set"
+        end
+      end
+
+      class ValidatedQuestion < Question
+        def initialize(validator)
+          @validator = validator
+        end
+
+        def call
+          @validator.call
+        end
+      end
+    end
+  end
+end
diff --git a/lib/capistrano/configuration/variables.rb b/lib/capistrano/configuration/variables.rb
new file mode 100644
index 0000000..424951d
--- /dev/null
+++ b/lib/capistrano/configuration/variables.rb
@@ -0,0 +1,112 @@
+require "capistrano/proc_helpers"
+
+module Capistrano
+  class Configuration
+    # Holds the variables assigned at Capistrano runtime via `set` and retrieved
+    # with `fetch`. Does internal bookkeeping to help identify user mistakes
+    # like spelling errors or unused variables that may lead to unexpected
+    # behavior.
+    class Variables
+      CAPISTRANO_LOCATION = File.expand_path("../..", __FILE__).freeze
+      IGNORED_LOCATIONS = [
+        "#{CAPISTRANO_LOCATION}/configuration/variables.rb:",
+        "#{CAPISTRANO_LOCATION}/configuration.rb:",
+        "#{CAPISTRANO_LOCATION}/dsl/env.rb:",
+        "/dsl.rb:",
+        "/forwardable.rb:"
+      ].freeze
+      private_constant :CAPISTRANO_LOCATION, :IGNORED_LOCATIONS
+
+      include Capistrano::ProcHelpers
+
+      def initialize(values={})
+        @trusted_keys = []
+        @fetched_keys = []
+        @locations = {}
+        @values = values
+        @trusted = true
+      end
+
+      def untrusted!
+        @trusted = false
+        yield
+      ensure
+        @trusted = true
+      end
+
+      def set(key, value=nil, &block)
+        @trusted_keys << key if trusted?
+        remember_location(key)
+        values[key] = block || value
+        trace_set(key)
+        values[key]
+      end
+
+      def fetch(key, default=nil, &block)
+        fetched_keys << key
+        peek(key, default, &block)
+      end
+
+      # Internal use only.
+      def peek(key, default=nil, &block)
+        value = fetch_for(key, default, &block)
+        while callable_without_parameters?(value)
+          value = (values[key] = value.call)
+        end
+        value
+      end
+
+      def fetch_for(key, default, &block)
+        block ? values.fetch(key, &block) : values.fetch(key, default)
+      end
+
+      def delete(key)
+        values.delete(key)
+      end
+
+      def trusted_keys
+        @trusted_keys.dup
+      end
+
+      def untrusted_keys
+        keys - @trusted_keys
+      end
+
+      def keys
+        values.keys
+      end
+
+      # Keys that have been set, but which have never been fetched.
+      def unused_keys
+        keys - fetched_keys
+      end
+
+      # Returns an array of source file location(s) where the given key was
+      # assigned (i.e. where `set` was called). If the key was never assigned,
+      # returns `nil`.
+      def source_locations(key)
+        locations[key]
+      end
+
+      private
+
+      attr_reader :locations, :values, :fetched_keys
+
+      def trusted?
+        @trusted
+      end
+
+      def remember_location(key)
+        location = caller.find do |line|
+          IGNORED_LOCATIONS.none? { |i| line.include?(i) }
+        end
+        (locations[key] ||= []) << location
+      end
+
+      def trace_set(key)
+        return unless fetch(:print_config_variables, false)
+        puts "Config variable set: #{key.inspect} => #{values[key].inspect}"
+      end
+    end
+  end
+end
diff --git a/lib/capistrano/defaults.rb b/lib/capistrano/defaults.rb
index 92dff7c..a158f52 100644
--- a/lib/capistrano/defaults.rb
+++ b/lib/capistrano/defaults.rb
@@ -1,14 +1,34 @@
+validate :application do |_key, value|
+  changed_value = value.gsub(/[^A-Z0-9\.\-]/i, "_")
+  if value != changed_value
+    warn %Q(The :application value "#{value}" is invalid!)
+    warn "Use only letters, numbers, hyphens, dots, and underscores. For example:"
+    warn "  set :application, '#{changed_value}'"
+    raise Capistrano::ValidationError
+  end
+end
+
+[:git_strategy, :hg_strategy, :svn_strategy].each do |strategy|
+  validate(strategy) do |key, _value|
+    warn(
+      "[Deprecation Warning] #{key} is deprecated and will be removed in "\
+      "Capistrano 3.7.0.\n"\
+      "https://github.com/capistrano/capistrano/blob/master/UPGRADING-3.7.md"
+    )
+  end
+end
+
 set_if_empty :scm, :git
-set_if_empty :branch, :master
+set_if_empty :branch, "master"
 set_if_empty :deploy_to, -> { "/var/www/#{fetch(:application)}" }
 set_if_empty :tmp_dir, "/tmp"
 
 set_if_empty :default_env, {}
 set_if_empty :keep_releases, 5
 
-set_if_empty :format, :pretty
+set_if_empty :format, :airbrussh
 set_if_empty :log_level, :debug
 
 set_if_empty :pty, false
 
-set_if_empty :local_user, -> { Etc.getlogin }
+set_if_empty :local_user, -> { ENV["USER"] || ENV["LOGNAME"] || ENV["USERNAME"] }
diff --git a/lib/capistrano/deploy.rb b/lib/capistrano/deploy.rb
index 48b1ef5..3ec8041 100644
--- a/lib/capistrano/deploy.rb
+++ b/lib/capistrano/deploy.rb
@@ -1,3 +1,3 @@
-require 'capistrano/framework'
+require "capistrano/framework"
 
 load File.expand_path("../tasks/deploy.rake", __FILE__)
diff --git a/lib/capistrano/doctor.rb b/lib/capistrano/doctor.rb
new file mode 100644
index 0000000..4fa4239
--- /dev/null
+++ b/lib/capistrano/doctor.rb
@@ -0,0 +1,6 @@
+require "capistrano/doctor/environment_doctor"
+require "capistrano/doctor/gems_doctor"
+require "capistrano/doctor/variables_doctor"
+require "capistrano/doctor/servers_doctor"
+
+load File.expand_path("../tasks/doctor.rake", __FILE__)
diff --git a/lib/capistrano/doctor/environment_doctor.rb b/lib/capistrano/doctor/environment_doctor.rb
new file mode 100644
index 0000000..460a4d5
--- /dev/null
+++ b/lib/capistrano/doctor/environment_doctor.rb
@@ -0,0 +1,19 @@
+require "capistrano/doctor/output_helpers"
+
+module Capistrano
+  module Doctor
+    class EnvironmentDoctor
+      include Capistrano::Doctor::OutputHelpers
+
+      def call
+        title("Environment")
+        puts <<-OUT.gsub(/^\s+/, "")
+          Ruby     #{RUBY_DESCRIPTION}
+          Rubygems #{Gem::VERSION}
+          Bundler  #{defined?(Bundler::VERSION) ? Bundler::VERSION : 'N/A'}
+          Command  #{$PROGRAM_NAME} #{ARGV.join(' ')}
+        OUT
+      end
+    end
+  end
+end
diff --git a/lib/capistrano/doctor/gems_doctor.rb b/lib/capistrano/doctor/gems_doctor.rb
new file mode 100644
index 0000000..fbcfc36
--- /dev/null
+++ b/lib/capistrano/doctor/gems_doctor.rb
@@ -0,0 +1,45 @@
+require "capistrano/doctor/output_helpers"
+
+module Capistrano
+  module Doctor
+    # Prints table of all Capistrano-related gems and their version numbers. If
+    # there is a newer version of a gem available, call attention to it.
+    class GemsDoctor
+      include Capistrano::Doctor::OutputHelpers
+
+      def call
+        title("Gems")
+        table(all_gem_names) do |gem, row|
+          row.yellow if update_available?(gem)
+          row << gem
+          row << installed_gem_version(gem)
+          row << "(update available)" if update_available?(gem)
+        end
+      end
+
+      private
+
+      def installed_gem_version(gem_name)
+        Gem.loaded_specs[gem_name].version
+      end
+
+      def update_available?(gem_name)
+        latest = Gem.latest_version_for(gem_name)
+        return false if latest.nil?
+        latest > installed_gem_version(gem_name)
+      end
+
+      def all_gem_names
+        core_gem_names + plugin_gem_names
+      end
+
+      def core_gem_names
+        %w(capistrano airbrussh rake sshkit net-ssh) & Gem.loaded_specs.keys
+      end
+
+      def plugin_gem_names
+        (Gem.loaded_specs.keys - ["capistrano"]).grep(/capistrano/).sort
+      end
+    end
+  end
+end
diff --git a/lib/capistrano/doctor/output_helpers.rb b/lib/capistrano/doctor/output_helpers.rb
new file mode 100644
index 0000000..7e035e9
--- /dev/null
+++ b/lib/capistrano/doctor/output_helpers.rb
@@ -0,0 +1,79 @@
+module Capistrano
+  module Doctor
+    # Helper methods for pretty-printing doctor output to stdout. All output
+    # (other than `title`) is indented by four spaces to facilitate copying and
+    # pasting this output into e.g. GitHub or Stack Overflow to achieve code
+    # formatting.
+    module OutputHelpers
+      class Row
+        attr_reader :color
+        attr_reader :values
+
+        def initialize
+          @values = []
+        end
+
+        def <<(value)
+          values << value
+        end
+
+        def yellow
+          @color = :yellow
+        end
+      end
+
+      # Prints a table for a given array of records. For each record, the block
+      # is yielded two arguments: the record and a Row object. To print values
+      # for that record, add values using `row << "some value"`. A row can
+      # optionally be highlighted in yellow using `row.yellow`.
+      def table(records, &block)
+        return if records.empty?
+        rows = collect_rows(records, &block)
+        col_widths = calculate_column_widths(rows)
+
+        rows.each do |row|
+          line = row.values.each_with_index.map do |value, col|
+            value.to_s.ljust(col_widths[col])
+          end.join(" ").rstrip
+          line = color.colorize(line, row.color) if row.color
+          puts line
+        end
+      end
+
+      # Prints a title in blue with surrounding newlines.
+      def title(text)
+        # Use $stdout directly to bypass the indentation that our `puts` does.
+        $stdout.puts(color.colorize("\n#{text}\n", :blue))
+      end
+
+      # Prints text in yellow.
+      def warning(text)
+        puts color.colorize(text, :yellow)
+      end
+
+      # Override `Kernel#puts` to prepend four spaces to each line.
+      def puts(string=nil)
+        $stdout.puts(string.to_s.gsub(/^/, "    "))
+      end
+
+      private
+
+      def collect_rows(records)
+        records.map do |rec|
+          Row.new.tap { |row| yield(rec, row) }
+        end
+      end
+
+      def calculate_column_widths(rows)
+        num_columns = rows.map { |row| row.values.length }.max
+        Array.new(num_columns) do |col|
+          rows.map { |row| row.values[col].to_s.length }.max
+        end
+      end
+
+      def color
+        @color ||= SSHKit::Color.new($stdout)
+      end
+    end
+  end
+end
diff --git a/lib/capistrano/doctor/servers_doctor.rb b/lib/capistrano/doctor/servers_doctor.rb
new file mode 100644
index 0000000..645c18c
--- /dev/null
+++ b/lib/capistrano/doctor/servers_doctor.rb
@@ -0,0 +1,105 @@
+require "capistrano/doctor/output_helpers"
+
+module Capistrano
+  module Doctor
+    class ServersDoctor
+      include Capistrano::Doctor::OutputHelpers
+
+      def initialize(env=Capistrano::Configuration.env)
+        @servers = env.servers.to_a
+      end
+
+      def call
+        title("Servers (#{servers.size})")
+        rwc = RoleWhitespaceChecker.new(servers)
+
+        table(servers) do |server, row|
+          sd = ServerDecorator.new(server)
+
+          row << sd.uri_form
+          row << sd.roles
+          row << sd.properties
+          row.yellow if rwc.any_has_whitespace?(server.roles)
+        end
+
+        if rwc.whitespace_roles.any?
+          warning "\nWhitespace detected in role(s) #{rwc.whitespace_roles_decorated}. " \
+            "This might be a result of a mistyped \"%w()\" array literal."
+        end
+        puts
+      end
+
+      private
+
+      attr_reader :servers
+
+      class RoleWhitespaceChecker
+        attr_reader :whitespace_roles, :servers
+
+        def initialize(servers)
+          @servers = servers
+          @whitespace_roles = find_whitespace_roles
+        end
+
+        def any_has_whitespace?(roles)
+          roles.any? { |role| include_whitespace?(role) }
+        end
+
+        def include_whitespace?(role)
+          role =~ /\s/
+        end
+
+        def whitespace_roles_decorated
+          whitespace_roles.map(&:inspect).join(", ")
+        end
+
+        private
+
+        def find_whitespace_roles
+          servers.map(&:roles).map(&:to_a).flatten.uniq
+                 .select { |role| include_whitespace?(role) }
+        end
+      end
+
+      class ServerDecorator
+        def initialize(server)
+          @server = server
+        end
+
+        def uri_form
+          [
+            server.user,
+            server.user && "@",
+            server.hostname,
+            server.port && ":",
+            server.port
+          ].compact.join
+        end
+
+        def roles
+          server.roles.to_a.inspect
+        end
+
+        def properties
+          return "" unless server.properties.keys.any?
+          pretty_inspect(server.properties.to_h)
+        end
+
+        private
+
+        attr_reader :server
+
+        # Hashes with proper padding
+        def pretty_inspect(element)
+          return element.inspect unless element.is_a?(Hash)
+
+          pairs_string = element.keys.map do |key|
+            [pretty_inspect(key), pretty_inspect(element.fetch(key))].join(" => ")
+          end.join(", ")
+
+          "{ #{pairs_string} }"
+        end
+      end
+    end
+  end
+end
diff --git a/lib/capistrano/doctor/variables_doctor.rb b/lib/capistrano/doctor/variables_doctor.rb
new file mode 100644
index 0000000..1333921
--- /dev/null
+++ b/lib/capistrano/doctor/variables_doctor.rb
@@ -0,0 +1,65 @@
+require "capistrano/doctor/output_helpers"
+
+module Capistrano
+  module Doctor
+    # Prints a table of all Capistrano variables and their current values. If
+    # there are unrecognized variables, print warnings for them.
+    class VariablesDoctor
+      # These are keys that have no default values in Capistrano, but are
+      # nonetheless expected to be set.
+      WHITELIST = [:application, :repo_url, :git_strategy, :hg_strategy, :svn_strategy].freeze
+      private_constant :WHITELIST
+
+      include Capistrano::Doctor::OutputHelpers
+
+      def initialize(env=Capistrano::Configuration.env)
+        @env = env
+      end
+
+      def call
+        title("Variables")
+        values = inspect_all_values
+
+        table(variables.keys.sort_by(&:to_s)) do |key, row|
+          row.yellow if suspicious_keys.include?(key)
+          row << key.inspect
+          row << values[key]
+        end
+
+        puts if suspicious_keys.any?
+
+        suspicious_keys.sort_by(&:to_s).each do |key|
+          warning("#{key.inspect} is not a recognized Capistrano setting "\
+                  "(#{location(key)})")
+        end
+      end
+
+      private
+
+      attr_reader :env
+
+      def variables
+        env.variables
+      end
+
+      def inspect_all_values
+        variables.keys.each_with_object({}) do |key, inspected|
+          inspected[key] = if env.is_question?(key)
+                             "<ask>"
+                           else
+                             variables.peek(key).inspect
+                           end
+        end
+      end
+
+      def suspicious_keys
+        (variables.untrusted_keys & variables.unused_keys) - WHITELIST
+      end
+
+      def location(key)
+        loc = variables.source_locations(key).first
+        loc && loc.sub(/^#{Regexp.quote(Dir.pwd)}/, "").sub(/:in.*/, "")
+      end
+    end
+  end
+end
diff --git a/lib/capistrano/dotfile.rb b/lib/capistrano/dotfile.rb
index 7a70a46..dab8e18 100644
--- a/lib/capistrano/dotfile.rb
+++ b/lib/capistrano/dotfile.rb
@@ -1,3 +1,2 @@
-dotfile = Pathname.new(File.join(Dir.home, '.capfile'))
+dotfile = Pathname.new(File.join(Dir.home, ".capfile"))
 load dotfile if dotfile.file?
-
diff --git a/lib/capistrano/dsl.rb b/lib/capistrano/dsl.rb
index 431967e..6317f7e 100644
--- a/lib/capistrano/dsl.rb
+++ b/lib/capistrano/dsl.rb
@@ -1,9 +1,8 @@
-require 'etc'
-require 'capistrano/dsl/task_enhancements'
-require 'capistrano/dsl/paths'
-require 'capistrano/dsl/stages'
-require 'capistrano/dsl/env'
-require 'capistrano/configuration/filter'
+require "capistrano/dsl/task_enhancements"
+require "capistrano/dsl/paths"
+require "capistrano/dsl/stages"
+require "capistrano/dsl/env"
+require "capistrano/configuration/filter"
 
 module Capistrano
   module DSL
@@ -12,8 +11,19 @@ module Capistrano
     include Paths
     include Stages
 
-    def invoke(task, *args)
-      Rake::Task[task].invoke(*args)
+    def invoke(task_name, *args)
+      task = Rake::Task[task_name]
+      # NOTE: We access instance variable since the accessor was only added recently. Once Capistrano depends on rake 11+, we can revert the following line
+      if task && task.instance_variable_get(:@already_invoked)
+        file, line, = caller.first.split(":")
+        colors = SSHKit::Color.new($stderr)
+        $stderr.puts colors.colorize("Skipping task `#{task_name}'.", :yellow)
+        $stderr.puts "Capistrano tasks may only be invoked once. Since task `#{task}' was previously invoked, invoke(\"#{task_name}\") at #{file}:#{line} will be skipped."
+        $stderr.puts "If you really meant to run this task again, first call Rake::Task[\"#{task_name}\"].reenable"
+        $stderr.puts colors.colorize("THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF CAPISTRANO. Please join the conversation here if this affects you.", :red)
+        $stderr.puts colors.colorize("https://github.com/capistrano/capistrano/issues/1686", :red)
+      end
+      task.invoke(*args)
     end
 
     def t(key, options={})
@@ -30,12 +40,11 @@ module Capistrano
 
     def revision_log_message
       fetch(:revision_log_message,
-        t(:revision_log_message,
-          branch: fetch(:branch),
-          user: local_user,
-          sha: fetch(:current_revision),
-          release: fetch(:release_timestamp))
-       )
+            t(:revision_log_message,
+              branch: fetch(:branch),
+              user: local_user,
+              sha: fetch(:current_revision),
+              release: fetch(:release_timestamp)))
     end
 
     def rollback_log_message
@@ -59,6 +68,20 @@ module Capistrano
       SSHKit::Backend::Local.new(&block).run
     end
 
+    # Catch common beginner mistake and give a helpful error message on stderr
+    def execute(*)
+      file, line, = caller.first.split(":")
+      colors = SSHKit::Color.new($stderr)
+      $stderr.puts colors.colorize("Warning: `execute' should be wrapped in an `on' scope in #{file}:#{line}.", :red)
+      $stderr.puts
+      $stderr.puts "  task :example do"
+      $stderr.puts colors.colorize("    on roles(:app) do", :yellow)
+      $stderr.puts "      execute 'whoami'"
+      $stderr.puts colors.colorize("    end", :yellow)
+      $stderr.puts "  end"
+      $stderr.puts
+      raise NoMethodError, "undefined method `execute' for main:Object"
+    end
   end
 end
-self.extend Capistrano::DSL
+extend Capistrano::DSL
diff --git a/lib/capistrano/dsl/env.rb b/lib/capistrano/dsl/env.rb
index c12a408..d609fd4 100644
--- a/lib/capistrano/dsl/env.rb
+++ b/lib/capistrano/dsl/env.rb
@@ -1,46 +1,20 @@
+require "forwardable"
+
 module Capistrano
   module DSL
     module Env
+      extend Forwardable
+      def_delegators :env,
+                     :configure_backend, :fetch, :set, :set_if_empty, :delete,
+                     :ask, :role, :server, :primary, :validate, :append,
+                     :remove, :dry_run?, :install_plugin
 
-      def configure_backend
-        env.configure_backend
-      end
-
-      def fetch(key, default=nil, &block)
-        env.fetch(key, default, &block)
+      def is_question?(key)
+        env.is_question?(key)
       end
 
       def any?(key)
-        value = fetch(key)
-        if value && value.respond_to?(:any?)
-          value.any?
-        else
-          !fetch(key).nil?
-        end
-      end
-
-      def set(key, value)
-        env.set(key, value)
-      end
-
-      def set_if_empty(key, value)
-        env.set_if_empty(key, value)
-      end
-
-      def delete(key)
-        env.delete(key)
-      end
-
-      def ask(key, value, options={})
-        env.ask(key, value, options)
-      end
-
-      def role(name, servers, options={})
-        env.role(name, servers, options)
-      end
-
-      def server(name, properties={})
-        env.server(name, properties)
+        env.any?(key)
       end
 
       def roles(*names)
@@ -53,17 +27,13 @@ module Capistrano
 
       def release_roles(*names)
         if names.last.is_a? Hash
-          names.last.merge!({ :exclude => :no_release })
+          names.last[:exclude] = :no_release
         else
           names << { exclude: :no_release }
         end
         roles(*names)
       end
 
-      def primary(role)
-        env.primary(role)
-      end
-
       def env
         Configuration.env
       end
@@ -75,7 +45,6 @@ module Capistrano
       def asset_timestamp
         env.timestamp.strftime("%Y%m%d%H%M.%S")
       end
-
     end
   end
 end
diff --git a/lib/capistrano/dsl/paths.rb b/lib/capistrano/dsl/paths.rb
index 7c8bde4..3d9ac05 100644
--- a/lib/capistrano/dsl/paths.rb
+++ b/lib/capistrano/dsl/paths.rb
@@ -1,8 +1,7 @@
-require 'pathname'
+require "pathname"
 module Capistrano
   module DSL
     module Paths
-
       def deploy_to
         fetch(:deploy_to)
       end
@@ -12,11 +11,11 @@ module Capistrano
       end
 
       def current_path
-        deploy_path.join('current')
+        deploy_path.join(fetch(:current_directory, "current"))
       end
 
       def releases_path
-        deploy_path.join('releases')
+        deploy_path.join("releases")
       end
 
       def release_path
@@ -29,17 +28,17 @@ module Capistrano
       end
 
       def stage_config_path
-        Pathname.new fetch(:stage_config_path, 'config/deploy')
+        Pathname.new fetch(:stage_config_path, "config/deploy")
       end
 
       def deploy_config_path
-        Pathname.new fetch(:deploy_config_path, 'config/deploy.rb')
+        Pathname.new fetch(:deploy_config_path, "config/deploy.rb")
       end
 
       def repo_url
-        require 'cgi'
-        require 'uri'
-        if fetch(:git_http_username) and fetch(:git_http_password)
+        require "cgi"
+        require "uri"
+        if fetch(:git_http_username) && fetch(:git_http_password)
           URI.parse(fetch(:repo_url)).tap do |repo_uri|
             repo_uri.user     = fetch(:git_http_username)
             repo_uri.password = CGI.escape(fetch(:git_http_password))
@@ -54,15 +53,15 @@ module Capistrano
       end
 
       def repo_path
-        Pathname.new(fetch(:repo_path, ->(){deploy_path.join('repo')}))
+        Pathname.new(fetch(:repo_path, ->() { deploy_path.join("repo") }))
       end
 
       def shared_path
-        deploy_path.join('shared')
+        deploy_path.join("shared")
       end
 
       def revision_log
-        deploy_path.join('revisions.log')
+        deploy_path.join("revisions.log")
       end
 
       def now
@@ -96,7 +95,7 @@ module Capistrano
       end
 
       def map_dirnames(paths)
-        paths.map { |path| path.dirname }
+        paths.map(&:dirname).uniq
       end
     end
   end
diff --git a/lib/capistrano/dsl/stages.rb b/lib/capistrano/dsl/stages.rb
index 990fd91..18dd91c 100644
--- a/lib/capistrano/dsl/stages.rb
+++ b/lib/capistrano/dsl/stages.rb
@@ -1,19 +1,31 @@
 module Capistrano
   module DSL
     module Stages
+      RESERVED_NAMES = %w(deploy doctor install).freeze
+      private_constant :RESERVED_NAMES
 
       def stages
-        Dir[stage_definitions].map { |f| File.basename(f, '.rb') }
+        names = Dir[stage_definitions].map { |f| File.basename(f, ".rb") }
+        assert_valid_stage_names(names)
+        names
       end
 
       def stage_definitions
-        stage_config_path.join('*.rb')
+        stage_config_path.join("*.rb")
       end
 
       def stage_set?
         !!fetch(:stage, false)
       end
 
+      private
+
+      def assert_valid_stage_names(names)
+        invalid = names.find { |n| RESERVED_NAMES.include?(n) }
+        return if invalid.nil?
+
+        raise t("error.invalid_stage_name", name: invalid, path: stage_config_path.join("#{invalid}.rb"))
+      end
     end
   end
 end
diff --git a/lib/capistrano/dsl/task_enhancements.rb b/lib/capistrano/dsl/task_enhancements.rb
index 940785b..9a21731 100644
--- a/lib/capistrano/dsl/task_enhancements.rb
+++ b/lib/capistrano/dsl/task_enhancements.rb
@@ -1,4 +1,4 @@
-require 'capistrano/upload_task'
+require "capistrano/upload_task"
 
 module Capistrano
   module TaskEnhancements
@@ -9,13 +9,18 @@ module Capistrano
 
     def after(task, post_task, *args, &block)
       Rake::Task.define_task(post_task, *args, &block) if block_given?
-      post_task = Rake::Task[post_task]
-      Rake::Task[task].enhance do
-        post_task.invoke
+      task = Rake::Task[task]
+      task.enhance do
+        post = Rake.application.lookup(post_task, task.scope)
+        raise ArgumentError, "Task #{post_task.inspect} not found" unless post
+        post.invoke
       end
     end
 
     def remote_file(task)
+      warn("[Deprecation Warning] `remote_file` is deprecated and will be "\
+           "removed in Capistrano 3.7.0")
+
       target_roles = task.delete(:roles) { :all }
       define_remote_file_task(task, target_roles)
     end
@@ -31,7 +36,6 @@ module Capistrano
             upload! File.open(prerequisite_file), file
           end
         end
-
       end
     end
 
@@ -54,13 +58,12 @@ module Capistrano
 
     def exit_deploy_because_of_exception(ex)
       warn t(:deploy_failed, ex: ex.message)
-      invoke 'deploy:failed'
+      invoke "deploy:failed"
       exit(false)
     end
 
     def deploying?
       fetch(:deploying, false)
     end
-
   end
 end
diff --git a/lib/capistrano/framework.rb b/lib/capistrano/framework.rb
index beeba10..a6466ff 100644
--- a/lib/capistrano/framework.rb
+++ b/lib/capistrano/framework.rb
@@ -1,2 +1,2 @@
 load File.expand_path("../tasks/framework.rake", __FILE__)
-require 'capistrano/install'
+require "capistrano/install"
diff --git a/lib/capistrano/git.rb b/lib/capistrano/git.rb
index 3a6727a..a8fe66c 100644
--- a/lib/capistrano/git.rb
+++ b/lib/capistrano/git.rb
@@ -1,14 +1,13 @@
 load File.expand_path("../tasks/git.rake", __FILE__)
 
-require 'capistrano/scm'
+require "capistrano/scm"
 
 class Capistrano::Git < Capistrano::SCM
-
   # execute git with argument in the context
   #
   def git(*args)
     args.unshift :git
-    context.execute *args
+    context.execute(*args)
   end
 
   # The Capistrano default strategy for git. You should want to use this.
@@ -22,25 +21,34 @@ class Capistrano::Git < Capistrano::SCM
     end
 
     def clone
-      git :clone, '--mirror', repo_url, repo_path
+      if (depth = fetch(:git_shallow_clone))
+        git :clone, "--mirror", "--depth", depth, "--no-single-branch", repo_url, repo_path
+      else
+        git :clone, "--mirror", repo_url, repo_path
+      end
     end
 
     def update
-      git :remote, :update
+      # Note: Requires git version 1.9 or greater
+      if (depth = fetch(:git_shallow_clone))
+        git :fetch, "--depth", depth, "origin", fetch(:branch)
+      else
+        git :remote, :update, "--prune"
+      end
     end
 
     def release
-      if tree = fetch(:repo_tree)
+      if (tree = fetch(:repo_tree))
         tree = tree.slice %r#^/?(.*?)/?$#, 1
-        components = tree.split('/').size
+        components = tree.split("/").size
         git :archive, fetch(:branch), tree, "| tar -x --strip-components #{components} -f - -C", release_path
       else
-        git :archive, fetch(:branch), '| tar -x -f - -C', release_path
+        git :archive, fetch(:branch), "| tar -x -f - -C", release_path
       end
     end
 
     def fetch_revision
-      context.capture(:git, "rev-list --max-count=1 --abbrev-commit #{fetch(:branch)}")
+      context.capture(:git, "rev-list --max-count=1 #{fetch(:branch)}")
     end
   end
 end
diff --git a/lib/capistrano/hg.rb b/lib/capistrano/hg.rb
index e7f0290..7e1749d 100644
--- a/lib/capistrano/hg.rb
+++ b/lib/capistrano/hg.rb
@@ -1,12 +1,12 @@
 load File.expand_path("../tasks/hg.rake", __FILE__)
 
-require 'capistrano/scm'
+require "capistrano/scm"
 
 class Capistrano::Hg < Capistrano::SCM
   # execute hg in context with arguments
   def hg(*args)
     args.unshift(:hg)
-    context.execute *args
+    context.execute(*args)
   end
 
   module DefaultStrategy
@@ -27,9 +27,9 @@ class Capistrano::Hg < Capistrano::SCM
     end
 
     def release
-      if tree = fetch(:repo_tree)
+      if (tree = fetch(:repo_tree))
         tree = tree.slice %r#^/?(.*?)/?$#, 1
-        components = tree.split('/').size
+        components = tree.split("/").size
         hg "archive --type tgz -p . -I", tree, "--rev", fetch(:branch), "| tar -x --strip-components #{components} -f - -C", release_path
       else
         hg "archive", release_path, "--rev", fetch(:branch)
diff --git a/lib/capistrano/i18n.rb b/lib/capistrano/i18n.rb
index 9d6e068..5b3a8be 100644
--- a/lib/capistrano/i18n.rb
+++ b/lib/capistrano/i18n.rb
@@ -1,37 +1,39 @@
-require 'i18n'
+require "i18n"
 
 en = {
-  starting:                    'Starting',
-  capified:                    'Capified',
-  start:                       'Start',
-  update:                      'Update',
-  finalize:                    'Finalise',
-  finishing:                   'Finishing',
-  finished:                    'Finished',
-  stage_not_set:               'Stage not set, please call something such as `cap production deploy`, where production is a stage you have defined.',
-  written_file:                'create %{file}',
-  question:                    'Please enter %{key} (%{default_value}): ',
-  keeping_releases:            'Keeping %{keep_releases} of %{releases} deployed releases on %{host}',
-  no_old_releases:             'No old releases (keeping newest %{keep_releases}) on %{host}',
-  linked_file_does_not_exist:  'linked file %{file} does not exist on %{host}',
-  cannot_rollback:             'There are no older releases to rollback to',
-  mirror_exists:               'The repository mirror is at %{at}',
-  revision_log_message:        'Branch %{branch} (at %{sha}) deployed as release %{release} by %{user}',
-  rollback_log_message:        '%{user} rolled back to release %{release}',
-  deploy_failed:               'The deploy has failed with an error: %{ex}',
+  starting: "Starting",
+  capified: "Capified",
+  start: "Start",
+  update: "Update",
+  finalize: "Finalise",
+  finishing: "Finishing",
+  finished: "Finished",
+  stage_not_set: "Stage not set, please call something such as `cap production deploy`, where production is a stage you have defined.",
+  written_file: "create %{file}",
+  question: "Please enter %{key} (%{default_value}): ",
+  keeping_releases: "Keeping %{keep_releases} of %{releases} deployed releases on %{host}",
+  no_old_releases: "No old releases (keeping newest %{keep_releases}) on %{host}",
+  linked_file_does_not_exist: "linked file %{file} does not exist on %{host}",
+  cannot_rollback: "There are no older releases to rollback to",
+  cannot_found_rollback_release: "Cannot rollback because release %{release} does not exist",
+  mirror_exists: "The repository mirror is at %{at}",
+  revision_log_message: "Branch %{branch} (at %{sha}) deployed as release %{release} by %{user}",
+  rollback_log_message: "%{user} rolled back to release %{release}",
+  deploy_failed: "The deploy has failed with an error: %{ex}",
   console: {
-    welcome:  'capistrano console - enter command to execute on %{stage}',
-    bye:      'bye'
+    welcome: "capistrano console - enter command to execute on %{stage}",
+    bye: "bye"
   },
   error: {
+    invalid_stage_name: '"%{name}" is a reserved word and cannot be used as a stage. Rename "%{path}" to something else.',
     user: {
-      does_not_exist:  'User %{user} does not exists',
-      cannot_switch:   'Cannot switch to user %{user}'
+      does_not_exist: "User %{user} does not exists",
+      cannot_switch: "Cannot switch to user %{user}"
     }
   }
 }
 
-I18n.backend.store_translations(:en, { capistrano: en })
+I18n.backend.store_translations(:en, capistrano: en)
 
 if I18n.respond_to?(:enforce_available_locales=)
   I18n.enforce_available_locales = true
diff --git a/lib/capistrano/immutable_task.rb b/lib/capistrano/immutable_task.rb
new file mode 100644
index 0000000..0e86f83
--- /dev/null
+++ b/lib/capistrano/immutable_task.rb
@@ -0,0 +1,29 @@
+module Capistrano
+  # This module extends a Rake::Task to freeze it to prevent it from being
+  # enhanced. This is used to prevent users from enhancing a task at the wrong
+  # point of Capistrano's boot process, which can happen if a Capistrano plugin
+  # is loaded in deploy.rb by mistake (instead of in the Capfile).
+  #
+  # Usage:
+  #
+  # task = Rake.application["load:defaults"]
+  # task.invoke
+  # task.extend(Capistrano::ImmutableTask) # prevent further modifications
+  #
+  module ImmutableTask
+    def self.extended(task)
+      task.freeze
+    end
+
+    def enhance(*args, &block)
+      $stderr.puts <<-MESSAGE
+WARNING: #{name} has already been invoked and can no longer be modified.
+Check that you haven't loaded a Capistrano plugin in deploy.rb by mistake.
+Plugins must be loaded in the Capfile to initialize properly.
+MESSAGE
+
+      # This will raise a frozen object error
+      super(*args, &block)
+    end
+  end
+end
diff --git a/lib/capistrano/install.rb b/lib/capistrano/install.rb
index 3882093..b0e55c6 100644
--- a/lib/capistrano/install.rb
+++ b/lib/capistrano/install.rb
@@ -1 +1 @@
-load File.expand_path(File.join(File.dirname(__FILE__),'tasks/install.rake'))
+load File.expand_path(File.join(File.dirname(__FILE__), "tasks/install.rake"))
diff --git a/lib/capistrano/plugin.rb b/lib/capistrano/plugin.rb
new file mode 100644
index 0000000..7c58b09
--- /dev/null
+++ b/lib/capistrano/plugin.rb
@@ -0,0 +1,95 @@
+require "capistrano/all"
+require "rake/tasklib"
+
+# IMPORTANT: The Capistrano::Plugin system is not yet considered a stable,
+# public API, and is subject to change without notice. Eventually it will be
+# officially documented and supported, but for now, use it at your own risk.
+#
+# Base class for Capistrano plugins. Makes building a Capistrano plugin as easy
+# as writing a `Capistrano::Plugin` subclass and overriding any or all of its
+# three template methods:
+#
+# * set_defaults
+# * register_hooks
+# * define_tasks
+#
+# Within the plugin you can use any methods of the Rake or Capistrano DSLs, like
+# `fetch`, `invoke`, etc. In cases when you need to use SSHKit's backend outside
+# of an `on` block, use the `backend` convenience method. E.g. `backend.test`,
+# `backend.execute`, or `backend.capture`.
+#
+# Package up and distribute your plugin class as a gem and you're good to go!
+#
+# To use a plugin, all a user has to do is install it in the Capfile, like this:
+#
+#   # Capfile
+#   require "capistrano/superfancy"
+#   install_plugin Capistrano::Superfancy
+#
+# Or, to install the plugin without its hooks:
+#
+#   # Capfile
+#   require "capistrano/superfancy"
+#   install_plugin Capistrano::Superfancy, load_hooks: false
+#
+class Capistrano::Plugin < Rake::TaskLib
+  include Capistrano::DSL
+
+  # Implemented by subclasses to provide default values for settings needed by
+  # this plugin. Typically done using the `set_if_empty` Capistrano DSL method.
+  #
+  # Example:
+  #
+  #   def set_defaults
+  #     set_if_empty :my_plugin_option, true
+  #   end
+  #
+  def set_defaults; end
+
+  # Implemented by subclasses to hook into Capistrano's deployment flow using
+  # using the `before` and `after` DSL methods. Note that `register_hooks` will
+  # not be called if the user has opted-out of hooks when installing the plugin.
+  #
+  # Example:
+  #
+  #   def register_hooks
+  #     after "deploy:updated", "my_plugin:do_something"
+  #   end
+  #
+  def register_hooks; end
+
+  # Implemented by subclasses to define Rake tasks. Typically a plugin will call
+  # `eval_rakefile` to load Rake tasks from a separate .rake file.
+  #
+  # Example:
+  #
+  #   def define_tasks
+  #     eval_rakefile File.expand_path("../tasks.rake", __FILE__)
+  #   end
+  #
+  # For simple tasks, you can define them inline. No need for a separate file.
+  #
+  #   def define_tasks
+  #     desc "Do something fantastic."
+  #     task "my_plugin:fantastic" do
+  #       ...
+  #     end
+  #   end
+  #
+  def define_tasks; end
+
+  private
+
+  # Read and eval a .rake file in such a way that `self` within the .rake file
+  # refers to this plugin instance. This gives the tasks in the file access to
+  # helper methods defined by the plugin.
+  def eval_rakefile(path)
+    contents = IO.read(path)
+    instance_eval(contents, path, 1)
+  end
+
+  # Convenience to access the current SSHKit backend outside of an `on` block.
+  def backend
+    SSHKit::Backend.current
+  end
+end
diff --git a/lib/capistrano/proc_helpers.rb b/lib/capistrano/proc_helpers.rb
new file mode 100644
index 0000000..e13cae7
--- /dev/null
+++ b/lib/capistrano/proc_helpers.rb
@@ -0,0 +1,13 @@
+module Capistrano
+  module ProcHelpers
+    module_function
+
+    # Tests whether the given object appears to respond to `call` with
+    # zero parameters. In Capistrano, such a proc is used to represent a
+    # "deferred value". That is, a value that is resolved by invoking `call` at
+    # the time it is first needed.
+    def callable_without_parameters?(x)
+      x.respond_to?(:call) && (!x.respond_to?(:arity) || x.arity.zero?)
+    end
+  end
+end
diff --git a/lib/capistrano/scm.rb b/lib/capistrano/scm.rb
index 0e75571..bba0c08 100644
--- a/lib/capistrano/scm.rb
+++ b/lib/capistrano/scm.rb
@@ -1,5 +1,4 @@
 module Capistrano
-
   # Base class for SCM strategy providers.
   #
   # @abstract
@@ -25,7 +24,7 @@ module Capistrano
 
     # Call test in context
     def test!(*args)
-      context.test *args
+      context.test(*args)
     end
 
     # The repository URL according to the context
@@ -59,9 +58,7 @@ module Capistrano
     # @return [Boolean]
     #
     def test
-      raise NotImplementedError.new(
-        "Your SCM strategy module should provide a #test method"
-      )
+      raise NotImplementedError, "Your SCM strategy module should provide a #test method"
     end
 
     # @abstract
@@ -72,9 +69,7 @@ module Capistrano
     # @return [Boolean]
     #
     def check
-      raise NotImplementedError.new(
-        "Your SCM strategy module should provide a #check method"
-      )
+      raise NotImplementedError, "Your SCM strategy module should provide a #check method"
     end
 
     # @abstract
@@ -84,9 +79,7 @@ module Capistrano
     # @return void
     #
     def clone
-      raise NotImplementedError.new(
-        "Your SCM strategy module should provide a #clone method"
-      )
+      raise NotImplementedError, "Your SCM strategy module should provide a #clone method"
     end
 
     # @abstract
@@ -96,9 +89,7 @@ module Capistrano
     # @return void
     #
     def update
-      raise NotImplementedError.new(
-        "Your SCM strategy module should provide a #update method"
-      )
+      raise NotImplementedError, "Your SCM strategy module should provide a #update method"
     end
 
     # @abstract
@@ -108,9 +99,7 @@ module Capistrano
     # @return void
     #
     def release
-      raise NotImplementedError.new(
-        "Your SCM strategy module should provide a #release method"
-      )
+      raise NotImplementedError, "Your SCM strategy module should provide a #release method"
     end
 
     # @abstract
@@ -120,9 +109,7 @@ module Capistrano
     # @return void
     #
     def fetch_revision
-      raise NotImplementedError.new(
-        "Your SCM strategy module should provide a #fetch_revision method"
-      )
+      raise NotImplementedError, "Your SCM strategy module should provide a #fetch_revision method"
     end
   end
 end
diff --git a/lib/capistrano/setup.rb b/lib/capistrano/setup.rb
index c13bdda..e35cfec 100644
--- a/lib/capistrano/setup.rb
+++ b/lib/capistrano/setup.rb
@@ -1,22 +1,36 @@
+require "capistrano/doctor"
+require "capistrano/immutable_task"
 include Capistrano::DSL
 
 namespace :load do
   task :defaults do
-    load 'capistrano/defaults.rb'
+    load "capistrano/defaults.rb"
   end
 end
 
+require "airbrussh/capistrano"
+# We don't need to show the "using Airbrussh" banner announcement since
+# Airbrussh is now the built-in formatter. Also enable command output by
+# default; hiding the output might be confusing to users new to Capistrano.
+Airbrussh.configure do |airbrussh|
+  airbrussh.banner = false
+  airbrussh.command_output = true
+end
+
 stages.each do |stage|
   Rake::Task.define_task(stage) do
     set(:stage, stage.to_sym)
 
-    invoke 'load:defaults'
-    load deploy_config_path
-    load stage_config_path.join("#{stage}.rb")
+    invoke "load:defaults"
+    Rake.application["load:defaults"].extend(Capistrano::ImmutableTask)
+    env.variables.untrusted! do
+      load deploy_config_path
+      load stage_config_path.join("#{stage}.rb")
+    end
     load "capistrano/#{fetch(:scm)}.rb"
     I18n.locale = fetch(:locale, :en)
     configure_backend
   end
 end
 
-require 'capistrano/dotfile'
+require "capistrano/dotfile"
diff --git a/lib/capistrano/svn.rb b/lib/capistrano/svn.rb
index f3f64b8..914af14 100644
--- a/lib/capistrano/svn.rb
+++ b/lib/capistrano/svn.rb
@@ -1,13 +1,15 @@
 load File.expand_path("../tasks/svn.rake", __FILE__)
 
-require 'capistrano/scm'
+require "capistrano/scm"
 
 class Capistrano::Svn < Capistrano::SCM
-  
   # execute svn in context with arguments
   def svn(*args)
     args.unshift(:svn)
-    context.execute *args
+    args.push "--username #{fetch(:svn_username)}" if fetch(:svn_username)
+    args.push "--password #{fetch(:svn_password)}" if fetch(:svn_password)
+    args.push "--revision #{fetch(:svn_revision)}" if fetch(:svn_revision)
+    context.execute(*args)
   end
 
   module DefaultStrategy
@@ -16,7 +18,9 @@ class Capistrano::Svn < Capistrano::SCM
     end
 
     def check
-      test! :svn, :info, repo_url
+      svn_username = fetch(:svn_username) ? "--username #{fetch(:svn_username)}" : ""
+      svn_password = fetch(:svn_password) ? "--password #{fetch(:svn_password)}" : ""
+      test! :svn, :info, repo_url, svn_username, svn_password
     end
 
     def clone
@@ -28,7 +32,7 @@ class Capistrano::Svn < Capistrano::SCM
     end
 
     def release
-      svn :export, '--force', '.', release_path
+      svn :export, "--force", ".", release_path
     end
 
     def fetch_revision
diff --git a/lib/capistrano/tasks/console.rake b/lib/capistrano/tasks/console.rake
index 2f27922..b85b464 100644
--- a/lib/capistrano/tasks/console.rake
+++ b/lib/capistrano/tasks/console.rake
@@ -1,20 +1,16 @@
-desc 'Execute remote commands'
+desc "Execute remote commands"
 task :console do
   stage = fetch(:stage)
-  puts I18n.t('console.welcome', scope: :capistrano, stage: stage)
+  puts I18n.t("console.welcome", scope: :capistrano, stage: stage)
   loop do
     print "#{stage}> "
 
-    if input = $stdin.gets
-      command = input.chomp
-    else
-      command = 'exit'
-    end
+    command = (input = $stdin.gets) ? input.chomp : "exit"
 
     next if command.empty?
 
     if %w{quit exit q}.include? command
-      puts t('console.bye')
+      puts t("console.bye")
       break
     else
       begin
diff --git a/lib/capistrano/tasks/deploy.rake b/lib/capistrano/tasks/deploy.rake
index ef1af89..1cb5f4a 100644
--- a/lib/capistrano/tasks/deploy.rake
+++ b/lib/capistrano/tasks/deploy.rake
@@ -1,70 +1,90 @@
 namespace :deploy do
-
   task :starting do
-    invoke 'deploy:check'
-    invoke 'deploy:set_previous_revision'
+    invoke "deploy:print_config_variables" if fetch(:print_config_variables, false)
+    invoke "deploy:check"
+    invoke "deploy:set_previous_revision"
+  end
+
+  task :print_config_variables do
+    puts
+    puts "------- Printing current config variables -------"
+    env.keys.each do |config_variable_key|
+      if is_question?(config_variable_key)
+        puts "#{config_variable_key.inspect} => Question (awaits user input on next fetch(#{config_variable_key.inspect}))"
+      else
+        puts "#{config_variable_key.inspect} => #{fetch(config_variable_key).inspect}"
+      end
+    end
+
+    puts
+    puts "------- Printing current config variables of SSHKit mechanism -------"
+    puts env.backend.config.inspect
+    # puts env.backend.config.backend.config.ssh_options.inspect
+    # puts env.backend.config.command_map.defaults.inspect
+
+    puts
   end
 
-  task :updating => :new_release_path do
+  task updating: :new_release_path do
     invoke "#{scm}:create_release"
     invoke "deploy:set_current_revision"
-    invoke 'deploy:symlink:shared'
+    invoke "deploy:symlink:shared"
   end
 
   task :reverting do
-    invoke 'deploy:revert_release'
+    invoke "deploy:revert_release"
   end
 
   task :publishing do
-    invoke 'deploy:symlink:release'
+    invoke "deploy:symlink:release"
   end
 
   task :finishing do
-    invoke 'deploy:cleanup'
+    invoke "deploy:cleanup"
   end
 
   task :finishing_rollback do
-    invoke 'deploy:cleanup_rollback'
+    invoke "deploy:cleanup_rollback"
   end
 
   task :finished do
-    invoke 'deploy:log_revision'
+    invoke "deploy:log_revision"
   end
 
-  desc 'Check required files and directories exist'
+  desc "Check required files and directories exist"
   task :check do
     invoke "#{scm}:check"
-    invoke 'deploy:check:directories'
-    invoke 'deploy:check:linked_dirs'
-    invoke 'deploy:check:make_linked_dirs'
-    invoke 'deploy:check:linked_files'
+    invoke "deploy:check:directories"
+    invoke "deploy:check:linked_dirs"
+    invoke "deploy:check:make_linked_dirs"
+    invoke "deploy:check:linked_files"
   end
 
   namespace :check do
-    desc 'Check shared and release directories exist'
+    desc "Check shared and release directories exist"
     task :directories do
       on release_roles :all do
-        execute :mkdir, '-p', shared_path, releases_path
+        execute :mkdir, "-p", shared_path, releases_path
       end
     end
 
-    desc 'Check directories to be linked exist in shared'
+    desc "Check directories to be linked exist in shared"
     task :linked_dirs do
       next unless any? :linked_dirs
       on release_roles :all do
-        execute :mkdir, '-p', linked_dirs(shared_path)
+        execute :mkdir, "-p", linked_dirs(shared_path)
       end
     end
 
-    desc 'Check directories of files to be linked exist in shared'
+    desc "Check directories of files to be linked exist in shared"
     task :make_linked_dirs do
       next unless any? :linked_files
-      on release_roles :all do |host|
-        execute :mkdir, '-p', linked_file_dirs(shared_path)
+      on release_roles :all do |_host|
+        execute :mkdir, "-p", linked_file_dirs(shared_path)
       end
     end
 
-    desc 'Check files to be linked exist in shared'
+    desc "Check files to be linked exist in shared"
     task :linked_files do
       next unless any? :linked_files
       on release_roles :all do |host|
@@ -79,64 +99,58 @@ namespace :deploy do
   end
 
   namespace :symlink do
-    desc 'Symlink release to current'
+    desc "Symlink release to current"
     task :release do
       on release_roles :all do
         tmp_current_path = release_path.parent.join(current_path.basename)
-        execute :ln, '-s', release_path, tmp_current_path
+        execute :ln, "-s", release_path, tmp_current_path
         execute :mv, tmp_current_path, current_path.parent
       end
     end
 
-    desc 'Symlink files and directories from shared to release'
+    desc "Symlink files and directories from shared to release"
     task :shared do
-      invoke 'deploy:symlink:linked_files'
-      invoke 'deploy:symlink:linked_dirs'
+      invoke "deploy:symlink:linked_files"
+      invoke "deploy:symlink:linked_dirs"
     end
 
-    desc 'Symlink linked directories'
+    desc "Symlink linked directories"
     task :linked_dirs do
       next unless any? :linked_dirs
       on release_roles :all do
-        execute :mkdir, '-p', linked_dir_parents(release_path)
+        execute :mkdir, "-p", linked_dir_parents(release_path)
 
         fetch(:linked_dirs).each do |dir|
           target = release_path.join(dir)
           source = shared_path.join(dir)
-          unless test "[ -L #{target} ]"
-            if test "[ -d #{target} ]"
-              execute :rm, '-rf', target
-            end
-            execute :ln, '-s', source, target
-          end
+          next if test "[ -L #{target} ]"
+          execute :rm, "-rf", target if test "[ -d #{target} ]"
+          execute :ln, "-s", source, target
         end
       end
     end
 
-    desc 'Symlink linked files'
+    desc "Symlink linked files"
     task :linked_files do
       next unless any? :linked_files
       on release_roles :all do
-        execute :mkdir, '-p', linked_file_dirs(release_path)
+        execute :mkdir, "-p", linked_file_dirs(release_path)
 
         fetch(:linked_files).each do |file|
           target = release_path.join(file)
           source = shared_path.join(file)
-          unless test "[ -L #{target} ]"
-            if test "[ -f #{target} ]"
-              execute :rm, target
-            end
-            execute :ln, '-s', source, target
-          end
+          next if test "[ -L #{target} ]"
+          execute :rm, target if test "[ -f #{target} ]"
+          execute :ln, "-s", source, target
         end
       end
     end
   end
 
-  desc 'Clean up old releases'
+  desc "Clean up old releases"
   task :cleanup do
     on release_roles :all do |host|
-      releases = capture(:ls, '-xtr', releases_path).split
+      releases = capture(:ls, "-xtr", releases_path).split
       if releases.count >= fetch(:keep_releases)
         info t(:keeping_releases, host: host.to_s, keep_releases: fetch(:keep_releases), releases: releases.count)
         directories = (releases - releases.last(fetch(:keep_releases)))
@@ -144,7 +158,7 @@ namespace :deploy do
           directories_str = directories.map do |release|
             releases_path.join(release)
           end.join(" ")
-          execute :rm, '-rf', directories_str
+          execute :rm, "-rf", directories_str
         else
           info t(:no_old_releases, host: host.to_s, keep_releases: fetch(:keep_releases))
         end
@@ -152,33 +166,33 @@ namespace :deploy do
     end
   end
 
-  desc 'Remove and archive rolled-back release.'
+  desc "Remove and archive rolled-back release."
   task :cleanup_rollback do
     on release_roles(:all) do
-      last_release = capture(:ls, '-xt', releases_path).split.first
+      last_release = capture(:ls, "-xt", releases_path).split.first
       last_release_path = releases_path.join(last_release)
       if test "[ `readlink #{current_path}` != #{last_release_path} ]"
-        execute :tar, '-czf',
-          deploy_path.join("rolled-back-release-#{last_release}.tar.gz"),
-        last_release_path
-        execute :rm, '-rf', last_release_path
+        execute :tar, "-czf",
+                deploy_path.join("rolled-back-release-#{last_release}.tar.gz"),
+                last_release_path
+        execute :rm, "-rf", last_release_path
       else
-        debug 'Last release is the current release, skip cleanup_rollback.'
+        debug "Last release is the current release, skip cleanup_rollback."
       end
     end
   end
 
-  desc 'Log details of the deploy'
+  desc "Log details of the deploy"
   task :log_revision do
     on release_roles(:all) do
       within releases_path do
-        execute %{echo "#{revision_log_message}" >> #{revision_log}}
+        execute :echo, %Q{"#{revision_log_message}" >> #{revision_log}}
       end
     end
   end
 
-  desc 'Revert to previous release timestamp'
-  task :revert_release => :rollback_release_path do
+  desc "Revert to previous release timestamp"
+  task revert_release: :rollback_release_path do
     on release_roles(:all) do
       set(:revision_log_message, rollback_log_message)
     end
@@ -190,12 +204,20 @@ namespace :deploy do
 
   task :rollback_release_path do
     on release_roles(:all) do
-      releases = capture(:ls, '-xt', releases_path).split
+      releases = capture(:ls, "-xt", releases_path).split
       if releases.count < 2
         error t(:cannot_rollback)
         exit 1
       end
-      last_release = releases[1]
+
+      rollback_release = ENV["ROLLBACK_RELEASE"]
+      index = rollback_release.nil? ? 1 : releases.index(rollback_release)
+      if index.nil?
+        error t(:cannot_found_rollback_release, release: rollback_release)
+        exit 1
+      end
+
+      last_release = releases[index]
       set_release_path(last_release)
       set(:rollback_timestamp, last_release)
     end
@@ -213,14 +235,13 @@ namespace :deploy do
 
   task :set_previous_revision do
     on release_roles(:all) do
-      target = release_path.join('REVISION')
+      target = release_path.join("REVISION")
       if test "[ -f #{target} ]"
-        set(:previous_revision, capture(:cat, target, '2>/dev/null'))
+        set(:previous_revision, capture(:cat, target, "2>/dev/null"))
       end
     end
   end
 
   task :restart
   task :failed
-
 end
diff --git a/lib/capistrano/tasks/doctor.rake b/lib/capistrano/tasks/doctor.rake
new file mode 100644
index 0000000..e588a2e
--- /dev/null
+++ b/lib/capistrano/tasks/doctor.rake
@@ -0,0 +1,24 @@
+desc "Display a Capistrano troubleshooting report (all doctor: tasks)"
+task doctor: ["doctor:environment", "doctor:gems", "doctor:variables", "doctor:servers"]
+
+namespace :doctor do
+  desc "Display Ruby environment details"
+  task :environment do
+    Capistrano::Doctor::EnvironmentDoctor.new.call
+  end
+
+  desc "Display Capistrano gem versions"
+  task :gems do
+    Capistrano::Doctor::GemsDoctor.new.call
+  end
+
+  desc "Display the values of all Capistrano variables"
+  task :variables do
+    Capistrano::Doctor::VariablesDoctor.new.call
+  end
+
+  desc "Display the effective servers configuration"
+  task :servers do
+    Capistrano::Doctor::ServersDoctor.new.call
+  end
+end
diff --git a/lib/capistrano/tasks/framework.rake b/lib/capistrano/tasks/framework.rake
index e2d9405..09d1a66 100644
--- a/lib/capistrano/tasks/framework.rake
+++ b/lib/capistrano/tasks/framework.rake
@@ -1,50 +1,49 @@
 namespace :deploy do
-
-  desc 'Start a deployment, make sure server(s) ready.'
+  desc "Start a deployment, make sure server(s) ready."
   task :starting do
   end
 
-  desc 'Started'
+  desc "Started"
   task :started do
   end
 
-  desc 'Update server(s) by setting up a new release.'
+  desc "Update server(s) by setting up a new release."
   task :updating do
   end
 
-  desc 'Updated'
+  desc "Updated"
   task :updated do
   end
 
-  desc 'Revert server(s) to previous release.'
+  desc "Revert server(s) to previous release."
   task :reverting do
   end
 
-  desc 'Reverted'
+  desc "Reverted"
   task :reverted do
   end
 
-  desc 'Publish the release.'
+  desc "Publish the release."
   task :publishing do
   end
 
-  desc 'Published'
+  desc "Published"
   task :published do
   end
 
-  desc 'Finish the deployment, clean up server(s).'
+  desc "Finish the deployment, clean up server(s)."
   task :finishing do
   end
 
-  desc 'Finish the rollback, clean up server(s).'
+  desc "Finish the rollback, clean up server(s)."
   task :finishing_rollback do
   end
 
-  desc 'Finished'
+  desc "Finished"
   task :finished do
   end
 
-  desc 'Rollback to previous release.'
+  desc "Rollback to previous release."
   task :rollback do
     %w{ starting started
         reverting reverted
@@ -55,7 +54,7 @@ namespace :deploy do
   end
 end
 
-desc 'Deploy a new release.'
+desc "Deploy a new release."
 task :deploy do
   set(:deploying, true)
   %w{ starting started
diff --git a/lib/capistrano/tasks/git.rake b/lib/capistrano/tasks/git.rake
index 3af4d07..14643c6 100644
--- a/lib/capistrano/tasks/git.rake
+++ b/lib/capistrano/tasks/git.rake
@@ -1,26 +1,32 @@
 namespace :git do
-
   def strategy
     @strategy ||= Capistrano::Git.new(self, fetch(:git_strategy, Capistrano::Git::DefaultStrategy))
   end
 
-  set :git_environmental_variables, ->() {
+  set :git_wrapper_path, lambda {
+    # Try to avoid permissions issues when multiple users deploy the same app
+    # by using different file names in the same dir for each deployer and stage.
+    suffix = [:application, :stage, :local_user].map { |key| fetch(key).to_s }.join("-").gsub(/\s+/, "-")
+    "#{fetch(:tmp_dir)}/git-ssh-#{suffix}.sh"
+  }
+
+  set :git_environmental_variables, lambda {
     {
       git_askpass: "/bin/echo",
-      git_ssh:     "#{fetch(:tmp_dir)}/#{fetch(:application)}/git-ssh.sh"
+      git_ssh: fetch(:git_wrapper_path)
     }
   }
 
-  desc 'Upload the git wrapper script, this script guarantees that we can script git without getting an interactive prompt'
+  desc "Upload the git wrapper script, this script guarantees that we can script git without getting an interactive prompt"
   task :wrapper do
     on release_roles :all do
-      execute :mkdir, "-p", "#{fetch(:tmp_dir)}/#{fetch(:application)}/"
-      upload! StringIO.new("#!/bin/sh -e\nexec /usr/bin/ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no \"$@\"\n"), "#{fetch(:tmp_dir)}/#{fetch(:application)}/git-ssh.sh"
-      execute :chmod, "+x", "#{fetch(:tmp_dir)}/#{fetch(:application)}/git-ssh.sh"
+      execute :mkdir, "-p", File.dirname(fetch(:git_wrapper_path))
+      upload! StringIO.new("#!/bin/sh -e\nexec /usr/bin/ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no \"$@\"\n"), fetch(:git_wrapper_path)
+      execute :chmod, "700", fetch(:git_wrapper_path)
     end
   end
 
-  desc 'Check that the repository is reachable'
+  desc "Check that the repository is reachable"
   task check: :'git:wrapper' do
     fetch(:branch)
     on release_roles :all do
@@ -30,7 +36,7 @@ namespace :git do
     end
   end
 
-  desc 'Clone the repo to the cache'
+  desc "Clone the repo to the cache"
   task clone: :'git:wrapper' do
     on release_roles :all do
       if strategy.test
@@ -45,7 +51,7 @@ namespace :git do
     end
   end
 
-  desc 'Update the repo mirror to reflect the origin state'
+  desc "Update the repo mirror to reflect the origin state"
   task update: :'git:clone' do
     on release_roles :all do
       within repo_path do
@@ -56,19 +62,19 @@ namespace :git do
     end
   end
 
-  desc 'Copy repo to releases'
+  desc "Copy repo to releases"
   task create_release: :'git:update' do
     on release_roles :all do
       with fetch(:git_environmental_variables) do
         within repo_path do
-          execute :mkdir, '-p', release_path
+          execute :mkdir, "-p", release_path
           strategy.release
         end
       end
     end
   end
 
-  desc 'Determine the revision that will be deployed'
+  desc "Determine the revision that will be deployed"
   task :set_current_revision do
     on release_roles :all do
       within repo_path do
diff --git a/lib/capistrano/tasks/hg.rake b/lib/capistrano/tasks/hg.rake
index 13c1e56..9210c29 100644
--- a/lib/capistrano/tasks/hg.rake
+++ b/lib/capistrano/tasks/hg.rake
@@ -3,14 +3,14 @@ namespace :hg do
     @strategy ||= Capistrano::Hg.new(self, fetch(:hg_strategy, Capistrano::Hg::DefaultStrategy))
   end
 
-  desc 'Check that the repo is reachable'
+  desc "Check that the repo is reachable"
   task :check do
     on release_roles :all do
       strategy.check
     end
   end
 
-  desc 'Clone the repo to the cache'
+  desc "Clone the repo to the cache"
   task :clone do
     on release_roles :all do
       if strategy.test
@@ -23,8 +23,8 @@ namespace :hg do
     end
   end
 
-  desc 'Pull changes from the remote repo'
-  task :update => :'hg:clone' do
+  desc "Pull changes from the remote repo"
+  task update: :'hg:clone' do
     on release_roles :all do
       within repo_path do
         strategy.update
@@ -32,8 +32,8 @@ namespace :hg do
     end
   end
 
-  desc 'Copy repo to releases'
-  task :create_release => :'hg:update' do
+  desc "Copy repo to releases"
+  task create_release: :'hg:update' do
     on release_roles :all do
       within repo_path do
         strategy.release
@@ -41,7 +41,7 @@ namespace :hg do
     end
   end
 
-  desc 'Determine the revision that will be deployed'
+  desc "Determine the revision that will be deployed"
   task :set_current_revision do
     on release_roles :all do
       within repo_path do
diff --git a/lib/capistrano/tasks/install.rake b/lib/capistrano/tasks/install.rake
index 6e3818e..70f01d3 100644
--- a/lib/capistrano/tasks/install.rake
+++ b/lib/capistrano/tasks/install.rake
@@ -1,12 +1,12 @@
-require 'erb'
-require 'pathname'
-desc 'Install Capistrano, cap install STAGES=staging,production'
+require "erb"
+require "pathname"
+desc "Install Capistrano, cap install STAGES=staging,production"
 task :install do
-  envs = ENV['STAGES'] || 'staging,production'
+  envs = ENV["STAGES"] || "staging,production"
 
-  tasks_dir = Pathname.new('lib/capistrano/tasks')
-  config_dir = Pathname.new('config')
-  deploy_dir = config_dir.join('deploy')
+  tasks_dir = Pathname.new("lib/capistrano/tasks")
+  config_dir = Pathname.new("config")
+  deploy_dir = config_dir.join("deploy")
 
   deploy_rb = File.expand_path("../../templates/deploy.rb.erb", __FILE__)
   stage_rb = File.expand_path("../../templates/stage.rb.erb", __FILE__)
@@ -14,14 +14,14 @@ task :install do
 
   mkdir_p deploy_dir
 
-  entries = [{template: deploy_rb, file: config_dir.join('deploy.rb')}]
-  entries += envs.split(',').map { |stage| {template: stage_rb, file: deploy_dir.join("#{stage}.rb")} }
+  entries = [{ template: deploy_rb, file: config_dir.join("deploy.rb") }]
+  entries += envs.split(",").map { |stage| { template: stage_rb, file: deploy_dir.join("#{stage}.rb") } }
 
   entries.each do |entry|
-    if File.exists?(entry[:file])
+    if File.exist?(entry[:file])
       warn "[skip] #{entry[:file]} already exists"
     else
-      File.open(entry[:file], 'w+') do |f|
+      File.open(entry[:file], "w+") do |f|
         f.write(ERB.new(File.read(entry[:template])).result(binding))
         puts I18n.t(:written_file, scope: :capistrano, file: entry[:file])
       end
@@ -30,13 +30,12 @@ task :install do
 
   mkdir_p tasks_dir
 
-  if File.exists?('Capfile')
+  if File.exist?("Capfile")
     warn "[skip] Capfile already exists"
   else
-    FileUtils.cp(capfile, 'Capfile')
-    puts I18n.t(:written_file, scope: :capistrano, file: 'Capfile')
+    FileUtils.cp(capfile, "Capfile")
+    puts I18n.t(:written_file, scope: :capistrano, file: "Capfile")
   end
 
-
   puts I18n.t :capified, scope: :capistrano
 end
diff --git a/lib/capistrano/tasks/svn.rake b/lib/capistrano/tasks/svn.rake
index 9246330..404ca96 100644
--- a/lib/capistrano/tasks/svn.rake
+++ b/lib/capistrano/tasks/svn.rake
@@ -3,14 +3,14 @@ namespace :svn do
     @strategy ||= Capistrano::Svn.new(self, fetch(:svn_strategy, Capistrano::Svn::DefaultStrategy))
   end
 
-  desc 'Check that the repo is reachable'
+  desc "Check that the repo is reachable"
   task :check do
     on release_roles :all do
       strategy.check
     end
   end
 
-  desc 'Clone the repo to the cache'
+  desc "Clone the repo to the cache"
   task :clone do
     on release_roles :all do
       if strategy.test
@@ -23,8 +23,8 @@ namespace :svn do
     end
   end
 
-  desc 'Pull changes from the remote repo'
-  task :update => :'svn:clone' do
+  desc "Pull changes from the remote repo"
+  task update: :'svn:clone' do
     on release_roles :all do
       within repo_path do
         strategy.update
@@ -32,8 +32,8 @@ namespace :svn do
     end
   end
 
-  desc 'Copy repo to releases'
-  task :create_release => :'svn:update' do
+  desc "Copy repo to releases"
+  task create_release: :'svn:update' do
     on release_roles :all do
       within repo_path do
         strategy.release
@@ -41,7 +41,7 @@ namespace :svn do
     end
   end
 
-  desc 'Determine the revision that will be deployed'
+  desc "Determine the revision that will be deployed"
   task :set_current_revision do
     on release_roles :all do
       within repo_path do
diff --git a/lib/capistrano/templates/Capfile b/lib/capistrano/templates/Capfile
index a0cf0b5..4667537 100644
--- a/lib/capistrano/templates/Capfile
+++ b/lib/capistrano/templates/Capfile
@@ -1,8 +1,8 @@
 # Load DSL and set up stages
-require 'capistrano/setup'
+require "capistrano/setup"
 
 # Include default deployment tasks
-require 'capistrano/deploy'
+require "capistrano/deploy"
 
 # Include tasks from other gems included in your Gemfile
 #
@@ -24,4 +24,4 @@ require 'capistrano/deploy'
 # require 'capistrano/passenger'
 
 # Load custom tasks from `lib/capistrano/tasks` if you have any defined
-Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
+Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
diff --git a/lib/capistrano/templates/deploy.rb.erb b/lib/capistrano/templates/deploy.rb.erb
index 49a3772..953be23 100644
--- a/lib/capistrano/templates/deploy.rb.erb
+++ b/lib/capistrano/templates/deploy.rb.erb
@@ -13,36 +13,24 @@ set :repo_url, 'git at example.com:me/my_repo.git'
 # Default value for :scm is :git
 # set :scm, :git
 
-# Default value for :format is :pretty
-# set :format, :pretty
+# Default value for :format is :airbrussh.
+# set :format, :airbrussh
 
-# Default value for :log_level is :debug
-# set :log_level, :debug
+# You can configure the Airbrussh format using :format_options.
+# These are the defaults.
+# set :format_options, command_output: true, log_file: 'log/capistrano.log', color: :auto, truncate: :auto
 
 # Default value for :pty is false
 # set :pty, true
 
 # Default value for :linked_files is []
-# set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml')
+# append :linked_files, 'config/database.yml', 'config/secrets.yml'
 
 # Default value for linked_dirs is []
-# set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')
+# append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/system'
 
 # Default value for default_env is {}
 # set :default_env, { path: "/opt/ruby/bin:$PATH" }
 
 # Default value for keep_releases is 5
 # set :keep_releases, 5
-
-namespace :deploy do
-
-  after :restart, :clear_cache do
-    on roles(:web), in: :groups, limit: 3, wait: 10 do
-      # Here we can do anything such as:
-      # within release_path do
-      #   execute :rake, 'cache:clear'
-      # end
-    end
-  end
-
-end
diff --git a/lib/capistrano/upload_task.rb b/lib/capistrano/upload_task.rb
index c38b710..c94654a 100644
--- a/lib/capistrano/upload_task.rb
+++ b/lib/capistrano/upload_task.rb
@@ -1,4 +1,4 @@
-require 'rake/file_creation_task'
+require "rake/file_creation_task"
 
 module Capistrano
   class UploadTask < Rake::FileCreationTask
diff --git a/lib/capistrano/version.rb b/lib/capistrano/version.rb
index 0d9779d..fed8439 100644
--- a/lib/capistrano/version.rb
+++ b/lib/capistrano/version.rb
@@ -1,3 +1,3 @@
 module Capistrano
-  VERSION = "3.4.0"
+  VERSION = "3.6.1".freeze
 end
diff --git a/lib/capistrano/version_validator.rb b/lib/capistrano/version_validator.rb
index ff61039..0c9843f 100644
--- a/lib/capistrano/version_validator.rb
+++ b/lib/capistrano/version_validator.rb
@@ -1,6 +1,5 @@
 module Capistrano
   class VersionValidator
-
     def initialize(version)
       @version = version
     end
@@ -9,13 +8,13 @@ module Capistrano
       if match?
         self
       else
-        fail "Capfile locked at #{version}, but #{current_version} is loaded"
+        raise "Capfile locked at #{version}, but #{current_version} is loaded"
       end
     end
 
     private
-    attr_reader :version
 
+    attr_reader :version
 
     def match?
       available =~ requested
@@ -26,12 +25,11 @@ module Capistrano
     end
 
     def available
-      Gem::Dependency.new('cap', version)
+      Gem::Dependency.new("cap", version)
     end
 
     def requested
-      Gem::Dependency.new('cap', current_version)
+      Gem::Dependency.new("cap", current_version)
     end
-
   end
 end
diff --git a/metadata.yml b/metadata.yml
deleted file mode 100644
index c33475c..0000000
--- a/metadata.yml
+++ /dev/null
@@ -1,252 +0,0 @@
---- !ruby/object:Gem::Specification
-name: capistrano
-version: !ruby/object:Gem::Version
-  version: 3.4.0
-platform: ruby
-authors:
-- Tom Clements
-- Lee Hambley
-autorequire: 
-bindir: bin
-cert_chain: []
-date: 2015-03-02 00:00:00.000000000 Z
-dependencies:
-- !ruby/object:Gem::Dependency
-  name: sshkit
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - "~>"
-      - !ruby/object:Gem::Version
-        version: '1.3'
-  type: :runtime
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - "~>"
-      - !ruby/object:Gem::Version
-        version: '1.3'
-- !ruby/object:Gem::Dependency
-  name: rake
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 10.0.0
-  type: :runtime
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: 10.0.0
-- !ruby/object:Gem::Dependency
-  name: i18n
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-  type: :runtime
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-- !ruby/object:Gem::Dependency
-  name: rspec
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-  type: :development
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-- !ruby/object:Gem::Dependency
-  name: mocha
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-  type: :development
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-description: Capistrano is a utility and framework for executing commands in parallel
-  on multiple remote machines, via SSH.
-email:
-- seenmyfate at gmail.com
-- lee.hambley at gmail.com
-executables:
-- cap
-- capify
-extensions: []
-extra_rdoc_files: []
-files:
-- ".gitignore"
-- ".travis.yml"
-- CHANGELOG.md
-- CONTRIBUTING.md
-- Gemfile
-- LICENSE.txt
-- README.md
-- Rakefile
-- bin/cap
-- bin/capify
-- capistrano.gemspec
-- features/configuration.feature
-- features/deploy.feature
-- features/deploy_failure.feature
-- features/installation.feature
-- features/remote_file_task.feature
-- features/sshconnect.feature
-- features/step_definitions/assertions.rb
-- features/step_definitions/cap_commands.rb
-- features/step_definitions/setup.rb
-- features/support/env.rb
-- features/support/remote_command_helpers.rb
-- features/support/vagrant_helpers.rb
-- lib/Capfile
-- lib/capistrano.rb
-- lib/capistrano/all.rb
-- lib/capistrano/application.rb
-- lib/capistrano/configuration.rb
-- lib/capistrano/configuration/filter.rb
-- lib/capistrano/configuration/question.rb
-- lib/capistrano/configuration/server.rb
-- lib/capistrano/configuration/servers.rb
-- lib/capistrano/console.rb
-- lib/capistrano/defaults.rb
-- lib/capistrano/deploy.rb
-- lib/capistrano/dotfile.rb
-- lib/capistrano/dsl.rb
-- lib/capistrano/dsl/env.rb
-- lib/capistrano/dsl/paths.rb
-- lib/capistrano/dsl/stages.rb
-- lib/capistrano/dsl/task_enhancements.rb
-- lib/capistrano/framework.rb
-- lib/capistrano/git.rb
-- lib/capistrano/hg.rb
-- lib/capistrano/i18n.rb
-- lib/capistrano/install.rb
-- lib/capistrano/scm.rb
-- lib/capistrano/setup.rb
-- lib/capistrano/svn.rb
-- lib/capistrano/tasks/console.rake
-- lib/capistrano/tasks/deploy.rake
-- lib/capistrano/tasks/framework.rake
-- lib/capistrano/tasks/git.rake
-- lib/capistrano/tasks/hg.rake
-- lib/capistrano/tasks/install.rake
-- lib/capistrano/tasks/svn.rake
-- lib/capistrano/templates/Capfile
-- lib/capistrano/templates/deploy.rb.erb
-- lib/capistrano/templates/stage.rb.erb
-- lib/capistrano/upload_task.rb
-- lib/capistrano/version.rb
-- lib/capistrano/version_validator.rb
-- spec/integration/dsl_spec.rb
-- spec/integration_spec_helper.rb
-- spec/lib/capistrano/application_spec.rb
-- spec/lib/capistrano/configuration/filter_spec.rb
-- spec/lib/capistrano/configuration/question_spec.rb
-- spec/lib/capistrano/configuration/server_spec.rb
-- spec/lib/capistrano/configuration/servers_spec.rb
-- spec/lib/capistrano/configuration_spec.rb
-- spec/lib/capistrano/dsl/paths_spec.rb
-- spec/lib/capistrano/dsl/task_enhancements_spec.rb
-- spec/lib/capistrano/dsl_spec.rb
-- spec/lib/capistrano/git_spec.rb
-- spec/lib/capistrano/hg_spec.rb
-- spec/lib/capistrano/scm_spec.rb
-- spec/lib/capistrano/svn_spec.rb
-- spec/lib/capistrano/upload_task_spec.rb
-- spec/lib/capistrano/version_validator_spec.rb
-- spec/lib/capistrano_spec.rb
-- spec/spec_helper.rb
-- spec/support/.gitignore
-- spec/support/Vagrantfile
-- spec/support/matchers.rb
-- spec/support/tasks/database.rake
-- spec/support/tasks/fail.rake
-- spec/support/tasks/failed.rake
-- spec/support/tasks/root.rake
-- spec/support/test_app.rb
-homepage: http://capistranorb.com/
-licenses:
-- MIT
-metadata: {}
-post_install_message: |
-  Capistrano 3.1 has some breaking changes. Please check the CHANGELOG: http://goo.gl/SxB0lr
-
-  If you're upgrading Capistrano from 2.x, we recommend to read the upgrade guide: http://goo.gl/4536kB
-
-  The `deploy:restart` hook for passenger applications is now in a separate gem called capistrano-passenger.  Just add it to your Gemfile and require it in your Capfile.
-rdoc_options: []
-require_paths:
-- lib
-required_ruby_version: !ruby/object:Gem::Requirement
-  requirements:
-  - - ">="
-    - !ruby/object:Gem::Version
-      version: 1.9.3
-required_rubygems_version: !ruby/object:Gem::Requirement
-  requirements:
-  - - ">="
-    - !ruby/object:Gem::Version
-      version: '0'
-requirements: []
-rubyforge_project: 
-rubygems_version: 2.4.3
-signing_key: 
-specification_version: 4
-summary: Capistrano - Welcome to easy deployment with Ruby over SSH
-test_files:
-- features/configuration.feature
-- features/deploy.feature
-- features/deploy_failure.feature
-- features/installation.feature
-- features/remote_file_task.feature
-- features/sshconnect.feature
-- features/step_definitions/assertions.rb
-- features/step_definitions/cap_commands.rb
-- features/step_definitions/setup.rb
-- features/support/env.rb
-- features/support/remote_command_helpers.rb
-- features/support/vagrant_helpers.rb
-- spec/integration/dsl_spec.rb
-- spec/integration_spec_helper.rb
-- spec/lib/capistrano/application_spec.rb
-- spec/lib/capistrano/configuration/filter_spec.rb
-- spec/lib/capistrano/configuration/question_spec.rb
-- spec/lib/capistrano/configuration/server_spec.rb
-- spec/lib/capistrano/configuration/servers_spec.rb
-- spec/lib/capistrano/configuration_spec.rb
-- spec/lib/capistrano/dsl/paths_spec.rb
-- spec/lib/capistrano/dsl/task_enhancements_spec.rb
-- spec/lib/capistrano/dsl_spec.rb
-- spec/lib/capistrano/git_spec.rb
-- spec/lib/capistrano/hg_spec.rb
-- spec/lib/capistrano/scm_spec.rb
-- spec/lib/capistrano/svn_spec.rb
-- spec/lib/capistrano/upload_task_spec.rb
-- spec/lib/capistrano/version_validator_spec.rb
-- spec/lib/capistrano_spec.rb
-- spec/spec_helper.rb
-- spec/support/.gitignore
-- spec/support/Vagrantfile
-- spec/support/matchers.rb
-- spec/support/tasks/database.rake
-- spec/support/tasks/fail.rake
-- spec/support/tasks/failed.rake
-- spec/support/tasks/root.rake
-- spec/support/test_app.rb
diff --git a/spec/integration/dsl_spec.rb b/spec/integration/dsl_spec.rb
index 49653a3..0134163 100644
--- a/spec/integration/dsl_spec.rb
+++ b/spec/integration/dsl_spec.rb
@@ -1,148 +1,177 @@
-require 'spec_helper'
+require "spec_helper"
 
 describe Capistrano::DSL do
-
   let(:dsl) { Class.new.extend Capistrano::DSL }
 
   before do
     Capistrano::Configuration.reset!
   end
 
-  describe 'setting and fetching hosts' do
-    describe 'when defining a host using the `server` syntax' do
+  describe "setting and fetching hosts" do
+    describe "when defining a host using the `server` syntax" do
       before do
-        dsl.server 'example1.com', roles: %w{web}, active: true
-        dsl.server 'example2.com', roles: %w{web}
-        dsl.server 'example3.com', roles: %w{app web}, active: true
-        dsl.server 'example4.com', roles: %w{app}, primary: true
-        dsl.server 'example5.com', roles: %w{db}, no_release: true, active:true
+        dsl.server "example1.com", roles: %w{web}, active: true
+        dsl.server "example2.com", roles: %w{web}
+        dsl.server "example3.com", roles: %w{app web}, active: true
+        dsl.server "example4.com", roles: %w{app}, primary: true
+        dsl.server "example5.com", roles: %w{db}, no_release: true, active: true
       end
 
-      describe 'fetching all servers' do
+      describe "fetching all servers" do
         subject { dsl.roles(:all) }
 
-        it 'returns all servers' do
+        it "returns all servers" do
           expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com example5.com}
         end
       end
 
-      describe 'fetching all release servers' do
-
-        context 'with no additional options' do
+      describe "fetching all release servers" do
+        context "with no additional options" do
           subject { dsl.release_roles(:all) }
 
-          it 'returns all release servers' do
+          it "returns all release servers" do
             expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com}
           end
         end
 
-        context 'with property filter options' do
+        context "with property filter options" do
           subject { dsl.release_roles(:all, filter: :active) }
 
-          it 'returns all release servers that match the property filter' do
+          it "returns all release servers that match the property filter" do
             expect(subject.map(&:hostname)).to eq %w{example1.com example3.com}
           end
         end
       end
 
-      describe 'fetching servers by multiple roles' do
+      describe "fetching servers by multiple roles" do
         it "does not confuse the last role with options" do
           expect(dsl.roles(:app, :web).count).to eq 4
           expect(dsl.roles(:app, :web, filter: :active).count).to eq 2
         end
       end
 
-      describe 'fetching servers by role' do
+      describe "fetching servers by role" do
         subject { dsl.roles(:app) }
 
-        it 'returns the servers' do
+        it "returns the servers" do
           expect(subject.map(&:hostname)).to eq %w{example3.com example4.com}
         end
       end
 
-      describe 'fetching servers by an array of roles' do
+      describe "fetching servers by an array of roles" do
         subject { dsl.roles([:app]) }
 
-        it 'returns the servers' do
+        it "returns the servers" do
           expect(subject.map(&:hostname)).to eq %w{example3.com example4.com}
         end
       end
 
-      describe 'fetching filtered servers by role' do
+      describe "fetching filtered servers by role" do
         subject { dsl.roles(:app, filter: :active) }
 
-        it 'returns the servers' do
+        it "returns the servers" do
           expect(subject.map(&:hostname)).to eq %w{example3.com}
         end
       end
 
-      describe 'fetching selected servers by role' do
+      describe "fetching selected servers by role" do
         subject { dsl.roles(:app, select: :active) }
 
-        it 'returns the servers' do
+        it "returns the servers" do
           expect(subject.map(&:hostname)).to eq %w{example3.com}
         end
       end
 
-      describe 'fetching the primary server by role' do
-        context 'when inferring primary status based on order' do
+      describe "fetching the primary server by role" do
+        context "when inferring primary status based on order" do
           subject { dsl.primary(:web) }
-          it 'returns the servers' do
-            expect(subject.hostname).to eq 'example1.com'
+          it "returns the servers" do
+            expect(subject.hostname).to eq "example1.com"
           end
         end
 
-        context 'when the attribute `primary` is explicitly set' do
+        context "when the attribute `primary` is explicitly set" do
           subject { dsl.primary(:app) }
-          it 'returns the servers' do
-            expect(subject.hostname).to eq 'example4.com'
+          it "returns the servers" do
+            expect(subject.hostname).to eq "example4.com"
           end
         end
       end
 
-      describe 'setting an internal host filter' do
+      describe "setting an internal host filter" do
         subject { dsl.roles(:app) }
-        it 'is ignored' do
-          dsl.set :filter, { host: 'example3.com' }
-          expect(subject.map(&:hostname)).to eq(['example3.com', 'example4.com'])
+        it "is ignored" do
+          dsl.set :filter, host: "example3.com"
+          expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
         end
       end
 
-      describe 'setting an internal role filter' do
+      describe "setting an internal role filter" do
         subject { dsl.roles(:app) }
-        it 'ignores it' do
-          dsl.set :filter, { role: :web }
-          expect(subject.map(&:hostname)).to eq(['example3.com','example4.com'])
+        it "ignores it" do
+          dsl.set :filter, role: :web
+          expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
         end
       end
 
-      describe 'setting an internal host and role filter' do
+      describe "setting an internal host and role filter" do
         subject { dsl.roles(:app) }
-        it 'ignores it' do
-          dsl.set :filter, { role: :web, host: 'example1.com' }
-          expect(subject.map(&:hostname)).to eq(['example3.com','example4.com'])
+        it "ignores it" do
+          dsl.set :filter, role: :web, host: "example1.com"
+          expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
         end
       end
 
-      describe 'setting an internal regexp host filter' do
+      describe "setting an internal regexp host filter" do
         subject { dsl.roles(:all) }
-        it 'is ignored' do
-          dsl.set :filter, { host: /1/ }
+        it "is ignored" do
+          dsl.set :filter, host: /1/
           expect(subject.map(&:hostname)).to eq(%w{example1.com example2.com example3.com example4.com example5.com})
         end
       end
 
+      describe "setting an internal hosts filter" do
+        subject { dsl.roles(:app) }
+        it "is ignored" do
+          dsl.set :filter, hosts: "example3.com"
+          expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
+        end
+      end
+
+      describe "setting an internal roles filter" do
+        subject { dsl.roles(:app) }
+        it "ignores it" do
+          dsl.set :filter, roles: :web
+          expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
+        end
+      end
+
+      describe "setting an internal hosts and roles filter" do
+        subject { dsl.roles(:app) }
+        it "ignores it" do
+          dsl.set :filter, roles: :web, hosts: "example1.com"
+          expect(subject.map(&:hostname)).to eq(["example3.com", "example4.com"])
+        end
+      end
+
+      describe "setting an internal regexp hosts filter" do
+        subject { dsl.roles(:all) }
+        it "is ignored" do
+          dsl.set :filter, hosts: /1/
+          expect(subject.map(&:hostname)).to eq(%w{example1.com example2.com example3.com example4.com example5.com})
+        end
+      end
     end
 
-    describe 'when defining role with reserved name' do
-      it 'fails with ArgumentError' do
-        expect {
+    describe "when defining role with reserved name" do
+      it "fails with ArgumentError" do
+        expect do
           dsl.role :all, %w{example1.com}
-        }.to raise_error(ArgumentError, "all reserved name for role. Please choose another name")
+        end.to raise_error(ArgumentError, "all reserved name for role. Please choose another name")
       end
     end
 
-    describe 'when defining hosts using the `role` syntax' do
+    describe "when defining hosts using the `role` syntax" do
       before do
         dsl.role :web, %w{example1.com example2.com example3.com}
         dsl.role :web, %w{example1.com}, active: true
@@ -152,243 +181,237 @@ describe Capistrano::DSL do
         dsl.role :db, %w{example5.com}, no_release: true
       end
 
-      describe 'fetching all servers' do
+      describe "fetching all servers" do
         subject { dsl.roles(:all) }
 
-        it 'returns all servers' do
+        it "returns all servers" do
           expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com example5.com}
         end
       end
 
-      describe 'fetching all release servers' do
-
-        context 'with no additional options' do
+      describe "fetching all release servers" do
+        context "with no additional options" do
           subject { dsl.release_roles(:all) }
 
-          it 'returns all release servers' do
+          it "returns all release servers" do
             expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com}
           end
         end
 
-        context 'with filter options' do
+        context "with filter options" do
           subject { dsl.release_roles(:all, filter: :active) }
 
-          it 'returns all release servers that match the filter' do
+          it "returns all release servers that match the filter" do
             expect(subject.map(&:hostname)).to eq %w{example1.com example3.com}
           end
         end
       end
 
-
-      describe 'fetching servers by role' do
+      describe "fetching servers by role" do
         subject { dsl.roles(:app) }
 
-        it 'returns the servers' do
+        it "returns the servers" do
           expect(subject.map(&:hostname)).to eq %w{example3.com example4.com}
         end
       end
 
-      describe 'fetching servers by an array of roles' do
+      describe "fetching servers by an array of roles" do
         subject { dsl.roles([:app]) }
 
-        it 'returns the servers' do
+        it "returns the servers" do
           expect(subject.map(&:hostname)).to eq %w{example3.com example4.com}
         end
       end
 
-      describe 'fetching filtered servers by role' do
+      describe "fetching filtered servers by role" do
         subject { dsl.roles(:app, filter: :active) }
 
-        it 'returns the servers' do
+        it "returns the servers" do
           expect(subject.map(&:hostname)).to eq %w{example3.com}
         end
       end
 
-      describe 'fetching selected servers by role' do
+      describe "fetching selected servers by role" do
         subject { dsl.roles(:app, select: :active) }
 
-        it 'returns the servers' do
+        it "returns the servers" do
           expect(subject.map(&:hostname)).to eq %w{example3.com}
         end
       end
 
-      describe 'fetching the primary server by role' do
-        context 'when inferring primary status based on order' do
+      describe "fetching the primary server by role" do
+        context "when inferring primary status based on order" do
           subject { dsl.primary(:web) }
-          it 'returns the servers' do
-            expect(subject.hostname).to eq 'example1.com'
+          it "returns the servers" do
+            expect(subject.hostname).to eq "example1.com"
           end
         end
 
-        context 'when the attribute `primary` is explicity set' do
+        context "when the attribute `primary` is explicity set" do
           subject { dsl.primary(:app) }
-          it 'returns the servers' do
-            expect(subject.hostname).to eq 'example4.com'
+          it "returns the servers" do
+            expect(subject.hostname).to eq "example4.com"
           end
         end
       end
-
     end
 
-    describe 'when defining a host using a combination of the `server` and `role` syntax' do
-
+    describe "when defining a host using a combination of the `server` and `role` syntax" do
       before do
-        dsl.server 'db at example1.com:1234', roles: %w{db}, active: true
-        dsl.server 'root at example1.com:1234', roles: %w{web}, active: true
-        dsl.server 'example1.com:5678', roles: %w{web}, active: true
+        dsl.server "db at example1.com:1234", roles: %w{db}, active: true
+        dsl.server "root at example1.com:1234", roles: %w{web}, active: true
+        dsl.server "example1.com:5678", roles: %w{web}, active: true
         dsl.role :app, %w{deployer at example1.com:1234}
         dsl.role :app, %w{example1.com:5678}
       end
 
-      describe 'fetching all servers' do
-        it 'creates one server per hostname, ignoring user and port combinations' do
-          expect(dsl.roles(:all).size).to eq(1)
+      describe "fetching all servers" do
+        it "creates one server per hostname, ignoring user combinations" do
+          expect(dsl.roles(:all).size).to eq(2)
         end
       end
 
-      describe 'fetching servers for a role' do
-        it 'roles defined using the `server` syntax are included' do
+      describe "fetching servers for a role" do
+        it "roles defined using the `server` syntax are included" do
           as = dsl.roles(:web).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }
-          expect(as.size).to eq(1)
-          expect(as[0]).to eq("deployer at example1.com:5678")
+          expect(as.size).to eq(2)
+          expect(as[0]).to eq("deployer at example1.com:1234")
+          expect(as[1]).to eq("@example1.com:5678")
         end
 
-        it 'roles defined using the `role` syntax are included' do
+        it "roles defined using the `role` syntax are included" do
           as = dsl.roles(:app).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }
-          expect(as.size).to eq(1)
-          expect(as[0]).to eq("deployer at example1.com:5678")
+          expect(as.size).to eq(2)
+          expect(as[0]).to eq("deployer at example1.com:1234")
+          expect(as[1]).to eq("@example1.com:5678")
         end
       end
-
     end
 
-    describe 'when setting user and port' do
+    describe "when setting user and port" do
       subject { dsl.roles(:all).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }.first }
 
       describe "using the :user property" do
         it "takes precedence over in the host string" do
-          dsl.server 'db at example1.com:1234', roles: %w{db}, active: true, user: 'brian'
+          dsl.server "db at example1.com:1234", roles: %w{db}, active: true, user: "brian"
           expect(subject).to eq("brian at example1.com:1234")
         end
       end
 
       describe "using the :port property" do
         it "takes precedence over in the host string" do
-          dsl.server 'db at example1.com:9090', roles: %w{db}, active: true, port: 1234
+          dsl.server "db at example1.com:9090", roles: %w{db}, active: true, port: 1234
           expect(subject).to eq("db at example1.com:1234")
         end
       end
     end
-
   end
 
-  describe 'setting and fetching variables' do
-
+  describe "setting and fetching variables" do
     before do
       dsl.set :scm, :git
     end
 
-    context 'without a default' do
-      context 'when the variables is defined' do
-        it 'returns the variable' do
+    context "without a default" do
+      context "when the variables is defined" do
+        it "returns the variable" do
           expect(dsl.fetch(:scm)).to eq :git
         end
       end
 
-      context 'when the variables is undefined' do
-        it 'returns nil' do
+      context "when the variables is undefined" do
+        it "returns nil" do
           expect(dsl.fetch(:source_control)).to be_nil
         end
       end
     end
 
-    context 'with a default' do
-      context 'when the variables is defined' do
-        it 'returns the variable' do
+    context "with a default" do
+      context "when the variables is defined" do
+        it "returns the variable" do
           expect(dsl.fetch(:scm, :svn)).to eq :git
         end
       end
 
-      context 'when the variables is undefined' do
-        it 'returns the default' do
+      context "when the variables is undefined" do
+        it "returns the default" do
           expect(dsl.fetch(:source_control, :svn)).to eq :svn
         end
       end
     end
 
-    context 'with a block' do
-      context 'when the variables is defined' do
-        it 'returns the variable' do
+    context "with a block" do
+      context "when the variables is defined" do
+        it "returns the variable" do
           expect(dsl.fetch(:scm) { :svn }).to eq :git
         end
       end
 
-      context 'when the variables is undefined' do
-        it 'calls the block' do
+      context "when the variables is undefined" do
+        it "calls the block" do
           expect(dsl.fetch(:source_control) { :svn }).to eq :svn
         end
       end
     end
-
   end
 
-  describe 'asking for a variable' do
+  describe "asking for a variable" do
     before do
       dsl.ask(:scm, :svn)
       $stdout.stubs(:print)
     end
 
-    context 'variable is provided' do
+    context "variable is provided" do
       before do
-        $stdin.expects(:gets).returns('git')
+        $stdin.expects(:gets).returns("git")
       end
 
-      it 'sets the input as the variable' do
-        expect(dsl.fetch(:scm)).to eq 'git'
+      it "sets the input as the variable" do
+        expect(dsl.fetch(:scm)).to eq "git"
       end
     end
 
-    context 'variable is not provided' do
+    context "variable is not provided" do
       before do
-        $stdin.expects(:gets).returns('')
+        $stdin.expects(:gets).returns("")
       end
 
-      it 'sets the variable as the default' do
+      it "sets the variable as the default" do
         expect(dsl.fetch(:scm)).to eq :svn
       end
     end
   end
 
-  describe 'checking for presence' do
+  describe "checking for presence" do
     subject { dsl.any? :linked_files }
 
     before do
       dsl.set(:linked_files, linked_files)
     end
 
-    context 'variable is an non-empty array' do
+    context "variable is an non-empty array" do
       let(:linked_files) { %w{1} }
 
       it { expect(subject).to be_truthy }
     end
 
-    context 'variable is an empty array' do
+    context "variable is an empty array" do
       let(:linked_files) { [] }
       it { expect(subject).to be_falsey }
     end
 
-    context 'variable exists, is not an array' do
+    context "variable exists, is not an array" do
       let(:linked_files) { stub }
       it { expect(subject).to be_truthy }
     end
 
-    context 'variable is nil' do
+    context "variable is nil" do
       let(:linked_files) { nil }
       it { expect(subject).to be_falsey }
     end
   end
 
-  describe 'configuration SSHKit' do
+  describe "configuration SSHKit" do
     let(:config) { SSHKit.config }
     let(:backend) { SSHKit.config.backend.config }
     let(:default_env) { { rails_env: :production } }
@@ -399,185 +422,209 @@ describe Capistrano::DSL do
       dsl.set(:default_env, default_env)
       dsl.set(:pty, true)
       dsl.set(:connection_timeout, 10)
-      dsl.set(:ssh_options, {
-        keys: %w(/home/user/.ssh/id_rsa),
-        forward_agent: false,
-        auth_methods: %w(publickey password)
-      })
+      dsl.set(:ssh_options, keys: %w(/home/user/.ssh/id_rsa),
+                            forward_agent: false,
+                            auth_methods: %w(publickey password))
       dsl.configure_backend
     end
 
-    it 'sets the output' do
+    it "sets the output" do
       expect(config.output).to be_a SSHKit::Formatter::Dot
     end
 
-    it 'sets the output verbosity' do
+    it "sets the output verbosity" do
       expect(config.output_verbosity).to eq 0
     end
 
-    it 'sets the default env' do
+    it "sets the default env" do
       expect(config.default_env).to eq default_env
     end
 
-    it 'sets the backend pty' do
+    it "sets the backend pty" do
       expect(backend.pty).to be_truthy
     end
 
-    it 'sets the backend connection timeout' do
+    it "sets the backend connection timeout" do
       expect(backend.connection_timeout).to eq 10
     end
 
-    it 'sets the backend ssh_options' do
+    it "sets the backend ssh_options" do
       expect(backend.ssh_options[:keys]).to eq %w(/home/user/.ssh/id_rsa)
       expect(backend.ssh_options[:forward_agent]).to eq false
       expect(backend.ssh_options[:auth_methods]).to eq %w(publickey password)
     end
-
   end
 
-  describe 'local_user' do
-    before do
-      dsl.set :local_user, -> { Etc.getlogin }
-    end
-
-    describe 'fetching local_user' do
-      subject { dsl.local_user }
+  describe "on()" do
+    describe "when passed server objects" do
+      before do
+        dsl.server "example1.com", roles: %w{web}, active: true
+        dsl.server "example2.com", roles: %w{web}
+        dsl.server "example3.com", roles: %w{app web}, active: true
+        dsl.server "example4.com", roles: %w{app}, primary: true
+        dsl.server "example5.com", roles: %w{db}, no_release: true
+        @coordinator = mock("coordinator")
+        @coordinator.expects(:each).returns(nil)
+        ENV.delete "ROLES"
+        ENV.delete "HOSTS"
+      end
 
-      context 'where a local_user is not set' do
-        before do
-          Etc.expects(:getlogin).returns('login')
-        end
+      it "filters by role from the :filter variable" do
+        hosts = dsl.roles(:web)
+        all = dsl.roles(:all)
+        SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
+        dsl.set :filter, role: "web"
+        dsl.on(all)
+      end
 
-        it 'returns the login name' do
-          expect(subject.to_s).to eq 'login'
-        end
+      it "filters by host and role from the :filter variable" do
+        all = dsl.roles(:all)
+        SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
+        dsl.set :filter, role: "db", host: "example3.com"
+        dsl.on(all)
       end
 
-      context 'where a local_user is set' do
-        before do
-          dsl.set(:local_user, -> { 'custom login' })
-        end
+      it "filters by roles from the :filter variable" do
+        hosts = dsl.roles(:web)
+        all = dsl.roles(:all)
+        SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
+        dsl.set :filter, roles: "web"
+        dsl.on(all)
+      end
 
-        it 'returns the custom name' do
-          expect(subject.to_s).to eq 'custom login'
-        end
+      it "filters by hosts and roles from the :filter variable" do
+        all = dsl.roles(:all)
+        SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
+        dsl.set :filter, roles: "db", hosts: "example3.com"
+        dsl.on(all)
       end
-    end
-  end
 
-  describe 'on()' do
+      it "filters from ENV[ROLES]" do
+        hosts = dsl.roles(:db)
+        all = dsl.roles(:all)
+        SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
+        ENV["ROLES"] = "db"
+        dsl.on(all)
+      end
 
-    before do
-      dsl.server 'example1.com', roles: %w{web}, active: true
-      dsl.server 'example2.com', roles: %w{web}
-      dsl.server 'example3.com', roles: %w{app web}, active: true
-      dsl.server 'example4.com', roles: %w{app}, primary: true
-      dsl.server 'example5.com', roles: %w{db}, no_release: true
-      @coordinator = mock('coordinator')
-      @coordinator.expects(:each).returns(nil)
-      ENV.delete 'ROLES'
-      ENV.delete 'HOSTS'
+      it "filters from ENV[HOSTS]" do
+        hosts = dsl.roles(:db)
+        all = dsl.roles(:all)
+        SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
+        ENV["HOSTS"] = "example5.com"
+        dsl.on(all)
+      end
 
+      it "filters by ENV[HOSTS] && ENV[ROLES]" do
+        all = dsl.roles(:all)
+        SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
+        ENV["HOSTS"] = "example5.com"
+        ENV["ROLES"] = "web"
+        dsl.on(all)
+      end
     end
 
-    it 'filters by role from the :filter variable' do
-      hosts = dsl.roles(:web)
-      all = dsl.roles(:all)
-      SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
-      dsl.set :filter, { role: 'web' }
-      dsl.on(all)
-    end
+    describe "when passed server literal names" do
+      before do
+        ENV.delete "ROLES"
+        ENV.delete "HOSTS"
+        @coordinator = mock("coordinator")
+        @coordinator.expects(:each).returns(nil)
+      end
 
-    it 'filters by host and role from the :filter variable' do
-      all = dsl.roles(:all)
-      SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
-      dsl.set :filter, { role: 'db', host: 'example3.com' }
-      dsl.on(all)
-    end
+      it "selects nothing when a role filter is present" do
+        dsl.set :filter, role: "web"
+        SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
+        dsl.on("my.server")
+      end
 
-    it 'filters from ENV[ROLES]' do
-      hosts = dsl.roles(:db)
-      all = dsl.roles(:all)
-      SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
-      ENV['ROLES'] = 'db'
-      dsl.on(all)
-    end
+      it "selects using the string when a host filter is present" do
+        dsl.set :filter, host: "server.local"
+        SSHKit::Coordinator.expects(:new).with(["server.local"]).returns(@coordinator)
+        dsl.on("server.local")
+      end
 
-    it 'filters from ENV[HOSTS]' do
-      hosts = dsl.roles(:db)
-      all = dsl.roles(:all)
-      SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
-      ENV['HOSTS'] = 'example5.com'
-      dsl.on(all)
-    end
+      it "doesn't select when a host filter is present that doesn't match" do
+        dsl.set :filter, host: "ruby.local"
+        SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
+        dsl.on("server.local")
+      end
 
-    it 'filters by ENV[HOSTS] && ENV[ROLES]' do
-      all = dsl.roles(:all)
-      SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
-      ENV['HOSTS'] = 'example5.com'
-      ENV['ROLES'] = 'web'
-      dsl.on(all)
-    end
+      it "selects nothing when a roles filter is present" do
+        dsl.set :filter, roles: "web"
+        SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
+        dsl.on("my.server")
+      end
 
-  end
+      it "selects using the string when a hosts filter is present" do
+        dsl.set :filter, hosts: "server.local"
+        SSHKit::Coordinator.expects(:new).with(["server.local"]).returns(@coordinator)
+        dsl.on("server.local")
+      end
 
-  describe 'role_properties()' do
+      it "doesn't select when a hosts filter is present that doesn't match" do
+        dsl.set :filter, hosts: "ruby.local"
+        SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
+        dsl.on("server.local")
+      end
+    end
+  end
 
+  describe "role_properties()" do
     before do
       dsl.role :redis, %w[example1.com example2.com], redis: { port: 6379, type: :slave }
-      dsl.server 'example1.com', roles: %w{web}, active: true, web: { port: 80 }
-      dsl.server 'example2.com', roles: %w{web redis}, web: { port: 81 }, redis: { type: :master }
-      dsl.server 'example3.com', roles: %w{app}, primary: true
+      dsl.server "example1.com", roles: %w{web}, active: true, web: { port: 80 }
+      dsl.server "example2.com", roles: %w{web redis}, web: { port: 81 }, redis: { type: :master }
+      dsl.server "example3.com", roles: %w{app}, primary: true
     end
 
-    it 'retrieves properties for a single role as a set' do
+    it "retrieves properties for a single role as a set" do
       rps = dsl.role_properties(:app)
-      expect(rps).to eq(Set[{ hostname: 'example3.com', role: :app}])
+      expect(rps).to eq(Set[{ hostname: "example3.com", role: :app }])
     end
 
-    it 'retrieves properties for multiple roles as a set' do
+    it "retrieves properties for multiple roles as a set" do
       rps = dsl.role_properties(:app, :web)
-      expect(rps).to eq(Set[{ hostname: 'example3.com', role: :app},{ hostname: 'example1.com', role: :web, port: 80},{ hostname: 'example2.com', role: :web, port: 81}])
+      expect(rps).to eq(Set[{ hostname: "example3.com", role: :app }, { hostname: "example1.com", role: :web, port: 80 }, { hostname: "example2.com", role: :web, port: 81 }])
     end
 
-    it 'yields the properties for a single role' do
-      recipient = mock('recipient')
-      recipient.expects(:doit).with('example1.com', :redis, { port: 6379, type: :slave})
-      recipient.expects(:doit).with('example2.com', :redis, { port: 6379, type: :master})
+    it "yields the properties for a single role" do
+      recipient = mock("recipient")
+      recipient.expects(:doit).with("example1.com", :redis, port: 6379, type: :slave)
+      recipient.expects(:doit).with("example2.com", :redis, port: 6379, type: :master)
       dsl.role_properties(:redis) do |host, role, props|
         recipient.doit(host, role, props)
       end
     end
 
-    it 'yields the properties for multiple roles' do
-      recipient = mock('recipient')
-      recipient.expects(:doit).with('example1.com', :redis, { port: 6379, type: :slave})
-      recipient.expects(:doit).with('example2.com', :redis, { port: 6379, type: :master})
-      recipient.expects(:doit).with('example3.com', :app, nil)
+    it "yields the properties for multiple roles" do
+      recipient = mock("recipient")
+      recipient.expects(:doit).with("example1.com", :redis, port: 6379, type: :slave)
+      recipient.expects(:doit).with("example2.com", :redis, port: 6379, type: :master)
+      recipient.expects(:doit).with("example3.com", :app, nil)
       dsl.role_properties(:redis, :app) do |host, role, props|
         recipient.doit(host, role, props)
       end
     end
 
-    it 'yields the merged properties for multiple roles' do
-      recipient = mock('recipient')
-      recipient.expects(:doit).with('example1.com', :redis, { port: 6379, type: :slave})
-      recipient.expects(:doit).with('example2.com', :redis, { port: 6379, type: :master})
-      recipient.expects(:doit).with('example1.com', :web, { port: 80 })
-      recipient.expects(:doit).with('example2.com', :web, { port: 81 })
+    it "yields the merged properties for multiple roles" do
+      recipient = mock("recipient")
+      recipient.expects(:doit).with("example1.com", :redis, port: 6379, type: :slave)
+      recipient.expects(:doit).with("example2.com", :redis, port: 6379, type: :master)
+      recipient.expects(:doit).with("example1.com", :web, port: 80)
+      recipient.expects(:doit).with("example2.com", :web, port: 81)
       dsl.role_properties(:redis, :web) do |host, role, props|
         recipient.doit(host, role, props)
       end
     end
 
-    it 'honours a property filter before yielding' do
-      recipient = mock('recipient')
-      recipient.expects(:doit).with('example1.com', :redis, { port: 6379, type: :slave})
-      recipient.expects(:doit).with('example1.com', :web, { port: 80 })
+    it "honours a property filter before yielding" do
+      recipient = mock("recipient")
+      recipient.expects(:doit).with("example1.com", :redis, port: 6379, type: :slave)
+      recipient.expects(:doit).with("example1.com", :web, port: 80)
       dsl.role_properties(:redis, :web, select: :active) do |host, role, props|
         recipient.doit(host, role, props)
       end
     end
   end
-
 end
diff --git a/spec/integration_spec_helper.rb b/spec/integration_spec_helper.rb
index 4fbaf2f..180713d 100644
--- a/spec/integration_spec_helper.rb
+++ b/spec/integration_spec_helper.rb
@@ -1,7 +1,5 @@
-require 'spec_helper'
-require 'support/test_app'
-require 'support/matchers'
+require "spec_helper"
+require "support/test_app"
+require "support/matchers"
 
 include TestApp
-
-
diff --git a/spec/lib/capistrano/application_spec.rb b/spec/lib/capistrano/application_spec.rb
index bb52765..41e5ce6 100644
--- a/spec/lib/capistrano/application_spec.rb
+++ b/spec/lib/capistrano/application_spec.rb
@@ -1,14 +1,13 @@
-require 'spec_helper'
+require "spec_helper"
 
 describe Capistrano::Application do
-
   it "provides a --trace option which enables SSHKit/NetSSH trace output"
 
   it "provides a --format option which enables the choice of output formatting"
 
   let(:help_output) do
-    out, _ = capture_io do
-      flags '--help', '-h'
+    out, _err = capture_io do
+      flags "--help", "-h"
     end
     out
   end
@@ -24,23 +23,30 @@ describe Capistrano::Application do
   end
 
   it "overrides the rake method, but still prints the rake version" do
-    out, _ = capture_io do
-      flags '--version', '-V'
+    out, _err = capture_io do
+      flags "--version", "-V"
     end
     expect(out).to match(/\bCapistrano Version\b/)
     expect(out).to match(/\b#{Capistrano::VERSION}\b/)
     expect(out).to match(/\bRake Version\b/)
-    expect(out).to match(/\b#{RAKEVERSION}\b/)
+    expect(out).to match(/\b#{Rake::VERSION}\b/)
   end
 
   it "overrides the rake method, and sets the sshkit_backend to SSHKit::Backend::Printer" do
-    out, _ = capture_io do
-      flags '--dry-run', '-n'
+    capture_io do
+      flags "--dry-run", "-n"
     end
     sshkit_backend = Capistrano::Configuration.fetch(:sshkit_backend)
     expect(sshkit_backend).to eq(SSHKit::Backend::Printer)
   end
 
+  it "enables printing all config variables on command line parameter" do
+    capture_io do
+      flags "--print-config-variables", "-p"
+    end
+    expect(Capistrano::Configuration.fetch(:print_config_variables)).to be true
+  end
+
   def flags(*sets)
     sets.each do |set|
       ARGV.clear
@@ -51,7 +57,7 @@ describe Capistrano::Application do
 
   def command_line(*options)
     options.each { |opt| ARGV << opt }
-    def subject.exit(*args)
+    subject.define_singleton_method(:exit) do |*_args|
       throw(:system_exit, :exit)
     end
     subject.run
@@ -59,11 +65,14 @@ describe Capistrano::Application do
   end
 
   def capture_io
-    require 'stringio'
+    require "stringio"
 
-    orig_stdout, orig_stderr         = $stdout, $stderr
-    captured_stdout, captured_stderr = StringIO.new, StringIO.new
-    $stdout, $stderr                 = captured_stdout, captured_stderr
+    orig_stdout = $stdout
+    orig_stderr = $stderr
+    captured_stdout = StringIO.new
+    captured_stderr = StringIO.new
+    $stdout = captured_stdout
+    $stderr = captured_stderr
 
     yield
 
@@ -72,5 +81,4 @@ describe Capistrano::Application do
     $stdout = orig_stdout
     $stderr = orig_stderr
   end
-
 end
diff --git a/spec/lib/capistrano/configuration/empty_filter_spec.rb b/spec/lib/capistrano/configuration/empty_filter_spec.rb
new file mode 100644
index 0000000..971db48
--- /dev/null
+++ b/spec/lib/capistrano/configuration/empty_filter_spec.rb
@@ -0,0 +1,17 @@
+require "spec_helper"
+
+module Capistrano
+  class Configuration
+    describe EmptyFilter do
+      subject(:empty_filter) { EmptyFilter.new }
+
+      describe "#filter" do
+        let(:servers) { mock("servers") }
+
+        it "returns an empty array" do
+          expect(empty_filter.filter(servers)).to eq([])
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/capistrano/configuration/filter_spec.rb b/spec/lib/capistrano/configuration/filter_spec.rb
index 65ad3f7..4d945dc 100644
--- a/spec/lib/capistrano/configuration/filter_spec.rb
+++ b/spec/lib/capistrano/configuration/filter_spec.rb
@@ -1,109 +1,107 @@
-require 'spec_helper'
+require "spec_helper"
 
 module Capistrano
   class Configuration
-
     describe Filter do
-      let(:available) { [ Server.new('server1').add_roles([:web,:db]),
-                          Server.new('server2').add_role(:web),
-                          Server.new('server3').add_role(:redis),
-                          Server.new('server4').add_role(:db),
-                          Server.new('server5').add_role(:stageweb) ] }
+      let(:available) do
+        [
+          Server.new("server1").add_roles([:web, :db]),
+          Server.new("server2").add_role(:web),
+          Server.new("server3").add_role(:redis),
+          Server.new("server4").add_role(:db),
+          Server.new("server5").add_role(:stageweb)
+        ]
+      end
 
-      describe '#new' do
+      describe "#new" do
         it "won't create an invalid type of filter" do
-          expect {
-            f = Filter.new(:zarg)
-          }.to raise_error RuntimeError
+          expect do
+            Filter.new(:zarg)
+          end.to raise_error RuntimeError
         end
 
-        it 'creates an empty host filter' do
-          expect(Filter.new(:host).filter(available)).to be_empty
-        end
+        context "with type :host" do
+          context "and no values" do
+            it "creates an EmptyFilter strategy" do
+              expect(Filter.new(:host).instance_variable_get(:@strategy)).to be_a(EmptyFilter)
+            end
+          end
 
-        it 'creates a null host filter' do
-          expect(Filter.new(:host, :all).filter(available)).to eq(available)
-        end
+          context "and :all" do
+            it "creates an NullFilter strategy" do
+              expect(Filter.new(:host, :all).instance_variable_get(:@strategy)).to be_a(NullFilter)
+            end
+          end
 
-        it 'creates an empty role filter' do
-          expect(Filter.new(:role).filter(available)).to be_empty
-        end
+          context "and [:all]" do
+            it "creates an NullFilter strategy" do
+              expect(Filter.new(:host, [:all]).instance_variable_get(:@strategy)).to be_a(NullFilter)
+            end
+          end
 
-        it 'creates a null role filter' do
-          expect(Filter.new(:role, :all).filter(available)).to eq(available)
+          context "and [:all]" do
+            it "creates an NullFilter strategy" do
+              expect(Filter.new(:host, "all").instance_variable_get(:@strategy)).to be_a(NullFilter)
+            end
+          end
         end
 
-      end
+        context "with type :role" do
+          context "and no values" do
+            it "creates an EmptyFilter strategy" do
+              expect(Filter.new(:role).instance_variable_get(:@strategy)).to be_a(EmptyFilter)
+            end
+          end
 
-      describe 'host filter' do
-        it 'works with a single server' do
-          set = Filter.new(:host, 'server1').filter(available.first)
-          expect(set.map(&:hostname)).to eq(%w{server1})
-        end
-        it 'returns all hosts matching a string' do
-          set = Filter.new(:host, 'server1').filter(available)
-          expect(set.map(&:hostname)).to eq(%w{server1})
-        end
-        it 'returns all hosts matching a comma-separated string' do
-          set = Filter.new(:host, 'server1,server3').filter(available)
-          expect(set.map(&:hostname)).to eq(%w{server1 server3})
-        end
-        it 'returns all hosts matching an array of strings' do
-          set = Filter.new(:host, %w{server1 server3}).filter(available)
-          expect(set.map(&:hostname)).to eq(%w{server1 server3})
-        end
-        it 'returns all hosts matching regexp' do
-          set = Filter.new(:host, 'server[13]$').filter(available)
-          expect(set.map(&:hostname)).to eq(%w{server1 server3})
-        end
-        it 'correctly identifies a regex with a comma in' do
-          set = Filter.new(:host, 'server\d{1,3}$').filter(available)
-          expect(set.map(&:hostname)).to eq(%w{server1 server2 server3 server4 server5})
+          context "and :all" do
+            it "creates an NullFilter strategy" do
+              expect(Filter.new(:role, :all).instance_variable_get(:@strategy)).to be_a(NullFilter)
+            end
+          end
+
+          context "and [:all]" do
+            it "creates an NullFilter strategy" do
+              expect(Filter.new(:role, [:all]).instance_variable_get(:@strategy)).to be_a(NullFilter)
+            end
+          end
+
+          context "and [:all]" do
+            it "creates an NullFilter strategy" do
+              expect(Filter.new(:role, "all").instance_variable_get(:@strategy)).to be_a(NullFilter)
+            end
+          end
         end
       end
 
-      describe 'role filter' do
-        it 'returns all hosts' do
-          set = Filter.new(:role, [:all]).filter(available)
-          expect(set.size).to eq(available.size)
-          expect(set.first.hostname).to eq('server1')
-        end
-        it 'returns hosts in a single string role' do
-          set = Filter.new(:role, 'web').filter(available)
-          expect(set.size).to eq(2)
-          expect(set.map(&:hostname)).to eq(%w{server1 server2})
-        end
-        it 'returns hosts in a single role' do
-          set = Filter.new(:role, [:web]).filter(available)
-          expect(set.size).to eq(2)
-          expect(set.map(&:hostname)).to eq(%w{server1 server2})
-        end
-        it 'returns hosts in multiple roles specified by a string' do
-          set = Filter.new(:role, 'web,db').filter(available)
-          expect(set.size).to eq(3)
-          expect(set.map(&:hostname)).to eq(%w{server1 server2 server4})
-        end
-        it 'returns hosts in multiple roles' do
-          set = Filter.new(:role, [:web, :db]).filter(available)
-          expect(set.size).to eq(3)
-          expect(set.map(&:hostname)).to eq(%w{server1 server2 server4})
+      describe "#filter" do
+        let(:strategy) { filter.instance_variable_get(:@strategy) }
+        let(:results) { mock("result") }
+
+        shared_examples "it calls #filter on its strategy" do
+          it "calls #filter on its strategy" do
+            strategy.expects(:filter).with(available).returns(results)
+            expect(filter.filter(available)).to eq(results)
+          end
         end
-        it 'returns only hosts for explicit roles' do
-          set = Filter.new(:role, [:web]).filter(available)
-          expect(set.size).to eq(2)
-          expect(set.map(&:hostname)).to eq(%w{server1 server2})
+
+        context "for an empty filter" do
+          let(:filter) { Filter.new(:role) }
+          it_behaves_like "it calls #filter on its strategy"
         end
-        it 'returns hosts with regex role selection' do
-          set = Filter.new(:role, /red/).filter(available)
-          expect(set.map(&:hostname)).to eq(%w{server3})
+
+        context "for a null filter" do
+          let(:filter) { Filter.new(:role, :all) }
+          it_behaves_like "it calls #filter on its strategy"
         end
-        it 'returns hosts with regex role selection using a string' do
-          set = Filter.new(:role, '/red|web/').filter(available)
-          expect(set.map(&:hostname)).to eq(%w{server1 server2 server3 server5})
+
+        context "for a role filter" do
+          let(:filter) { Filter.new(:role, "web") }
+          it_behaves_like "it calls #filter on its strategy"
         end
-        it 'returns hosts with combination of string role and regex' do
-          set = Filter.new(:role, 'db,/red/').filter(available)
-          expect(set.map(&:hostname)).to eq(%w{server1 server3 server4})
+
+        context "for a host filter" do
+          let(:filter) { Filter.new(:host, "server1") }
+          it_behaves_like "it calls #filter on its strategy"
         end
       end
     end
diff --git a/spec/lib/capistrano/configuration/host_filter_spec.rb b/spec/lib/capistrano/configuration/host_filter_spec.rb
new file mode 100644
index 0000000..9e97073
--- /dev/null
+++ b/spec/lib/capistrano/configuration/host_filter_spec.rb
@@ -0,0 +1,61 @@
+require "spec_helper"
+
+module Capistrano
+  class Configuration
+    describe HostFilter do
+      subject(:host_filter) { HostFilter.new(values) }
+
+      let(:available) do
+        [Server.new("server1"),
+         Server.new("server2"),
+         Server.new("server3"),
+         Server.new("server4"),
+         Server.new("server5")]
+      end
+
+      shared_examples "it filters hosts correctly" do |expected|
+        it "filters correctly" do
+          set = host_filter.filter(available)
+          expect(set.map(&:hostname)).to eq(expected)
+        end
+      end
+
+      describe "#filter" do
+        context "with a string" do
+          let(:values) { "server1" }
+          it_behaves_like "it filters hosts correctly", %w{server1}
+
+          context "and a single server" do
+            let(:available) { Server.new("server1") }
+            it_behaves_like "it filters hosts correctly", %w{server1}
+          end
+        end
+
+        context "with a comma separated string" do
+          let(:values) { "server1,server3" }
+          it_behaves_like "it filters hosts correctly", %w{server1 server3}
+        end
+
+        context "with an array of strings" do
+          let(:values) { %w{server1 server3} }
+          it_behaves_like "it filters hosts correctly", %w{server1 server3}
+        end
+
+        context "with a regexp" do
+          let(:values) { "server[13]$" }
+          it_behaves_like "it filters hosts correctly", %w{server1 server3}
+        end
+
+        context "with a regexp with line boundaries" do
+          let(:values) { "^server" }
+          it_behaves_like "it filters hosts correctly", %w{server1 server2 server3 server4 server5}
+        end
+
+        context "with a regexp with a comma" do
+          let(:values) { 'server\d{1,3}$' }
+          it_behaves_like "it filters hosts correctly", %w{server1 server2 server3 server4 server5}
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/capistrano/configuration/null_filter_spec.rb b/spec/lib/capistrano/configuration/null_filter_spec.rb
new file mode 100644
index 0000000..fc11614
--- /dev/null
+++ b/spec/lib/capistrano/configuration/null_filter_spec.rb
@@ -0,0 +1,17 @@
+require "spec_helper"
+
+module Capistrano
+  class Configuration
+    describe NullFilter do
+      subject(:null_filter) { NullFilter.new }
+
+      describe "#filter" do
+        let(:servers) { mock("servers") }
+
+        it "returns the servers passed in as arguments" do
+          expect(null_filter.filter(servers)).to eq(servers)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/capistrano/configuration/question_spec.rb b/spec/lib/capistrano/configuration/question_spec.rb
index 289d7f7..eefdd3a 100644
--- a/spec/lib/capistrano/configuration/question_spec.rb
+++ b/spec/lib/capistrano/configuration/question_spec.rb
@@ -1,38 +1,36 @@
-require 'spec_helper'
+require "spec_helper"
 
 module Capistrano
   class Configuration
-
     describe Question do
-
       let(:question) { Question.new(key, default, options) }
       let(:question_without_echo) { Question.new(key, default, echo: false) }
       let(:default) { :default }
       let(:key) { :branch }
       let(:options) { nil }
 
-      describe '.new' do
-        it 'takes a key, default, options' do
+      describe ".new" do
+        it "takes a key, default, options" do
           question
         end
       end
 
-      describe '#call' do
-        context 'value is entered' do
-          let(:branch) { 'branch' }
+      describe "#call" do
+        context "value is entered" do
+          let(:branch) { "branch" }
 
           before do
-            $stdout.expects(:print).with('Please enter branch (default): ')
+            $stdout.expects(:print).with("Please enter branch (default): ")
           end
 
-          it 'returns the echoed value' do
+          it "returns the echoed value" do
             $stdin.expects(:gets).returns(branch)
             $stdin.expects(:noecho).never
 
             expect(question.call).to eq(branch)
           end
 
-          it 'returns the value but does not echo it' do
+          it "returns the value but does not echo it" do
             $stdin.expects(:noecho).returns(branch)
             $stdout.expects(:print).with("\n")
 
@@ -40,21 +38,19 @@ module Capistrano
           end
         end
 
-        context 'value is not entered' do
+        context "value is not entered" do
           let(:branch) { default }
 
           before do
-            $stdout.expects(:print).with('Please enter branch (default): ')
-            $stdin.expects(:gets).returns('')
+            $stdout.expects(:print).with("Please enter branch (default): ")
+            $stdin.expects(:gets).returns("")
           end
 
-
-          it 'returns the default as the value' do
+          it "returns the default as the value" do
             expect(question.call).to eq(branch)
           end
         end
       end
     end
-
   end
 end
diff --git a/spec/lib/capistrano/configuration/role_filter_spec.rb b/spec/lib/capistrano/configuration/role_filter_spec.rb
new file mode 100644
index 0000000..aa3173c
--- /dev/null
+++ b/spec/lib/capistrano/configuration/role_filter_spec.rb
@@ -0,0 +1,64 @@
+require "spec_helper"
+
+module Capistrano
+  class Configuration
+    describe RoleFilter do
+      subject(:role_filter) { RoleFilter.new(values) }
+
+      let(:available) do
+        [
+          Server.new("server1").add_roles([:web, :db]),
+          Server.new("server2").add_role(:web),
+          Server.new("server3").add_role(:redis),
+          Server.new("server4").add_role(:db),
+          Server.new("server5").add_role(:stageweb)
+        ]
+      end
+
+      shared_examples "it filters roles correctly" do |expected_size, expected|
+        it "filters correctly" do
+          set = role_filter.filter(available)
+          expect(set.size).to eq(expected_size)
+          expect(set.map(&:hostname)).to eq(expected)
+        end
+      end
+
+      describe "#filter" do
+        context "with a single role string" do
+          let(:values) { "web" }
+          it_behaves_like "it filters roles correctly", 2, %w{server1 server2}
+        end
+
+        context "with a single role" do
+          let(:values) { [:web] }
+          it_behaves_like "it filters roles correctly", 2, %w{server1 server2}
+        end
+
+        context "with multiple roles in a string" do
+          let(:values) { "web,db" }
+          it_behaves_like "it filters roles correctly", 3, %w{server1 server2 server4}
+        end
+
+        context "with multiple roles" do
+          let(:values) { [:web, :db] }
+          it_behaves_like "it filters roles correctly", 3, %w{server1 server2 server4}
+        end
+
+        context "with a regex" do
+          let(:values) { /red/ }
+          it_behaves_like "it filters roles correctly", 1, %w{server3}
+        end
+
+        context "with a regex string" do
+          let(:values) { "/red|web/" }
+          it_behaves_like "it filters roles correctly", 4, %w{server1 server2 server3 server5}
+        end
+
+        context "with both a string and regex" do
+          let(:values) { "db,/red/" }
+          it_behaves_like "it filters roles correctly", 3, %w{server1 server3 server4}
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/capistrano/configuration/server_spec.rb b/spec/lib/capistrano/configuration/server_spec.rb
index ad81f3d..0faa385 100644
--- a/spec/lib/capistrano/configuration/server_spec.rb
+++ b/spec/lib/capistrano/configuration/server_spec.rb
@@ -1,141 +1,139 @@
-require 'spec_helper'
+require "spec_helper"
 
 module Capistrano
   class Configuration
     describe Server do
-      let(:server) { Server.new('root at hostname:1234') }
+      let(:server) { Server.new("root at hostname:1234") }
 
-      describe 'adding a role' do
+      describe "adding a role" do
         subject { server.add_role(:test) }
-        it 'adds the role' do
-          expect{subject}.to change{server.roles.size}.from(0).to(1)
+        it "adds the role" do
+          expect { subject }.to change { server.roles.size }.from(0).to(1)
         end
       end
 
-      describe 'adding roles' do
+      describe "adding roles" do
         subject { server.add_roles([:things, :stuff]) }
-        it 'adds the roles' do
-          expect{subject}.to change{server.roles.size}.from(0).to(2)
+        it "adds the roles" do
+          expect { subject }.to change { server.roles.size }.from(0).to(2)
         end
       end
 
-
-      describe 'checking roles' do
+      describe "checking roles" do
         subject { server.has_role?(:test) }
 
         before do
           server.add_role(:test)
         end
 
-        it 'adds the role' do
+        it "adds the role" do
           expect(subject).to be_truthy
         end
       end
 
-      describe 'comparing identity' do
+      describe "comparing identity" do
         subject { server.hostname == Server[hostname].hostname }
 
-        context 'with the same user, hostname and port' do
-          let(:hostname) { 'root at hostname:1234' }
+        context "with the same user, hostname and port" do
+          let(:hostname) { "root at hostname:1234" }
           it { expect(subject).to be_truthy }
         end
 
-        context 'with a different user' do
-          let(:hostname) { 'deployer at hostname:1234' }
+        context "with a different user" do
+          let(:hostname) { "deployer at hostname:1234" }
           it { expect(subject).to be_truthy }
         end
 
-        context 'with a different port' do
-          let(:hostname) { 'root at hostname:5678' }
+        context "with a different port" do
+          let(:hostname) { "root at hostname:5678" }
           it { expect(subject).to be_truthy }
         end
 
-        context 'with a different hostname' do
-          let(:hostname) { 'root at otherserver:1234' }
+        context "with a different hostname" do
+          let(:hostname) { "root at otherserver:1234" }
           it { expect(subject).to be_falsey }
         end
       end
 
-      describe 'identifying as primary' do
+      describe "identifying as primary" do
         subject { server.primary }
-        context 'server is primary' do
+        context "server is primary" do
           before do
             server.set(:primary, true)
           end
-          it 'returns self' do
+          it "returns self" do
             expect(subject).to eq server
           end
         end
 
-        context 'server is not primary' do
-          it 'is falesy' do
+        context "server is not primary" do
+          it "is falesy" do
             expect(subject).to be_falsey
           end
         end
       end
 
-      describe 'assigning properties' do
-
+      describe "assigning properties" do
         before do
           server.with(properties)
         end
 
-        context 'properties contains roles' do
-          let(:properties) { {roles: [:clouds]} }
+        context "properties contains roles" do
+          let(:properties) { { roles: [:clouds] } }
 
-          it 'adds the roles' do
+          it "adds the roles" do
             expect(server.roles.first).to eq :clouds
           end
         end
 
-        context 'properties contains user' do
-          let(:properties) { {user: 'tomc'} }
+        context "properties contains user" do
+          let(:properties) { { user: "tomc" } }
 
-          it 'sets the user' do
-            expect(server.user).to eq 'tomc'
+          it "sets the user" do
+            expect(server.user).to eq "tomc"
           end
 
-          it 'sets the netssh_options user' do
-            expect(server.netssh_options[:user]).to eq 'tomc'
+          it "sets the netssh_options user" do
+            expect(server.netssh_options[:user]).to eq "tomc"
           end
         end
 
-        context 'properties contains port' do
-          let(:properties) { {port: 2222} }
+        context "properties contains port" do
+          let(:properties) { { port: 2222 } }
 
-          it 'sets the port' do
+          it "sets the port" do
             expect(server.port).to eq 2222
           end
         end
 
-        context 'properties contains key' do
-          let(:properties) { {key: '/key'} }
+        context "properties contains key" do
+          let(:properties) { { key: "/key" } }
 
-          it 'adds the key' do
-            expect(server.keys).to include '/key'
+          it "adds the key" do
+            expect(server.keys).to include "/key"
           end
         end
 
-        context 'properties contains password' do
-          let(:properties) { {password: 'supersecret'} }
+        context "properties contains password" do
+          let(:properties) { { password: "supersecret" } }
 
-          it 'adds the key' do
-            expect(server.password).to eq 'supersecret'
+          it "adds the key" do
+            expect(server.password).to eq "supersecret"
           end
         end
 
-        context 'new properties' do
+        context "new properties" do
           let(:properties) { { webscales: 5 } }
 
-          it 'adds the properties' do
+          it "adds the properties" do
             expect(server.properties.webscales).to eq 5
           end
         end
 
-        context 'existing properties' do
+        context "existing properties" do
           let(:properties) { { webscales: 6 } }
 
-          it 'keeps the existing properties' do
+          it "keeps the existing properties" do
             expect(server.properties.webscales).to eq 6
             server.properties.webscales = 5
             expect(server.properties.webscales).to eq 5
@@ -143,7 +141,7 @@ module Capistrano
         end
       end
 
-      describe '#include?' do
+      describe "#include?" do
         let(:options) { {} }
 
         subject { server.select?(options) }
@@ -152,162 +150,157 @@ module Capistrano
           server.properties.active = true
         end
 
-        context 'options are empty' do
+        context "options are empty" do
           it { expect(subject).to be_truthy }
         end
 
-        context 'value is a symbol' do
-          context 'value matches server property' do
-
-            context 'with :filter' do
-              let(:options) { { filter: :active }}
+        context "value is a symbol" do
+          context "value matches server property" do
+            context "with :filter" do
+              let(:options) { { filter: :active } }
               it { expect(subject).to be_truthy }
             end
 
-            context 'with :select' do
-              let(:options) { { select: :active }}
+            context "with :select" do
+              let(:options) { { select: :active } }
               it { expect(subject).to be_truthy }
             end
 
-            context 'with :exclude' do
-              let(:options) { { exclude: :active }}
+            context "with :exclude" do
+              let(:options) { { exclude: :active } }
               it { expect(subject).to be_falsey }
             end
           end
 
-          context 'value does not match server properly' do
-            context 'with :active true' do
-              let(:options) { { active: true }}
+          context "value does not match server properly" do
+            context "with :active true" do
+              let(:options) { { active: true } }
               it { expect(subject).to be_truthy }
             end
 
-            context 'with :active false' do
-              let(:options) { { active: false }}
+            context "with :active false" do
+              let(:options) { { active: false } }
               it { expect(subject).to be_falsey }
             end
           end
 
-          context 'value does not match server properly' do
-            context 'with :filter' do
-              let(:options) { { filter: :inactive }}
+          context "value does not match server properly" do
+            context "with :filter" do
+              let(:options) { { filter: :inactive } }
               it { expect(subject).to be_falsey }
             end
 
-            context 'with :select' do
-              let(:options) { { select: :inactive }}
+            context "with :select" do
+              let(:options) { { select: :inactive } }
               it { expect(subject).to be_falsey }
             end
 
-            context 'with :exclude' do
-              let(:options) { { exclude: :inactive }}
+            context "with :exclude" do
+              let(:options) { { exclude: :inactive } }
               it { expect(subject).to be_truthy }
             end
           end
         end
 
-        context 'key is a property' do
-          context 'with :active true' do
-            let(:options) { { active: true }}
+        context "key is a property" do
+          context "with :active true" do
+            let(:options) { { active: true } }
             it { expect(subject).to be_truthy }
           end
 
-          context 'with :active false' do
-            let(:options) { { active: false }}
+          context "with :active false" do
+            let(:options) { { active: false } }
             it { expect(subject).to be_falsey }
           end
         end
 
-        context 'value is a proc' do
-          context 'value matches server property' do
-
-            context 'with :filter' do
+        context "value is a proc" do
+          context "value matches server property" do
+            context "with :filter" do
               let(:options) { { filter: ->(s) { s.properties.active } } }
               it { expect(subject).to be_truthy }
             end
 
-            context 'with :select' do
+            context "with :select" do
               let(:options) { { select: ->(s) { s.properties.active } } }
               it { expect(subject).to be_truthy }
             end
 
-            context 'with :exclude' do
+            context "with :exclude" do
               let(:options) { { exclude: ->(s) { s.properties.active } } }
               it { expect(subject).to be_falsey }
             end
-
           end
 
-          context 'value does not match server properly' do
-            context 'with :filter' do
+          context "value does not match server properly" do
+            context "with :filter" do
               let(:options) { { filter: ->(s) { s.properties.inactive } } }
               it { expect(subject).to be_falsey }
             end
 
-            context 'with :select' do
+            context "with :select" do
               let(:options) { { select: ->(s) { s.properties.inactive } } }
               it { expect(subject).to be_falsey }
             end
 
-            context 'with :exclude' do
+            context "with :exclude" do
               let(:options) { { exclude: ->(s) { s.properties.inactive } } }
               it { expect(subject).to be_truthy }
             end
-
           end
         end
-
       end
 
-      describe 'assign ssh_options' do
-        let(:server) { Server.new('user_name at hostname') }
+      describe "assign ssh_options" do
+        let(:server) { Server.new("user_name at hostname") }
 
-        context 'defaults' do
-          it 'forward agent' do
+        context "defaults" do
+          it "forward agent" do
             expect(server.netssh_options[:forward_agent]).to eq true
           end
-          it 'contains user' do
-            expect(server.netssh_options[:user]).to eq 'user_name'
+          it "contains user" do
+            expect(server.netssh_options[:user]).to eq "user_name"
           end
         end
 
-        context 'custom' do
+        context "custom" do
           let(:properties) do
             { ssh_options: {
-              user: 'another_user',
+              user: "another_user",
               keys: %w(/home/another_user/.ssh/id_rsa),
               forward_agent: false,
-              auth_methods: %w(publickey password) } }
+              auth_methods: %w(publickey password)
+            } }
           end
 
           before do
             server.with(properties)
           end
 
-          it 'not forward agent' do
+          it "not forward agent" do
             expect(server.netssh_options[:forward_agent]).to eq false
           end
-          it 'contains correct user' do
-            expect(server.netssh_options[:user]).to eq 'another_user'
+          it "contains correct user" do
+            expect(server.netssh_options[:user]).to eq "another_user"
           end
-          it 'does not affect server user in host' do
-            expect(server.user).to eq 'user_name'
+          it "does not affect server user in host" do
+            expect(server.user).to eq "user_name"
           end
-          it 'contains keys' do
+          it "contains keys" do
             expect(server.netssh_options[:keys]).to eq %w(/home/another_user/.ssh/id_rsa)
           end
-          it 'contains auth_methods' do
+          it "contains auth_methods" do
             expect(server.netssh_options[:auth_methods]).to eq %w(publickey password)
           end
         end
-
       end
 
       describe ".[]" do
-        it 'creates a server if its argument is not already a server' do
-          expect(Server['hostname:1234']).to be_a Server
+        it "creates a server if its argument is not already a server" do
+          expect(Server["hostname:1234"]).to be_a Server
         end
 
-        it 'returns its argument if it is already a server' do
+        it "returns its argument if it is already a server" do
           expect(Server[server]).to be server
         end
       end
diff --git a/spec/lib/capistrano/configuration/servers_spec.rb b/spec/lib/capistrano/configuration/servers_spec.rb
index 6582524..d3d4b3e 100644
--- a/spec/lib/capistrano/configuration/servers_spec.rb
+++ b/spec/lib/capistrano/configuration/servers_spec.rb
@@ -1,63 +1,60 @@
-require 'spec_helper'
+require "spec_helper"
 
 module Capistrano
   class Configuration
     describe Servers do
       let(:servers) { Servers.new }
 
-      describe 'adding a role' do
-
-        it 'adds two new server instances' do
-          expect{servers.add_role(:app, %w{1 2})}.
-            to change{servers.count}.from(0).to(2)
+      describe "adding a role" do
+        it "adds two new server instances" do
+          expect { servers.add_role(:app, %w{1 2}) }
+            .to change { servers.count }.from(0).to(2)
         end
 
-        it 'handles de-duplification within roles' do
+        it "handles de-duplification within roles" do
           servers.add_role(:app, %w{1})
           servers.add_role(:app, %w{1})
           expect(servers.count).to eq 1
         end
 
-        it 'handles de-duplification within roles with users' do
-          servers.add_role(:app, %w{1}, user: 'nick')
-          servers.add_role(:app, %w{1}, user: 'fred')
+        it "handles de-duplification within roles with users" do
+          servers.add_role(:app, %w{1}, user: "nick")
+          servers.add_role(:app, %w{1}, user: "fred")
           expect(servers.count).to eq 1
         end
 
-        it 'accepts instances of server objects' do
-          servers.add_role(:app, [Capistrano::Configuration::Server.new('example.net'), 'example.com'])
+        it "accepts instances of server objects" do
+          servers.add_role(:app, [Capistrano::Configuration::Server.new("example.net"), "example.com"])
           expect(servers.roles_for([:app]).length).to eq 2
         end
 
-        it 'accepts non-enumerable types' do
-          servers.add_role(:app, '1')
+        it "accepts non-enumerable types" do
+          servers.add_role(:app, "1")
           expect(servers.roles_for([:app]).count).to eq 1
         end
 
-        it 'creates distinct server properties' do
-          servers.add_role(:db, %w{1 2}, db: { port: 1234 } )
-          servers.add_host('1', db: { master: true })
+        it "creates distinct server properties" do
+          servers.add_role(:db, %w{1 2}, db: { port: 1234 })
+          servers.add_host("1", db: { master: true })
           expect(servers.count).to eq(2)
           expect(servers.roles_for([:db]).count).to eq 2
-          expect(servers.find(){|s| s.hostname == '1'}.properties.db).to eq({ port: 1234, master: true })
-          expect(servers.find(){|s| s.hostname == '2'}.properties.db).to eq({ port: 1234 })
+          expect(servers.find { |s| s.hostname == "1" }.properties.db).to eq(port: 1234, master: true)
+          expect(servers.find { |s| s.hostname == "2" }.properties.db).to eq(port: 1234)
         end
-
       end
 
-      describe 'adding a role to an existing server' do
+      describe "adding a role to an existing server" do
         before do
           servers.add_role(:web, %w{1 2})
           servers.add_role(:app, %w{1 2})
         end
 
-        it 'adds new roles to existing servers' do
+        it "adds new roles to existing servers" do
           expect(servers.count).to eq 2
         end
-
       end
 
-      describe 'collecting server roles' do
+      describe "collecting server roles" do
         let(:app) { Set.new([:app]) }
         let(:web_app) { Set.new([:web, :app]) }
         let(:web) { Set.new([:web]) }
@@ -67,280 +64,266 @@ module Capistrano
           servers.add_role(:web, %w{2 3 4})
         end
 
-        it 'returns an array of the roles' do
+        it "returns an array of the roles" do
           expect(servers.roles_for([:app]).collect(&:roles)).to eq [app, web_app, web_app]
           expect(servers.roles_for([:web]).collect(&:roles)).to eq [web_app, web_app, web]
         end
       end
 
-      describe 'finding the primary server' do
+      describe "finding the primary server" do
         after do
           Configuration.reset!
         end
-        it 'takes the first server if none have the primary property' do
+        it "takes the first server if none have the primary property" do
           servers.add_role(:app, %w{1 2})
-          expect(servers.fetch_primary(:app).hostname).to eq('1')
+          expect(servers.fetch_primary(:app).hostname).to eq("1")
         end
 
-        it 'takes the first server with the primary have the primary flag' do
+        it "takes the first server with the primary have the primary flag" do
           servers.add_role(:app, %w{1 2})
-          servers.add_host('2', primary: true)
-          expect(servers.fetch_primary(:app).hostname).to eq('2')
+          servers.add_host("2", primary: true)
+          expect(servers.fetch_primary(:app).hostname).to eq("2")
         end
 
-        it 'ignores any on_filters' do
-          Configuration.env.set :filter, { host: '1'}
+        it "ignores any on_filters" do
+          Configuration.env.set :filter, host: "1"
           servers.add_role(:app, %w{1 2})
-          servers.add_host('2', primary: true)
-          expect(servers.fetch_primary(:app).hostname).to eq('2')
+          servers.add_host("2", primary: true)
+          expect(servers.fetch_primary(:app).hostname).to eq("2")
         end
       end
 
-      describe 'fetching servers' do
+      describe "fetching servers" do
         before do
           servers.add_role(:app, %w{1 2})
           servers.add_role(:web, %w{2 3})
         end
 
-        it 'returns the correct app servers' do
+        it "returns the correct app servers" do
           expect(servers.roles_for([:app]).map(&:hostname)).to eq %w{1 2}
         end
 
-        it 'returns the correct web servers' do
+        it "returns the correct web servers" do
           expect(servers.roles_for([:web]).map(&:hostname)).to eq %w{2 3}
         end
 
-        it 'returns the correct app and web servers' do
+        it "returns the correct app and web servers" do
           expect(servers.roles_for([:app, :web]).map(&:hostname)).to eq %w{1 2 3}
         end
 
-        it 'returns all servers' do
+        it "returns all servers" do
           expect(servers.roles_for([:all]).map(&:hostname)).to eq %w{1 2 3}
         end
       end
 
-      describe 'adding a server' do
-
+      describe "adding a server" do
         before do
-          servers.add_host('1', roles: [:app, 'web'], test: :value)
+          servers.add_host("1", roles: [:app, "web"], test: :value)
         end
 
-        it 'can create a server with properties' do
-          expect(servers.roles_for([:app]).first.hostname).to eq '1'
-          expect(servers.roles_for([:web]).first.hostname).to eq '1'
+        it "can create a server with properties" do
+          expect(servers.roles_for([:app]).first.hostname).to eq "1"
+          expect(servers.roles_for([:web]).first.hostname).to eq "1"
           expect(servers.roles_for([:all]).first.properties.test).to eq :value
           expect(servers.roles_for([:all]).first.properties.keys).to eq [:test]
         end
 
-        it 'can accept multiple servers with the same hostname but different ports or users' do
-          servers.add_host('1', roles: [:app, 'web'], test: :value, port: 12)
-          servers.add_host('1', roles: [:app, 'web'], test: :value, port: 34)
-          servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'root')
-          servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'deployer')
-          servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'root', port: 34)
-          servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'deployer', port: 34)
-          servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'deployer', port: 56)
-          expect(servers.count).to eq(1)
+        it "can accept multiple servers with the same hostname but different ports or users" do
+          servers.add_host("1", roles: [:app, "web"], test: :value, port: 12)
+          servers.add_host("1", roles: [:app, "web"], test: :value, port: 34)
+          servers.add_host("1", roles: [:app, "web"], test: :value, user: "root")
+          servers.add_host("1", roles: [:app, "web"], test: :value, user: "deployer")
+          servers.add_host("1", roles: [:app, "web"], test: :value, user: "root", port: 34)
+          servers.add_host("1", roles: [:app, "web"], test: :value, user: "deployer", port: 34)
+          servers.add_host("1", roles: [:app, "web"], test: :value, user: "deployer", port: 56)
+          expect(servers.count).to eq(5)
         end
 
         describe "with a :user property" do
-
-          it 'sets the server ssh username' do
-            servers.add_host('1', roles: [:app, 'web'], user: 'nick')
+          it "sets the server ssh username" do
+            servers.add_host("1", roles: [:app, "web"], user: "nick")
             expect(servers.count).to eq(1)
-            expect(servers.roles_for([:all]).first.user).to eq 'nick'
+            expect(servers.roles_for([:all]).first.user).to eq "nick"
           end
 
-          it 'overwrites the value of a user specified in the hostname' do
-            servers.add_host('brian at 1', roles: [:app, 'web'], user: 'nick')
+          it "overwrites the value of a user specified in the hostname" do
+            servers.add_host("brian at 1", roles: [:app, "web"], user: "nick")
             expect(servers.count).to eq(1)
-            expect(servers.roles_for([:all]).first.user).to eq 'nick'
+            expect(servers.roles_for([:all]).first.user).to eq "nick"
           end
-
         end
 
-        it 'overwrites the value of a previously defined scalar property' do
-          servers.add_host('1', roles: [:app, 'web'], test: :volatile)
+        it "overwrites the value of a previously defined scalar property" do
+          servers.add_host("1", roles: [:app, "web"], test: :volatile)
           expect(servers.count).to eq(1)
           expect(servers.roles_for([:all]).first.properties.test).to eq :volatile
         end
 
-        it 'merges previously defined hash properties' do
-          servers.add_host('1', roles: [:b], db: { port: 1234 })
-          servers.add_host('1', roles: [:b], db: { master: true })
+        it "merges previously defined hash properties" do
+          servers.add_host("1", roles: [:b], db: { port: 1234 })
+          servers.add_host("1", roles: [:b], db: { master: true })
           expect(servers.count).to eq(1)
-          expect(servers.roles_for([:b]).first.properties.db).to eq({ port: 1234, master: true })
+          expect(servers.roles_for([:b]).first.properties.db).to eq(port: 1234, master: true)
         end
 
-        it 'concatenates previously defined array properties' do
-          servers.add_host('1', roles: [:b], steps: [1,3,5])
-          servers.add_host('1', roles: [:b], steps: [1,9])
+        it "concatenates previously defined array properties" do
+          servers.add_host("1", roles: [:b], steps: [1, 3, 5])
+          servers.add_host("1", roles: [:b], steps: [1, 9])
           expect(servers.count).to eq(1)
-          expect(servers.roles_for([:b]).first.properties.steps).to eq([1,3,5,1,9])
+          expect(servers.roles_for([:b]).first.properties.steps).to eq([1, 3, 5, 1, 9])
         end
 
-        it 'merges previously defined set properties' do
-          servers.add_host('1', roles: [:b], endpoints: Set[123,333])
-          servers.add_host('1', roles: [:b], endpoints: Set[222,333])
+        it "merges previously defined set properties" do
+          servers.add_host("1", roles: [:b], endpoints: Set[123, 333])
+          servers.add_host("1", roles: [:b], endpoints: Set[222, 333])
           expect(servers.count).to eq(1)
-          expect(servers.roles_for([:b]).first.properties.endpoints).to eq(Set[123,222,333])
+          expect(servers.roles_for([:b]).first.properties.endpoints).to eq(Set[123, 222, 333])
         end
 
-        it 'adds array property value only ones for a new host' do
-          servers.add_host('2', roles: [:array_test], array_property: [1,2])
-          expect(servers.roles_for([:array_test]).first.properties.array_property).to eq [1,2]
+        it "adds array property value only ones for a new host" do
+          servers.add_host("2", roles: [:array_test], array_property: [1, 2])
+          expect(servers.roles_for([:array_test]).first.properties.array_property).to eq [1, 2]
         end
 
-        it 'updates roles when custom user defined' do
-          servers.add_host('1', roles: ['foo'], user: 'custom')
-          servers.add_host('1', roles: ['bar'], user: 'custom')
-          expect(servers.roles_for([:foo]).first.hostname).to eq '1'
-          expect(servers.roles_for([:bar]).first.hostname).to eq '1'
+        it "updates roles when custom user defined" do
+          servers.add_host("1", roles: ["foo"], user: "custom")
+          servers.add_host("1", roles: ["bar"], user: "custom")
+          expect(servers.roles_for([:foo]).first.hostname).to eq "1"
+          expect(servers.roles_for([:bar]).first.hostname).to eq "1"
         end
 
-        it 'updates roles when custom port defined' do
-          servers.add_host('1', roles: ['foo'], port: 1234)
-          servers.add_host('1', roles: ['bar'], port: 1234)
-          expect(servers.roles_for([:foo]).first.hostname).to eq '1'
-          expect(servers.roles_for([:bar]).first.hostname).to eq '1'
+        it "updates roles when custom port defined" do
+          servers.add_host("1", roles: ["foo"], port: 1234)
+          servers.add_host("1", roles: ["bar"], port: 1234)
+          expect(servers.roles_for([:foo]).first.hostname).to eq "1"
+          expect(servers.roles_for([:bar]).first.hostname).to eq "1"
         end
-
       end
 
-      describe 'selecting roles' do
-
+      describe "selecting roles" do
         before do
-          servers.add_host('1', roles: :app, active: true)
-          servers.add_host('2', roles: :app)
+          servers.add_host("1", roles: :app, active: true)
+          servers.add_host("2", roles: :app)
         end
 
-        it 'is empty if the filter would remove all matching hosts' do
+        it "is empty if the filter would remove all matching hosts" do
           expect(servers.roles_for([:app, select: :inactive])).to be_empty
         end
 
-        it 'can filter hosts by properties on the host object using symbol as shorthand' do
+        it "can filter hosts by properties on the host object using symbol as shorthand" do
           expect(servers.roles_for([:app, filter: :active]).length).to eq 1
         end
 
-        it 'can select hosts by properties on the host object using symbol as shorthand' do
+        it "can select hosts by properties on the host object using symbol as shorthand" do
           expect(servers.roles_for([:app, select: :active]).length).to eq 1
         end
 
-        it 'can filter hosts by properties on the host using a regular proc' do
+        it "can filter hosts by properties on the host using a regular proc" do
           expect(servers.roles_for([:app, filter: ->(h) { h.properties.active }]).length).to eq 1
         end
 
-        it 'can select hosts by properties on the host using a regular proc' do
+        it "can select hosts by properties on the host using a regular proc" do
           expect(servers.roles_for([:app, select: ->(h) { h.properties.active }]).length).to eq 1
         end
 
-        it 'is empty if the regular proc filter would remove all matching hosts' do
+        it "is empty if the regular proc filter would remove all matching hosts" do
           expect(servers.roles_for([:app, select: ->(h) { h.properties.inactive }])).to be_empty
         end
-
       end
 
-      describe 'excluding by property' do
-
+      describe "excluding by property" do
         before do
-          servers.add_host('1', roles: :app, active: true)
-          servers.add_host('2', roles: :app, active: true, no_release: true)
+          servers.add_host("1", roles: :app, active: true)
+          servers.add_host("2", roles: :app, active: true, no_release: true)
         end
 
-        it 'is empty if the filter would remove all matching hosts' do
+        it "is empty if the filter would remove all matching hosts" do
           hosts = servers.roles_for([:app, exclude: :active])
           expect(hosts.map(&:hostname)).to be_empty
         end
 
-        it 'returns the servers without the attributes specified' do
+        it "returns the servers without the attributes specified" do
           hosts = servers.roles_for([:app, exclude: :no_release])
           expect(hosts.map(&:hostname)).to eq %w{1}
         end
 
-        it 'can exclude hosts by properties on the host using a regular proc' do
+        it "can exclude hosts by properties on the host using a regular proc" do
           hosts = servers.roles_for([:app, exclude: ->(h) { h.properties.no_release }])
           expect(hosts.map(&:hostname)).to eq %w{1}
         end
 
-        it 'is empty if the regular proc filter would remove all matching hosts' do
+        it "is empty if the regular proc filter would remove all matching hosts" do
           hosts = servers.roles_for([:app, exclude: ->(h) { h.properties.active }])
           expect(hosts.map(&:hostname)).to be_empty
         end
-
       end
 
-      describe 'filtering roles internally' do
-
+      describe "filtering roles internally" do
         before do
-          servers.add_host('1', roles: :app, active: true)
-          servers.add_host('2', roles: :app)
-          servers.add_host('3', roles: :web)
-          servers.add_host('4', roles: :web)
-          servers.add_host('5', roles: :db)
+          servers.add_host("1", roles: :app, active: true)
+          servers.add_host("2", roles: :app)
+          servers.add_host("3", roles: :web)
+          servers.add_host("4", roles: :web)
+          servers.add_host("5", roles: :db)
         end
 
         subject { servers.roles_for(roles).map(&:hostname) }
 
-        context 'with the ROLES environment variable set' do
-
+        context "with the ROLES environment variable set" do
           before do
-            ENV.stubs(:[]).with('ROLES').returns('web,db')
-            ENV.stubs(:[]).with('HOSTS').returns(nil)
+            ENV.stubs(:[]).with("ROLES").returns("web,db")
+            ENV.stubs(:[]).with("HOSTS").returns(nil)
           end
 
-          context 'when selecting all roles' do
+          context "when selecting all roles" do
             let(:roles) { [:all] }
-            it 'ignores it' do
+            it "ignores it" do
               expect(subject).to eq %w{1 2 3 4 5}
             end
           end
 
-          context 'when selecting specific roles' do
+          context "when selecting specific roles" do
             let(:roles) { [:app, :web] }
-            it 'ignores it' do
+            it "ignores it" do
               expect(subject).to eq %w{1 2 3 4}
             end
           end
 
-          context 'when selecting roles not included in ROLE' do
+          context "when selecting roles not included in ROLE" do
             let(:roles) { [:app] }
-            it 'ignores it' do
+            it "ignores it" do
               expect(subject).to eq %w{1 2}
             end
           end
-
         end
 
-        context 'with the HOSTS environment variable set' do
-
+        context "with the HOSTS environment variable set" do
           before do
-            ENV.stubs(:[]).with('ROLES').returns(nil)
-            ENV.stubs(:[]).with('HOSTS').returns('3,5')
+            ENV.stubs(:[]).with("ROLES").returns(nil)
+            ENV.stubs(:[]).with("HOSTS").returns("3,5")
           end
 
-          context 'when selecting all roles' do
+          context "when selecting all roles" do
             let(:roles) { [:all] }
-            it 'ignores it' do
+            it "ignores it" do
               expect(subject).to eq %w{1 2 3 4 5}
             end
           end
 
-          context 'when selecting specific roles' do
+          context "when selecting specific roles" do
             let(:roles) { [:app, :web] }
-            it 'ignores it' do
+            it "ignores it" do
               expect(subject).to eq %w{1 2 3 4}
             end
           end
 
-          context 'when selecting no roles' do
+          context "when selecting no roles" do
             let(:roles) { [] }
-            it 'ignores it' do
+            it "ignores it" do
               expect(subject).to be_empty
             end
           end
-
         end
-
       end
     end
   end
diff --git a/spec/lib/capistrano/configuration_spec.rb b/spec/lib/capistrano/configuration_spec.rb
index ea2fa52..30c5584 100644
--- a/spec/lib/capistrano/configuration_spec.rb
+++ b/spec/lib/capistrano/configuration_spec.rb
@@ -1,34 +1,34 @@
-require 'spec_helper'
+require "spec_helper"
 
 module Capistrano
   describe Configuration do
     let(:config) { Configuration.new }
     let(:servers) { stub }
 
-    describe '.new' do
-      it 'accepts initial hash' do
-        configuration = described_class.new(custom: 'value')
-        expect(configuration.fetch(:custom)).to eq('value')
+    describe ".new" do
+      it "accepts initial hash" do
+        configuration = described_class.new(custom: "value")
+        expect(configuration.fetch(:custom)).to eq("value")
       end
     end
 
-    describe '.env' do
-      it 'is a global accessor to a single instance' do
+    describe ".env" do
+      it "is a global accessor to a single instance" do
         Configuration.env.set(:test, true)
         expect(Configuration.env.fetch(:test)).to be_truthy
       end
     end
 
-    describe '.reset!' do
-      it 'blows away the existing `env` and creates a new one' do
+    describe ".reset!" do
+      it "blows away the existing `env` and creates a new one" do
         old_env = Configuration.env
         Configuration.reset!
         expect(Configuration.env).not_to be old_env
       end
     end
 
-    describe 'roles' do
-      context 'adding a role' do
+    describe "roles" do
+      context "adding a role" do
         subject { config.role(:app, %w{server1 server2}) }
 
         before do
@@ -36,103 +36,200 @@ module Capistrano
           servers.expects(:add_role).with(:app, %w{server1 server2}, {})
         end
 
-        it 'adds the role' do
+        it "adds the role" do
           expect(subject)
         end
       end
     end
 
-    describe 'setting and fetching' do
+    describe "setting and fetching" do
       subject { config.fetch(:key, :default) }
 
-      context 'value is set' do
-        before do
+      context "set" do
+        it "sets by value" do
           config.set(:key, :value)
+          expect(subject).to eq :value
         end
 
-        it 'returns the set value' do
+        it "sets by block" do
+          config.set(:key) { :value }
           expect(subject).to eq :value
         end
+
+        it "raises an exception when given both a value and block" do
+          expect { config.set(:key, :value) { :value } }.to raise_error(Capistrano::ValidationError)
+        end
       end
 
-      context 'set_if_empty' do
-        it 'sets the value when none is present' do
+      context "set_if_empty" do
+        it "sets by value when none is present" do
           config.set_if_empty(:key, :value)
           expect(subject).to eq :value
         end
 
-        it 'does not overwrite the value' do
+        it "sets by block when none is present" do
+          config.set_if_empty(:key) { :value }
+          expect(subject).to eq :value
+        end
+
+        it "does not overwrite existing values" do
           config.set(:key, :value)
           config.set_if_empty(:key, :update)
+          config.set_if_empty(:key) { :update }
           expect(subject).to eq :value
         end
       end
 
-      context 'value is not set' do
-        it 'returns the default value' do
+      context "value is not set" do
+        it "returns the default value" do
           expect(subject).to eq :default
         end
       end
 
-      context 'value is a proc' do
-        subject { config.fetch(:key, Proc.new { :proc } ) }
-        it 'calls the proc' do
+      context "value is a proc" do
+        subject { config.fetch(:key, proc { :proc }) }
+        it "calls the proc" do
           expect(subject).to eq :proc
         end
       end
 
-      context 'value is a lambda' do
-        subject { config.fetch(:key, lambda { :lambda } ) }
-        it 'calls the lambda' do
+      context "value is a lambda" do
+        subject { config.fetch(:key, -> { :lambda }) }
+        it "calls the lambda" do
           expect(subject).to eq :lambda
         end
       end
 
-      context 'value inside proc inside a proc' do
-        subject { config.fetch(:key, Proc.new { Proc.new { "some value" } } ) }
-        it 'calls all procs and lambdas' do
+      context "value inside proc inside a proc" do
+        subject { config.fetch(:key, proc { proc { "some value" } }) }
+        it "calls all procs and lambdas" do
           expect(subject).to eq "some value"
         end
       end
 
-      context 'value inside lambda inside a lambda' do
-        subject { config.fetch(:key, lambda { lambda { "some value" } } ) }
-        it 'calls all procs and lambdas' do
+      context "value inside lambda inside a lambda" do
+        subject { config.fetch(:key, -> { -> { "some value" } }) }
+        it "calls all procs and lambdas" do
           expect(subject).to eq "some value"
         end
       end
 
-      context 'value inside lambda inside a proc' do
-        subject { config.fetch(:key, Proc.new { lambda { "some value" } } ) }
-        it 'calls all procs and lambdas' do
+      context "value inside lambda inside a proc" do
+        subject { config.fetch(:key, proc { -> { "some value" } }) }
+        it "calls all procs and lambdas" do
           expect(subject).to eq "some value"
         end
       end
 
-      context 'value inside proc inside a lambda' do
-        subject { config.fetch(:key, lambda { Proc.new { "some value" } } ) }
-        it 'calls all procs and lambdas' do
+      context "value inside proc inside a lambda" do
+        subject { config.fetch(:key, -> { proc { "some value" } }) }
+        it "calls all procs and lambdas" do
           expect(subject).to eq "some value"
         end
       end
 
-      context 'lambda with parameters' do
-        subject { config.fetch(:key, lambda { |c| c }).call(42) }
-        it 'is returned as a lambda' do
+      context "lambda with parameters" do
+        subject { config.fetch(:key, ->(c) { c }).call(42) }
+        it "is returned as a lambda" do
           expect(subject).to eq 42
         end
       end
 
-      context 'block is passed to fetch' do
-        subject { config.fetch(:key, :default) { fail 'we need this!' } }
+      context "block is passed to fetch" do
+        subject { config.fetch(:key, :default) { raise "we need this!" } }
 
-        it 'returns the block value' do
-          expect { subject }.to raise_error
+        it "returns the block value" do
+          expect { subject }.to raise_error(RuntimeError)
+        end
+      end
+
+      context "validations" do
+        before do
+          config.validate :key do |_, value|
+            raise Capistrano::ValidationError unless value.length > 3
+          end
+        end
+
+        it "validates string without error" do
+          config.set(:key, "longer_value")
+        end
+
+        it "validates block without error" do
+          config.set(:key) { "longer_value" }
+          expect(config.fetch(:key)).to eq "longer_value"
+        end
+
+        it "validates lambda without error" do
+          config.set :key, -> { "longer_value" }
+          expect(config.fetch(:key)).to eq "longer_value"
+        end
+
+        it "raises an exception on invalid string" do
+          expect { config.set(:key, "sho") }.to raise_error(Capistrano::ValidationError)
+        end
+
+        it "raises an exception on invalid string provided by block" do
+          config.set(:key) { "sho" }
+          expect { config.fetch(:key) }.to raise_error(Capistrano::ValidationError)
+        end
+
+        it "raises an exception on invalid string provided by lambda" do
+          config.set :key, -> { "sho" }
+          expect { config.fetch(:key) }.to raise_error(Capistrano::ValidationError)
+        end
+      end
+
+      context "appending" do
+        subject { config.append(:linked_dirs, "vendor/bundle", "tmp") }
+
+        it "returns appended value" do
+          expect(subject).to eq ["vendor/bundle", "tmp"]
+        end
+
+        context "on non-array variable" do
+          before { config.set(:linked_dirs, "string") }
+          subject { config.append(:linked_dirs, "vendor/bundle") }
+
+          it "returns appended value" do
+            expect(subject).to eq ["string", "vendor/bundle"]
+          end
+        end
+      end
+
+      context "removing" do
+        before :each do
+          config.set(:linked_dirs, ["vendor/bundle", "tmp"])
+        end
+
+        subject { config.remove(:linked_dirs, "vendor/bundle") }
+
+        it "returns without removed value" do
+          expect(subject).to eq ["tmp"]
+        end
+
+        context "on non-array variable" do
+          before { config.set(:linked_dirs, "string") }
+
+          context "when removing same value" do
+            subject { config.remove(:linked_dirs, "string") }
+
+            it "returns without removed value" do
+              expect(subject).to eq []
+            end
+          end
+
+          context "when removing different value" do
+            subject { config.remove(:linked_dirs, "othervalue") }
+
+            it "returns without removed value" do
+              expect(subject).to eq ["string"]
+            end
+          end
         end
       end
     end
 
-    describe 'keys' do
+    describe "keys" do
       subject { config.keys }
 
       before do
@@ -140,57 +237,72 @@ module Capistrano
         config.set(:key2, :value2)
       end
 
-      it 'returns all set keys' do
+      it "returns all set keys" do
         expect(subject).to match_array [:key1, :key2]
       end
     end
 
-    describe 'deleting' do
+    describe "deleting" do
       before do
         config.set(:key, :value)
       end
 
-      it 'deletes the value' do
+      it "deletes the value" do
         config.delete(:key)
         expect(config.fetch(:key)).to be_nil
       end
     end
 
-    describe 'asking' do
+    describe "asking" do
       let(:question) { stub }
       let(:options) { Hash.new }
 
       before do
-        Configuration::Question.expects(:new).with(:branch, :default, options).
-          returns(question)
+        Configuration::Question.expects(:new).with(:branch, :default, options)
+                               .returns(question)
       end
 
-      it 'prompts for the value when fetching' do
+      it "prompts for the value when fetching" do
         config.ask(:branch, :default, options)
         expect(config.fetch(:branch)).to eq question
       end
     end
 
-    describe 'setting the backend' do
-      it 'by default, is SSHKit' do
+    describe "setting the backend" do
+      it "by default, is SSHKit" do
         expect(config.backend).to eq SSHKit
       end
 
-      it 'can be set to another class' do
+      it "can be set to another class" do
         config.backend = :test
         expect(config.backend).to eq :test
       end
 
       describe "ssh_options for Netssh" do
-        it 'merges them with the :ssh_options variable' do
+        it "merges them with the :ssh_options variable" do
           config.set :format, :pretty
           config.set :log_level, :debug
-          config.set :ssh_options, { user: 'albert' }
-          SSHKit::Backend::Netssh.configure do |ssh| ssh.ssh_options = { password: 'einstein' } end
+          config.set :ssh_options, user: "albert"
+          SSHKit::Backend::Netssh.configure { |ssh| ssh.ssh_options = { password: "einstein" } }
           config.configure_backend
-          expect(config.backend.config.backend.config.ssh_options).to eq({ user: 'albert', password: 'einstein' })
+
+          expect(
+            config.backend.config.backend.config.ssh_options
+          ).to include(user: "albert", password: "einstein")
         end
       end
     end
+
+    describe "dry_run?" do
+      it "returns false when using default backend" do
+        expect(config.dry_run?).to eq(false)
+      end
+
+      it "returns true when using printer backend" do
+        config.set :sshkit_backend, SSHKit::Backend::Printer
+
+        expect(config.dry_run?).to eq(true)
+      end
+    end
   end
 end
diff --git a/spec/lib/capistrano/doctor/environment_doctor_spec.rb b/spec/lib/capistrano/doctor/environment_doctor_spec.rb
new file mode 100644
index 0000000..9b4ffc2
--- /dev/null
+++ b/spec/lib/capistrano/doctor/environment_doctor_spec.rb
@@ -0,0 +1,44 @@
+require "spec_helper"
+require "capistrano/doctor/environment_doctor"
+
+module Capistrano
+  module Doctor
+    describe EnvironmentDoctor do
+      let(:doc) { EnvironmentDoctor.new }
+
+      it "prints using 4-space indentation" do
+        expect { doc.call }.to output(/^ {4}/).to_stdout
+      end
+
+      it "prints the Ruby version" do
+        expect { doc.call }.to\
+          output(/#{Regexp.quote(RUBY_DESCRIPTION)}/).to_stdout
+      end
+
+      it "prints the Rubygems version" do
+        expect { doc.call }.to output(/#{Regexp.quote(Gem::VERSION)}/).to_stdout
+      end
+
+      describe "Rake" do
+        before do
+          load File.expand_path("../../../../../lib/capistrano/doctor.rb",
+                                __FILE__)
+        end
+
+        after do
+          Rake::Task.clear
+        end
+
+        it "has an doctor:environment task that calls EnvironmentDoctor" do
+          EnvironmentDoctor.any_instance.expects(:call)
+          Rake::Task["doctor:environment"].invoke
+        end
+
+        it "has a doctor task that depends on doctor:environment" do
+          expect(Rake::Task["doctor"].prerequisites).to \
+            include("doctor:environment")
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/capistrano/doctor/gems_doctor_spec.rb b/spec/lib/capistrano/doctor/gems_doctor_spec.rb
new file mode 100644
index 0000000..c31d4d8
--- /dev/null
+++ b/spec/lib/capistrano/doctor/gems_doctor_spec.rb
@@ -0,0 +1,67 @@
+require "spec_helper"
+require "capistrano/doctor/gems_doctor"
+require "airbrussh/version"
+require "sshkit/version"
+require "net/ssh/version"
+
+module Capistrano
+  module Doctor
+    describe GemsDoctor do
+      let(:doc) { GemsDoctor.new }
+
+      it "prints using 4-space indentation" do
+        expect { doc.call }.to output(/^ {4}/).to_stdout
+      end
+
+      it "prints the Capistrano version" do
+        expect { doc.call }.to\
+          output(/capistrano\s+#{Regexp.quote(Capistrano::VERSION)}/).to_stdout
+      end
+
+      it "prints the Rake version" do
+        expect { doc.call }.to\
+          output(/rake\s+#{Regexp.quote(Rake::VERSION)}/).to_stdout
+      end
+
+      it "prints the SSHKit version" do
+        expect { doc.call }.to\
+          output(/sshkit\s+#{Regexp.quote(SSHKit::VERSION)}/).to_stdout
+      end
+
+      it "prints the Airbrussh version" do
+        expect { doc.call }.to\
+          output(/airbrussh\s+#{Regexp.quote(Airbrussh::VERSION)}/).to_stdout
+      end
+
+      it "prints the net-ssh version" do
+        expect { doc.call }.to\
+          output(/net-ssh\s+#{Regexp.quote(Net::SSH::Version::STRING)}/).to_stdout
+      end
+
+      it "warns that new version is available" do
+        Gem.stubs(:latest_version_for).returns(Gem::Version.new("99.0.0"))
+        expect { doc.call }.to output(/\(update available\)/).to_stdout
+      end
+
+      describe "Rake" do
+        before do
+          load File.expand_path("../../../../../lib/capistrano/doctor.rb",
+                                __FILE__)
+        end
+
+        after do
+          Rake::Task.clear
+        end
+
+        it "has an doctor:gems task that calls GemsDoctor" do
+          GemsDoctor.any_instance.expects(:call)
+          Rake::Task["doctor:gems"].invoke
+        end
+
+        it "has a doctor task that depends on doctor:gems" do
+          expect(Rake::Task["doctor"].prerequisites).to include("doctor:gems")
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/capistrano/doctor/output_helpers_spec.rb b/spec/lib/capistrano/doctor/output_helpers_spec.rb
new file mode 100644
index 0000000..cd01a21
--- /dev/null
+++ b/spec/lib/capistrano/doctor/output_helpers_spec.rb
@@ -0,0 +1,47 @@
+require "spec_helper"
+require "capistrano/doctor/output_helpers"
+
+module Capistrano
+  module Doctor
+    describe OutputHelpers do
+      include OutputHelpers
+
+      # Force color for the purpose of these tests
+      before { ENV.stubs(:[]).with("SSHKIT_COLOR").returns("1") }
+
+      it "prints titles in blue with newlines and without indentation" do
+        expect { title("Hello!") }.to\
+          output("\e[0;34;49m\nHello!\n\e[0m\n").to_stdout
+      end
+
+      it "prints warnings in yellow with 4-space indentation" do
+        expect { warning("Yikes!") }.to\
+          output("    \e[0;33;49mYikes!\e[0m\n").to_stdout
+      end
+
+      it "overrides puts to indent 4 spaces per line" do
+        expect { puts("one\ntwo") }.to output("    one\n    two\n").to_stdout
+      end
+
+      it "formats tables with indent, aligned columns and per-row color" do
+        data = [
+          ["one", ".", "1"],
+          ["two", "..", "2"],
+          ["three", "...", "3"]
+        ]
+        block = proc do |record, row|
+          row.yellow if record.first == "two"
+          row << record[0]
+          row << record[1]
+          row << record[2]
+        end
+        expected_output = <<-OUT
+    one   .   1
+    \e[0;33;49mtwo   ..  2\e[0m
+    three ... 3
+        OUT
+        expect { table(data, &block) }.to output(expected_output).to_stdout
+      end
+    end
+  end
+end
diff --git a/spec/lib/capistrano/doctor/servers_doctor_spec.rb b/spec/lib/capistrano/doctor/servers_doctor_spec.rb
new file mode 100644
index 0000000..67a8b5e
--- /dev/null
+++ b/spec/lib/capistrano/doctor/servers_doctor_spec.rb
@@ -0,0 +1,86 @@
+require "spec_helper"
+require "capistrano/doctor/servers_doctor"
+
+module Capistrano
+  module Doctor
+    describe ServersDoctor do
+      include Capistrano::DSL
+      let(:doc) { ServersDoctor.new }
+
+      before { Capistrano::Configuration.reset! }
+      after { Capistrano::Configuration.reset! }
+
+      it "prints using 4-space indentation" do
+        expect { doc.call }.to output(/^ {4}/).to_stdout
+      end
+
+      it "prints the number of defined servers" do
+        role :app, %w(example.com)
+        server "www at example.com:22"
+
+        expect { doc.call }.to output(/Servers \(2\)/).to_stdout
+      end
+
+      describe "prints the server's details" do
+        it "including username" do
+          server "www at example.com"
+          expect { doc.call }.to output(/www at example.com/).to_stdout
+        end
+
+        it "including port" do
+          server "www at example.com:22"
+          expect { doc.call }.to output(/www at example.com:22/).to_stdout
+        end
+
+        it "including roles" do
+          role :app, %w(example.com)
+          expect { doc.call }.to output(/example.com\s+\[:app\]/).to_stdout
+        end
+
+        it "including empty roles" do
+          server "example.com"
+          expect { doc.call }.to output(/example.com\s+\[\]/).to_stdout
+        end
+
+        it "including properties" do
+          server "example.com", roles: %w(app db), primary: true
+          expect { doc.call }.to \
+            output(/example.com\s+\[:app, :db\]\s+\{ :primary => true \}/).to_stdout
+        end
+
+        it "including misleading role name alert" do
+          server "example.com", roles: ["web app db"]
+          warning_msg = 'Whitespace detected in role(s) :"web app db". ' \
+            'This might be a result of a mistyped "%w()" array literal'
+
+          expect { doc.call }.to output(/#{Regexp.escape(warning_msg)}/).to_stdout
+        end
+      end
+
+      it "doesn't fail for no servers" do
+        expect { doc.call }.to output("\nServers (0)\n    \n").to_stdout
+      end
+
+      describe "Rake" do
+        before do
+          load File.expand_path("../../../../../lib/capistrano/doctor.rb",
+                                __FILE__)
+        end
+
+        after do
+          Rake::Task.clear
+        end
+
+        it "has an doctor:servers task that calls ServersDoctor" do
+          ServersDoctor.any_instance.expects(:call)
+          Rake::Task["doctor:servers"].invoke
+        end
+
+        it "has a doctor task that depends on doctor:servers" do
+          expect(Rake::Task["doctor"].prerequisites).to \
+            include("doctor:servers")
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/capistrano/doctor/variables_doctor_spec.rb b/spec/lib/capistrano/doctor/variables_doctor_spec.rb
new file mode 100644
index 0000000..f02ac90
--- /dev/null
+++ b/spec/lib/capistrano/doctor/variables_doctor_spec.rb
@@ -0,0 +1,88 @@
+require "spec_helper"
+require "capistrano/doctor/variables_doctor"
+
+module Capistrano
+  module Doctor
+    describe VariablesDoctor do
+      include Capistrano::DSL
+
+      let(:doc) { VariablesDoctor.new }
+
+      before do
+        set :branch, "master"
+        set :pty, false
+
+        env.variables.untrusted! do
+          set :application, "my_app"
+          set :repo_url, ".git"
+          set :git_strategy, "Capistrano::Git::DefaultStrategy"
+          set :copy_strategy, :scp
+          set :custom_setting, "hello"
+          set "string_setting", "hello"
+          ask :secret
+        end
+
+        fetch :custom_setting
+      end
+
+      after { Capistrano::Configuration.reset! }
+
+      it "prints using 4-space indentation" do
+        expect { doc.call }.to output(/^ {4}/).to_stdout
+      end
+
+      it "prints variable names and values" do
+        expect { doc.call }.to output(/:branch\s+"master"$/).to_stdout
+        expect { doc.call }.to output(/:pty\s+false$/).to_stdout
+        expect { doc.call }.to output(/:application\s+"my_app"$/).to_stdout
+        expect { doc.call }.to output(/:repo_url\s+".git"$/).to_stdout
+        expect { doc.call }.to output(/:copy_strategy\s+:scp$/).to_stdout
+        expect { doc.call }.to output(/:custom_setting\s+"hello"$/).to_stdout
+        expect { doc.call }.to output(/"string_setting"\s+"hello"$/).to_stdout
+      end
+
+      it "prints unanswered question variable as <ask>" do
+        expect { doc.call }.to output(/:secret\s+<ask>$/).to_stdout
+      end
+
+      it "prints warning for unrecognized variable" do
+        expect { doc.call }.to \
+          output(/:copy_strategy is not a recognized Capistrano setting/)\
+          .to_stdout
+      end
+
+      it "does not print warning for unrecognized variable that is fetched" do
+        expect { doc.call }.not_to \
+          output(/:custom_setting is not a recognized Capistrano setting/)\
+          .to_stdout
+      end
+
+      it "does not print warning for the whitelisted git_strategy variable" do
+        expect { doc.call }.not_to \
+          output(/:git_strategy is not a recognized Capistrano setting/)\
+          .to_stdout
+      end
+
+      describe "Rake" do
+        before do
+          load File.expand_path("../../../../../lib/capistrano/doctor.rb",
+                                __FILE__)
+        end
+
+        after do
+          Rake::Task.clear
+        end
+
+        it "has an doctor:variables task that calls VariablesDoctor" do
+          VariablesDoctor.any_instance.expects(:call)
+          Rake::Task["doctor:variables"].invoke
+        end
+
+        it "has a doctor task that depends on doctor:variables" do
+          expect(Rake::Task["doctor"].prerequisites).to \
+            include("doctor:variables")
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/capistrano/dsl/paths_spec.rb b/spec/lib/capistrano/dsl/paths_spec.rb
index 330f08f..8d5905e 100644
--- a/spec/lib/capistrano/dsl/paths_spec.rb
+++ b/spec/lib/capistrano/dsl/paths_spec.rb
@@ -1,189 +1,197 @@
-require 'spec_helper'
+require "spec_helper"
 
 describe Capistrano::DSL::Paths do
-
   let(:dsl) { Class.new.extend Capistrano::DSL }
-  let(:parent) { Pathname.new('/var/shared') }
+  let(:parent) { Pathname.new("/var/shared") }
   let(:paths) { Class.new.extend Capistrano::DSL::Paths }
 
   let(:linked_dirs) { %w{log public/system} }
-  let(:linked_files) { %w{config/database.yml log/my.log} }
+  let(:linked_files) { %w{config/database.yml log/my.log log/access.log} }
 
   before do
-    dsl.set(:deploy_to, '/var/www')
+    dsl.set(:deploy_to, "/var/www")
   end
 
-  describe '#linked_dirs' do
+  describe "#linked_dirs" do
     subject { paths.linked_dirs(parent) }
 
     before do
       paths.expects(:fetch).with(:linked_dirs).returns(linked_dirs)
     end
 
-    it 'returns the full pathnames' do
-      expect(subject).to eq [Pathname.new('/var/shared/log'), Pathname.new('/var/shared/public/system')]
+    it "returns the full pathnames" do
+      expect(subject).to eq [
+        Pathname.new("/var/shared/log"),
+        Pathname.new("/var/shared/public/system")
+      ]
     end
   end
 
-
-  describe '#linked_files' do
+  describe "#linked_files" do
     subject { paths.linked_files(parent) }
 
     before do
       paths.expects(:fetch).with(:linked_files).returns(linked_files)
     end
 
-    it 'returns the full pathnames' do
-      expect(subject).to eq [Pathname.new('/var/shared/config/database.yml'), Pathname.new('/var/shared/log/my.log')]
+    it "returns the full pathnames" do
+      expect(subject).to eq [
+        Pathname.new("/var/shared/config/database.yml"),
+        Pathname.new("/var/shared/log/my.log"),
+        Pathname.new("/var/shared/log/access.log")
+      ]
     end
   end
 
-  describe '#linked_file_dirs' do
+  describe "#linked_file_dirs" do
     subject { paths.linked_file_dirs(parent) }
 
     before do
       paths.expects(:fetch).with(:linked_files).returns(linked_files)
     end
 
-    it 'returns the full paths names of the parent dirs' do
-      expect(subject).to eq [Pathname.new('/var/shared/config'), Pathname.new('/var/shared/log')]
+    it "returns the full paths names of the parent dirs" do
+      expect(subject).to eq [
+        Pathname.new("/var/shared/config"),
+        Pathname.new("/var/shared/log")
+      ]
     end
   end
 
-  describe '#linked_dir_parents' do
+  describe "#linked_dir_parents" do
     subject { paths.linked_dir_parents(parent) }
 
     before do
       paths.expects(:fetch).with(:linked_dirs).returns(linked_dirs)
     end
 
-    it 'returns the full paths names of the parent dirs' do
-      expect(subject).to eq [Pathname.new('/var/shared'), Pathname.new('/var/shared/public')]
+    it "returns the full paths names of the parent dirs" do
+      expect(subject).to eq [
+        Pathname.new("/var/shared"),
+        Pathname.new("/var/shared/public")
+      ]
     end
   end
 
-  describe '#release path' do
-
+  describe "#release path" do
     subject { dsl.release_path }
 
-    context 'where no release path has been set' do
+    context "where no release path has been set" do
       before do
         dsl.delete(:release_path)
       end
 
-      it 'returns the `current_path` value' do
-        expect(subject.to_s).to eq '/var/www/current'
+      it "returns the `current_path` value" do
+        expect(subject.to_s).to eq "/var/www/current"
       end
     end
 
-    context 'where the release path has been set' do
+    context "where the release path has been set" do
       before do
-        dsl.set(:release_path,'/var/www/release_path')
+        dsl.set(:release_path, "/var/www/release_path")
       end
 
-      it 'returns the set `release_path` value' do
-        expect(subject.to_s).to eq '/var/www/release_path'
+      it "returns the set `release_path` value" do
+        expect(subject.to_s).to eq "/var/www/release_path"
       end
     end
   end
 
-  describe '#set_release_path' do
+  describe "#set_release_path" do
     let(:now) { Time.parse("Oct 21 16:29:00 2015") }
     subject { dsl.release_path }
 
-    context 'without a timestamp' do
+    context "without a timestamp" do
       before do
         dsl.env.expects(:timestamp).returns(now)
         dsl.set_release_path
       end
 
-      it 'returns the release path with the current env timestamp' do
-        expect(subject.to_s).to eq '/var/www/releases/20151021162900'
+      it "returns the release path with the current env timestamp" do
+        expect(subject.to_s).to eq "/var/www/releases/20151021162900"
       end
     end
 
-    context 'with a timestamp' do
+    context "with a timestamp" do
       before do
-        dsl.set_release_path('timestamp')
+        dsl.set_release_path("timestamp")
       end
 
-      it 'returns the release path with the timestamp' do
-        expect(subject.to_s).to eq '/var/www/releases/timestamp'
+      it "returns the release path with the timestamp" do
+        expect(subject.to_s).to eq "/var/www/releases/timestamp"
       end
     end
   end
 
-  describe '#deploy_config_path' do
+  describe "#deploy_config_path" do
     subject { dsl.deploy_config_path.to_s }
 
-    context 'when not specified' do
+    context "when not specified" do
       before do
         dsl.delete(:deploy_config_path)
       end
 
       it 'returns "config/deploy.rb"' do
-        expect(subject).to eq 'config/deploy.rb'
+        expect(subject).to eq "config/deploy.rb"
       end
     end
 
-    context 'when the variable :deploy_config_path is set' do
+    context "when the variable :deploy_config_path is set" do
       before do
-        dsl.set(:deploy_config_path, 'my/custom/path.rb')
+        dsl.set(:deploy_config_path, "my/custom/path.rb")
       end
 
-      it 'returns the custom path' do
-        expect(subject).to eq 'my/custom/path.rb'
+      it "returns the custom path" do
+        expect(subject).to eq "my/custom/path.rb"
       end
     end
   end
 
-  describe '#stage_config_path' do
+  describe "#stage_config_path" do
     subject { dsl.stage_config_path.to_s }
 
-    context 'when not specified' do
-
+    context "when not specified" do
       before do
         dsl.delete(:stage_config_path)
       end
 
       it 'returns "config/deploy"' do
-        expect(subject).to eq 'config/deploy'
+        expect(subject).to eq "config/deploy"
       end
     end
 
-    context 'when the variable :stage_config_path is set' do
+    context "when the variable :stage_config_path is set" do
       before do
-        dsl.set(:stage_config_path, 'my/custom/path')
+        dsl.set(:stage_config_path, "my/custom/path")
       end
 
-      it 'returns the custom path' do
-        expect(subject).to eq 'my/custom/path'
+      it "returns the custom path" do
+        expect(subject).to eq "my/custom/path"
       end
     end
   end
 
-  describe '#repo_path' do
+  describe "#repo_path" do
     subject { dsl.repo_path.to_s }
 
-    context 'when not specified' do
-
+    context "when not specified" do
       before do
         dsl.delete(:repo_path)
       end
 
       it 'returns the default #{deploy_to}/repo' do
-        dsl.set(:deploy_to, '/var/www')
-        expect(subject).to eq '/var/www/repo'
+        dsl.set(:deploy_to, "/var/www")
+        expect(subject).to eq "/var/www/repo"
       end
     end
 
-    context 'when the variable :repo_path is set' do
+    context "when the variable :repo_path is set" do
       before do
-        dsl.set(:repo_path, 'my/custom/path')
+        dsl.set(:repo_path, "my/custom/path")
       end
 
-      it 'returns the custom path' do
-        expect(subject).to eq 'my/custom/path'
+      it "returns the custom path" do
+        expect(subject).to eq "my/custom/path"
       end
     end
   end
diff --git a/spec/lib/capistrano/dsl/task_enhancements_spec.rb b/spec/lib/capistrano/dsl/task_enhancements_spec.rb
index 2c960fb..33e5669 100644
--- a/spec/lib/capistrano/dsl/task_enhancements_spec.rb
+++ b/spec/lib/capistrano/dsl/task_enhancements_spec.rb
@@ -1,4 +1,4 @@
-require 'spec_helper'
+require "spec_helper"
 
 module Capistrano
   class DummyTaskEnhancements
@@ -8,8 +8,7 @@ module Capistrano
   describe TaskEnhancements do
     let(:task_enhancements) { DummyTaskEnhancements.new }
 
-    describe 'ordering' do
-
+    describe "ordering" do
       after do
         task.clear
         before_task.clear
@@ -19,68 +18,104 @@ module Capistrano
 
       let(:order) { [] }
       let!(:task) do
-        Rake::Task.define_task('task', [:order]) do |t, args|
-          args['order'].push 'task'
+        Rake::Task.define_task("task", [:order]) do |_t, args|
+          args["order"].push "task"
         end
       end
 
       let!(:before_task) do
-        Rake::Task.define_task('before_task') do
-          order.push 'before_task'
+        Rake::Task.define_task("before_task") do
+          order.push "before_task"
         end
       end
 
       let!(:after_task) do
-        Rake::Task.define_task('after_task') do
-          order.push 'after_task'
+        Rake::Task.define_task("after_task") do
+          order.push "after_task"
         end
       end
 
-      it 'invokes in proper order if define after than before' do
-        task_enhancements.after('task', 'after_task')
-        task_enhancements.before('task', 'before_task')
+      it "invokes in proper order if define after than before" do
+        task_enhancements.after("task", "after_task")
+        task_enhancements.before("task", "before_task")
 
-        Rake::Task['task'].invoke order
+        Rake::Task["task"].invoke order
 
-        expect(order).to eq(['before_task', 'task', 'after_task'])
+        expect(order).to eq(%w(before_task task after_task))
       end
 
-      it 'invokes in proper order if define before than after' do
-        task_enhancements.before('task', 'before_task')
-        task_enhancements.after('task', 'after_task')
+      it "invokes in proper order if define before than after" do
+        task_enhancements.before("task", "before_task")
+        task_enhancements.after("task", "after_task")
+
+        Rake::Task["task"].invoke order
+
+        expect(order).to eq(%w(before_task task after_task))
+      end
+
+      it "invokes in proper order when referring to as-yet undefined tasks" do
+        task_enhancements.after("task", "not_loaded_task")
+
+        Rake::Task.define_task("not_loaded_task") do
+          order.push "not_loaded_task"
+        end
 
-        Rake::Task['task'].invoke order
+        Rake::Task["task"].invoke order
 
-        expect(order).to eq(['before_task', 'task', 'after_task'])
+        expect(order).to eq(%w(task not_loaded_task))
       end
 
-      it 'invokes in proper order and with arguments and block' do
-        task_enhancements.after('task', 'after_task_custom', :order) do |t, args|
-          order.push 'after_task'
+      it "invokes in proper order and with arguments and block" do
+        task_enhancements.after("task", "after_task_custom", :order) do |_t, _args|
+          order.push "after_task"
         end
 
-        task_enhancements.before('task', 'before_task_custom', :order) do |t, args|
-          order.push 'before_task'
+        task_enhancements.before("task", "before_task_custom", :order) do |_t, _args|
+          order.push "before_task"
         end
 
-        Rake::Task['task'].invoke(order)
+        Rake::Task["task"].invoke(order)
 
-        expect(order).to eq(['before_task', 'task', 'after_task'])
+        expect(order).to eq(%w(before_task task after_task))
       end
 
+      it "invokes using the correct namespace when defined within a namespace" do
+        Rake.application.in_namespace("namespace") do
+          Rake::Task.define_task("task") do |t|
+            order.push(t.name)
+          end
+          task_enhancements.before("task", "before_task", :order) do |t|
+            order.push(t.name)
+          end
+          task_enhancements.after("task", "after_task", :order) do |t|
+            order.push(t.name)
+          end
+        end
+
+        Rake::Task["namespace:task"].invoke
+
+        expect(order).to eq(
+          ["namespace:before_task", "namespace:task", "namespace:after_task"]
+        )
+      end
+
+      it "raises a sensible error if the task isn't found" do
+        task_enhancements.after("task", "non_existent_task")
+        expect { Rake::Task["task"].invoke order }.to raise_error(ArgumentError, 'Task "non_existent_task" not found')
+      end
     end
 
-    describe 'remote_file' do
-      subject(:remote_file) { task_enhancements.remote_file('source' => 'destination') }
+    describe "remote_file" do
+      subject(:remote_file) { task_enhancements.remote_file("source" => "destination") }
 
-      it { expect(remote_file.name).to eq('source') }
+      it { expect(remote_file.name).to eq("source") }
       it { is_expected.to be_a(Capistrano::UploadTask) }
 
-      describe 'namespaced' do
+      describe "namespaced" do
         let(:app) { Rake.application }
-        around { |ex| app.in_namespace('namespace', &ex) }
+        around { |ex| app.in_namespace("namespace", &ex) }
 
-        it { expect(remote_file.name).to eq('source') }
+        it { expect(remote_file.name).to eq("source") }
         it { is_expected.to be_a(Capistrano::UploadTask) }
       end
     end
diff --git a/spec/lib/capistrano/dsl_spec.rb b/spec/lib/capistrano/dsl_spec.rb
index 12e4391..f7b4422 100644
--- a/spec/lib/capistrano/dsl_spec.rb
+++ b/spec/lib/capistrano/dsl_spec.rb
@@ -1,7 +1,6 @@
-require 'spec_helper'
+require "spec_helper"
 
 module Capistrano
-
   class DummyDSL
     include DSL
   end
@@ -10,27 +9,27 @@ module Capistrano
   describe DSL do
     let(:dsl) { DummyDSL.new }
 
-    describe '#t' do
+    describe "#t" do
       before do
-        I18n.expects(:t).with(:phrase, {count: 2, scope: :capistrano})
+        I18n.expects(:t).with(:phrase, count: 2, scope: :capistrano)
       end
 
-      it 'delegates to I18n' do
+      it "delegates to I18n" do
         dsl.t(:phrase, count: 2)
       end
     end
 
-    describe '#stage_set?' do
+    describe "#stage_set?" do
       subject { dsl.stage_set? }
 
-      context 'stage is set' do
+      context "stage is set" do
         before do
           dsl.set(:stage, :sandbox)
         end
         it { expect(subject).to be_truthy }
       end
 
-      context 'stage is not set' do
+      context "stage is not set" do
         before do
           dsl.set(:stage, nil)
         end
@@ -38,15 +37,48 @@ module Capistrano
       end
     end
 
-    describe '#sudo' do
-
+    describe "#sudo" do
       before do
         dsl.expects(:execute).with(:sudo, :my, :command)
       end
 
-      it 'prepends sudo, delegates to execute' do
+      it "prepends sudo, delegates to execute" do
         dsl.sudo(:my, :command)
       end
     end
+
+    describe "#execute" do
+      context "use outside of on scope" do
+        after do
+          task.clear
+          Rake::Task.clear
+        end
+
+        let(:task) do
+          Rake::Task.define_task("execute_outside_scope") do
+            dsl.execute "whoami"
+          end
+        end
+
+        it "prints helpful message to stderr" do
+          expect do
+            expect do
+              task.invoke
+            end.to output(/^.*Warning: `execute' should be wrapped in an `on' scope/).to_stderr
+          end.to raise_error(NoMethodError)
+        end
+      end
+    end
+
+    describe "#invoke" do
+      it "will print a message on stderr, when reinvoking task" do
+        Rake::Task.define_task("some_task")
+
+        dsl.invoke("some_task")
+        expect do
+          dsl.invoke("some_task")
+        end.to output(/.*Capistrano tasks may only be invoked once.*/).to_stderr
+      end
+    end
   end
 end
diff --git a/spec/lib/capistrano/git_spec.rb b/spec/lib/capistrano/git_spec.rb
index 0876038..a74ae21 100644
--- a/spec/lib/capistrano/git_spec.rb
+++ b/spec/lib/capistrano/git_spec.rb
@@ -1,6 +1,6 @@
-require 'spec_helper'
+require "spec_helper"
 
-require 'capistrano/git'
+require "capistrano/git"
 
 module Capistrano
   describe Git do
@@ -39,10 +39,20 @@ module Capistrano
 
     describe "#clone" do
       it "should run git clone" do
+        context.expects(:fetch).with(:git_shallow_clone).returns(nil)
         context.expects(:repo_url).returns(:url)
         context.expects(:repo_path).returns(:path)
+        context.expects(:execute).with(:git, :clone, "--mirror", :url, :path)
 
-        context.expects(:execute).with(:git, :clone, '--mirror', :url, :path)
+        subject.clone
+      end
+
+      it "should run git clone in shallow mode" do
+        context.expects(:fetch).with(:git_shallow_clone).returns("1")
+        context.expects(:repo_url).returns(:url)
+        context.expects(:repo_path).returns(:path)
+
+        context.expects(:execute).with(:git, :clone, "--mirror", "--depth", "1", "--no-single-branch", :url, :path)
 
         subject.clone
       end
@@ -50,7 +60,16 @@ module Capistrano
 
     describe "#update" do
       it "should run git update" do
-        context.expects(:execute).with(:git, :remote, :update)
+        context.expects(:fetch).with(:git_shallow_clone).returns(nil)
+        context.expects(:execute).with(:git, :remote, :update, "--prune")
+
+        subject.update
+      end
+
+      it "should run git update in shallow mode" do
+        context.expects(:fetch).with(:git_shallow_clone).returns("1")
+        context.expects(:fetch).with(:branch).returns(:branch)
+        context.expects(:execute).with(:git, :fetch, "--depth", "1", "origin", :branch)
 
         subject.update
       end
@@ -62,20 +81,29 @@ module Capistrano
         context.expects(:fetch).with(:branch).returns(:branch)
         context.expects(:release_path).returns(:path)
 
-        context.expects(:execute).with(:git, :archive, :branch, '| tar -x -f - -C', :path)
+        context.expects(:execute).with(:git, :archive, :branch, "| tar -x -f - -C", :path)
 
         subject.release
       end
 
       it "should run git archive with a subtree" do
-        context.expects(:fetch).with(:repo_tree).returns('tree')
+        context.expects(:fetch).with(:repo_tree).returns("tree")
         context.expects(:fetch).with(:branch).returns(:branch)
         context.expects(:release_path).returns(:path)
 
-        context.expects(:execute).with(:git, :archive, :branch, 'tree', '| tar -x --strip-components 1 -f - -C', :path)
+        context.expects(:execute).with(:git, :archive, :branch, "tree", "| tar -x --strip-components 1 -f - -C", :path)
 
         subject.release
       end
     end
+
+    describe "#fetch_revision" do
+      it "should capture git rev-list" do
+        context.expects(:fetch).with(:branch).returns(:branch)
+        context.expects(:capture).with(:git, "rev-list --max-count=1 branch").returns("81cec13b777ff46348693d327fc8e7832f79bf43")
+        revision = subject.fetch_revision
+        expect(revision).to eq("81cec13b777ff46348693d327fc8e7832f79bf43")
+      end
+    end
   end
 end
diff --git a/spec/lib/capistrano/hg_spec.rb b/spec/lib/capistrano/hg_spec.rb
index d7d5147..42007c3 100644
--- a/spec/lib/capistrano/hg_spec.rb
+++ b/spec/lib/capistrano/hg_spec.rb
@@ -1,6 +1,6 @@
-require 'spec_helper'
+require "spec_helper"
 
-require 'capistrano/hg'
+require "capistrano/hg"
 
 module Capistrano
   describe Hg do
@@ -42,7 +42,7 @@ module Capistrano
         context.expects(:repo_url).returns(:url)
         context.expects(:repo_path).returns(:path)
 
-        context.expects(:execute).with(:hg, "clone", '--noupdate', :url, :path)
+        context.expects(:execute).with(:hg, "clone", "--noupdate", :url, :path)
 
         subject.clone
       end
@@ -68,14 +68,23 @@ module Capistrano
       end
 
       it "should run hg archive with a subtree" do
-        context.expects(:fetch).with(:repo_tree).returns('tree')
+        context.expects(:fetch).with(:repo_tree).returns("tree")
         context.expects(:fetch).with(:branch).returns(:branch)
         context.expects(:release_path).returns(:path)
 
-        context.expects(:execute).with(:hg, "archive --type tgz -p . -I", 'tree', "--rev", :branch, '| tar -x --strip-components 1 -f - -C', :path)
+        context.expects(:execute).with(:hg, "archive --type tgz -p . -I", "tree", "--rev", :branch, "| tar -x --strip-components 1 -f - -C", :path)
 
         subject.release
       end
     end
+
+    describe "#fetch_revision" do
+      it "should capture hg log" do
+        context.expects(:fetch).with(:branch).returns(:branch)
+        context.expects(:capture).with(:hg, "log --rev branch --template \"{node}\n\"").returns("01abcde")
+        revision = subject.fetch_revision
+        expect(revision).to eq("01abcde")
+      end
+    end
   end
 end
diff --git a/spec/lib/capistrano/immutable_task_spec.rb b/spec/lib/capistrano/immutable_task_spec.rb
new file mode 100644
index 0000000..01f7a39
--- /dev/null
+++ b/spec/lib/capistrano/immutable_task_spec.rb
@@ -0,0 +1,31 @@
+require "spec_helper"
+require "rake"
+require "capistrano/immutable_task"
+
+module Capistrano
+  describe ImmutableTask do
+    after do
+      # Ensure that any tasks we create in these tests don't pollute other tests
+      Rake::Task.clear
+    end
+
+    it "prints warning and raises when task is enhanced" do
+      extend(Rake::DSL)
+
+      load_defaults = Rake::Task.define_task("load:defaults")
+      load_defaults.extend(Capistrano::ImmutableTask)
+
+      $stderr.expects(:puts).with do |message|
+        message =~ /^WARNING: load:defaults has already been invoked/
+      end
+
+      expect do
+        namespace :load do
+          task :defaults do
+            # Never reached since load_defaults is frozen and can't be enhanced
+          end
+        end
+      end.to raise_error(/frozen/i)
+    end
+  end
+end
diff --git a/spec/lib/capistrano/plugin_spec.rb b/spec/lib/capistrano/plugin_spec.rb
new file mode 100644
index 0000000..f30ba79
--- /dev/null
+++ b/spec/lib/capistrano/plugin_spec.rb
@@ -0,0 +1,84 @@
+require "spec_helper"
+require "capistrano/plugin"
+
+module Capistrano
+  describe Plugin do
+    include Rake::DSL
+    include Capistrano::DSL
+
+    class DummyPlugin < Capistrano::Plugin
+      def define_tasks
+        task :hello do
+        end
+      end
+
+      def register_hooks
+        before "deploy:published", "hello"
+      end
+    end
+
+    class ExternalTasksPlugin < Capistrano::Plugin
+      def define_tasks
+        eval_rakefile(
+          File.expand_path("../../../support/tasks/plugin.rake", __FILE__)
+        )
+      end
+
+      # Called from plugin.rake to demonstrate that helper methods work
+      def hello
+        set :plugin_result, "hello"
+      end
+    end
+
+    before do
+      # Define an example task to allow testing hooks
+      task "deploy:published"
+    end
+
+    after do
+      # Clean up any tasks or variables we created during the tests
+      Rake::Task.clear
+      Capistrano::Configuration.reset!
+    end
+
+    it "defines tasks when constructed" do
+      install_plugin(DummyPlugin)
+      expect(Rake::Task["hello"]).not_to be_nil
+    end
+
+    it "registers hooks when constructed" do
+      install_plugin(DummyPlugin)
+      expect(Rake::Task["deploy:published"].prerequisites).to include("hello")
+    end
+
+    it "skips registering hooks if load_hooks: false" do
+      install_plugin(DummyPlugin, load_hooks: false)
+      expect(Rake::Task["deploy:published"].prerequisites).to be_empty
+    end
+
+    it "doesn't call set_defaults immediately" do
+      dummy = DummyPlugin.new
+      install_plugin(dummy)
+      dummy.expects(:set_defaults).never
+    end
+
+    it "calls set_defaults during load:defaults" do
+      dummy = DummyPlugin.new
+      dummy.expects(:set_defaults).once
+      install_plugin(dummy)
+      Rake::Task["load:defaults"].invoke
+    end
+
+    it "is able to load tasks from a .rake file" do
+      install_plugin(ExternalTasksPlugin)
+      Rake::Task["plugin_test"].invoke
+      expect(fetch(:plugin_result)).to eq("hello")
+    end
+
+    it "exposes the SSHKit backend to subclasses" do
+      SSHKit::Backend.expects(:current).returns(:backend)
+      plugin = DummyPlugin.new
+      expect(plugin.send(:backend)).to eq(:backend)
+    end
+  end
+end
diff --git a/spec/lib/capistrano/scm_spec.rb b/spec/lib/capistrano/scm_spec.rb
index af0f1b2..abe9e60 100644
--- a/spec/lib/capistrano/scm_spec.rb
+++ b/spec/lib/capistrano/scm_spec.rb
@@ -1,13 +1,13 @@
-require 'spec_helper'
+require "spec_helper"
 
-require 'capistrano/scm'
+require "capistrano/scm"
 
 module RaiseNotImplementedMacro
   def raise_not_implemented_on(method)
     it "should raise NotImplemented on #{method}" do
-      expect {
+      expect do
         subject.send(method)
-      }.to raise_error(NotImplementedError)
+      end.to raise_error(NotImplementedError)
     end
   end
 end
@@ -63,8 +63,8 @@ module Capistrano
 
       describe "#release_path" do
         it "should return the release path according to the context" do
-          context.expects(:release_path).returns('/path/to/nowhere')
-          expect(subject.release_path).to eq('/path/to/nowhere')
+          context.expects(:release_path).returns("/path/to/nowhere")
+          expect(subject.release_path).to eq("/path/to/nowhere")
         end
       end
 
@@ -101,4 +101,3 @@ module Capistrano
     end
   end
 end
-
diff --git a/spec/lib/capistrano/svn_spec.rb b/spec/lib/capistrano/svn_spec.rb
index b8a9fb9..4953326 100644
--- a/spec/lib/capistrano/svn_spec.rb
+++ b/spec/lib/capistrano/svn_spec.rb
@@ -1,6 +1,6 @@
-require 'spec_helper'
+require "spec_helper"
 
-require 'capistrano/svn'
+require "capistrano/svn"
 
 module Capistrano
   describe Svn do
@@ -9,7 +9,10 @@ module Capistrano
 
     describe "#svn" do
       it "should call execute svn in the context, with arguments" do
-        context.expects(:execute).with(:svn, :init)
+        context.expects(:execute).with(:svn, :init, "--username someuser", "--password somepassword")
+        context.expects(:fetch).twice.with(:svn_username).returns("someuser")
+        context.expects(:fetch).twice.with(:svn_password).returns("somepassword")
+        context.expects(:fetch).once.with(:svn_revision).returns(nil)
         subject.svn(:init)
       end
     end
@@ -20,7 +23,7 @@ module Capistrano
     subject { Capistrano::Svn.new(context, Capistrano::Svn::DefaultStrategy) }
 
     describe "#test" do
-      it "should call test for repo HEAD" do  
+      it "should call test for repo HEAD" do
         context.expects(:repo_path).returns("/path/to/repo")
         context.expects(:test).with " [ -d /path/to/repo/.svn ] "
 
@@ -31,7 +34,9 @@ module Capistrano
     describe "#check" do
       it "should test the repo url" do
         context.expects(:repo_url).returns(:url)
-        context.expects(:test).with(:svn, :info, :url).returns(true)
+        context.expects(:test).with(:svn, :info, :url, "--username someuser", "--password somepassword").returns(true)
+        context.expects(:fetch).twice.with(:svn_username).returns("someuser")
+        context.expects(:fetch).twice.with(:svn_password).returns("somepassword")
 
         subject.check
       end
@@ -41,8 +46,11 @@ module Capistrano
       it "should run svn checkout" do
         context.expects(:repo_url).returns(:url)
         context.expects(:repo_path).returns(:path)
- 
-        context.expects(:execute).with(:svn, :checkout, :url, :path)
+        context.expects(:fetch).twice.with(:svn_username).returns("someuser")
+        context.expects(:fetch).twice.with(:svn_password).returns("somepassword")
+        context.expects(:fetch).once.with(:svn_revision).returns(nil)
+
+        context.expects(:execute).with(:svn, :checkout, :url, :path, "--username someuser", "--password somepassword")
 
         subject.clone
       end
@@ -50,29 +58,47 @@ module Capistrano
 
     describe "#update" do
       it "should run svn update" do
-        context.expects(:execute).with(:svn, :update)
+        context.expects(:execute).with(:svn, :update, "--username someuser", "--password somepassword")
+        context.expects(:fetch).twice.with(:svn_username).returns("someuser")
+        context.expects(:fetch).twice.with(:svn_password).returns("somepassword")
+        context.expects(:fetch).once.with(:svn_revision).returns(nil)
+
+        subject.update
+      end
+    end
+
+    describe "#update_specific_revision" do
+      it "should run svn update and update to a specific revision" do
+        context.expects(:execute).with(:svn, :update, "--username someuser", "--password somepassword", "--revision 12345")
+        context.expects(:fetch).twice.with(:svn_username).returns("someuser")
+        context.expects(:fetch).twice.with(:svn_password).returns("somepassword")
+        context.expects(:fetch).twice.with(:svn_revision).returns("12345")
 
         subject.update
       end
     end
 
     describe "#release" do
-      it "should run svn export" do        
+      it "should run svn export" do
         context.expects(:release_path).returns(:path)
-        
-        context.expects(:execute).with(:svn, :export, '--force', '.', :path)
+        context.expects(:fetch).twice.with(:svn_username).returns("someuser")
+        context.expects(:fetch).twice.with(:svn_password).returns("somepassword")
+        context.expects(:fetch).once.with(:svn_revision).returns(nil)
+
+        context.expects(:execute).with(:svn, :export, "--force", ".", :path, "--username someuser", "--password somepassword")
 
         subject.release
       end
     end
 
     describe "#fetch_revision" do
-      it "should run fetch revision" do
+      it "should capture svn version" do
         context.expects(:repo_path).returns(:path)
 
-        context.expects(:capture).with(:svnversion, :path)
+        context.expects(:capture).with(:svnversion, :path).returns("12345")
 
-        subject.fetch_revision
+        revision = subject.fetch_revision
+        expect(revision).to eq("12345")
       end
     end
   end
diff --git a/spec/lib/capistrano/upload_task_spec.rb b/spec/lib/capistrano/upload_task_spec.rb
index 353cd4f..60c1162 100644
--- a/spec/lib/capistrano/upload_task_spec.rb
+++ b/spec/lib/capistrano/upload_task_spec.rb
@@ -1,19 +1,19 @@
-require 'spec_helper'
+require "spec_helper"
 
 describe Capistrano::UploadTask do
   let(:app) { Rake.application = Rake::Application.new }
 
-  subject(:upload_task) { described_class.define_task('path/file.yml') }
+  subject(:upload_task) { described_class.define_task("path/file.yml") }
 
   it { is_expected.to be_a(Rake::FileCreationTask) }
   it { is_expected.to be_needed }
 
-  context 'inside namespace' do
-    let(:normal_task) { Rake::Task.define_task('path/other_file.yml') }
+  context "inside namespace" do
+    let(:normal_task) { Rake::Task.define_task("path/other_file.yml") }
 
-    around { |ex| app.in_namespace('namespace', &ex) }
+    around { |ex| app.in_namespace("namespace", &ex) }
 
-    it { expect(upload_task.name).to eq('path/file.yml') }
-    it { expect(upload_task.scope.path).to eq('namespace') }
+    it { expect(upload_task.name).to eq("path/file.yml") }
+    it { expect(upload_task.scope.path).to eq("namespace") }
   end
 end
diff --git a/spec/lib/capistrano/version_validator_spec.rb b/spec/lib/capistrano/version_validator_spec.rb
index a0a42c0..1e2a72b 100644
--- a/spec/lib/capistrano/version_validator_spec.rb
+++ b/spec/lib/capistrano/version_validator_spec.rb
@@ -1,19 +1,18 @@
-require 'spec_helper'
+require "spec_helper"
 
 module Capistrano
-
   describe VersionValidator do
     let(:validator) { VersionValidator.new(version) }
     let(:version) { stub }
 
-    describe '#new' do
-      it 'takes a version' do
+    describe "#new" do
+      it "takes a version" do
         expect(validator)
       end
     end
 
-    describe '#verify' do
-      let(:current_version) { '3.0.1' }
+    describe "#verify" do
+      let(:current_version) { "3.0.1" }
 
       subject { validator.verify }
 
@@ -21,83 +20,76 @@ module Capistrano
         validator.stubs(:current_version).returns(current_version)
       end
 
-      context 'with exact version' do
-        context 'valid' do
-          let(:version) { '3.0.1' }
+      context "with exact version" do
+        context "valid" do
+          let(:version) { "3.0.1" }
           it { expect(subject).to be_truthy }
         end
 
-        context 'invalid - lower' do
-          let(:version) { '3.0.0' }
+        context "invalid - lower" do
+          let(:version) { "3.0.0" }
 
-          it 'fails' do
-            expect { subject }.to raise_error
+          it "fails" do
+            expect { subject }.to raise_error(RuntimeError)
           end
         end
 
-        context 'invalid - higher' do
-          let(:version) { '3.0.2' }
+        context "invalid - higher" do
+          let(:version) { "3.0.2" }
 
-          it 'fails' do
-            expect { subject }.to raise_error
+          it "fails" do
+            expect { subject }.to raise_error(RuntimeError)
           end
         end
-
       end
 
-      context 'with optimistic versioning' do
-        context 'valid' do
-          let(:version) { '>= 3.0.0' }
+      context "with optimistic versioning" do
+        context "valid" do
+          let(:version) { ">= 3.0.0" }
           it { expect(subject).to be_truthy }
         end
 
-        context 'invalid - lower' do
-          let(:version) { '<= 2.0.0' }
+        context "invalid - lower" do
+          let(:version) { "<= 2.0.0" }
 
-          it 'fails' do
-            expect { subject }.to raise_error
+          it "fails" do
+            expect { subject }.to raise_error(RuntimeError)
           end
         end
       end
 
-
-
-      context 'with pessimistic versioning' do
-        context '2 decimal places' do
-          context 'valid' do
-            let(:version) { '~> 3.0.0' }
+      context "with pessimistic versioning" do
+        context "2 decimal places" do
+          context "valid" do
+            let(:version) { "~> 3.0.0" }
             it { expect(subject).to be_truthy }
           end
 
-          context 'invalid' do
-            let(:version) { '~> 3.1.0' }
+          context "invalid" do
+            let(:version) { "~> 3.1.0" }
 
-            it 'fails' do
-              expect { subject }.to raise_error
+            it "fails" do
+              expect { subject }.to raise_error(RuntimeError)
             end
           end
         end
 
-        context '1 decimal place' do
-          let(:current_version) { '3.5.0' }
+        context "1 decimal place" do
+          let(:current_version) { "3.5.0" }
 
-          context 'valid' do
-            let(:version) { '~> 3.1' }
+          context "valid" do
+            let(:version) { "~> 3.1" }
             it { expect(subject).to be_truthy }
           end
 
-          context 'invalid' do
-            let(:version) { '~> 3.6' }
-            it 'fails' do
-              expect { subject }.to raise_error
+          context "invalid" do
+            let(:version) { "~> 3.6" }
+            it "fails" do
+              expect { subject }.to raise_error(RuntimeError)
             end
           end
         end
-
       end
-
     end
-
   end
-
 end
diff --git a/spec/lib/capistrano_spec.rb b/spec/lib/capistrano_spec.rb
index 6281a22..4da8c8c 100644
--- a/spec/lib/capistrano_spec.rb
+++ b/spec/lib/capistrano_spec.rb
@@ -1,8 +1,7 @@
-require 'spec_helper'
+require "spec_helper"
 
 module Capistrano
-
   describe Application do
-    let(:app) { Application.new  }
+    let(:app) { Application.new }
   end
 end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 9e4e1b3..8db5893 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,16 +1,16 @@
-$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
 $LOAD_PATH.unshift(File.dirname(__FILE__))
-require 'capistrano/all'
-require 'rspec'
-require 'mocha/api'
-require 'time'
+require "capistrano/all"
+require "rspec"
+require "mocha/api"
+require "time"
 
 # Requires supporting files with custom matchers and macros, etc,
 # in ./support/ and its subdirectories.
-Dir['#{File.dirname(__FILE__)}/support/**/*.rb'].each {|f| require f}
+Dir['#{File.dirname(__FILE__)}/support/**/*.rb'].each { |f| require f }
 
 RSpec.configure do |config|
-	config.raise_errors_for_deprecations!
+  config.raise_errors_for_deprecations!
   config.mock_framework = :mocha
-  config.order = 'random'
+  config.order = "random"
 end
diff --git a/spec/support/Vagrantfile b/spec/support/Vagrantfile
index 7ef80db..74b4414 100644
--- a/spec/support/Vagrantfile
+++ b/spec/support/Vagrantfile
@@ -1,20 +1,19 @@
-require 'open-uri'
+require "open-uri"
 
 Vagrant.configure("2") do |config|
-
   config.ssh.insert_key = false
 
   [:app].each_with_index do |role, i|
-    config.vm.define(role, primary: true) do |config|
-      config.vm.define role
-      config.vm.box = 'hashicorp/precise64'
-      config.vm.network "forwarded_port", guest: 22, host: "222#{i}".to_i
-      config.vm.provision :shell, inline: 'sudo apt-get -y install git-core'
+    config.vm.define(role, primary: true) do |primary|
+      primary.vm.define role
+      primary.vm.box = "hashicorp/precise64"
+      primary.vm.network "forwarded_port", guest: 22, host: "222#{i}".to_i
+      primary.vm.provision :shell, inline: "sudo apt-get -y install git-core"
 
-      vagrantkey = open("https://raw.githubusercontent.com/mitchellh/vagrant/master/keys/vagrant.pub", "r",&:read)
+      vagrantkey = open("https://raw.githubusercontent.com/mitchellh/vagrant/master/keys/vagrant.pub", "r", &:read)
 
-      config.vm.provision :shell,
-        inline: <<-INLINE
+      primary.vm.provision :shell,
+                           inline: <<-INLINE
           install -d -m 700 /root/.ssh
           echo -e "#{vagrantkey}" > /root/.ssh/authorized_keys
           chmod 0600 /root/.ssh/authorized_keys
diff --git a/spec/support/tasks/database.rake b/spec/support/tasks/database.rake
index a557264..47adec2 100644
--- a/spec/support/tasks/database.rake
+++ b/spec/support/tasks/database.rake
@@ -1,11 +1,11 @@
 namespace :deploy do
   namespace :check do
-    task :linked_files => 'config/database.yml'
+    task linked_files: "config/database.yml"
   end
 end
 
-remote_file 'config/database.yml' => '/tmp/database.yml', roles: :all
+remote_file "config/database.yml" => "/tmp/database.yml", :roles => :all
 
-file '/tmp/database.yml' do |t|
+file "/tmp/database.yml" do |t|
   sh "touch #{t.name}"
 end
diff --git a/spec/support/tasks/fail.rake b/spec/support/tasks/fail.rake
index aec54a7..ec4092e 100644
--- a/spec/support/tasks/fail.rake
+++ b/spec/support/tasks/fail.rake
@@ -1,7 +1,8 @@
-set :fail, proc { fail }
-before 'deploy:starting', :fail do
+set :fail, proc { raise }
+before "deploy:starting", :fail do
   on roles :all do
-    execute :touch, shared_path.join('fail')
+    execute :mkdir, "-p", shared_path
+    execute :touch, shared_path.join("fail")
   end
   fetch(:fail)
 end
diff --git a/spec/support/tasks/failed.rake b/spec/support/tasks/failed.rake
index d1f6b58..2aba806 100644
--- a/spec/support/tasks/failed.rake
+++ b/spec/support/tasks/failed.rake
@@ -1,5 +1,5 @@
-after 'deploy:failed', :failed do
+after "deploy:failed", :custom_failed do
   on roles :all do
-    execute :touch, shared_path.join('failed')
+    execute :touch, shared_path.join("failed")
   end
 end
diff --git a/spec/support/tasks/plugin.rake b/spec/support/tasks/plugin.rake
new file mode 100644
index 0000000..3234ebc
--- /dev/null
+++ b/spec/support/tasks/plugin.rake
@@ -0,0 +1,6 @@
+# This rake file is used by plugin_spec.rb.
+
+task :plugin_test do
+  # Example of invoking a helper method provided by the plugin
+  hello
+end
diff --git a/spec/support/tasks/root.rake b/spec/support/tasks/root.rake
index c82a0f8..d3a02dd 100644
--- a/spec/support/tasks/root.rake
+++ b/spec/support/tasks/root.rake
@@ -1,11 +1,11 @@
 task :am_i_root do
   on roles(:all) do |host|
-    host.user = 'root'
-    ident = capture :id, '-a'
+    host.user = "root"
+    ident = capture :id, "-a"
     info "I am #{ident}"
   end
-  on roles(:all) do |host|
-    ident = capture :id, '-a'
+  on roles(:all) do |_host|
+    ident = capture :id, "-a"
     info "I am #{ident}"
   end
 end
diff --git a/spec/support/test_app.rb b/spec/support/test_app.rb
index c5d9eaa..299fe50 100644
--- a/spec/support/test_app.rb
+++ b/spec/support/test_app.rb
@@ -1,5 +1,6 @@
-require 'fileutils'
-require 'pathname'
+require "English"
+require "fileutils"
+require "pathname"
 
 module TestApp
   extend self
@@ -9,7 +10,7 @@ module TestApp
   end
 
   def default_config
-    %{
+    <<-CONFIG
       set :deploy_to, '#{deploy_to}'
       set :repo_url, 'git://github.com/capistrano/capistrano.git'
       set :branch, 'master'
@@ -17,7 +18,8 @@ module TestApp
       server 'vagrant at localhost:2220', roles: %w{web app}
       set :linked_files, #{linked_files}
       set :linked_dirs, #{linked_dirs}
-    }
+      set :format_options, log_file: nil
+    CONFIG
   end
 
   def linked_files
@@ -29,45 +31,51 @@ module TestApp
   end
 
   def linked_dirs
-    %w{bin log public/system vendor/bundle}
+    %w{bin log public/system}
   end
 
   def create_test_app
     FileUtils.rm_rf(test_app_path)
     FileUtils.mkdir(test_app_path)
 
-    File.open(gemfile, 'w+') do |file|
+    File.open(gemfile, "w+") do |file|
       file.write "gem 'capistrano', path: '#{path_to_cap}'"
     end
 
     Dir.chdir(test_app_path) do
-      %x[bundle]
+      `bundle`
     end
   end
 
   def install_test_app_with(config)
     create_test_app
     Dir.chdir(test_app_path) do
-      %x[bundle exec cap install STAGES=#{stage}]
+      `bundle exec cap install STAGES=#{stage}`
     end
     write_local_deploy_file(config)
   end
 
   def write_local_deploy_file(config)
-    File.open(test_stage_path, 'w') do |file|
+    File.open(test_stage_path, "w") do |file|
       file.write config
     end
   end
 
+  def write_local_stage_file(filename, config=nil)
+    File.open(test_app_path.join("config/deploy/#{filename}"), "w") do |file|
+      file.write(config) if config
+    end
+  end
+
   def append_to_deploy_file(config)
-    File.open(test_stage_path, 'a') do |file|
+    File.open(test_stage_path, "a") do |file|
       file.write config + "\n"
     end
   end
 
   def prepend_to_capfile(config)
     current_capfile = File.read(capfile)
-    File.open(capfile, 'w') do |file|
+    File.open(capfile, "w") do |file|
       file.write config
       file.write current_capfile
     end
@@ -78,7 +86,7 @@ module TestApp
   end
 
   def create_shared_file(path)
-    File.open(shared_path.join(path), 'w')
+    File.open(shared_path.join(path), "w")
   end
 
   def cap(task)
@@ -88,37 +96,37 @@ module TestApp
   def run(command)
     output = nil
     Dir.chdir(test_app_path) do
-      output = %x[#{command}]
+      output = `#{command}`
     end
-    [$?.success?, output]
+    [$CHILD_STATUS.success?, output]
   end
 
   def stage
-    'test'
+    "test"
   end
 
   def test_stage_path
-    test_app_path.join('config/deploy/test.rb')
+    test_app_path.join("config/deploy/test.rb")
   end
 
   def test_app_path
-    Pathname.new('/tmp/test_app')
+    Pathname.new("/tmp/test_app")
   end
 
   def deploy_to
-    Pathname.new('/home/vagrant/var/www/deploy')
+    Pathname.new("/home/vagrant/var/www/deploy")
   end
 
   def shared_path
-    deploy_to.join('shared')
+    deploy_to.join("shared")
   end
 
   def current_path
-    deploy_to.join('current')
+    deploy_to.join("current")
   end
 
   def releases_path
-    deploy_to.join('releases')
+    deploy_to.join("releases")
   end
 
   def release_path
@@ -130,19 +138,19 @@ module TestApp
   end
 
   def repo_path
-    deploy_to.join('repo')
+    deploy_to.join("repo")
   end
 
   def path_to_cap
-    File.expand_path('.')
+    File.expand_path(".")
   end
 
   def gemfile
-    test_app_path.join('Gemfile')
+    test_app_path.join("Gemfile")
   end
 
   def capfile
-    test_app_path.join('Capfile')
+    test_app_path.join("Capfile")
   end
 
   def current_user
@@ -150,7 +158,7 @@ module TestApp
   end
 
   def task_dir
-    test_app_path.join('lib/capistrano/tasks')
+    test_app_path.join("lib/capistrano/tasks")
   end
 
   def copy_task_to_test_app(source)
@@ -158,15 +166,15 @@ module TestApp
   end
 
   def config_path
-    test_app_path.join('config')
+    test_app_path.join("config")
   end
 
   def move_configuration_to_custom_location(location)
     prepend_to_capfile(
-      %{
+      <<-CONFIG
         set :stage_config_path, "app/config/deploy"
         set :deploy_config_path, "app/config/deploy.rb"
-      }
+      CONFIG
     )
 
     location = test_app_path.join(location)
@@ -174,4 +182,7 @@ module TestApp
     FileUtils.mv(config_path, location)
   end
 
+  def git_wrapper_path
+    "/tmp/git-ssh-my_app_name-#{stage}-#{current_user}.sh"
+  end
 end

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/capistrano.git



More information about the Pkg-ruby-extras-commits mailing list