#!/usr/bin/env ruby
#
# Managed by Puppet
#

require 'fileutils'
require 'etc'

$stdout.sync = true
$stderr.sync = true

# Set this to where you want to keep your environments
ENVIRONMENT_BASEDIR = "<%= scope.lookupvar("puppet::server::config::primary_envs_dir") %>"

# post-receive hooks set GIT_DIR to the current repository. If you want to
# clone from a non-local repository, set this to the URL of the repository,
# such as git@git.host:puppet.git
SOURCE_REPOSITORY = File.expand_path(ENV['GIT_DIR'])

# Mapping of branches to directories.  In many cases, the server branch is
# checked out to the 'development' environment.
BRANCH_MAP = {
<% if @git_branch_map -%>
<%   @git_branch_map.sort.each do |g,p| -%>
  "<%= g %>" => "<%= p %>",
<%   end -%>
<% end -%>
}

# The git_dir environment variable will override the --git-dir, so we remove it
# to allow us to create new directories cleanly.
ENV.delete('GIT_DIR')

# Ensure that we have the underlying directories, otherwise the later commands
# may fail in somewhat cryptic manners.
unless File.directory? ENVIRONMENT_BASEDIR
  puts %Q{#{ENVIRONMENT_BASEDIR} does not exist, cannot create environment directories.}
  exit 1
end

# If we're running as root we change our UID to the owner of this file.
file_uid = File.stat($0).uid
if file_uid != Process.uid and Process.uid == 0
  Process::UID.change_privilege(file_uid)
  # Set LOGNAME and HOME directories to file-owning user's values
  # so git can read ~/.config/git/attributes (for example) without error
  file_pwuid = Etc::getpwuid(file_uid)
  ENV.store('LOGNAME',file_pwuid.name)
  ENV.store('HOME',file_pwuid.dir)
end

# Run a command, return its output and abort if it fails
def do_cmd(cmd)
   ret = %x{#{cmd}}
   if $?.exitstatus != 0
     puts("'#{cmd}' failed. Giving up.")
     exit 1
   end
   ret
end

# You can push multiple refspecs at once, like 'git push origin branch1 branch2',
# so we need to handle each one.
$stdin.each_line do |line|
  oldrev, newrev, refname = line.split(" ")

  # Determine the branch name from the refspec we're received, which is in the
  # format refs/heads/<branch>, and make sure that it doesn't have any possibly
  # dangerous characters
  branchname = refname.sub(%r{^refs/heads/(.*$)}) { $1 }
  if branchname =~ /[\W]/
    puts %Q{Branch "#{branchname}" contains non-word characters, ignoring it.}
    next
  end

  if BRANCH_MAP[branchname] != nil
    environment_name = BRANCH_MAP[branchname]
    environment_path = "#{ENVIRONMENT_BASEDIR}/#{BRANCH_MAP[branchname]}"
  else
    environment_name = branchname
    environment_path = "#{ENVIRONMENT_BASEDIR}/#{branchname}"
  end

  if newrev =~ /^0+$/
    # We've received a push with a null revision, something like 000000000000,
    # which means that we should delete the given branch.
    puts "Deleting existing environment #{environment_name}"
    if File.directory? environment_path
      FileUtils.rm_rf environment_path, :secure => true
    end
  else
    # We have been given a branch that needs to be created or updated. If the
    # environment exists, update it. Else, create it.

    if File.directory? environment_path
      # Update an existing environment. We do a fetch and then reset in the
      # case that someone did a force push to a branch.

      puts "Updating existing environment #{environment_name}"
      Dir.chdir environment_path
      do_cmd("git fetch --all")
      do_cmd("git reset --hard 'origin/#{branchname}'")
      if File.exists? "#{environment_path}/.gitmodules"
        # ensure that we remove deleted sub modules too
        do_cmd("git status --short").split("\n").each do |file|
          # ?? old_submodule/
          if file =~ /\s*\?{2}\s*(\S*)/
            puts "Found a few unknown files.. deleting #{$1}"
            FileUtils.rm_rf $1, :secure => true
          end
        end
        do_cmd("git submodule sync")
        do_cmd("git submodule update --init --recursive")
      end
    else
      # Instantiate a new environment from the current repository.

      puts "Creating new environment #{environment_name}"
      do_cmd("git clone --recursive #{SOURCE_REPOSITORY} #{environment_path} --branch #{branchname}")
    end
  end
end
