Skip to content

Commit 5916e2c

Browse files
committedApr 9, 2015
Add GitRepo.staged_submodule_removals helper
Add a helper which will allow the pre-commit hook to check which submodules are staged for removal so it can deal with the strange subodule removal behavior accordingly.
1 parent 3c7c761 commit 5916e2c

File tree

4 files changed

+119
-0
lines changed

4 files changed

+119
-0
lines changed
 

‎lib/overcommit/exceptions.rb

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ class ConfigurationError < StandardError; end
55
# Raised when trying to read/write to/from the local repo git config fails.
66
class GitConfigError < StandardError; end
77

8+
# Raised when there was a problem reading submodule information for a repo.
9+
class GitSubmoduleError < StandardError; end
10+
811
# Raised when a {HookContext} is unable to setup the environment before a run.
912
class HookSetupFailed < StandardError; end
1013

‎lib/overcommit/git_repo.rb

+48
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'iniparse'
2+
13
module Overcommit
24
# Provide a set of utilities for certain interactions with `git`.
35
module GitRepo
@@ -189,5 +191,51 @@ def restore_cherry_pick_state
189191
@cherry_head = nil
190192
end
191193
end
194+
195+
# Contains information on a submodule.
196+
Submodule = Struct.new(:path, :url)
197+
198+
# Returns the submodules that have been staged for removal.
199+
#
200+
# `git` has an unexpected behavior where removing a submodule without
201+
# committing (i.e. such that the submodule directory is removed and the
202+
# changes to the index are staged) and then doing a hard reset results in
203+
# the index being wiped but the empty directory of the once existent
204+
# submodule being restored (but with no content).
205+
#
206+
# This prevents restoration of the stash of the submodule index changes,
207+
# which breaks pre-commit hook restorations of the working index.
208+
#
209+
# Thus we expose this helper so the restoration code can manually delete the
210+
# directory.
211+
#
212+
# @raise [Overcommit::Exceptions::GitConfigError] when
213+
def staged_submodule_removals
214+
# There were no submodules before, so none could have been removed
215+
return [] if `git ls-files .gitmodules`.empty?
216+
217+
previous = submodules(ref: 'HEAD')
218+
current = submodules
219+
220+
previous - current
221+
end
222+
223+
# Returns the current set of submodules.
224+
#
225+
# @param options [Hash]
226+
# @return [Array<Overcommit::GitRepo::Submodule>]
227+
def submodules(options = {})
228+
ref = options[:ref]
229+
230+
modules = []
231+
IniParse.parse(`git show #{ref}:.gitmodules`).each do |section|
232+
modules << Submodule.new(section['path'], section['url'])
233+
end
234+
235+
modules
236+
rescue IniParse::IniParseError => ex
237+
raise Overcommit::Exceptions::GitSubmoduleError,
238+
"Unable to read submodule information from #{ref}:.gitmodules file: #{ex.message}"
239+
end
192240
end
193241
end

‎overcommit.gemspec

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Gem::Specification.new do |s|
2727
s.required_ruby_version = '>= 1.9.3'
2828

2929
s.add_dependency 'childprocess', '~> 0.5.6'
30+
s.add_dependency 'iniparse', '~> 1.4'
3031

3132
s.add_development_dependency 'rspec', '~> 3.0'
3233
s.add_development_dependency 'travis', '~> 1.7'

‎spec/overcommit/git_repo_spec.rb

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
require 'spec_helper'
2+
3+
describe Overcommit::GitRepo do
4+
describe '.staged_submodule_removals' do
5+
subject { described_class.staged_submodule_removals }
6+
7+
around do |example|
8+
submodule = repo do
9+
`git commit --allow-empty -m "Submodule commit"`
10+
end
11+
12+
repo do
13+
`git submodule add #{submodule} sub-repo 2>&1 > /dev/null`
14+
`git commit -m "Initial commit"`
15+
example.run
16+
end
17+
end
18+
19+
context 'when there are no submodule removals staged' do
20+
it { should be_empty }
21+
end
22+
23+
context 'when there are submodule additions staged' do
24+
before do
25+
another_submodule = repo do
26+
`git commit --allow-empty -m "Another submodule"`
27+
end
28+
29+
`git submodule add #{another_submodule} another-sub-repo 2>&1 > /dev/null`
30+
end
31+
32+
it { should be_empty }
33+
end
34+
35+
context 'when there is one submodule removal staged' do
36+
before do
37+
`git rm sub-repo`
38+
end
39+
40+
it 'returns the submodule that was removed' do
41+
subject.size.should == 1
42+
subject.first.tap do |sub|
43+
sub.path.should == 'sub-repo'
44+
File.directory?(sub.url).should == true
45+
end
46+
end
47+
end
48+
49+
context 'when there are multiple submodule removals staged' do
50+
before do
51+
another_submodule = repo do
52+
`git commit --allow-empty -m "Another submodule"`
53+
end
54+
55+
`git submodule add #{another_submodule} yet-another-sub-repo 2>&1 > /dev/null`
56+
`git commit -m "Add yet another submodule"`
57+
`git rm sub-repo`
58+
`git rm yet-another-sub-repo`
59+
end
60+
61+
it 'returns all submodules that were removed' do
62+
subject.size.should == 2
63+
subject.map(&:path).sort.should == ['sub-repo', 'yet-another-sub-repo']
64+
end
65+
end
66+
end
67+
end

0 commit comments

Comments
 (0)