class ChefCLI::CookbookProfiler::Git

def self.uncache

def self.uncache
  @@git_memo = {}
end

def clean?

def clean?
  git!("diff-files --quiet", returns: [0, 1]).exitstatus == 0
end

def current_branch

def current_branch
  @current_branch ||= detect_current_branch
end

def detect_current_branch

def detect_current_branch
  branch = git!("rev-parse --abbrev-ref HEAD").stdout.strip
  @unborn_branch = false
  branch
rescue Mixlib::ShellOut::ShellCommandFailed => e
  # "unborn" branch, i.e. one with no commits or
  # verify_ref_cmd didn't error, we don't know why
  # the original git command failed, so re-raise.
  unborn_branch_ref || raise(e)
end

def get_repo_path

def get_repo_path
  unless @repo_path
    @repo_path = system_command("git rev-parse --show-toplevel", { cwd: cookbook_path }).stdout.strip
  end
  @repo_path
end

def git(subcommand, options = {})

def git(subcommand, options = {})
  # memoize commands per-repo
  repo_path = get_repo_path
  memo_key = [repo_path, subcommand, options]
  if @@git_memo.key?(memo_key)
    rv = @@git_memo[memo_key]
  else
    options = { cwd: cookbook_path }.merge(options)
    rv = system_command("git #{subcommand}", options)
    @@git_memo[memo_key] = rv
  end
  rv
end

def git!(subcommand, options = {})

def git!(subcommand, options = {})
  cmd = git(subcommand, options)
  cmd.error!
  cmd
end

def have_remote?

def have_remote?
  !remote_name.empty? && remote_name != "."
end

def initialize(cookbook_path)

def initialize(cookbook_path)
  @cookbook_path = cookbook_path
  @repo_path = nil
  @unborn_branch = nil
  @unborn_branch_ref = nil
end

def profile_data

Returns:
  • (Hash) - Hashed used for pinning cookbook versions within a Policyfile.lock
def profile_data
  {
    "scm" => "git",
    # To get this info, you need to do something like:
    # figure out branch or assume 'main'
    # git config --get branch.main.remote
    # git config --get remote.opscode.url
    "remote" => remote,
    "revision" => revision,
    "working_tree_clean" => clean?,
    "published" => !unpublished_commits?,
    "synchronized_remote_branches" => synchronized_remotes,
  }
end

def remote

def remote
  @remote_url ||=
    if have_remote?
      git!("config --get remote.#{remote_name}.url").stdout.strip
    else
      nil
    end
end

def remote_name

def remote_name
  @remote_name ||= git!("config --get branch.#{current_branch}.remote", returns: [0, 1]).stdout.strip
end

def revision

def revision
  git!("rev-parse HEAD").stdout.strip
rescue Mixlib::ShellOut::ShellCommandFailed => e
  # We may have an "unborn" branch, i.e. one with no commits.
  if unborn_branch_ref
    nil
  else
    # if we got here, but verify_ref_cmd didn't error, we don't know why
    # the original git command failed, so re-raise.
    raise e
  end
end

def synchronized_remotes

def synchronized_remotes
  @synchronized_remotes ||= git!("branch -r --contains #{revision}").stdout.lines.map(&:strip)
rescue Mixlib::ShellOut::ShellCommandFailed => e
  # We may have an "unborn" branch, i.e. one with no commits.
  if unborn_branch_ref
    []
  else
    # if we got here, but verify_ref_cmd didn't error, we don't know why
    # the original git command failed, so re-raise.
    raise e
  end
end

def unborn_branch_ref

def unborn_branch_ref
  @unborn_branch_ref ||=
    begin
      strict_branch_ref = git!("symbolic-ref -q HEAD").stdout.strip
      verify_ref_cmd = git("show-ref --verify #{strict_branch_ref}")
      if verify_ref_cmd.error?
        @unborn_branch = true
        strict_branch_ref
      else
        # if we got here, but verify_ref_cmd didn't error, then `git
        # rev-parse` is probably failing for a reason we haven't anticipated.
        # Calling code should detect this and re-raise.
        nil
      end
    end
end

def unpublished_commits?

def unpublished_commits?
  synchronized_remotes.empty?
end