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