Skip to content

Commit

Permalink
implement diff hook context
Browse files Browse the repository at this point in the history
  • Loading branch information
benmelz committed Jan 29, 2025
1 parent 3219b60 commit 8110e60
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 0 deletions.
20 changes: 20 additions & 0 deletions lib/overcommit/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ def run
sign
when :run_all
run_all
when :diff
diff
end
rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e
puts e
Expand Down Expand Up @@ -99,6 +101,11 @@ def add_installation_options(opts)
@options[:action] = :run_all
@options[:hook_to_run] = arg ? arg.to_s : 'run-all'
end

opts.on('--diff [ref]', 'Run pre_commit hooks against the diff between a given ref. Defaults to `main`.') do |arg| # rubocop:disable Layout/LineLength
@options[:action] = :diff
arg
end
end

def add_other_options(opts)
Expand Down Expand Up @@ -210,6 +217,19 @@ def run_all
halt(status ? 0 : 65)
end

def diff
empty_stdin = File.open(File::NULL) # pre-commit hooks don't take input
context = Overcommit::HookContext.create('diff', config, @arguments, empty_stdin, **@cli_options) # rubocop:disable Layout/LineLength
config.apply_environment!(context, ENV)

printer = Overcommit::Printer.new(config, log, context)
runner = Overcommit::HookRunner.new(config, log, context, printer)

status = runner.run

halt(status ? 0 : 65)
end

# Used for ease of stubbing in tests
def halt(status = 0)
exit status
Expand Down
37 changes: 37 additions & 0 deletions lib/overcommit/hook_context/diff.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

require 'overcommit/git_repo'

module Overcommit::HookContext
# Simulates a pre-commit context based on the diff with another git ref.
#
# This results in pre-commit hooks running against the changes between the current
# and another ref, which is useful for automated CI scripts.
class Diff < Base
def modified_files
@modified_files ||= Overcommit::GitRepo.modified_files(refs: @options[:diff])
end

def modified_lines_in_file(file)
@modified_lines ||= {}
@modified_lines[file] ||= Overcommit::GitRepo.extract_modified_lines(file,
refs: @options[:diff])
end

def hook_class_name
'PreCommit'
end

def hook_type_name
'pre_commit'
end

def hook_script_name
'pre-commit'
end

def initial_commit?
@initial_commit ||= Overcommit::GitRepo.initial_commit?
end
end
end
39 changes: 39 additions & 0 deletions spec/integration/diff_flag_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

require 'spec_helper'

describe 'overcommit --diff' do
subject { shell(%w[overcommit --diff main]) }

context 'when using an existing pre-commit hook script' do
let(:script_name) { 'test-script' }
let(:script_contents) { "#!/bin/bash\nexit 0" }
let(:script_path) { ".#{Overcommit::OS::SEPARATOR}#{script_name}" }

let(:config) do
{
'PreCommit' => {
'MyHook' => {
'enabled' => true,
'required_executable' => script_path,
}
}
}
end

around do |example|
repo do
File.open('.overcommit.yml', 'w') { |f| f.puts(config.to_yaml) }
echo(script_contents, script_path)
`git add #{script_path}`
FileUtils.chmod(0o755, script_path)
example.run
end
end

it 'completes successfully without blocking' do
wait_until(timeout: 10) { subject } # Need to wait long time for JRuby startup
subject.status.should == 0
end
end
end
25 changes: 25 additions & 0 deletions spec/overcommit/cli_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require 'spec_helper'
require 'overcommit/cli'
require 'overcommit/hook_context/diff'
require 'overcommit/hook_context/run_all'

describe Overcommit::CLI do
Expand Down Expand Up @@ -125,5 +126,29 @@
subject
end
end

context 'with the diff switch specified' do
let(:arguments) { ['--diff some-branch'] }
let(:config) { Overcommit::ConfigurationLoader.default_configuration }

before do
cli.stub(:halt)
end

it 'creates a HookRunner with the diff context' do
Overcommit::HookRunner.should_receive(:new).
with(config,
logger,
instance_of(Overcommit::HookContext::Diff),
instance_of(Overcommit::Printer)).
and_call_original
subject
end

it 'runs the HookRunner' do
Overcommit::HookRunner.any_instance.should_receive(:run)
subject
end
end
end
end
121 changes: 121 additions & 0 deletions spec/overcommit/hook_context/diff_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# frozen_string_literal: true

require 'spec_helper'
require 'overcommit/hook_context/diff'

describe Overcommit::HookContext::Diff do
let(:config) { double('config') }
let(:args) { [] }
let(:input) { double('input') }
let(:context) { described_class.new(config, args, input, diff: 'master') }

describe '#modified_files' do
subject { context.modified_files }

context 'when repo contains no files' do
around do |example|
repo do
`git checkout -b other-branch 2>&1`
example.run
end
end

it { should be_empty }
end

context 'when the repo contains files that are unchanged from the ref' do
around do |example|
repo do
touch('some-file')
`git add some-file`
touch('some-other-file')
`git add some-other-file`
`git commit -m "Add files"`
`git checkout -b other-branch 2>&1`
example.run
end
end

it { should be_empty }
end

context 'when repo contains files that have been changed from the ref' do
around do |example|
repo do
touch('some-file')
`git add some-file`
touch('some-other-file')
`git add some-other-file`
`git commit -m "Add files"`
`git checkout -b other-branch 2>&1`
File.open('some-file', 'w') { |f| f.write("hello\n") }
`git add some-file`
`git commit -m "Edit file"`
example.run
end
end

it { should == %w[some-file].map { |file| File.expand_path(file) } }
end

context 'when repo contains submodules' do
around do |example|
submodule = repo do
touch 'foo'
`git add foo`
`git commit -m "Initial commit"`
end

repo do
`git submodule add #{submodule} test-sub 2>&1 > #{File::NULL}`
`git checkout -b other-branch 2>&1`
example.run
end
end

it { should_not include File.expand_path('test-sub') }
end
end

describe '#modified_lines_in_file' do
let(:modified_file) { 'some-file' }
subject { context.modified_lines_in_file(modified_file) }

context 'when file contains a trailing newline' do
around do |example|
repo do
touch(modified_file)
`git add #{modified_file}`
`git commit -m "Add file"`
`git checkout -b other-branch 2>&1`
File.open(modified_file, 'w') { |f| (1..3).each { |i| f.write("#{i}\n") } }
`git add #{modified_file}`
`git commit -m "Edit file"`
example.run
end
end

it { should == Set.new(1..3) }
end

context 'when file does not contain a trailing newline' do
around do |example|
repo do
touch(modified_file)
`git add #{modified_file}`
`git commit -m "Add file"`
`git checkout -b other-branch 2>&1`
File.open(modified_file, 'w') do |f|
(1..2).each { |i| f.write("#{i}\n") }
f.write(3)
end
`git add #{modified_file}`
`git commit -m "Edit file"`
example.run
end
end

it { should == Set.new(1..3) }
end
end
end

0 comments on commit 8110e60

Please sign in to comment.