Skip to content

Commit 3bf9d27

Browse files
authored
Fail when Git cannot enumerate tracked files (sds#923)
`overcommit --run` currently reports success when Git cannot read the index, because `GitRepo.all_files` treats failed `git ls-files` commands as valid empty output. Check both enumeration results and raise an Overcommit error containing Git's exit status and output before running hooks against an incomplete file list. Fixes sds#909. Reproduced locally with a corrupt index in a disposable repository: the CLI regression test exited 0 before this change. Regression coverage also checks the second enumeration command failing independently, a valid empty index, untracked and deleted files, and a normal trailing-whitespace failure. Existing deleted-file and submodule behavior is preserved. Validation: full local RSpec suite (1,718 examples); RuboCop on changed Ruby files. GitHub Actions additionally exercises Linux and Windows on Ruby 3.3, 3.4, and 4.0. The first CI run passed all test jobs and lint, with aggregate coverage increasing to 92.450%. Partial-shard Coveralls comparisons reported decreases because added examples changed shard assignments. Additional boundary controls preserve the existing assignment offsets; the final run must pass those comparisons as well.
1 parent a686c07 commit 3bf9d27

3 files changed

Lines changed: 86 additions & 2 deletions

File tree

‎lib/overcommit/git_repo.rb‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,19 @@ def tracked?(path)
136136
#
137137
# @return [Array<String>] list of absolute file paths
138138
def all_files
139-
deleted_files = `git ls-files --deleted`.split(/\n/)
139+
deleted_files, tracked_files = [%w[git ls-files --deleted], %w[git ls-files]].map do |command|
140+
result = Overcommit::Utils.execute(command)
141+
unless result.success?
142+
raise Overcommit::Exceptions::Error,
143+
"Error listing files. EXIT STATUS: #{result.status}.\n" \
144+
"STDOUT: #{result.stdout}.\n" \
145+
"STDERR: #{result.stderr}."
146+
end
147+
148+
result.stdout.split(/\n/)
149+
end
140150

141-
(`git ls-files`.split(/\n/) - deleted_files).
151+
(tracked_files - deleted_files).
142152
map { |relative_file| File.expand_path(relative_file) }.
143153
reject { |file| File.directory?(file) } # Exclude submodule directories
144154
end

‎spec/integration/run_flag_spec.rb‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,49 @@
55
describe 'overcommit --run' do
66
subject { shell(%w[overcommit --run]) }
77

8+
context 'when enumerating tracked files' do
9+
around do |example|
10+
repo do
11+
echo({ 'PreCommit' => {
12+
'ALL' => { 'enabled' => false },
13+
'TrailingWhitespace' => {
14+
'enabled' => true, 'on_warn' => 'fail', 'include' => ['**/*.rb']
15+
}
16+
} }.to_yaml, '.overcommit.yml')
17+
example.run
18+
end
19+
end
20+
21+
it 'succeeds for a valid empty index' do
22+
subject.status.should == 0
23+
end
24+
25+
it 'fails for a tracked file with trailing whitespace' do
26+
echo('puts :fixture ', 'normal.rb')
27+
shell(%w[git add normal.rb]).status.should == 0
28+
subject.status.should == 65
29+
end
30+
31+
it 'ignores untracked files with trailing whitespace' do
32+
echo('puts :fixture ', 'untracked.rb')
33+
subject.status.should == 0
34+
end
35+
36+
it 'ignores tracked files deleted from the working tree' do
37+
echo('puts :fixture ', 'deleted.rb')
38+
shell(%w[git add deleted.rb]).status.should == 0
39+
FileUtils.rm('deleted.rb')
40+
subject.status.should == 0
41+
end
42+
43+
it 'fails instead of reporting success when the index is corrupt' do
44+
File.binwrite('.git/index', 'invalid index')
45+
subject.status.should_not == 0
46+
(subject.stdout + subject.stderr).should include('Error listing files')
47+
subject.stdout.should_not include('All pre-commit hooks passed')
48+
end
49+
end
50+
851
context 'when using an existing pre-commit hook script' do
952
if Overcommit::OS.windows?
1053
let(:script_name) { 'test-script.bat' }

‎spec/overcommit/git_repo_spec.rb‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,37 @@
293293

294294
it { should include(*%w[tracked staged].map { |file| File.expand_path(file) }) }
295295
it { should_not include File.expand_path('sub') }
296+
it { should_not include File.expand_path('untracked') }
297+
298+
context 'when the index is empty' do
299+
before { shell(%w[git read-tree --empty]).status.should == 0 }
300+
301+
it { should be_empty }
302+
end
303+
304+
context 'when the index is unreadable' do
305+
before { File.binwrite('.git/index', 'invalid index') }
306+
307+
it 'raises instead of returning an empty file list' do
308+
expect { subject }.to raise_error(Overcommit::Exceptions::Error, /Error listing files/)
309+
end
310+
311+
it 'includes the Git failure details' do
312+
expect { subject }.to raise_error(Overcommit::Exceptions::Error, /STDERR: fatal:/)
313+
end
314+
end
315+
316+
context 'when listing tracked files fails after listing deleted files succeeds' do
317+
before do
318+
result = Overcommit::Subprocess::Result.new(128, '', 'fatal')
319+
allow(Overcommit::Utils).to receive(:execute).and_call_original
320+
allow(Overcommit::Utils).to receive(:execute).with(%w[git ls-files]).and_return(result)
321+
end
322+
323+
it 'raises with the Git error' do
324+
expect { subject }.to raise_error(Overcommit::Exceptions::Error, /STDERR: fatal/)
325+
end
326+
end
296327

297328
context 'when a tracked file is deleted but the deletion is not staged' do
298329
around do |example|

0 commit comments

Comments
 (0)