require 'capistrano/recipes/deploy/scm/base'
module Capistrano
module Deploy
module SCM
# An SCM module for using Git as your source control tool with Capistrano
# 2.0. If you are using Capistrano 1.x, use this plugin instead:
#
# http://scie.nti.st/2007/3/16/capistrano-with-git-shared-repository
#
# Assumes you are using a shared Git repository.
#
# Parts of this plugin borrowed from Scott Chacon's version, which I
# found on the Capistrano mailing list but failed to be able to get
# working.
#
# FEATURES:
#
# * Very simple, only requiring 2 lines in your deploy.rb.
# * Can deploy different branches, tags, or any SHA1 easily.
# * Supports prompting for password / passphrase upon checkout.
# (I am amazed at how some plugins don't do this)
# * Supports :scm_command, :scm_password, :scm_passphrase Capistrano
# directives.
#
# CONFIGURATION
# -------------
#
# Use this plugin by adding the following line in your config/deploy.rb:
#
# set :scm, :git
#
# Set :repository to the path of your Git repo:
#
# set :repository, "someuser@somehost:/home/myproject"
#
# The above two options are required to be set, the ones below are
# optional.
#
# You may set :branch, which is the reference to the branch, tag,
# or any SHA1 you are deploying, for example:
#
# set :branch, "master"
#
# Otherwise, HEAD is assumed. I strongly suggest you set this. HEAD is
# not always the best assumption.
#
# You may also set :remote, which will be used as a name for remote
# tracking of repositories. This option is intended for use with the
# :remote_cache strategy in a distributed git environment.
#
# For example in the projects config/deploy.rb:
#
# set :repository, "#{scm_user}@somehost:~/projects/project.git"
# set :remote, "#{scm_user}"
#
# Then each person with deploy priveledges can add the following to their
# local ~/.caprc file:
#
# set :scm_user, 'someuser'
#
# Now any time a person deploys the project, their repository will be
# setup as a remote git repository within the cached repository.
#
# The :scm_command configuration variable, if specified, will
# be used as the full path to the git executable on the *remote* machine:
#
# set :scm_command, "/opt/local/bin/git"
#
# For compatibility with deploy scripts that may have used the 1.x
# version of this plugin before upgrading, :git is still
# recognized as an alias for :scm_command.
#
# Set :scm_password to the password needed to clone your repo
# if you don't have password-less (public key) entry:
#
# set :scm_password, "my_secret'
#
# Otherwise, you will be prompted for a password.
#
# :scm_passphrase is also supported.
#
# The remote cache strategy is also supported.
#
# set :repository_cache, "git_master"
# set :deploy_via, :remote_cache
#
# For faster clone, you can also use shallow cloning. This will set the
# '--depth' flag using the depth specified. This *cannot* be used
# together with the :remote_cache strategy
#
# set :git_shallow_clone, 1
#
# For those that don't like to leave your entire repository on
# your production server you can:
#
# set :deploy_via, :export
#
# To deploy from a local repository:
#
# set :repository, "file://."
# set :deploy_via, :copy
#
# AUTHORS
# -------
#
# Garry Dolley http://scie.nti.st
# Contributions by Geoffrey Grosenbach http://topfunky.com
# Scott Chacon http://jointheconversation.org
# Alex Arnell http://twologic.com
# and Phillip Goldenburg
class Git < Base
# Sets the default command name for this SCM on your *local* machine.
# Users may override this by setting the :scm_command variable.
default_command "git"
# When referencing "head", use the branch we want to deploy or, by
# default, Git's reference of HEAD (the latest changeset in the default
# branch, usually called "master").
def head
variable(:branch) || 'HEAD'
end
def origin
variable(:remote) || 'origin'
end
# Performs a clone on the remote machine, then checkout on the branch
# you want to deploy.
def checkout(revision, destination)
git = command
remote = origin
args = []
args << "-o #{remote}" unless remote == 'origin'
if depth = variable(:git_shallow_clone)
args << "--depth #{depth}"
end
execute = []
execute << "#{git} clone #{verbose} #{args.join(' ')} #{variable(:repository)} #{destination}"
# checkout into a local branch rather than a detached HEAD
execute << "cd #{destination} && #{git} checkout #{verbose} -b deploy #{revision}"
if variable(:git_enable_submodules)
execute << "#{git} submodule #{verbose} init"
execute << "#{git} submodule #{verbose} sync"
execute << "#{git} submodule #{verbose} update --init --recursive"
end
execute.join(" && ")
end
# An expensive export. Performs a checkout as above, then
# removes the repo.
def export(revision, destination)
checkout(revision, destination) << " && rm -Rf #{destination}/.git"
end
# Merges the changes to 'head' since the last fetch, for remote_cache
# deployment strategy
def sync(revision, destination)
git = command
remote = origin
execute = []
execute << "cd #{destination}"
# Use git-config to setup a remote tracking branches. Could use
# git-remote but it complains when a remote of the same name already
# exists, git-config will just silenty overwrite the setting every
# time. This could cause wierd-ness in the remote cache if the url
# changes between calls, but as long as the repositories are all
# based from each other it should still work fine.
if remote != 'origin'
execute << "#{git} config remote.#{remote}.url #{variable(:repository)}"
execute << "#{git} config remote.#{remote}.fetch +refs/heads/*:refs/remotes/#{remote}/*"
end
# since we're in a local branch already, just reset to specified revision rather than merge
execute << "#{git} fetch #{verbose} #{remote} && #{git} fetch --tags #{verbose} #{remote} && #{git} reset #{verbose} --hard #{revision}"
if variable(:git_enable_submodules)
execute << "#{git} submodule #{verbose} init"
execute << "for mod in `#{git} submodule status | awk '{ print $2 }'`; do #{git} config -f .git/config submodule.${mod}.url `#{git} config -f .gitmodules --get submodule.${mod}.url` && echo Synced $mod; done"
execute << "#{git} submodule #{verbose} sync"
execute << "#{git} submodule #{verbose} update --init --recursive"
end
# Make sure there's nothing else lying around in the repository (for
# example, a submodule that has subsequently been removed).
execute << "#{git} clean #{verbose} -d -x -f"
execute.join(" && ")
end
# Returns a string of diffs between two revisions
def diff(from, to=nil)
from << "..#{to}" if to
scm :diff, from
end
# Returns a log of changes between the two revisions (inclusive).
def log(from, to=nil)
scm :log, "#{from}..#{to}"
end
# Getting the actual commit id, in case we were passed a tag
# or partial sha or something - it will return the sha if you pass a sha, too
def query_revision(revision)
raise ArgumentError, "Deploying remote branches is no longer supported. Specify the remote branch as a local branch for the git repository you're deploying from (ie: '#{revision.gsub('origin/', '')}' rather than '#{revision}')." if revision =~ /^origin\//
return revision if revision =~ /^[0-9a-f]{40}$/
command = scm('ls-remote', repository, revision)
result = yield(command)
revdata = result.split(/[\t\n]/)
newrev = nil
revdata.each_slice(2) do |refs|
rev, ref = *refs
if ref.sub(/refs\/.*?\//, '').strip == revision.to_s
newrev = rev
break
end
end
raise "Unable to resolve revision for '#{revision}' on repository '#{repository}'." unless newrev =~ /^[0-9a-f]{40}$/
return newrev
end
def command
# For backwards compatibility with 1.x version of this module
variable(:git) || super
end
# Determines what the response should be for a particular bit of text
# from the SCM. Password prompts, connection requests, passphrases,
# etc. are handled here.
def handle_data(state, stream, text)
host = state[:channel][:host]
logger.info "[#{host} :: #{stream}] #{text}"
case text
when /\bpassword.*:/i
# git is prompting for a password
unless pass = variable(:scm_password)
pass = Capistrano::CLI.password_prompt
end
"#{pass}\n"
when %r{\(yes/no\)}
# git is asking whether or not to connect
"yes\n"
when /passphrase/i
# git is asking for the passphrase for the user's key
unless pass = variable(:scm_passphrase)
pass = Capistrano::CLI.password_prompt
end
"#{pass}\n"
when /accept \(t\)emporarily/
# git is asking whether to accept the certificate
"t\n"
end
end
private
# If verbose output is requested, return nil, otherwise return the
# command-line switch for "quiet" ("-q").
def verbose
variable(:scm_verbose) ? nil : "-q"
end
end
end
end
end