Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion lib/overcommit/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,29 @@ def windows_parent_command
native_windows_parent_command
rescue LoadError, StandardError
# win32ole is optional and absent from newer Ruby distributions.
# Fall back when it is unavailable or the COM query is denied.
# Use Script Host when it is unavailable or the COM query is denied.
scripted_windows_parent_command
end

def scripted_windows_parent_command
begin
result = execute([
'cscript.exe', '//nologo', '//B', '//U',
File.expand_path('../../libexec/overcommit-parent-command.js', __dir__),
Process.ppid.to_s,
])
if result.success?
command = result.stdout.dup.force_encoding('UTF-16LE').encode('UTF-8').
delete_prefix("\uFEFF").strip
return command.empty? ? nil : command
end
rescue Errno::ENOENT, ChildProcess::LaunchError, EncodingError
# Preserve PowerShell when Script Host is disabled or unavailable.
end
powershell_windows_parent_command
end

def powershell_windows_parent_command
result = execute([
'powershell.exe', '-NoProfile', '-NonInteractive', '-Command',
"(Get-CimInstance Win32_Process -Filter 'ProcessId = #{Process.ppid}').CommandLine",
Expand Down
11 changes: 11 additions & 0 deletions libexec/overcommit-parent-command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Query the same WMI property as PowerShell without its startup overhead.
// cscript //U writes redirected output as UTF-16LE.
var pid = String(WScript.Arguments(0));
if (!/^\d+$/.test(pid)) WScript.Quit(2);
var service = GetObject('winmgmts:root/cimv2');
var processes = service.ExecQuery('SELECT CommandLine FROM Win32_Process WHERE ProcessId = ' + pid);
var items = new Enumerator(processes);
for (; !items.atEnd(); items.moveNext()) {
var command = items.item().CommandLine;
if (command !== null) WScript.StdOut.WriteLine(command);
}
66 changes: 66 additions & 0 deletions spec/overcommit/utils_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,72 @@
end
end

context 'when Windows falls back to Script Host' do
let(:command) { 'git -C "C:/Users/José/项目" commit --amend' }
let(:output) { "\uFEFF#{command}\r\n".encode('UTF-16LE') }
let(:script_result) { Overcommit::Subprocess::Result.new(0, output, '') }

before do
Overcommit::OS.stub(:windows?).and_return(true)
described_class.stub(:require).with('win32ole').and_raise(LoadError)
described_class.stub(:execute).with(array_including('cscript.exe')).
and_return(script_result)
end

it 'decodes the complete Unicode command line and removes its BOM' do
described_class.should_not_receive(:execute).with(array_including('powershell.exe'))
subject.should == command
end

context 'when the process has exited' do
let(:output) { '' }

it 'returns nil without retrying PowerShell' do
described_class.should_not_receive(:execute).with(array_including('powershell.exe'))
subject.should be_nil
end
end

context 'when the script fails' do
let(:script_result) { Overcommit::Subprocess::Result.new(1, '', 'Access denied') }

before do
described_class.stub(:execute).with(array_including('powershell.exe')).
and_return(Overcommit::Subprocess::Result.new(0, "#{command}\n", ''))
end

it 'falls back to PowerShell' do
subject.should == command
end
end

context 'when Script Host is unavailable' do
before do
described_class.stub(:execute).with(array_including('cscript.exe')).
and_raise(Errno::ENOENT)
described_class.stub(:execute).with(array_including('powershell.exe')).
and_return(Overcommit::Subprocess::Result.new(0, "#{command}\n", ''))
end

it 'falls back to PowerShell' do
subject.should == command
end
end

context 'when Script Host returns invalid Unicode' do
let(:output) { "\x00".b }

before do
described_class.stub(:execute).with(array_including('powershell.exe')).
and_return(Overcommit::Subprocess::Result.new(0, "#{command}\n", ''))
end

it 'falls back to PowerShell' do
subject.should == command
end
end
end

context 'when there is no parent' do
before do
Process.stub(:ppid) { 0 }
Expand Down
Loading