class Overcommit::HookSigner

Calculates, stores, and retrieves stored signatures of hook plugins.

Constants

IGNORED_CONFIG_KEYS

We don’t want to include the skip setting as it is set by Overcommit itself

Attributes

hook_name[R]

Public Class Methods

new(hook_name, config, context) click to toggle source

@param hook_name [String] name of the hook @param config [Overcommit::Configuration] @param context [Overcommit::HookContext]

# File lib/overcommit/hook_signer.rb, line 15
def initialize(hook_name, config, context)
  @hook_name = hook_name
  @config = config
  @context = context
end

Public Instance Methods

hook_path() click to toggle source

Returns the path of the file that should be incorporated into this hooks signature.

@return [String]

# File lib/overcommit/hook_signer.rb, line 25
def hook_path
  @hook_path ||= begin
    plugin_path = File.join(@config.plugin_directory,
                            @context.hook_type_name,
                            "#{Overcommit::Utils.snake_case(@hook_name)}.rb")

    if File.exist?(plugin_path)
      plugin_path
    else
      # Otherwise this is an ad hoc hook using an existing hook script
      hook_config = @config.for_hook(@hook_name, @context.hook_class_name)

      command = Array(hook_config['command'] || hook_config['required_executable'])

      if @config.verify_signatures? &&
        signable_file?(command.first) &&
        !Overcommit::GitRepo.tracked?(command.first)
        raise Overcommit::Exceptions::InvalidHookDefinition,
              'Hook specified a `required_executable` or `command` that ' \
              'is a path relative to the root of the repository, and so ' \
              'must be tracked by Git in order to be signed'
      end

      File.join(Overcommit::Utils.repo_root, command.first.to_s)
    end
  end
end
signable_file?(file) click to toggle source
# File lib/overcommit/hook_signer.rb, line 53
def signable_file?(file)
  return unless file

  sep = Overcommit::OS.windows? ? '\\' : File::SEPARATOR
  file.start_with?(".#{sep}") ||
    file.start_with?(Overcommit::Utils.repo_root)
end
signature_changed?() click to toggle source

Return whether the signature for this hook has changed since it was last calculated.

@return [true,false]

# File lib/overcommit/hook_signer.rb, line 65
def signature_changed?
  signature != stored_signature
end
update_signature!() click to toggle source

Update the current stored signature for this hook.

# File lib/overcommit/hook_signer.rb, line 70
def update_signature!
  result = Overcommit::Utils.execute(
    %w[git config --local] + [signature_config_key, signature]
  )

  unless result.success?
    raise Overcommit::Exceptions::GitConfigError,
          "Unable to write to local repo git config: #{result.stderr}"
  end
end

Private Instance Methods

hook_contents() click to toggle source
# File lib/overcommit/hook_signer.rb, line 101
def hook_contents
  File.read(hook_path)
end
signature() click to toggle source

Calculates a hash of a hook using a combination of its configuration and file contents.

This way, if either the plugin code changes or its configuration changes, the hash will change and we can alert the user to this change.

# File lib/overcommit/hook_signer.rb, line 88
def signature
  hook_config = @config.for_hook(@hook_name, @context.hook_class_name).
                        dup.
                        tap { |config| IGNORED_CONFIG_KEYS.each { |k| config.delete(k) } }

  content_to_sign =
    if signable_file?(hook_path) && Overcommit::GitRepo.tracked?(hook_path)
      hook_contents
    end

  Digest::SHA256.hexdigest(content_to_sign.to_s + hook_config.to_s)
end
signature_config_key() click to toggle source
# File lib/overcommit/hook_signer.rb, line 120
def signature_config_key
  "overcommit.#{@context.hook_class_name}.#{@hook_name}.signature"
end
stored_signature() click to toggle source
# File lib/overcommit/hook_signer.rb, line 105
def stored_signature
  result = Overcommit::Utils.execute(
    %w[git config --local --get] + [signature_config_key]
  )

  if result.status == 1 # Key doesn't exist
    return ''
  elsif result.status != 0
    raise Overcommit::Exceptions::GitConfigError,
          "Unable to read from local repo git config: #{result.stderr}"
  end

  result.stdout.chomp
end