Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ coverage/
pkg/
.bundle
.idea
/spec_example_status
1 change: 1 addition & 0 deletions lib/overcommit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@
require 'overcommit/installer'
require 'overcommit/logger'
require 'overcommit/version'
require 'overcommit/signature'
75 changes: 32 additions & 43 deletions lib/overcommit/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,12 @@ def plugin_hook?(hook_context_or_type, hook_name)
File.exist?(File.join(plugin_directory, hook_type_name, "#{hook_name}.rb"))
end

# Return whether the signature for this configuration has changed since it
# was last calculated.
# Return whether the signature for this configuration has been verified.
#
# @return [true,false]
def signature_changed?
signature != stored_signature
def signature_verified?
signature_unchanged = signature == stored_signature
signature_unchanged || Overcommit::Signature.verified?(object_signature)
end

# Return whether a previous signature has been recorded for this
Expand All @@ -214,37 +214,22 @@ def verify_signatures?
return false if ENV['OVERCOMMIT_NO_VERIFY']
return true if @hash['verify_signatures'] != false

result = Overcommit::Utils.execute(
%W[git config --local --get #{verify_signature_config_key}]
)

if result.status == 1 # Key doesn't exist
return true
elsif result.status != 0
raise Overcommit::Exceptions::GitConfigError,
"Unable to read from local repo git config: #{result.stderr}"
verify = Overcommit::GitRepo.get_local_config(verify_signature_config_key)
if verify.nil?
true
else
# We don't cast since we want to allow anything to count as "true" except
# a literal zero
verify.strip != '0'
end

# We don't cast since we want to allow anything to count as "true" except
# a literal zero
result.stdout.strip != '0'
end

# Update the currently stored signature for this hook.
def update_signature!
result = Overcommit::Utils.execute(
%w[git config --local] + [signature_config_key, signature]
)

Overcommit::Signature.verify(object_signature)
Overcommit::GitRepo.set_local_config(signature_config_key, signature)
verify_signature_value = @hash['verify_signatures'] ? 1 : 0
result &&= Overcommit::Utils.execute(
%W[git config --local #{verify_signature_config_key} #{verify_signature_value}]
)

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

protected
Expand Down Expand Up @@ -315,28 +300,32 @@ def smart_merge(parent, child)
#
# @return [String]
def signature
Digest::SHA256.hexdigest(@hash.to_json)
Overcommit::Signature.sign(@hash.to_json)
end

# Returns the stored signature of this repo's Overcommit configuration.
# Returns the git object ID of this configuration.
#
# @return [String]
def git_object_id
Overcommit::GitRepo.blob_id(@hash.to_json)
end

# Returns the object signature of this configuration.
#
# @return [Hash]
def object_signature
Overcommit::Signature.object_signature(@hash.to_json)
end

# Returns the last stored signature of this repo's Overcommit configuration.
#
# This is intended to be compared against the current signature of this
# configuration object.
#
# @return [String]
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
signature = Overcommit::GitRepo.get_local_config(signature_config_key)
signature || ''
end

def signature_config_key
Expand Down
2 changes: 1 addition & 1 deletion lib/overcommit/configuration_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def verify_signatures(config)
"No previously recorded signature for configuration file.\n" \
'Run `overcommit --sign` if you trust the hooks in this repository.'

elsif config.signature_changed?
elsif !config.signature_verified?
raise Overcommit::Exceptions::ConfigurationSignatureChanged,
"Signature of configuration file has changed!\n" \
"Run `overcommit --sign` once you've verified the configuration changes."
Expand Down
6 changes: 6 additions & 0 deletions lib/overcommit/exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ class GitSubmoduleError < StandardError; end
# Raised when there was a problem reading git revision information with `rev-list`.
class GitRevListError < StandardError; end

# Raised when there was a problem computing the ID of git repository objects.
class GitHashObjectError < StandardError; end

# Raised when there was a problem retrieving information from git repository objects.
class GitCatFileError < StandardError; end

# Raised when a {HookContext} is unable to setup the environment before a run.
class HookSetupFailed < StandardError; end

Expand Down
70 changes: 70 additions & 0 deletions lib/overcommit/git_repo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -282,5 +282,75 @@ def branches_containing_commit(commit_ref)
def current_branch
`git symbolic-ref --short -q HEAD`.chomp
end

# Get the value of a local configuration option.
#
# @param name [String] the name of the option
# @return [String, nil] the value of the option or _nil_ if the option does not exist
def get_local_config(name)
result = Overcommit::Utils.execute(
%w[git config --local --get] << name
)
if result.status == 1 # Key doesn't exist
return nil
elsif result.status != 0
raise Overcommit::Exceptions::GitConfigError,
"Unable to read from local repo git config: #{result.stderr}"
end

result.stdout.chomp
end

# Sets the value of a local configuration option.
#
# @param name [String] the name of the option
# @param value [String] the value to set
def set_local_config(name, value)
result = Overcommit::Utils.execute(
%W[git config --local #{name} #{value}]
)
unless result.success?
raise Overcommit::Exceptions::GitConfigError,
"Unable to write to local repo git config: #{result.stderr}"
end
end

# Computes the git object ID for the given blob contents and optionally writes that blob to the
# git repository.
#
# @param blob [String] the blob contents
# @param write [Boolean] true if this blob should be written to the git repository
# @return [String] the git object ID
def blob_id(blob, write = false)
write_args = write ? %w[-w] : []
result = Overcommit::Utils.execute(
%w[git hash-object] + write_args + %w[--stdin],
input: blob
)
if result.success?
result.stdout.chomp
else
raise Overcommit::Exceptions::GitHashObjectError,
"Failed to compute blob ID: #{result.stderr}"
end
end

# Retrieves the contents of a blob from the git repository.
#
# @param blob_id [String] the blob ID
# @return [String, nil] the contents of the blob or _nil_ if it does not exist
def blob_contents(blob_id)
result = Overcommit::Utils.execute(
%W[git cat-file -p #{blob_id}^{blob}]
)
if result.success?
result.stdout.chomp
elsif result.status == 128 # 128 is returned when the blob does not exist.
nil
else
raise Overcommit::Exceptions::GitCatFileError,
"Failed to get blob contents: #{result.stderr}"
end
end
end
end
16 changes: 8 additions & 8 deletions lib/overcommit/hook_loader/plugin_hook_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Overcommit::HookLoader
# is running in.
class PluginHookLoader < Base
def load_hooks
check_for_modified_plugins if @config.verify_signatures?
check_for_unverified_plugins if @config.verify_signatures?

hooks = plugin_paths.map do |plugin_path|
require plugin_path
Expand All @@ -22,9 +22,9 @@ def load_hooks
end

def update_signatures
log.success('No plugin signatures have changed') if modified_plugins.empty?
log.success('All plugin signatures have been verified') if unverified_plugins.empty?

modified_plugins.each do |plugin|
unverified_plugins.each do |plugin|
plugin.update_signature!
log.warning "Updated signature of plugin #{plugin.hook_name}"
end
Expand All @@ -47,20 +47,20 @@ def ad_hoc_hook_names
@config.enabled_ad_hoc_hooks(@context)
end

def modified_plugins
def unverified_plugins
(plugin_hook_names + ad_hoc_hook_names).
map { |hook_name| Overcommit::HookSigner.new(hook_name, @config, @context) }.
select(&:signature_changed?)
reject(&:signature_verified?)
end

def check_for_modified_plugins
return if modified_plugins.empty?
def check_for_unverified_plugins
return if unverified_plugins.empty?

log.bold_warning "The following #{@context.hook_script_name} plugins " \
'have been added, changed, or had their configuration modified:'
log.newline

modified_plugins.each do |signer|
unverified_plugins.each do |signer|
log.warning " * #{signer.hook_name} in #{signer.hook_path}"
end

Expand Down
64 changes: 32 additions & 32 deletions lib/overcommit/hook_signer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,24 +57,18 @@ def signable_file?(file)
file.start_with?(Overcommit::Utils.repo_root)
end

# Return whether the signature for this hook has changed since it was last
# calculated.
# Return whether the signature for this hook has been verified.
#
# @return [true,false]
def signature_changed?
signature != stored_signature
def signature_verified?
signature_unchanged = signature == stored_signature
signature_unchanged || Overcommit::Signature.verified?(object_signatures)
end

# Update the current stored signature for this hook.
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
Overcommit::Signature.verify(object_signatures)
Overcommit::GitRepo.set_local_config(signature_config_key, signature)
end

private
Expand All @@ -85,35 +79,41 @@ def update_signature!
# 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.
def signature
hook_config = @config.for_hook(@hook_name, @context.hook_class_name).
dup.
tap { |config| IGNORED_CONFIG_KEYS.each { |k| config.delete(k) } }
Overcommit::Signature.sign_signatures(object_signatures)
end

content_to_sign =
if signable_file?(hook_path) && Overcommit::GitRepo.tracked?(hook_path)
hook_contents
end
# Returns the object signatures of this configuration.
#
# @return [Hash]
def object_signatures
sig = Overcommit::Signature.object_signature(hook_config.to_json)
content = content_to_sign
if content.nil?
sig
else
sig.merge(Overcommit::Signature.object_signature(content))
end
end

def hook_config
@config.for_hook(@hook_name, @context.hook_class_name).
dup.
tap { |config| IGNORED_CONFIG_KEYS.each { |k| config.delete(k) } }
end

Digest::SHA256.hexdigest(content_to_sign.to_s + hook_config.to_s)
def content_to_sign
if signable_file?(hook_path) && Overcommit::GitRepo.tracked?(hook_path)
hook_contents
end
end

def hook_contents
File.read(hook_path)
end

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
signature = Overcommit::GitRepo.get_local_config(signature_config_key)
signature || ''
end

def signature_config_key
Expand Down
Loading