diff --git a/lib/overcommit/git_repo.rb b/lib/overcommit/git_repo.rb index 1af73e81..1f30759f 100644 --- a/lib/overcommit/git_repo.rb +++ b/lib/overcommit/git_repo.rb @@ -158,9 +158,12 @@ def store_merge_state # conflict. This is necessary since stashing removes the merge state. if merge_head != 'MERGE_HEAD' @merge_head = merge_head + + merge_mode_file = Overcommit::Utils.git_path('MERGE_MODE') + @merge_mode = File.open(merge_mode_file).read if File.exist?(merge_mode_file) end - merge_msg_file = File.expand_path('MERGE_MSG', Overcommit::Utils.git_dir) + merge_msg_file = Overcommit::Utils.git_path('MERGE_MSG') @merge_msg = File.open(merge_msg_file).read if File.exist?(merge_msg_file) end @@ -182,17 +185,20 @@ def store_cherry_pick_state # of a merge. def restore_merge_state if @merge_head - FileUtils.touch(File.expand_path('MERGE_MODE', Overcommit::Utils.git_dir)) + File.open(Overcommit::Utils.git_path('MERGE_MODE'), 'w') do |f| + f.write(@merge_mode || '') + end + @merge_mode = nil - File.open(File.expand_path('MERGE_HEAD', Overcommit::Utils.git_dir), 'w') do |f| + File.open(Overcommit::Utils.git_path('MERGE_HEAD'), 'w') do |f| f.write(@merge_head) end @merge_head = nil end if @merge_msg - File.open(File.expand_path('MERGE_MSG', Overcommit::Utils.git_dir), 'w') do |f| - f.write("#{@merge_msg}\n") + File.open(Overcommit::Utils.git_path('MERGE_MSG'), 'w') do |f| + f.write(@merge_msg) end @merge_msg = nil end @@ -202,8 +208,7 @@ def restore_merge_state # of a cherry-pick. def restore_cherry_pick_state if @cherry_head - File.open(File.expand_path('CHERRY_PICK_HEAD', - Overcommit::Utils.git_dir), 'w') do |f| + File.open(Overcommit::Utils.git_path('CHERRY_PICK_HEAD'), 'w') do |f| f.write(@cherry_head) end @cherry_head = nil diff --git a/lib/overcommit/utils.rb b/lib/overcommit/utils.rb index b315edac..ab5f1246 100644 --- a/lib/overcommit/utils.rb +++ b/lib/overcommit/utils.rb @@ -74,6 +74,31 @@ def git_dir end end + # Returns an absolute path to a file in the current worktree's Git + # directory. + # + # @param path [String] + # @return [String] + def git_path(path) + cmd = %w[git rev-parse] + if GIT_VERSION < '2.5' + cmd << '--git-dir' + else + cmd += ['--git-path', path] + end + + result = execute(cmd) + unless result.success? + raise Overcommit::Exceptions::InvalidGitRepo, + 'Unable to determine Git path. ' \ + 'Not a recognizable Git repository!' + end + + resolved_path = result.stdout.chomp("\n") + resolved_path = File.join(resolved_path, path) if GIT_VERSION < '2.5' + File.expand_path(resolved_path, Dir.pwd) + end + # Remove ANSI escape sequences from a string. # # This is useful for stripping colorized output from external tools. diff --git a/spec/overcommit/git_repo_spec.rb b/spec/overcommit/git_repo_spec.rb index b887415b..29fc52fd 100644 --- a/spec/overcommit/git_repo_spec.rb +++ b/spec/overcommit/git_repo_spec.rb @@ -320,6 +320,58 @@ end end + describe 'operation state in a linked worktree', + if: Overcommit::GIT_VERSION >= '2.5' do + around do |example| + repo do + `git commit --allow-empty -m "Initial commit"` + linked_worktree = File.join(File.dirname(Dir.pwd), 'linked-worktree') + result = Overcommit::Utils.execute( + %W[git worktree add -b linked-worktree #{linked_worktree}], + ) + raise "Unable to create worktree: #{result.stderr}" unless result.success? + + Dir.chdir(linked_worktree) { example.run } + end + end + + let(:git_dir) { File.expand_path(`git rev-parse --git-dir`.chomp) } + let(:common_dir) { File.expand_path(`git rev-parse --git-common-dir`.chomp) } + let(:head) { `git rev-parse HEAD`.chomp } + + it 'restores merge state only to the linked worktree' do + merge_message = "Merge linked branch\n" + File.write(File.join(git_dir, 'MERGE_HEAD'), head) + File.write(File.join(git_dir, 'MERGE_MODE'), 'no-ff') + File.write(File.join(git_dir, 'MERGE_MSG'), merge_message) + + described_class.store_merge_state + FileUtils.rm( + %w[MERGE_HEAD MERGE_MODE MERGE_MSG]. + map { |file| File.join(git_dir, file) }, + ) + described_class.restore_merge_state + + File.read(File.join(git_dir, 'MERGE_HEAD')).should == head + File.read(File.join(git_dir, 'MERGE_MODE')).should == 'no-ff' + File.read(File.join(git_dir, 'MERGE_MSG')).should == merge_message + File.exist?(File.join(common_dir, 'MERGE_HEAD')).should == false + File.exist?(File.join(common_dir, 'MERGE_MODE')).should == false + File.exist?(File.join(common_dir, 'MERGE_MSG')).should == false + end + + it 'restores cherry-pick state only to the linked worktree' do + File.write(File.join(git_dir, 'CHERRY_PICK_HEAD'), head) + + described_class.store_cherry_pick_state + FileUtils.rm(File.join(git_dir, 'CHERRY_PICK_HEAD')) + described_class.restore_cherry_pick_state + + File.read(File.join(git_dir, 'CHERRY_PICK_HEAD')).should == head + File.exist?(File.join(common_dir, 'CHERRY_PICK_HEAD')).should == false + end + end + describe '.staged_submodule_removals' do subject { described_class.staged_submodule_removals }