Deploying a Git subdirectory in Capistrano

Ruby on-RailsGitDeploymentCapistrano

Ruby on-Rails Problem Overview


My master branch layout is like this:

/ <-- top level

/client <-- desktop client source files

/server <-- Rails app

What I'd like to do is only pull down the /server directory in my deploy.rb, but I can't seem to find any way to do that. The /client directory is huge, so setting up a hook to copy /server to / won't work very well, it needs to only pull down the Rails app.

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

Without any dirty forking action but even dirtier !

In my config/deploy.rb :

set :deploy_subdir, "project/subdir"

Then I added this new strategy to my Capfile :

require 'capistrano/recipes/deploy/strategy/remote_cache'

class RemoteCacheSubdir < Capistrano::Deploy::Strategy::RemoteCache
  
  private
  
  def repository_cache_subdir
    if configuration[:deploy_subdir] then
      File.join(repository_cache, configuration[:deploy_subdir])
    else
      repository_cache
    end
  end
  
  def copy_repository_cache
    logger.trace "copying the cached version to #{configuration[:release_path]}"
    if copy_exclude.empty? 
      run "cp -RPp #{repository_cache_subdir} #{configuration[:release_path]} && #{mark}"
    else
      exclusions = copy_exclude.map { |e| "--exclude=\"#{e}\"" }.join(' ')
      run "rsync -lrpt #{exclusions} #{repository_cache_subdir}/* #{configuration[:release_path]} && #{mark}"
    end
  end
  
end


set :strategy, RemoteCacheSubdir.new(self)

Solution 2 - Ruby on-Rails

For Capistrano 3.0, I use the following:

In my Capfile:

# Define a new SCM strategy, so we can deploy only a subdirectory of our repo.
module RemoteCacheWithProjectRootStrategy
  def test
    test! " [ -f #{repo_path}/HEAD ] "
  end

  def check
    test! :git, :'ls-remote', repo_url
  end

  def clone
    git :clone, '--mirror', repo_url, repo_path
  end

  def update
    git :remote, :update
  end

  def release
	git :archive, fetch(:branch), fetch(:project_root), '| tar -x -C', release_path, "--strip=#{fetch(:project_root).count('/')+1}"
  end
end

And in my deploy.rb:

# Set up a strategy to deploy only a project directory (not the whole repo)
set :git_strategy, RemoteCacheWithProjectRootStrategy
set :project_root, 'relative/path/from/your/repo'

All the important code is in the strategy release method, which uses git archive to archive only a subdirectory of the repo, then uses the --strip argument to tar to extract the archive at the right level.

UPDATE

As of Capistrano 3.3.3, you can now use the :repo_tree configuration variable, which makes this answer obsolete. For example:

set :repo_url, 'https://example.com/your_repo.git'
set :repo_tree, 'relative/path/from/your/repo' # relative path to project root in repo

See http://capistranorb.com/documentation/getting-started/configuration.

Solution 3 - Ruby on-Rails

We're also doing this with Capistrano by cloning down the full repository, deleting the unused files and folders and move the desired folder up the hierarchy.

deploy.rb

set :repository,  "[email protected]:name/project.git"
set :branch, "master"
set :subdir, "server"

after "deploy:update_code", "deploy:checkout_subdir"

namespace :deploy do

    desc "Checkout subdirectory and delete all the other stuff"
    task :checkout_subdir do
        run "mv #{current_release}/#{subdir}/ /tmp && rm -rf #{current_release}/* && mv /tmp/#{subdir}/* #{current_release}"
    end

end

As long as the project doesn't get too big this works pretty good for us, but if you can, create an own repository for each component and group them together with git submodules.

Solution 4 - Ruby on-Rails

You can have two git repositories (client and server) and add them to a "super-project" (app). In this "super-project" you can add the two repositories as submodules (check this tutorial).

Another possible solution (a bit more dirty) is to have separate branches for client and server, and then you can pull from the 'server' branch.

Solution 5 - Ruby on-Rails

There is a solution. Grab crdlo's patch for capistrano and the capistrano source from github. Remove your existing capistrano gem, appy the patch, setup.rb install, and then you can use his very simple configuration line set :project, "mysubdirectory" to set a subdirectory.

The only gotcha is that apparently github doesn't "support the archive command" ... at least when he wrote it. I'm using my own private git repo over svn and it works fine, I haven't tried it with github but I imagine if enough people complain they'll add that feature.

Also see if you can get capistrano authors to add this feature into cap at the relevant bug.

Solution 6 - Ruby on-Rails

For Capistrano 3, based on @Thomas Fankhauser answer:

set :repository,  "[email protected]:name/project.git"
set :branch, "master"
set :subdir, "relative_path_to_my/subdir"


