Skip to content

Commit 8d751bd

Browse files
committedNov 5, 2014
Gracefully handle .git submodule files
It turns out that the `.git` directory can also be a file whose contents point to another directory. If users used this kind of `.git` file, Overcommit would fail spectacularly. Add support for detecting this case and handling it gracefully. In the process, I noticed a number of tests depended on the `repo_root` and `git_dir` helpers of the `Utils` module, so I extracted some code to clear their internal caches before each example. Change-Id: I18941d9c5ae72d240f1fef44593330592d2398ca Reviewed-on: http://gerrit.causes.com/44223 Tested-by: jenkins <jenkins@brigade.com> Reviewed-by: Shane da Silva <shane.dasilva@brigade.com>
1 parent 8e315f6 commit 8d751bd

9 files changed

+108
-26
lines changed
 

‎CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
[JSXCS](https://github.com/orktes/node-jsxcs)
1111
* Add pre-commit Ruby code smell checking with
1212
[Reek](https://github.com/troessner/reek)
13+
* Gracefully handle `.git` files that point to an external git directory
1314

1415
## 0.18.0
1516

‎lib/overcommit/git_repo.rb

+6-6
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def store_merge_state
6868
@merge_head = merge_head
6969
end
7070

71-
merge_msg_file = File.expand_path('.git/MERGE_MSG', Overcommit::Utils.repo_root)
71+
merge_msg_file = File.expand_path('MERGE_MSG', Overcommit::Utils.git_dir)
7272
@merge_msg = File.open(merge_msg_file).read if File.exist?(merge_msg_file)
7373
end
7474

@@ -90,16 +90,16 @@ def store_cherry_pick_state
9090
# of a merge.
9191
def restore_merge_state
9292
if @merge_head
93-
FileUtils.touch(File.expand_path('.git/MERGE_MODE', Overcommit::Utils.repo_root))
93+
FileUtils.touch(File.expand_path('MERGE_MODE', Overcommit::Utils.git_dir))
9494

95-
File.open(File.expand_path('.git/MERGE_HEAD', Overcommit::Utils.repo_root), 'w') do |f|
95+
File.open(File.expand_path('MERGE_HEAD', Overcommit::Utils.git_dir), 'w') do |f|
9696
f.write("#{@merge_head}\n")
9797
end
9898
@merge_head = nil
9999
end
100100

101101
if @merge_msg
102-
File.open(File.expand_path('.git/MERGE_MSG', Overcommit::Utils.repo_root), 'w') do |f|
102+
File.open(File.expand_path('MERGE_MSG', Overcommit::Utils.git_dir), 'w') do |f|
103103
f.write("#{@merge_msg}\n")
104104
end
105105
@merge_msg = nil
@@ -110,8 +110,8 @@ def restore_merge_state
110110
# of a cherry-pick.
111111
def restore_cherry_pick_state
112112
if @cherry_head
113-
File.open(File.expand_path('.git/CHERRY_PICK_HEAD',
114-
Overcommit::Utils.repo_root), 'w') do |f|
113+
File.open(File.expand_path('CHERRY_PICK_HEAD',
114+
Overcommit::Utils.git_dir), 'w') do |f|
115115
f.write("#{@cherry_head}\n")
116116
end
117117
@cherry_head = nil

‎lib/overcommit/installer.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def update
6060

6161
def hooks_path
6262
absolute_target = File.expand_path(@target)
63-
File.join(absolute_target, '.git', 'hooks')
63+
File.join(Overcommit::Utils.git_dir(absolute_target), 'hooks')
6464
end
6565

6666
def master_hook_install_path

‎lib/overcommit/utils.rb

+38-2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,47 @@ def script_path(script)
99
end
1010

1111
# Returns an absolute path to the root of the repository.
12+
#
13+
# We do this ourselves rather than call `git rev-parse --show-toplevel` to
14+
# solve an issue where the .git directory might not actually be valid in
15+
# tests.
16+
#
17+
# @return [String]
1218
def repo_root
1319
@repo_root ||=
1420
begin
15-
result = `git rev-parse --show-toplevel`.chomp
16-
result if $?.success?
21+
git_dir = Pathname.new(File.expand_path('.')).enum_for(:ascend).find do |path|
22+
File.exist?(File.join(path, '.git'))
23+
end
24+
25+
unless git_dir
26+
raise Overcommit::Exceptions::InvalidGitRepo, 'no .git directory found'
27+
end
28+
29+
git_dir.to_s
30+
end
31+
end
32+
33+
# Returns an absolute path to the .git directory for a repo.
34+
#
35+
# @param repo_dir [String] root directory of git repo
36+
# @return [String]
37+
def git_dir(repo_dir = repo_root)
38+
@git_dir ||=
39+
begin
40+
git_dir = File.expand_path('.git', repo_dir)
41+
42+
# .git could also be a file that contains the location of the git directory
43+
unless File.directory?(git_dir)
44+
git_dir = File.read(git_dir)[/^gitdir: (.*)$/, 1]
45+
46+
# Resolve relative paths
47+
unless git_dir.start_with?('/')
48+
git_dir = File.expand_path(git_dir, repo_dir)
49+
end
50+
end
51+
52+
git_dir
1753
end
1854
end
1955

‎libexec/index-tags

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
set -e
88

9-
trap "rm -f .git/tags.$$" EXIT
10-
err_file=.git/ctags.err
11-
if ctags --tag-relative -Rf.git/tags.$$ --exclude=.git "$@" 2>${err_file}; then
12-
mv .git/tags.$$ .git/tags
9+
trap "rm -f $GIT_DIR/tags.$$" EXIT
10+
err_file=$GIT_DIR/ctags.err
11+
if ctags --tag-relative -Rf$GIT_DIR/tags.$$ --exclude=.git "$@" 2>${err_file}; then
12+
mv $GIT_DIR/tags.$$ $GIT_DIR/tags
1313
[ -e ${err_file} ] && rm -f ${err_file}
1414
else
1515
# Ignore STDERR unless `ctags` returned a non-zero exit code

‎spec/overcommit/configuration_loader_spec.rb

-5
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@
44
describe '.load_repo_config' do
55
subject { described_class.load_repo_config }
66

7-
before do
8-
# Ensure memoized repo root gets reset
9-
Overcommit::Utils.instance_variable_set(:@repo_root, nil)
10-
end
11-
127
context 'when repo does not contain a configuration file' do
138
around do |example|
149
repo do

‎spec/overcommit/configuration_spec.rb

-4
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44
let(:hash) { {} }
55
let(:config) { described_class.new(hash) }
66

7-
before do
8-
Overcommit::Utils.instance_variable_set(:@repo_root, nil)
9-
end
10-
117
describe '#new' do
128
let(:internal_hash) { config.instance_variable_get(:@hash) }
139
subject { config }

‎spec/overcommit/utils_spec.rb

+50-4
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,64 @@
1313
let(:repo_dir) { repo }
1414
subject { described_class.repo_root }
1515

16-
before do
17-
described_class.instance_variable_set(:@repo_root, nil)
16+
around do |example|
17+
Dir.chdir(repo_dir) do
18+
example.run
19+
end
20+
end
21+
22+
it 'returns the path to the repository root' do
23+
subject.should == repo_dir
1824
end
1925

26+
context 'when there is no .git directory' do
27+
before do
28+
`rm -rf .git`
29+
end
30+
31+
it 'raises an exception' do
32+
expect { subject }.to raise_error Overcommit::Exceptions::InvalidGitRepo
33+
end
34+
end
35+
end
36+
37+
describe '.git_dir' do
38+
let(:repo_dir) { repo }
39+
subject { described_class.git_dir }
40+
2041
around do |example|
2142
Dir.chdir(repo_dir) do
2243
example.run
2344
end
2445
end
2546

26-
it 'returns the path to the repository root' do
27-
subject.should end_with repo_dir
47+
context 'when .git is a directory' do
48+
it 'returns the path to the directory' do
49+
subject.should end_with File.join(repo_dir, '.git')
50+
end
51+
end
52+
53+
context 'when .git is a file' do
54+
before do
55+
`rm -rf .git`
56+
`echo "gitdir: #{git_dir_path}" > .git`
57+
end
58+
59+
context 'and is a relative path' do
60+
let(:git_dir_path) { '../.git' }
61+
62+
it 'returns the path contained in the file' do
63+
subject.should == File.join(File.dirname(repo_dir), '.git')
64+
end
65+
end
66+
67+
context 'and is an absolute path' do
68+
let(:git_dir_path) { '/some/arbitrary/path/.git' }
69+
70+
it 'returns the path contained in the file' do
71+
subject.should == git_dir_path
72+
end
73+
end
2874
end
2975
end
3076

‎spec/spec_helper.rb

+8
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,12 @@
2828
config.mock_with :rspec do |c|
2929
c.syntax = :should
3030
end
31+
32+
# Much of Overcommit depends on these helpers, so they are aggressively
33+
# cached. Unset them before each example so we always get fresh values.
34+
config.before(:each) do
35+
%w[git_dir repo_root].each do |var|
36+
Overcommit::Utils.instance_variable_set(:"@#{var}", nil)
37+
end
38+
end
3139
end

0 commit comments

Comments
 (0)