require 'capistrano/recipes/deploy/scm/base'

module Capistrano
  module Deploy
    module SCM

      # Implements the Capistrano SCM interface for the Git revision
      # control system (http://http://git.or.cz/)
      # Works for the 1.5 clients - earlier versions may not work
      #
      # In addition to the normal SCM switches, you must set the 'git_dir', which
      # tells the system where the git clone is.  You'll want to run 'git clone' on
      # the servers, or locally, if you're using ':deploy_via, :copy'
      #
      #   set :git_dir, '/path/to/git_repo'
      #
      # if you have a local clone of the repo, and it is not the same as where you are
      # checking it out remotely, set this var, so we can use it to run rev-parse commands on
      # to get the current sha for 'HEAD' and such
      # 
      #   set :local_git_dir, '/path/to/git_repo'
      # 
      # You may also specify a subdirectory of the git repo as your working directory
      #
      #   set :git_project, 'project'
      #
      # Also, if you're using git < 1.5, you have to use 'git tar-tree' rather than 'git archive'
      # so set the following flag in your deploy.rb
      #
      #   set :git_use_tar_tree, true
      #
      class Git < Base
        # Sets the default command name for this SCM. Users may override this
        # by setting the :scm_command variable.
        default_command "git"

        # Git understands 'HEAD' to refer to the latest revision in the
        # repository.
        def head
          'HEAD'
        end

        # Returns the command that will sync the given revision to the given
        # destination directory. Git has a fixed destination so
        # the files must be copied from there to their intended resting place.
        def checkout(revision, destination)
          git_sync revision, destination
        end

        # Returns the command that will sync the given revision to the given
        # destination directory. Git has a fixed destination so
        # the files must be copied from there to their intended resting place.
        def export(revision, destination)
          git_sync(revision, destination)
        end

        # Returns the command that will sync the given revision to the given
        # destination directory. Git has a fixed destination so
        # the files must be copied from there to their intended resting place.
        def sync(revision, destination)
          git_sync(revision, destination)
        end

        # returns a string of diffs between two revisions
        def diff(from, to=nil)
          system(git_fetch)
          from << "..#{to}" if to
          scm :diff, from
        end

        # Returns a log of changes between the two revisions (inclusive).
        def log(from, to=nil)
          system(git_fetch)
          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)
          system(git_fetch)
          `#{scm('rev-parse', revision)}`.chomp
        end

        # Running the inital clone - this only needs to happen once, 
        # probably during a server setup.  After that it should be all fetches
        def clone
          scm :clone, '--bare', repository, variable(:git_dir)
        end

        def scm(*args)
          super("--git-dir=#{variable(:git_dir)}", *args)
        end

        private

          def git_sync(revision, destination)
            cmds = [git_fetch]

            if gs = variable(:git_project)  # if we're looking for a sub-directory
              tree = revision + ':' + gs 
            else
              tree = revision
            end

            cmds << [scm(git_archive_tar, tree, " | (mkdir -p #{destination} && cd #{destination} && tar xf -)")]
            cmds.join(' && ')
          end

          # for backwards compatibility with git < 1.5
          def git_archive_tar
            if variable(:git_use_tar_tree)
              'tar-tree'
            else
              'archive --format=tar'
            end
          end

          def git_fetch
            [scm(:fetch, repository), scm('update-ref', 'HEAD', 'FETCH_HEAD')].join(' && ')
          end
      end

    end
  end
end

