From f27ab48e7e06008525d212b849cab886796e71a3 Mon Sep 17 00:00:00 2001 From: Shane da Silva Date: Thu, 5 Nov 2026 11:08:52 -0800 Subject: [PATCH] Query Windows parent commands without starting PowerShell when COM is available --- lib/overcommit/utils.rb | 30 ++++++++++++++---- spec/overcommit/utils_spec.rb | 59 +++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/lib/overcommit/utils.rb b/lib/overcommit/utils.rb index 3acdfa21..9e9d045e 100644 --- a/lib/overcommit/utils.rb +++ b/lib/overcommit/utils.rb @@ -166,12 +166,7 @@ def parent_command return if Process.ppid.zero? if OS.windows? - result = execute([ - 'powershell.exe', '-NoProfile', '-NonInteractive', '-Command', - "(Get-CimInstance Win32_Process -Filter 'ProcessId = #{Process.ppid}').CommandLine", - ]) - command = result.stdout.strip - command if result.success? && !command.empty? + windows_parent_command elsif OS.cygwin? # Cygwin's `ps` command behaves differently than the traditional # Linux version, but a comparable `procps` is provided to compensate. @@ -294,6 +289,29 @@ def matches_path?(pattern, path) private + 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. + result = execute([ + 'powershell.exe', '-NoProfile', '-NonInteractive', '-Command', + "(Get-CimInstance Win32_Process -Filter 'ProcessId = #{Process.ppid}').CommandLine", + ]) + command = result.stdout.strip + command if result.success? && !command.empty? + end + + # Avoid starting PowerShell for every query when Ruby provides COM support. + def native_windows_parent_command + require 'win32ole' + service = WIN32OLE.connect('winmgmts:root/cimv2') + processes = service.ExecQuery( + "SELECT CommandLine FROM Win32_Process WHERE ProcessId = #{Process.ppid}" + ) + processes.to_enum.first&.CommandLine + end + # Log debug output. # # This is necessary since some specs indirectly call utility functions but diff --git a/spec/overcommit/utils_spec.rb b/spec/overcommit/utils_spec.rb index 7c341617..b69dafe2 100644 --- a/spec/overcommit/utils_spec.rb +++ b/spec/overcommit/utils_spec.rb @@ -144,6 +144,7 @@ context 'when the Windows process query cannot be launched' do before do Overcommit::OS.stub(:windows?).and_return(true) + described_class.stub(:native_windows_parent_command).and_raise(LoadError) described_class.stub(:execute).and_raise(ChildProcess::LaunchError, 'Access denied') end @@ -153,6 +154,7 @@ context 'when the Windows parent has already exited' do before do Overcommit::OS.stub(:windows?).and_return(true) + described_class.stub(:native_windows_parent_command).and_raise(LoadError) described_class.stub(:execute). and_return(Overcommit::Subprocess::Result.new(0, '', '')) end @@ -160,6 +162,63 @@ it { should be_nil } end + context 'when Windows provides COM support' do + let(:service) { double('WMI service') } + let(:command) { 'git -C "C:/Users/José/项目" commit --amend' } + let(:processes) { [double('process', CommandLine: command)] } + + before do + Overcommit::OS.stub(:windows?).and_return(true) + described_class.stub(:require).with('win32ole').and_return(true) + stub_const('WIN32OLE', double(connect: service)) + service.stub(:ExecQuery). + with("SELECT CommandLine FROM Win32_Process WHERE ProcessId = #{Process.pid}"). + and_return(processes) + end + + it 'returns the complete Unicode command line without launching PowerShell' do + described_class.should_not_receive(:execute) + subject.should == command + end + + context 'when the process has exited' do + let(:processes) { [] } + + it { should be_nil } + end + + context 'when the command line is inaccessible' do + let(:command) { nil } + + it { should be_nil } + end + + context 'when COM access is denied' do + before do + service.stub(:ExecQuery).and_raise(StandardError, 'Access denied') + described_class.stub(:execute). + and_return(Overcommit::Subprocess::Result.new(0, "git commit\n", '')) + end + + it 'falls back to PowerShell' do + subject.should == 'git commit' + end + end + end + + context 'when win32ole is not installed' do + before do + Overcommit::OS.stub(:windows?).and_return(true) + described_class.stub(:require).with('win32ole').and_raise(LoadError) + described_class.stub(:execute). + and_return(Overcommit::Subprocess::Result.new(0, "git commit\n", '')) + end + + it 'falls back to PowerShell' do + subject.should == 'git commit' + end + end + context 'when there is no parent' do before do Process.stub(:ppid) { 0 }