class Releasinator::Validator

Public Class Methods

new(releasinator_config) click to toggle source
# File lib/validator.rb, line 49
def initialize(releasinator_config)
  @releasinator_config = releasinator_config
end

Public Instance Methods

line_match_in_file?(contains_string, filename) click to toggle source
# File lib/validator.rb, line 202
def line_match_in_file?(contains_string, filename)
  File.open("#{filename}", "r") do |f|
    f.each_line do |line|
      if line.match /^#{Regexp.escape(contains_string)}$/
        Printer.success("#{filename} contains #{contains_string}")
        return true
      end
    end
  end
  false
end
validate_base_dir() click to toggle source
# File lib/validator.rb, line 260
def validate_base_dir()
  if !File.exist? @releasinator_config.base_dir
    Printer.fail("Directory specified by base_docs_dir '#{@releasinator_config.base_dir}' not found.  Please fix the config, or add this directory.")
    abort()
  end
end
validate_branches(version) click to toggle source
# File lib/validator.rb, line 349
def validate_branches(version)
  GitUtil.fetch()
  current_branch = GitUtil.get_current_branch()

  if @releasinator_config.use_git_flow()
    expected_release_branch = "release/#{version}"

    unless current_branch == expected_release_branch || current_branch == "develop"
      Printer.fail("git flow expects the current branch to be either 'develop' or 'release/#{version}'.  Current branch is '#{current_branch}'")
      abort()
    end

    # validate that develop is an ancestor of release branch.  Warn if not, but allow user to proceed, as this may be desired for maintenance releases.
    if current_branch == expected_release_branch
      root_branch = "develop"
      if !GitUtil.is_ancestor?(root_branch, current_branch)
        Printer.check_proceed("#{current_branch} is missing commits from #{root_branch}.  Are you sure you want to continue?", "Please rebase #{current_branch} to include the latest from #{root_branch}.")
      end
    end

    # validate that master is up to date, because git flow requires this.
    validate_local_matches_remote("master")
  else
    unless current_branch == "master"
      Printer.fail("non-git flow expects releases to come from the master branch.  Current branch is '#{current_branch}'")
      abort()
    end
  end

  validate_local_matches_remote(current_branch)
end
validate_changelog(search_ignore_path=nil) click to toggle source
# File lib/validator.rb, line 105
def validate_changelog(search_ignore_path=nil)
  validate_base_dir
  validate_exist(@releasinator_config.base_dir, "CHANGELOG.md", search_ignore_path, ["release_notes.md"])

  changelog_contents = get_changelog_contents
  Changelog::Validator.new(@releasinator_config).validate_changelog_contents(changelog_contents)
end
validate_clean_git() click to toggle source
# File lib/validator.rb, line 267
def validate_clean_git
  untracked_files = GitUtil.untracked_files
  diff = GitUtil.diff
  diff_cached = GitUtil.cached

  if '' != untracked_files
    puts untracked_files.red if @releasinator_config[:verbose]
    error = true
    Printer.fail("Untracked files found.")
  else
    Printer.success("No untracked files found.")
  end

  if '' != diff
    puts diff.red if @releasinator_config[:verbose]
    error = true
    Printer.fail("Unstaged changes found.")
  else
    Printer.success("No unstaged changes found.")
  end

  if '' != diff_cached
    puts diff_cached.red if @releasinator_config[:verbose]
    error = true
    Printer.fail("Uncommitted changes found.")
  else
    Printer.success("No uncommitted changes found.")
  end

  abort() if error
end
validate_config() click to toggle source
# File lib/validator.rb, line 147
def validate_config()
  validate_required_configatron_key(:product_name)
  validate_required_configatron_key(:prerelease_checklist_items)
  validate_required_configatron_key(:build_method)
  validate_required_configatron_key(:publish_to_package_manager_method)
  validate_required_configatron_key(:wait_for_package_manager_method)
  validate_required_configatron_key(:release_to_github)

  validate_method_convention(@releasinator_config)

  if @releasinator_config.has_key? :downstream_repos
    @releasinator_config[:downstream_repos].each do |downsteam_repo|
      validate_is_type downsteam_repo, DownstreamRepo

      validate_method_convention(downsteam_repo.options)
    end
  end
end
validate_eof_newlines() click to toggle source
# File lib/validator.rb, line 68
def validate_eof_newlines
  all_git_files = GitUtil.all_files.split

  important_git_text_files = all_git_files.select{ |filename|
    TEXT_FILE_EXTENSIONS.any? { |extension|
      filename.end_with?(extension)
    }
  }

  important_git_text_files.each do |filename|
    CommandProcessor.command("tail -c1 #{filename} | read -r _ || echo >> #{filename}")
  end
