Skip to content

Commit 1b71fc9

Browse files
author
Shane da Silva
committed
Add support for invoking processes in JRuby
Previously, we used the `Wopen3` library to simplify the act of executing an external process and collecting its standard out/error output and exit status. Unfortunately, Wopen is implemented using `fork`, which isn't supported by JRuby. After much research it's clear that this is not a trivial task to support across multiple platforms, but fortunately there is a gem called `childprocess` which takes care of this for us. Unfortunately, to use the gem we have to change the signature of `command` to take an array of arguments rather than a single shell string, due to the underlying commands `childprocess` utilizes. Another thing worth mentioning is that while it's perhaps not ideal to be using temporary files to capture output when spawning external processes, I was unable to get this to work consistently when using straight-up pipes. For now, this implementation works consistently and is acceptable. Fixes sds#32 Change-Id: Ic4aa71ca61d50f38a8e4ea15b8e8dbcf146c1f69 Reviewed-on: http://gerrit.causes.com/35997 Tested-by: jenkins <jenkins@causes.com> Reviewed-by: Shane da Silva <shane@causes.com>
1 parent 86d096e commit 1b71fc9

24 files changed

+80
-34
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Overcommit Changelog
22

3+
## master (unreleased)
4+
5+
* Change `command` hook helper signature to accept an array of arguments
6+
instead of a shell string
7+
38
## 0.6.3
49

510
* `TextWidth` pre-commit hook now supports customized maximum subject line

