Skip to content


Subversion checkout URL

You can clone with
Download ZIP


Group finalize_update shell commands into one command #282

merged 1 commit into from

3 participants


Individual runs had an overhead of ~500ms, so this saves me about 4 seconds per deploy.


@ndbroadbent Looks good! Would you mind hardening the shell calls with the Shellwords#shellescape mixin and using -- to separate optional arguments from positional ones? I think that this PR should be the point at which we turn over a new leaf with regards to shell coding policy.


Ok, how about that? I think that seems like a good policy.


@ndbroadbent Great work, and getting there! Some more nitpicks:

  1. Don't shell escape the directory string so soon. I know it's meant for conciseness; however, escaping too soon might throw off file and path processing routines (unlikely, but best be on the safe side).
  2. Shell escape latest_release.
  3. chmod and touch both take -- separators. Strange but true. There can be one after the -R.

Thanks for your contribution, and expect to hear from me on another thread.


Ah, I see, I was only escaping things that the user can configure in their deploy.rb, I didn't think it was important to escape things latest_release, and thus I didn't think the chmod command needed a -- either. Also, I don't think touch needs a --, since we are 100% in control of the stamp variable. But ok, I'll make all those changes :)


@ndbroadbent I don't know how enforceable this policy will be in the future -- just trying the all-out strategy for now. Thanks for bearing with me.


Sure, no problem! Ok, have made those changes


@ndbroadbent Do you think it would be better to move the require "shellwords" into the deploy.rb file?


Right, that does make more sense! The intention was to go for a 'global' require, but yeah, deploy.rb will also be loaded every time. Pushed

@carsomyr carsomyr merged commit 483d253 into capistrano:master

1 check passed

Details default The Travis build passed

Merged, thanks!


This actually breaks deployments, if your release path is relative to the users home (e.g. set :deploy_to, "~/releases"), because ~ gets escaped to \\~. It is also incomplete, because e.g. shared_path is not escaped.

Found this issue to be covered in #300.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Sep 30, 2012
  1. @ndbroadbent

    Group finalize_update shell commands into one command, harden shell c…

    ndbroadbent authored
    …alls with #shellescape, and separate arguments with --
This page is out of date. Refresh to see the latest.
Showing with 15 additions and 8 deletions.
  1. +15 −8 lib/capistrano/recipes/deploy.rb
23 lib/capistrano/recipes/deploy.rb
@@ -1,5 +1,6 @@
require 'benchmark'
require 'yaml'
+require 'shellwords'
require 'capistrano/recipes/deploy/scm'
require 'capistrano/recipes/deploy/strategy'
@@ -240,23 +241,29 @@ def try_runner(*args)
using the :public_children variable.
task :finalize_update, :except => { :no_release => true } do
- run "chmod -R g+w #{latest_release}" if fetch(:group_writable, true)
+ escaped_release = latest_release.to_s.shellescape
+ commands = []
+ commands << "chmod -R -- g+w #{escaped_release}" if fetch(:group_writable, true)
# mkdir -p is making sure that the directories are there for some SCM's that don't
# save empty folders
- do |d|
- if (d.rindex('/')) then
- run "rm -rf #{latest_release}/#{d} && mkdir -p #{latest_release}/#{d.slice(0..(d.rindex('/')))}"
+ do |dir|
+ d = dir.shellescape
+ if (dir.rindex('/')) then
+ commands += ["rm -rf -- #{escaped_release}/#{d}",
+ "mkdir -p -- #{escaped_release}/#{dir.slice(0..(dir.rindex('/'))).shellescape}"]
- run "rm -rf #{latest_release}/#{d}"
+ commands << "rm -rf -- #{escaped_release}/#{d}"
- run "ln -s #{shared_path}/#{d.split('/').last} #{latest_release}/#{d}"
+ commands << "ln -s -- #{shared_path}/#{dir.split('/').last.shellescape} #{escaped_release}/#{d}"
+ run commands.join(' && ') if commands.any?
if fetch(:normalize_asset_timestamps, true)
stamp ="%Y%m%d%H%M.%S")
- asset_paths = fetch(:public_children, %w(images stylesheets javascripts)).map { |p| "#{latest_release}/public/#{p}" }
- run("find #{asset_paths.join(" ")} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" }) if asset_paths.any?
+ asset_paths = fetch(:public_children, %w(images stylesheets javascripts)).map { |p| "#{escaped_release}/public/#{p}" }
+ run("find #{asset_paths.join(" ").shellescape} -exec touch -t -- #{stamp} {} ';'; true", :env => { "TZ" => "UTC" }) if asset_paths.any?
Something went wrong with that request. Please try again.