end
validate_exist(dir, expected_file_name, search_ignore_path=nil, alternate_names=[]) click to toggle source
# File lib/validator.rb, line 230
def validate_exist(dir, expected_file_name, search_ignore_path=nil, alternate_names=[])
  if !File.exist? dir
    Printer.fail("Directory #{dir} not found.")
    abort()
  end
  
  Dir.chdir(dir) do
    if !GitUtil.exist?(expected_file_name)
      puts "#{dir}/#{expected_file_name} not found using a case sensitive search within git, searching for similar files...".yellow
    
      # search for files that are somewhat similar to the file being searched, ignoring case
      filename_prefix = expected_file_name[0,5]
      similar_files = CommandProcessor.command("find . -type f -not -path \"./#{search_ignore_path}/*\" -iname '#{filename_prefix}*'| sed 's|./||'").strip
      num_similar_files = similar_files.split.count
      puts similar_files
      if num_similar_files == 1
        Printer.check_proceed("Found a single similar file: #{similar_files}.  Do you want to rename this to the expected #{expected_file_name}?","Please commit #{dir}/#{expected_file_name}")
        rename_file(similar_files, expected_file_name)
      elsif num_similar_files > 1
        Printer.fail("Found more than 1 file similar to #{expected_file_name}.  Please rename one, and optionally remove the others to not confuse users.")
        abort()
      elsif !rename_alternate_name(expected_file_name, alternate_names)
        Printer.fail("Please commit #{dir}/#{expected_file_name}.")
        abort()
      end
    end
    Printer.success("#{dir}/#{expected_file_name} found.")
  end
end
validate_git_version() click to toggle source
# File lib/validator.rb, line 91
def validate_git_version
  version_output = CommandProcessor.command("git version")
  # version where the parallel git fetch features were added
  expected_git_version = "2.8.0"
  actual_git_version = version_output.split[2]

  if Gem::Version.new(expected_git_version) > Gem::Version.new(actual_git_version)
    Printer.fail("Actual git version " + actual_git_version.bold + " is smaller than expected git version " + expected_git_version.bold)
    abort()
  else
    Printer.success("Git version " + actual_git_version.bold + " found, and is higher than or equal to expected git version " + expected_git_version.bold)
  end
end
validate_github_permissions(repo_url) click to toggle source
# File lib/validator.rb, line 166
def validate_github_permissions(repo_url)
  github_repo = GitHubRepo.new(repo_url)
  github_client = github_repo.client

  begin
    # get the list of collaborators.
    puts "Checking collaborators on #{repo_url}." if @releasinator_config[:verbose]
    github_collaborators = github_client.collaborators "#{github_repo.org}/#{github_repo.repo}"
    if ! github_collaborators
      Printer.fail("request failed with code:#{res.code}\nbody:#{res.body}")
      abort()
    end
    puts github_collaborators.inspect if @releasinator_config[:trace]
    Printer.success("User has push permissions on #{repo_url}.")
  rescue => error
    #This will fail if the user does not have push permissions.
    Printer.fail(error.inspect)
    abort()
  end
end
validate_gitignore_contents(line) click to toggle source
# File lib/validator.rb, line 187
def validate_gitignore_contents(line)
  if !line_match_in_file?(line, ".gitignore")
    is_git_already_clean = GitUtil.is_clean_git?
    File.open('.gitignore', 'a') do |f|
      f.puts line
    end

    Printer.success("Added missing line '#{line}' to .gitignore.")

    if is_git_already_clean
      CommandProcessor.command("git add . && git commit -m \"#{@releasinator_config[:releasinator_name]}: add missing line to .gitignore\"")
    end
  end
end
validate_in_path(executable) click to toggle source
# File lib/validator.rb, line 82
def validate_in_path(executable)
  if "" == CommandProcessor.command("which #{executable} | cat")
    Printer.fail(executable.bold + " not found on path.")
    abort()
  else
    Printer.success(executable.bold + " found on path.")
  end
end
validate_is_type(obj, type) click to toggle source
# File lib/validator.rb, line 113
def validate_is_type(obj, type)
  if !obj.is_a? type
    Printer.fail("#{obj} is not a #{type}.")
    abort()
  end 
end
validate_method_convention(hash) click to toggle source
# File lib/validator.rb, line 120
def validate_method_convention(hash)
  hash.each do |key, value|
    if key.to_s.end_with? "_methods" 
      # validate that anything ending in _methods is a list of methods
      if !value.respond_to? :each
        Printer.fail("#{key} is not a list.")
        abort()
      end
      value.each do |list_item|
        validate_is_type list_item, Method
      end
    elsif key.to_s.end_with? "_method"
      # anything ending in _method is a method
      validate_is_type value, Method
    else
      # ignore everything else
    end
  end
end
validate_referenced_in_readme(filename) click to toggle source
# File lib/validator.rb, line 214
def validate_referenced_in_readme(filename)
  validate_base_dir
  Dir.chdir(@releasinator_config.base_dir) do
    File.open("README.md", "r") do |f|
      f.each_line do |line|
        if line.include?("(#{filename})") or line.include?("(./#{filename})")
          Printer.success("#{filename} referenced in #{@releasinator_config.base_dir}/README.md")
          return
        end
      end
    end
  end
  Printer.fail("Please link to the #{filename} file somewhere in #{@releasinator_config.base_dir}/README.md.")
  abort()
