diff --git a/CHANGELOG b/CHANGELOG index 44812274a..2f290ba02 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Git SCM module [Garry Dolley, Geoffrey Grosenbach, Scott Chacon] + * Use the --password switch for subversion by default, but add :scm_prefer_prompt variable (defaults to false) [Jamis Buck] diff --git a/lib/capistrano/recipes/deploy/scm/git.rb b/lib/capistrano/recipes/deploy/scm/git.rb new file mode 100644 index 000000000..d819ce879 --- /dev/null +++ b/lib/capistrano/recipes/deploy/scm/git.rb @@ -0,0 +1,191 @@ +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. + # + # REQUIREMENTS + # ------------ + # + # Git is required to be installed on your remote machine(s), because a + # clone and checkout is done to get the code up there. This is the way + # I prefer to deploy; there is no alternative to this, so :deploy_via + # is ignored. + # + # 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, "origin/master" + # + # Otherwise, HEAD is assumed. I strongly suggest you set this. HEAD is + # not always the best assumption. + # + # 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 + # + # AUTHORS + # ------- + # + # Garry Dolley http://scie.nti.st + # Contributions by Geoffrey Grosenbach http://topfunky.com + # and Scott Chacon http://jointheconversation.org + + 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 + configuration[:branch] || 'HEAD' + end + + # Performs a clone on the remote machine, then checkout on the branch + # you want to deploy. + def checkout(revision, destination) + git = command + + branch = head + + fail "No branch specified, use for example 'set :branch, \"origin/master\"' in your deploy.rb" unless branch + + if depth = configuration[:git_shallow_clone] + execute = "#{git} clone --depth #{depth} #{configuration[:repository]} #{destination} && " + else + execute = "#{git} clone #{configuration[:repository]} #{destination} && " + end + + execute += "cd #{destination} && #{git} checkout -b deploy #{branch}" + + execute + end + + # Merges the changes to 'head' since the last fetch, for remote_cache + # deployment strategy + def sync(revision, destination) + execute = "cd #{destination} && git fetch origin && " + + if head == 'HEAD' + execute += "git merge origin/HEAD" + else + execute += "git merge #{head}" + end + + execute + 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) + from << "..#{to}" if to + scm :log, from + 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) + yield(scm('rev-parse', revision)).chomp + end + + def command + # For backwards compatibility with 1.x version of this module + configuration[: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) + logger.info "[#{stream}] #{text}" + case text + when /\bpassword.*:/i + # git is prompting for a password + unless pass = configuration[: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 = configuration[: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 + end + end + end +end diff --git a/test/deploy/scm/git_test.rb b/test/deploy/scm/git_test.rb new file mode 100644 index 000000000..efda9b56c --- /dev/null +++ b/test/deploy/scm/git_test.rb @@ -0,0 +1,112 @@ +require "#{File.dirname(__FILE__)}/../../utils" +require 'capistrano/recipes/deploy/scm/git' + +class DeploySCMGitTest < Test::Unit::TestCase + class TestSCM < Capistrano::Deploy::SCM::Git + default_command "git" + end + + def setup + @config = { } + def @config.exists?(name); key?(name); end + + @source = TestSCM.new(@config) + end + + def test_head + assert_equal "HEAD", @source.head + @config[:branch] = "master" + assert_equal "master", @source.head + end + + def test_checkout + @config[:repository] = "git@somehost.com:project.git" + dest = "/var/www" + assert_equal "git clone git@somehost.com:project.git /var/www && cd /var/www && git checkout -b deploy HEAD", @source.checkout('Not used', dest) + + # With branch + @config[:branch] = "origin/foo" + assert_equal "git clone git@somehost.com:project.git /var/www && cd /var/www && git checkout -b deploy origin/foo", @source.checkout('Not used', dest) + end + + def test_diff + assert_equal "git diff master", @source.diff('master') + assert_equal "git diff master..branch", @source.diff('master', 'branch') + end + + def test_log + assert_equal "git log master", @source.log('master') + assert_equal "git log master..branch", @source.log('master', 'branch') + end + + def test_query_revision + assert_equal "git rev-parse HEAD", @source.query_revision('HEAD') { |o| o } + end + + def test_command_should_be_backwards_compatible + # 1.x version of this module used ":git", not ":scm_command" + @config[:git] = "/srv/bin/git" + assert_equal "/srv/bin/git", @source.command + end + + def test_sync + dest = "/var/www" + assert_equal "cd #{dest} && git fetch origin && git merge origin/HEAD", @source.sync('Not used', dest) + + # With branch + @config[:branch] = "origin/foo" + assert_equal "cd #{dest} && git fetch origin && git merge origin/foo", @source.sync('Not used', dest) + end + + def test_shallow_clone + @config[:repository] = "git@somehost.com:project.git" + @config[:git_shallow_clone] = 1 + dest = "/var/www" + assert_equal "git clone --depth 1 git@somehost.com:project.git /var/www && cd /var/www && git checkout -b deploy HEAD", @source.checkout('Not used', dest) + + # With branch + @config[:branch] = "origin/foo" + assert_equal "git clone --depth 1 git@somehost.com:project.git /var/www && cd /var/www && git checkout -b deploy origin/foo", @source.checkout('Not used', dest) + end + + # Tests from base_test.rb, makin' sure we didn't break anything up there! + def test_command_should_default_to_default_command + assert_equal "git", @source.command + @source.local { assert_equal "git", @source.command } + end + + def test_command_should_use_scm_command_if_available + @config[:scm_command] = "/opt/local/bin/git" + assert_equal "/opt/local/bin/git", @source.command + end + + def test_command_should_use_scm_command_in_local_mode_if_local_scm_command_not_set + @config[:scm_command] = "/opt/local/bin/git" + @source.local { assert_equal "/opt/local/bin/git", @source.command } + end + + def test_command_should_use_local_scm_command_in_local_mode_if_local_scm_command_is_set + @config[:scm_command] = "/opt/local/bin/git" + @config[:local_scm_command] = "/usr/local/bin/git" + assert_equal "/opt/local/bin/git", @source.command + @source.local { assert_equal "/usr/local/bin/git", @source.command } + end + + def test_command_should_use_default_if_scm_command_is_default + @config[:scm_command] = :default + assert_equal "git", @source.command + end + + def test_command_should_use_default_in_local_mode_if_local_scm_command_is_default + @config[:scm_command] = "/foo/bar/git" + @config[:local_scm_command] = :default + @source.local { assert_equal "git", @source.command } + end + + def test_local_mode_proxy_should_treat_messages_as_being_in_local_mode + @config[:scm_command] = "/foo/bar/git" + @config[:local_scm_command] = :default + assert_equal "git", @source.local.command + assert_equal "/foo/bar/git", @source.command + end +end