README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@ about Overcommit on our [engineering blog](http://causes.github.io).
2929

3030
## Requirements
3131

32-
* Ruby 1.8.7+ is supported
32+
The following Ruby versions are supported:
33+
34+
* 1.8.7
35+
* 1.9.2
36+
* 1.9.3
37+
* 2.0.0
38+
* 2.1.0
3339

3440
### Dependencies
3541

lib/overcommit/hook/commit_msg/gerrit_change_id.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module Overcommit::Hook::CommitMsg
77
# edit the message after a prepare-commit-msg hook was run.
88
class GerritChangeId < Base
99
def run
10-
result = command("#{SCRIPT_LOCATION} #{commit_message_file}")
10+
result = command([SCRIPT_LOCATION, commit_message_file])
1111
return (result.success? ? :good : :bad), result.stdout
1212
end
1313

lib/overcommit/hook/post_checkout/bundle_check.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ def run
1515
private
1616

1717
def dependencies_changed?
18-
result = command("git diff --exit-code #{new_head} #{previous_head} --name-only")
18+
result = command(%w[git diff --exit-code --name-only] + [new_head, previous_head])
1919

2020
result.stdout.split("\n").any? do |file|
2121
Array(@config['include']).any? { |glob| File.fnmatch(glob, file) }
2222
end
2323
end
2424

2525
def dependencies_satisfied?
26-
command('bundle check').success?
26+
command(%w[bundle check]).success?
2727
end
2828
end
2929
end

lib/overcommit/hook/pre_commit/author_email.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module Overcommit::Hook::PreCommit
22
# Checks the format of an author's email address.
33
class AuthorEmail < Base
44
def run
5-
result = command('git config --get user.email')
5+
result = command(%w[git config --get user.email])
66
email = result.stdout.chomp
77

88
unless email =~ /#{@config['pattern']}/

lib/overcommit/hook/pre_commit/author_name.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module Overcommit::Hook::PreCommit
22
# Ensures that a commit author has a name with at least first and last names.
33
class AuthorName < Base
44
def run
5-
result = command('git config --get user.name')
5+
result = command(%w[git config --get user.name])
66
name = result.stdout.chomp
77

88
unless name.split(' ').count >= 2

lib/overcommit/hook/pre_commit/bundle_check.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ def run
88
end
99

1010
# Ignore if Gemfile.lock is not tracked by git
11-
return :good if command("git check-ignore #{LOCK_FILE}").success?
11+
return :good if command(%w[git check-ignore] + [LOCK_FILE]).success?
1212

13-
result = command('bundle check')
13+
result = command(%w[bundle check])
1414
unless result.success?
1515
return :bad, result.stdout
1616
end
1717

18-
result = command("git diff --quiet -- #{LOCK_FILE}")
18+
result = command(%w[git diff --quiet --] + [LOCK_FILE])
1919
unless result.success?
2020
return :bad, "#{LOCK_FILE} is not up-to-date -- run `bundle check`"
2121
end

lib/overcommit/hook/pre_commit/coffee_lint.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ def run
66
return :warn, 'Run `npm install -g coffeelint`'
77
end
88

9-
result = command("coffeelint --quiet #{applicable_files.join(' ')}")
9+
result = command(%w[coffeelint --quiet] + applicable_files)
1010
return :good if result.success?
1111
return :bad, result.stdout
1212
end

lib/overcommit/hook/pre_commit/css_lint.rb

+1-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ def run
66
return :warn, 'csslint not installed -- run `npm install -g csslint`'
77
end
88

9-
paths = applicable_files.join(' ')
10-
11-
result = command("csslint --quiet --format=compact #{paths} | grep 'Error - '")
9+
result = command(%w[csslint --quiet --format=compact] + applicable_files)
1210
output = result.stdout
1311
return (output !~ /Error - (?!Unknown @ rule)/ ? :good : :bad), output
1412
end

lib/overcommit/hook/pre_commit/haml_lint.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ def run
66
return :warn, 'haml-lint not installed -- run `gem install haml-lint`'
77
end
88

9-
result = command("haml-lint #{applicable_files.join(' ')}")
9+
result = command(%w[haml-lint] + applicable_files)
1010
return :good if result.success?
1111

1212
# Keep lines from the output for files that we actually modified

lib/overcommit/hook/pre_commit/hard_tabs.rb

+1-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ module Overcommit::Hook::PreCommit
22
# Checks for hard tabs in files.
33
class HardTabs < Base
44
def run
5-
paths = applicable_files.join(' ')
6-
75
# Catches hard tabs
8-
result = command("grep -IHn \"\t\" #{paths}")
6+
result = command(%w[grep -IHn] + ["\t"] + applicable_files)
97
unless result.stdout.empty?
108
return :bad, "Hard tabs detected:\n#{result.stdout}"
119
end

lib/overcommit/hook/pre_commit/js_hint.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ def run
66
return :warn, 'jshint not installed -- run `npm install -g jshint`'
77
end
88

9-
result = command("jshint #{applicable_files.join(' ')}")
9+
result = command(%w[jshint] + applicable_files)
1010
output = result.stdout
1111

1212
return (output.empty? ? :good : :bad), output

lib/overcommit/hook/pre_commit/jscs.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def run
77
return :warn, 'jscs not installed -- run `npm install -g jscs`'
88
end
99

10-
result = command("jscs --reporter=inline #{applicable_files.join(' ')}")
10+
result = command(%w[jscs --reporter=inline] + applicable_files)
1111
return :good if result.success?
1212

1313
if /Config.*not found/i =~ result.stderr

lib/overcommit/hook/pre_commit/python_flake8.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ def run
66
return :warn, 'flake8 not installed -- run `pip install flake8`'
77
end
88

9-
result = command("flake8 #{applicable_files.join(' ')}")
9+
result = command(%w[flake8] + applicable_files)
1010

1111
return (result.success? ? :good : :bad), result.stdout
1212
end

lib/overcommit/hook/pre_commit/rubocop.rb

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ def run
66
return :warn, 'Rubocop not installed -- run `gem install rubocop`'
77
end
88

9-
result = command("rubocop --format=emacs #{applicable_files.join(' ')} 2>&1")
9+
result = command(%w[rubocop --format=emacs] + applicable_files)
1010
return :good if result.success?
1111

12+
output = result.stdout + result.stderr
13+
1214
# Keep lines from the output for files that we actually modified
13-
error_lines, warning_lines = result.stdout.split("\n").partition do |output_line|
15+
error_lines, warning_lines = output.split("\n").partition do |output_line|
1416
if match = output_line.match(/^([^:]+):(\d+)/)
1517
file = match[1]
1618
line = match[2]

lib/overcommit/hook/pre_commit/scss_lint.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ def run
66
return :warn, 'scss-lint not installed -- run `gem install scss-lint`'
77
end
88

9-
result = command("scss-lint #{applicable_files.join(' ')}")
9+
result = command(%w[scss-lint] + applicable_files)
1010
return :good if result.success?
1111

1212
# Keep lines from the output for files that we actually modified

lib/overcommit/hook/pre_commit/trailing_whitespace.rb

+1-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ module Overcommit::Hook::PreCommit
22
# Checks for trailing whitespace in files.
33
class TrailingWhitespace < Base
44
def run
5-
paths = applicable_files.join(' ')
6-
7-
result = command("grep -IHn \"\\s$\" #{paths}")
5+
result = command(%w[grep -IHn \s$] + applicable_files)
86
unless result.stdout.empty?
97
return :bad, "Trailing whitespace detected:\n#{result.stdout}"
108
end

lib/overcommit/subprocess.rb

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
require 'childprocess'
2+
require 'tempfile'
3+
4+
module Overcommit
5+
# Manages execution of a child process, collecting the exit status and
6+
# standard out/error output.
7+
class Subprocess
8+
# Encapsulates the result of a process.
9+
Result = Struct.new(:status, :stderr, :stdout) do
10+
def success?
11+
status == 0
12+
end
13+
end
14+
15+
# Spawns a new process using the given array of arguments (the first
16+
# element is the command).
17+
def self.spawn(args)
18+
process = ChildProcess.build(*args)
19+
20+
err = ::Tempfile.new('err')
21+
err.sync = true
22+
out = ::Tempfile.new('out')
23+
out.sync = true
24+
25+
process.io.stderr = err
26+
process.io.stdout = out
27+
28+
process.start
29+
process.wait
30+
31+
err.rewind
32+
out.rewind
33+
34+
Result.new(process.exit_code, err.read, out.read)
35+
end
36+
end
37+
end

lib/overcommit/utils.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
require 'wopen3'
1+
require 'overcommit/subprocess'
22

33
module Overcommit
44
# Utility functions for general use.
@@ -62,9 +62,9 @@ def in_path?(cmd)
6262
# Overcommit to call other Ruby executables without requiring that they be
6363
# specified in Overcommit's Gemfile--a nasty consequence of using
6464
# `bundle exec overcommit` while developing locally.
65-
def command(command)
65+
def command(args)
6666
with_environment 'RUBYOPT' => nil do
67-
Wopen3.system(command)
67+
Subprocess.spawn(args)
6868
end
6969
end
7070

overcommit.gemspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
2626

2727
s.required_ruby_version = '>= 1.8.7'
2828

29-
s.add_dependency 'wopen3'
29+
s.add_dependency 'childprocess', '>= 0.5.1'
3030

3131
s.add_development_dependency 'rspec', '2.14.1'
3232
s.add_development_dependency 'image_optim', '0.11.1'

spec/integration/committing_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
require 'spec_helper'
22

33
describe 'commiting' do
4-
subject { shell('git commit --allow-empty -m "Test"') }
4+
subject { shell(%w[git commit --allow-empty -m Test]) }
55

66
let(:config) { <<-YML }
77
CommitMsg:

spec/overcommit/hook/pre_commit/bundle_check_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
before do
3939
result.stub(:success? => success, :stdout => 'Bundler error message')
4040
subject.stub(:command).and_call_original
41-
subject.stub(:command).with('bundle check').and_return(result)
41+
subject.stub(:command).with(%w[bundle check]).and_return(result)
4242
end
4343

4444
context 'and bundle check exits unsuccessfully' do

spec/overcommit/hook/pre_commit/rubocop_spec.rb

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
result.stub(:stdout).and_return([
4242
'file1.rb:1:1: C: Missing top-level class documentation',
4343
].join("\n"))
44+
result.stub(:stderr).and_return('')
4445

4546
subject.stub(:modified_lines).and_return([2, 3])
4647
end
@@ -53,6 +54,7 @@
5354
result.stub(:stdout).and_return([
5455
'file1.rb:1:1: C: Missing top-level class documentation',
5556
].join("\n"))
57+
result.stub(:stderr).and_return('')
5658

5759
subject.stub(:modified_lines).and_return([1, 2])
5860
end

spec/support/shell_helpers.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
require 'wopen3'
1+
require 'overcommit/subprocess'
22

33
# Helpers for executing shell commands in tests.
44
module ShellHelpers
55
def shell(command)
6-
Wopen3.system(command)
6+
Overcommit::Subprocess.spawn(command)
77
end
88
end

0 commit comments

Comments
 (0)