end
validate_releasinator_version() click to toggle source
# File lib/validator.rb, line 53
def validate_releasinator_version
  uri = URI('https://rubygems.org/api/v1/gems/releasinator.json')
  latest_version = JSON.parse(Net::HTTP.get(uri))["version"]
  current_version = Releasinator::VERSION

  if Gem::Version.new(latest_version) > Gem::Version.new(current_version)
    Printer.fail("Please upgrade to the latest releasinator version:" + latest_version.bold + ".  Current version:" + current_version.bold)
    abort()
  elsif Gem::Version.new(latest_version) < Gem::Version.new(current_version)
    Printer.success("Releasinator version: " + current_version.bold + " is newer than one from rubygems.org: " + latest_version.bold + ".  You're probably testing a development version.")
  else
    Printer.success("Releasinator version: " + latest_version.bold + " is the latest from rubygems.org.")
  end
end
validate_required_configatron_key(key) click to toggle source
# File lib/validator.rb, line 140
def validate_required_configatron_key(key)
  if !@releasinator_config.has_key?(key)
    Printer.fail("No #{key} found in configatron.")
    abort()
  end
end
validate_submodules() click to toggle source
# File lib/validator.rb, line 300
def validate_submodules
  if File.exist?(".gitmodules")
    submodules = Array.new

    current_name = nil
    current_path = nil
    current_url = nil
    File.open(".gitmodules", "r") do |f|
      f.each_line do |line|

        if line.include? "\""
          current_name = line.strip.split(' ').last.to_s.split("\"").at(1)
        elsif line.include? "path = "
          current_path = line.strip.split(' ').last.to_s
        elsif line.include? "url = "
          current_url = line.strip.split(' ').last.to_s
          submodules << Submodule.new(current_name, current_path, current_url)
        end
      end
    end

    Printer.success("Found " + submodules.count.to_s.bold + " submodules in .gitmodules.")
    submodules.each do |submodule|
      Dir.chdir(submodule.path) do
        if GitUtil.detached?
          local_sha1 = GitUtil.get_local_head_sha1()
        else
          local_branch_name = GitUtil.get_current_branch
          local_sha1 = GitUtil.get_local_branch_sha1(local_branch_name)
        end

        origin_master_sha1 = GitUtil.get_remote_branch_sha1("master")

        if local_sha1 != origin_master_sha1
          abort_string = "Submodule #{Dir.pwd} not on latest master.  Currently at #{local_sha1}, but origin/master is at #{origin_master_sha1}."\
          "\nYou should update this submodule to the latest in origin/master."
          Printer.fail(abort_string)
          abort()
        else
          Printer.success("Submodule #{Dir.pwd} matches origin/master.")
        end
        validate_clean_git()
      end
    end
  else
    Printer.success("No submodules found.")
  end
end

Private Instance Methods

get_changelog_contents() click to toggle source
# File lib/validator.rb, line 430
def get_changelog_contents()
  validate_base_dir
  Dir.chdir(@releasinator_config.base_dir) do
    open('CHANGELOG.md').read
  end
end
rename_alternate_name(expected_file_name, alternate_names) click to toggle source
# File lib/validator.rb, line 419
def rename_alternate_name(expected_file_name, alternate_names)
  alternate_names.each do |name|
    Dir.glob(name) do |entry|
      puts "Found similar file: #{name}."
      rename_file(name, expected_file_name)
      return true
    end
  end
  false
end
rename_file(old_name, new_name) click to toggle source
# File lib/validator.rb, line 408
def rename_file(old_name, new_name)
  is_git_already_clean = GitUtil.is_clean_git?

  GitUtil.move(old_name, new_name)
  # fix any references to file in readme
  replace_string("README.md", "(#{old_name})", "(#{new_name})")
  if is_git_already_clean
    CommandProcessor.command("git add . && git commit -m \"#{@releasinator_config[:releasinator_name]}: rename #{old_name} to #{new_name}\"")
  end
end
validate_local_matches_remote(branch_name) click to toggle source
# File lib/validator.rb, line 383
def validate_local_matches_remote(branch_name)
  local_branch_sha1 = GitUtil.get_local_branch_sha1(branch_name)
  origin_branch_sha1 = GitUtil.get_remote_branch_sha1(branch_name)
  if local_branch_sha1 != origin_branch_sha1
    abort_string = "Branches not in sync: #{Dir.pwd} branch:#{branch_name} at #{local_branch_sha1}, but origin/#{branch_name} is at #{origin_branch_sha1}."\
    "\nIf you received this error on the root project, you may need to:"\
    "\n  1. pull the latest changes from the remote,"\
    "\n  2. push changes up to the remote,"\
    "\n  3. back out a current release in progress."
    Printer.fail(abort_string)
    abort()
  else
    Printer.success("Repo #{Dir.pwd} matches origin/#{branch_name}.")
  end
end