lib/hoe/perforce.rb



require 'find'

# TODO: release requirements have been met
# TODO: passes tests? (with warning or a flag to skip)

##
# seattle.rb perforce projects are structured as:
#
# project_name/
#   dev/
#     History.txt
#     Manifest.txt
#     README.txt
#     Rakefile
#     bin/
#     lib/
#     test/
#     ...
#   1.0.0/...
#   1.0.1/...
#   ...
#
# Each release is a branch from project_name/dev to
# project_name/version. In perforce, branches can be explicit (created
# via a branch spec) or implicit (created by command-line). This
# structure can accommodate either but the code below is implicit.

module Hoe::Perforce

  ##
  # Files fore perforce to ignore. Probably not needed now that they
  # support a dot-ignore file. I have yet to use that so... yeah.

  attr_accessor :perforce_ignore

  ##
  # Initializes the perforce plugin.

  def initialize_perforce
    self.perforce_ignore = []
  end

  ##
  # Defines tasks for the perforce plugin.

  def define_perforce_tasks
    warn :define_perforce_tasks if $DEBUG

    desc "branch the project from dev to version dir"
    task :branch do
      original_dir = File.basename(Dir.pwd)

      Dir.chdir ".."

      target_dir = File.directory?(version) ? version : original_dir
      branching  = target_dir == original_dir && target_dir != version
      pkg = File.basename(Dir.pwd)

      begin
        p4_integrate original_dir, version if branching
        validate_manifest_file version
        p4_submit "Branching #{pkg} to version #{version}" if branching
      rescue => e
        warn e
        p4_revert version
        raise e
      end

      Dir.chdir version
    end

    task :prerelease => :branch

    task :postrelease => :announce do
      system 'rake clean'
    end

    desc "Generate historical flog/flay data for all releases"
    task :history do
      p4_history
    end
  end

  ##
  # perforce has an annoying "feature" that reads PWD and chdir's to
  # it. Without either mucking in ENV or forcing a different kind of
  # system call, it'll be in the original rake directory. I don't want
  # that for these recipes, so we tack on ";" in order to force the
  # right type of exec.

  def p4sh cmd
    sh "#{cmd};"
  end

  ##
  # Audit the manifest file against files on the file system.

  def validate_manifest_file manifest_dir
    manifest_file = "#{manifest_dir}/Manifest.txt"
    raise "#{manifest_file} does not exist" unless test ?f, manifest_file
    manifest = File.new(manifest_file).readlines.map { |x| x.strip }
    manifest -= perforce_ignore

    exclusions = with_config do |config, _|
      config["exclude"]
    end

    local_manifest = []

    Dir.chdir manifest_dir do
      system 'rake clean'
      Find.find '.' do |f|
        next if f =~ exclusions
        local_manifest << f.sub(/^\.\//, '') if File.file? f
      end
    end

    local_manifest -= perforce_ignore

    extra_files   = local_manifest - manifest
    missing_files = manifest - local_manifest

    msg = []

    unless extra_files.empty? then
      msg << "You have files that are not in your manifest"
      msg << "  #{extra_files.inspect}"
    end

    unless missing_files.empty? then
      msg << "You have files that are missing from your manifest"
      msg << "  #{missing_files.inspect}"
    end

    raise msg.join("\n") unless msg.empty?
  end

  ##
  # Revert the pending (release) directory.

  def p4_revert dir
    puts "reverting #{dir}"
    p4sh "p4 revert #{dir}/..."
  end

  ##
  # Branch a release from +from+ to +to+.

  def p4_integrate from, to
    opened = `p4 opened #{from}/... #{to}/... 2>&1`
    raise "You have files open" if opened =~ /\/\/[^#]+#\d+/
    p4sh "p4 integrate #{from}/... #{to}/..."
  end

  ##
  # Submit the current directory with a description message.

  def p4_submit description
    p4sh "p4 submit -d #{description.inspect} ..."
  end

  ##
  # Return all version directories (and dev).

  def p4_versions
    dirs = Dir["../[0-9]*"].sort_by { |s| Gem::Version.new(File.basename(s)) }
    dirs << "../dev"
    dirs.map { |d| File.basename d }
  end

  ##
  # Return the flog & flay history of all releases.

  def p4_history
    history p4_versions do |version|
      Dir.chdir "../#{version}" do
        flog_flay
      end
    end
  end
end