namespace :deploy do

  desc "Checkout subdirectory and delete all the other stuff"
  task :checkout_subdir do

    subdir = fetch(:subdir)
    subdir_last_folder  = File.basename(subdir)
    release_subdir_path = File.join(release_path, subdir)

    tmp_base_folder = File.join("/tmp", "capistrano_subdir_hack")
    tmp_destination = File.join(tmp_base_folder, subdir_last_folder)

    cmd = []
    # Settings for my-zsh
    # cmd << "unsetopt nomatch && setopt rmstarsilent" 
    # create temporary folder
    cmd << "mkdir -p #{tmp_base_folder}"  
    # delete previous temporary files                
    cmd << "rm -rf #{tmp_base_folder}/*"  
    # move subdir contents to tmp           
    cmd << "mv #{release_subdir_path}/ #{tmp_destination}"   
    # delete contents inside release      
    cmd << "rm -rf #{release_path}/*"   
    # move subdir contents to release             
    cmd << "mv #{tmp_destination}/* #{release_path}" 
    cmd = cmd.join(" && ")

    on roles(:app) do
      within release_path do
        execute cmd
      end
    end
  end

end

after "deploy:updating", "deploy:checkout_subdir"

Solution 7 - Ruby on-Rails

Unfortunately, git provides no way to do this. Instead, the 'git way' is to have two repositories -- client and server, and clone the one(s) you need.

Solution 8 - Ruby on-Rails

I created a snipped that works with Capistrano 3.x based in previous anwers and other information found in github:

# Usage: 
# 1. Drop this file into lib/capistrano/remote_cache_with_project_root_strategy.rb
# 2. Add the following to your Capfile:
#   require 'capistrano/git'
#   require './lib/capistrano/remote_cache_with_project_root_strategy'
# 3. Add the following to your config/deploy.rb
#    set :git_strategy, RemoteCacheWithProjectRootStrategy
#    set :project_root, 'subdir/path'
 
# Define a new SCM strategy, so we can deploy only a subdirectory of our repo.
module RemoteCacheWithProjectRootStrategy
  include Capistrano::Git::DefaultStrategy
  def test
    test! " [ -f #{repo_path}/HEAD ] "
  end
 
  def check
    test! :git, :'ls-remote -h', repo_url
  end
 
  def clone
    git :clone, '--mirror', repo_url, repo_path
  end
 
  def update
    git :remote, :update
  end
 
  def release
    git :archive, fetch(:branch), fetch(:project_root), '| tar -x -C', release_path, "--strip=#{fetch(:project_root).count('/')+1}"
  end
end

It's also available as a Gist on Github.

Solution 9 - Ruby on-Rails

dont know if anyone is still interested on this. but just letting you guys if anyone is looking for an answer. now we can use: :repo_tree

https://capistranorb.com/documentation/getting-started/configuration/

Solution 10 - Ruby on-Rails

Looks like it's also not working with codebasehq.com so I ended up making capistrano tasks that cleans the mess :-) Maybe there's actually a less hacky way of doing this by overriding some capistrano tasks...

Solution 11 - Ruby on-Rails

This has been working for me for a few hours.

# Capistrano assumes that the repository root is Rails.root
namespace :uploads do
  # We have the Rails application in a subdirectory rails_app
  # Capistrano doesn't provide an elegant way to deal with that
  # for the git case. (For subversion it is straightforward.)
  task :mv_rails_app_dir, :roles => :app do
    run "mv #{release_path}/rails_app/* #{release_path}/ "
  end
end

before 'deploy:finalize_update', 'uploads:mv_rails_app_dir'

You might declare a variable for the directory (here rails_app).

Let's see how robust it is. Using "before" is pretty weak.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJarin UdomView Question on Stackoverflow
Solution 1 - Ruby on-RailsthodgView Answer on Stackoverflow
Solution 2 - Ruby on-RailsMr FriendlyView Answer on Stackoverflow
Solution 3 - Ruby on-RailsThomas FankhauserView Answer on Stackoverflow
Solution 4 - Ruby on-RailsFederico BuilesView Answer on Stackoverflow
Solution 5 - Ruby on-RailsSimon WoodsideView Answer on Stackoverflow
Solution 6 - Ruby on-RailsfsainzView Answer on Stackoverflow
Solution 7 - Ruby on-RailsSilas SniderView Answer on Stackoverflow
Solution 8 - Ruby on-RailsJAlbertoView Answer on Stackoverflow
Solution 9 - Ruby on-RailsfFaceView Answer on Stackoverflow
Solution 10 - Ruby on-RailsStuFF mcView Answer on Stackoverflow
Solution 11 - Ruby on-RailsStephan WehnerView Answer on Stackoverflow