grit 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grit might be problematic. Click here for more details.

@@ -1,3 +1,38 @@
1
+ == 2.4.0 / 2011-01-06
2
+ * Major Enhancements
3
+ * Add support for parsing git notes.
4
+ * Add `git cat-file --batch` support with Grit::Repo#batch.
5
+ * Grit::Process is a custom written external command invocation heavily
6
+ optimized for running git commands quickly and efficiently.
7
+ * Grit::Git#native takes an :input option for piping data into git
8
+ commands
9
+ * Grit::Git#native takes an :env option for setting the git child
10
+ process's
11
+ environment without futsing with the parent's environment.
12
+ * Grit::Git#native takes an :chdir option for setting the current working
13
+ directory (PWD) of the git child process.
14
+ * Grit::Git#native takes an :raise => true option that causes an exception
15
+ to be raised when the git child process exits non-zero.
16
+ * Minor Enhancements
17
+ * Grit::Index#commit supports custom committer/author names and dates.
18
+ * Performance enhancements with internal command output buffering.
19
+ * Reduce fork/execs needed to execute a smoke command from between 3-4
20
+ to 1.
21
+ * Git child processes are now properly parented under the grit Ruby
22
+ process instead of being dropped under init.
23
+ * Bug Fixes
24
+ * Zero-Padding issue in Grit::Index was fixed.
25
+ * Fix issue where Repo#diff skips the first diff (#42)
26
+ * Fix Repo.init_bare for repo names not ending in .git (#40)
27
+ * Fix a variety of process hangs when git stderr output or data written
28
+ to stdin exceeded PIPE_BUF bytes.
29
+
30
+ == 2.3.2 / 2011-01-06
31
+ * Erroneously released. SemVer violation and misc release screwups.
32
+
33
+ == 2.3.1
34
+ * Skipped for unknown reasons.
35
+
1
36
  == 2.3.0 / 2010-09-29
2
37
  * Minor Enhancements
3
38
  * Add Grit::Repo.init.
@@ -4,8 +4,8 @@ Gem::Specification.new do |s|
4
4
  s.rubygems_version = '1.3.5'
5
5
 
6
6
  s.name = 'grit'
7
- s.version = '2.3.0'
8
- s.date = '2010-09-29'
7
+ s.version = '2.4.0'
8
+ s.date = '2011-01-06'
9
9
  s.rubyforge_project = 'grit'
10
10
 
11
11
  s.summary = "Ruby Git bindings."
@@ -59,9 +59,10 @@ Gem::Specification.new do |s|
59
59
  lib/grit/git-ruby/repository.rb
60
60
  lib/grit/git.rb
61
61
  lib/grit/index.rb
62
+ lib/grit/jruby.rb
62
63
  lib/grit/lazy.rb
63
64
  lib/grit/merge.rb
64
- lib/grit/open3_detach.rb
65
+ lib/grit/process.rb
65
66
  lib/grit/ref.rb
66
67
  lib/grit/repo.rb
67
68
  lib/grit/ruby1.9.rb
@@ -9,27 +9,26 @@ require 'timeout'
9
9
  require 'logger'
10
10
  require 'digest/sha1'
11
11
 
12
- if defined? RUBY_ENGINE && RUBY_ENGINE == 'jruby'
13
- require 'open3'
14
- elsif RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin/
15
- require 'win32/open3'
16
- else
17
- require 'grit/open3_detach'
18
- end
19
-
20
12
  # third party
21
- require 'rubygems'
13
+
22
14
  begin
23
- gem "mime-types", ">=0"
24
15
  require 'mime/types'
25
- rescue Gem::LoadError => e
26
- puts "WARNING: Gem LoadError: #{e.message}"
16
+ require 'rubygems'
17
+ rescue LoadError
18
+ require 'rubygems'
19
+ begin
20
+ gem "mime-types", ">=0"
21
+ require 'mime/types'
22
+ rescue Gem::LoadError => e
23
+ puts "WARNING: Gem LoadError: #{e.message}"
24
+ end
27
25
  end
28
26
 
29
27
  # ruby 1.9 compatibility
30
28
  require 'grit/ruby1.9'
31
29
 
32
30
  # internal requires
31
+ require 'grit/process'
33
32
  require 'grit/lazy'
34
33
  require 'grit/errors'
35
34
  require 'grit/git-ruby'
@@ -50,8 +49,13 @@ require 'grit/submodule'
50
49
  require 'grit/blame'
51
50
  require 'grit/merge'
52
51
 
52
+ # platform specific requires
53
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/
54
+ require 'grit/jruby'
55
+ end
56
+
53
57
  module Grit
54
- VERSION = '2.3.0'
58
+ VERSION = '2.4.0'
55
59
 
56
60
  class << self
57
61
  # Set +debug+ to true to log all git calls and responses
@@ -11,12 +11,10 @@ module Grit
11
11
  alias_method :to_s, :name
12
12
 
13
13
  # Create an Actor from a string.
14
- # +str+ is the string, which is expected to be in regular git format
15
14
  #
16
- # Format
17
- # John Doe <jdoe@example.com>
15
+ # str - The String in this format: 'John Doe <jdoe@example.com>'
18
16
  #
19
- # Returns Actor
17
+ # Returns Git::Actor.
20
18
  def self.from_string(str)
21
19
  case str
22
20
  when /<.+>/
@@ -27,6 +25,24 @@ module Grit
27
25
  end
28
26
  end
29
27
 
28
+ # Outputs an actor string for Git commits.
29
+ #
30
+ # actor = Actor.new('bob', 'bob@email.com')
31
+ # actor.output(time) # => "bob <bob@email.com> UNIX_TIME +0700"
32
+ #
33
+ # time - The Time the commit was authored or committed.
34
+ #
35
+ # Returns a String.
36
+ def output(time)
37
+ out = @name.to_s.dup
38
+ if @email
39
+ out << " <#{@email}>"
40
+ end
41
+ hours = (time.utc_offset.to_f / 3600).to_i # 60 * 60, seconds to hours
42
+ rem = time.utc_offset.abs % 3600
43
+ out << " #{time.to_i} #{hours >= 0 ? :+ : :-}#{hours.abs.to_s.rjust(2, '0')}#{rem.to_s.rjust(2, '0')}"
44
+ end
45
+
30
46
  # Pretty object inspection
31
47
  def inspect
32
48
  %Q{#<Grit::Actor "#{@name} <#{@email}>">}
@@ -26,16 +26,21 @@ module Grit
26
26
  if line[0, 1] == "\t"
27
27
  lines << line[1, line.size]
28
28
  elsif m = /^(\w{40}) (\d+) (\d+)/.match(line)
29
- if !commits[m[1]]
30
- commits[m[1]] = @repo.commit(m[1])
31
- end
32
- info[m[3].to_i] = [commits[m[1]], m[2].to_i]
29
+ commit_id, old_lineno, lineno = m[1], m[2].to_i, m[3].to_i
30
+ commits[commit_id] = nil if !commits.key?(commit_id)
31
+ info[lineno] = [commit_id, old_lineno]
33
32
  end
34
33
  end
35
34
 
35
+ # load all commits in single call
36
+ @repo.batch(*commits.keys).each do |commit|
37
+ commits[commit.id] = commit
38
+ end
39
+
36
40
  # get it together
37
- info.sort.each do |lineno, commit|
38
- final << BlameLine.new(lineno, commit[1], commit[0], lines[lineno - 1])
41
+ info.sort.each do |lineno, (commit_id, old_lineno)|
42
+ commit = commits[commit_id]
43
+ final << BlameLine.new(lineno, old_lineno, commit, lines[lineno - 1])
39
44
  end
40
45
 
41
46
  @lines = final
@@ -58,4 +63,4 @@ module Grit
58
63
 
59
64
  end # Blame
60
65
 
61
- end # Grit
66
+ end # Grit
@@ -1,6 +1,8 @@
1
1
  module Grit
2
2
 
3
3
  class Commit
4
+ extend Lazy
5
+
4
6
  attr_reader :id
5
7
  attr_reader :repo
6
8
  lazy_reader :parents
@@ -13,6 +15,31 @@ module Grit
13
15
  lazy_reader :short_message
14
16
  lazy_reader :author_string
15
17
 
18
+ # Parses output from the `git-cat-file --batch'.
19
+ #
20
+ # repo - Grit::Repo instance.
21
+ # sha - String SHA of the Commit.
22
+ # size - Fixnum size of the object.
23
+ # object - Parsed String output from `git cat-file --batch`.
24
+ #
25
+ # Returns an Array of Grit::Commit objects.
26
+ def self.parse_batch(repo, sha, size, object)
27
+ info, message = object.split("\n\n", 2)
28
+
29
+ lines = info.split("\n")
30
+ tree = lines.shift.split(' ', 2).last
31
+ parents = []
32
+ parents << lines.shift[7..-1] while lines.first[0, 6] == 'parent'
33
+ author, authored_date = Grit::Commit.actor(lines.shift)
34
+ committer, committed_date = Grit::Commit.actor(lines.shift)
35
+
36
+ Grit::Commit.new(
37
+ repo, sha, parents, tree,
38
+ author, authored_date,
39
+ committer, committed_date,
40
+ message.to_s.split("\n"))
41
+ end
42
+
16
43
  # Instantiate a new Commit
17
44
  # +id+ is the id of the commit
18
45
  # +parents+ is an array of commit ids (will be converted into Commit instances)
@@ -167,7 +194,7 @@ module Grit
167
194
 
168
195
  def show
169
196
  if parents.size > 1
170
- diff = @repo.git.native("diff #{parents[0].id}...#{parents[1].id}", {:full_index => true})
197
+ diff = @repo.git.native(:diff, {:full_index => true}, "#{parents[0].id}...#{parents[1].id}")
171
198
  else
172
199
  diff = @repo.git.show({:full_index => true, :pretty => 'raw'}, @id)
173
200
  end
@@ -214,6 +241,17 @@ module Grit
214
241
  @repo.git.format_patch({'1' => true, :stdout => true}, to_s)
215
242
  end
216
243
 
244
+ def notes
245
+ ret = {}
246
+ notes = Note.find_all(@repo)
247
+ notes.each do |note|
248
+ if n = note.commit.tree/(self.id)
249
+ ret[note.name] = n.data
250
+ end
251
+ end
252
+ ret
253
+ end
254
+
217
255
  # Pretty object inspection
218
256
  def inspect
219
257
  %Q{#<Grit::Commit "#{@id}">}
@@ -317,7 +317,7 @@ module Grit
317
317
  if indata.size == 0
318
318
  raise PackFormatError, 'error reading pack data'
319
319
  end
320
- outdata += zstr.inflate(indata)
320
+ outdata << zstr.inflate(indata)
321
321
  end
322
322
  if outdata.size > destsize
323
323
  raise PackFormatError, 'error reading pack data'
@@ -351,9 +351,9 @@ module Grit
351
351
  cp_size |= delta.getord(pos += 1) << 16 if c & 0x40 != 0
352
352
  cp_size = 0x10000 if cp_size == 0
353
353
  pos += 1
354
- dest += base[cp_off,cp_size]
354
+ dest << base[cp_off,cp_size]
355
355
  elsif c != 0
356
- dest += delta[pos,c]
356
+ dest << delta[pos,c]
357
357
  pos += c
358
358
  else
359
359
  raise PackFormatError, 'invalid delta data'
@@ -701,21 +701,35 @@ module Grit
701
701
  @loose
702
702
  end
703
703
 
704
- def load_alternate_loose(path)
705
- # load alternate loose, too
704
+ def each_alternate_path(path)
706
705
  alt = File.join(path, 'info/alternates')
707
- if File.exists?(alt)
708
- File.readlines(alt).each do |line|
709
- next if @loaded.include?(line.chomp)
710
- if line[0, 2] == '..'
711
- line = File.expand_path(File.join(@git_dir, line))
712
- end
713
- load_loose(line.chomp)
714
- load_alternate_loose(line.chomp)
706
+ return if !File.exists?(alt)
707
+
708
+ File.readlines(alt).each do |line|
709
+ path = line.chomp
710
+ if path[0, 2] == '..'
711
+ yield File.expand_path(File.join(@git_dir, 'objects', path))
712
+
713
+ # XXX this is here for backward compatibility with grit < 2.3.0
714
+ # relative alternate objects paths are expanded relative to the
715
+ # objects directory, not the git repository directory.
716
+ yield File.expand_path(File.join(@git_dir, path))
717
+ else
718
+ yield path
715
719
  end
716
720
  end
717
721
  end
718
722
 
723
+ def load_alternate_loose(path)
724
+ # load alternate loose, too
725
+ each_alternate_path path do |path|
726
+ next if @loaded.include?(path)
727
+ next if !File.exist?(path)
728
+ load_loose(path)
729
+ load_alternate_loose(path)
730
+ end
731
+ end
732
+
719
733
  def load_loose(path)
720
734
  @loaded << path
721
735
  return if !File.exists?(path)
@@ -732,17 +746,11 @@ module Grit
732
746
  end
733
747
 
734
748
  def load_alternate_packs(path)
735
- alt = File.join(path, 'info/alternates')
736
- if File.exists?(alt)
737
- File.readlines(alt).each do |line|
738
- if line[0, 2] == '..'
739
- line = File.expand_path(File.join(@git_dir, line))
740
- end
741
- full_pack = File.join(line.chomp, 'pack')
742
- next if @loaded_packs.include?(full_pack)
743
- load_packs(full_pack)
744
- load_alternate_packs(File.join(line.chomp))
745
- end
749
+ each_alternate_path path do |path|
750
+ full_pack = File.join(path, 'pack')
751
+ next if @loaded_packs.include?(full_pack)
752
+ load_packs(full_pack)
753
+ load_alternate_packs(path)
746
754
  end
747
755
  end
748
756
 
@@ -3,7 +3,8 @@ module Grit
3
3
 
4
4
  class Git
5
5
  class GitTimeout < RuntimeError
6
- attr_reader :command, :bytes_read
6
+ attr_accessor :command
7
+ attr_accessor :bytes_read
7
8
 
8
9
  def initialize(command = nil, bytes_read = nil)
9
10
  @command = command
@@ -11,6 +12,25 @@ module Grit
11
12
  end
12
13
  end
13
14
 
15
+ # Raised when a native git command exits with non-zero.
16
+ class CommandFailed < StandardError
17
+ # The full git command that failed as a String.
18
+ attr_reader :command
19
+
20
+ # The integer exit status.
21
+ attr_reader :exitstatus
22
+
23
+ # Everything output on the command's stderr as a String.
24
+ attr_reader :err
25
+
26
+ def initialize(command, exitstatus, err='')
27
+ @command = command
28
+ @exitstatus = exitstatus
29
+ @err = err
30
+ super "Command exited with #{exitstatus}: #{command}"
31
+ end
32
+ end
33
+
14
34
  undef_method :clone
15
35
 
16
36
  include GitRuby
@@ -34,14 +54,16 @@ module Grit
34
54
  end
35
55
 
36
56
  class << self
37
- attr_accessor :git_binary, :git_timeout, :git_max_size
57
+ attr_accessor :git_timeout, :git_max_size
58
+ def git_binary
59
+ @git_binary ||=
60
+ ENV['PATH'].split(':').
61
+ map { |p| File.join(p, 'git') }.
62
+ find { |p| File.exist?(p) }
63
+ end
64
+ attr_writer :git_binary
38
65
  end
39
66
 
40
- if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin/
41
- self.git_binary = "git" # using search path
42
- else
43
- self.git_binary = "/usr/bin/env git"
44
- end
45
67
  self.git_timeout = 10
46
68
  self.git_max_size = 5242880 # 5.megabytes
47
69
 
@@ -206,35 +228,157 @@ module Grit
206
228
  # RAW CALLS WITH ENV SETTINGS END
207
229
 
208
230
 
209
-
210
- # Run the given git command with the specified arguments and return
211
- # the result as a String
212
- # +cmd+ is the command
213
- # +options+ is a hash of Ruby style options
214
- # +args+ is the list of arguments (to be joined by spaces)
231
+ # Execute a git command, bypassing any library implementation.
232
+ #
233
+ # cmd - The name of the git command as a Symbol. Underscores are
234
+ # converted to dashes as in :rev_parse => 'rev-parse'.
235
+ # options - Command line option arguments passed to the git command.
236
+ # Single char keys are converted to short options (:a => -a).
237
+ # Multi-char keys are converted to long options (:arg => '--arg').
238
+ # Underscores in keys are converted to dashes. These special options
239
+ # are used to control command execution and are not passed in command
240
+ # invocation:
241
+ # :timeout - Maximum amount of time the command can run for before
242
+ # being aborted. When true, use Grit::Git.git_timeout; when numeric,
243
+ # use that number of seconds; when false or 0, disable timeout.
244
+ # :base - Set false to avoid passing the --git-dir argument when
245
+ # invoking the git command.
246
+ # :env - Hash of environment variable key/values that are set on the
247
+ # child process.
248
+ # :raise - When set true, commands that exit with a non-zero status
249
+ # raise a CommandFailed exception. This option is available only on
250
+ # platforms that support fork(2).
251
+ # args - Non-option arguments passed on the command line.
252
+ #
253
+ # Optionally yields to the block an IO object attached to the child
254
+ # process's STDIN.
215
255
  #
216
256
  # Examples
257
+ # git.native(:rev_list, {:max_count => 10, :header => true}, "master")
258
+ #
259
+ # Returns a String with all output written to the child process's stdout.
260
+ # Raises Grit::Git::GitTimeout when the timeout is exceeded or when more
261
+ # than Grit::Git.git_max_size bytes are output.
262
+ # Raises Grit::Git::CommandFailed when the :raise option is set true and the
263
+ # git command exits with a non-zero exit status. The CommandFailed's #command,
264
+ # #exitstatus, and #err attributes can be used to retrieve additional
265
+ # detail about the error.
266
+ def native(cmd, options = {}, *args, &block)
267
+ args = args.first if args.size == 1 && args[0].is_a?(Array)
268
+ args.map! { |a| a.to_s.strip }
269
+ args.reject! { |a| a.empty? }
270
+
271
+ # special option arguments
272
+ env = options.delete(:env) || {}
273
+ raise_errors = options.delete(:raise)
274
+
275
+ # fall back to using a shell when the last argument looks like it wants to
276
+ # start a pipeline for compatibility with previous versions of grit.
277
+ return run(prefix, cmd, '', options, args) if args[-1].to_s[0] == ?|
278
+
279
+ # more options
280
+ input = options.delete(:input)
281
+ timeout = options.delete(:timeout); timeout = true if timeout.nil?
282
+ base = options.delete(:base); base = true if base.nil?
283
+ chdir = options.delete(:chdir)
284
+
285
+ # build up the git process argv
286
+ argv = []
287
+ argv << Git.git_binary
288
+ argv << "--git-dir=#{git_dir}" if base
289
+ argv << cmd.to_s.tr('_', '-')
290
+ argv.concat(options_to_argv(options))
291
+ argv.concat(args)
292
+
293
+ # run it and deal with fallout
294
+ Grit.log(argv.join(' ')) if Grit.debug
295
+
296
+ process =
297
+ Grit::Process.new(argv, env,
298
+ :input => input,
299
+ :chdir => chdir,
300
+ :timeout => (Grit::Git.git_timeout if timeout == true),
301
+ :max => (Grit::Git.git_max_size if timeout == true)
302
+ )
303
+ status = process.status
304
+ Grit.log(process.out) if Grit.debug
305
+ Grit.log(process.err) if Grit.debug
306
+ if raise_errors && !status.success?
307
+ raise CommandFailed.new(argv.join(' '), status.exitstatus, process.err)
308
+ else
309
+ process.out
310
+ end
311
+ rescue Grit::Process::TimeoutExceeded, Grit::Process::MaximumOutputExceeded
312
+ raise GitTimeout, argv.join(' ')
313
+ end
314
+
315
+ # Methods not defined by a library implementation execute the git command
316
+ # using #native, passing the method name as the git command name.
317
+ #
318
+ # Examples:
217
319
  # git.rev_list({:max_count => 10, :header => true}, "master")
320
+ def method_missing(cmd, options={}, *args, &block)
321
+ native(cmd, options, *args, &block)
322
+ end
323
+
324
+ # Transform a ruby-style options hash to command-line arguments sutiable for
325
+ # use with Kernel::exec. No shell escaping is performed.
218
326
  #
219
- # Returns String
220
- def method_missing(cmd, options = {}, *args)
221
- run('', cmd, '', options, args)
327
+ # Returns an Array of String option arguments.
328
+ def options_to_argv(options)
329
+ argv = []
330
+ options.each do |key, val|
331
+ if key.to_s.size == 1
332
+ if val == true
333
+ argv << "-#{key}"
334
+ elsif val == false
335
+ # ignore
336
+ else
337
+ argv << "-#{key}"
338
+ argv << val.to_s
339
+ end
340
+ else
341
+ if val == true
342
+ argv << "--#{key.to_s.tr('_', '-')}"
343
+ elsif val == false
344
+ # ignore
345
+ else
346
+ argv << "--#{key.to_s.tr('_', '-')}=#{val}"
347
+ end
348
+ end
349
+ end
350
+ argv
222
351
  end
223
352
 
224
- # Bypass any pure Ruby implementations and go straight to the native Git command
353
+ # Simple wrapper around Timeout::timeout.
354
+ #
355
+ # seconds - Float number of seconds before a Timeout::Error is raised. When
356
+ # true, the Grit::Git.git_timeout value is used. When the timeout is less
357
+ # than or equal to 0, no timeout is established.
225
358
  #
226
- # Returns String
227
- def native(cmd, options = {}, *args)
228
- method_missing(cmd, options, *args)
359
+ # Raises Timeout::Error when the timeout has elapsed.
360
+ def timeout_after(seconds)
361
+ seconds = self.class.git_timeout if seconds == true
362
+ if seconds && seconds > 0
363
+ Timeout.timeout(seconds) { yield }
364
+ else
365
+ yield
366
+ end
229
367
  end
230
368
 
231
- def run(prefix, cmd, postfix, options, args)
369
+ # DEPRECATED OPEN3-BASED COMMAND EXECUTION
370
+
371
+ def run(prefix, cmd, postfix, options, args, &block)
232
372
  timeout = options.delete(:timeout) rescue nil
233
373
  timeout = true if timeout.nil?
234
374
 
235
375
  base = options.delete(:base) rescue nil
236
376
  base = true if base.nil?
237
377
 
378
+ if input = options.delete(:input)
379
+ block = lambda { |stdin| stdin.write(input) }
380
+ end
381
+
238
382
  opt_args = transform_options(options)
239
383
 
240
384
  if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin/
@@ -246,50 +390,29 @@ module Grit
246
390
  gitdir = base ? "--git-dir='#{self.git_dir}'" : ""
247
391
  call = "#{prefix}#{Git.git_binary} #{gitdir} #{cmd.to_s.gsub(/_/, '-')} #{(opt_args + ext_args).join(' ')}#{e(postfix)}"
248
392
  end
393
+
249
394
  Grit.log(call) if Grit.debug
250
- response, err = timeout ? sh(call) : wild_sh(call)
395
+ response, err = timeout ? sh(call, &block) : wild_sh(call, &block)
251
396
  Grit.log(response) if Grit.debug
252
397
  Grit.log(err) if Grit.debug
253
398
  response
254
399
  end
255
400
 
256
- def sh(command)
257
- ret, err = '', ''
258
- Open3.popen3(command) do |_, stdout, stderr|
259
- Timeout.timeout(self.class.git_timeout) do
260
- while tmp = stdout.read(1024)
261
- ret += tmp
262
- if (@bytes_read += tmp.size) > self.class.git_max_size
263
- bytes = @bytes_read
264
- @bytes_read = 0
265
- raise GitTimeout.new(command, bytes)
266
- end
267
- end
268
- end
269
-
270
- while tmp = stderr.read(1024)
271
- err += tmp
272
- end
273
- end
274
- [ret, err]
275
- rescue Timeout::Error, Grit::Git::GitTimeout
276
- bytes = @bytes_read
277
- @bytes_read = 0
278
- raise GitTimeout.new(command, bytes)
279
- end
280
-
281
- def wild_sh(command)
282
- ret, err = '', ''
283
- Open3.popen3(command) do |_, stdout, stderr|
284
- while tmp = stdout.read(1024)
285
- ret += tmp
286
- end
401
+ def sh(command, &block)
402
+ process =
403
+ Grit::Process.new(
404
+ command, {},
405
+ :timeout => Git.git_timeout,
406
+ :max => Git.git_max_size
407
+ )
408
+ [process.out, process.err]
409
+ rescue Grit::Process::TimeoutExceeded, Grit::Process::MaximumOutputExceeded
410
+ raise GitTimeout, command
411
+ end
287
412
 
288
- while tmp = stderr.read(1024)
289
- err += tmp
290
- end
291
- end
292
- [ret, err]
413
+ def wild_sh(command, &block)
414
+ process = Grit::Process.new(command)
415
+ [process.out, process.err]
293
416
  end
294
417
 
295
418
  # Transform Ruby style options into git command line options
@@ -63,7 +63,27 @@ module Grit
63
63
  self.current_tree = self.repo.tree(tree)
64
64
  end
65
65
 
66
- # Public: Commit the contents of the index
66
+ # Public: Commit the contents of the index. This method supports two
67
+ # formats for arguments:
68
+ #
69
+ # message - The String commit message.
70
+ # options - An optional Hash of index options.
71
+ # :parents - Array of String commit SHA1s or Grit::Commit
72
+ # objects to attach this commit to to form a
73
+ # new head (default: nil).
74
+ # :actor - The Grit::Actor details of the user making
75
+ # the commit (default: nil).
76
+ # :last_tree - The String SHA1 of a tree to compare with
77
+ # in order to avoid making empty commits
78
+ # (default: nil).
79
+ # :head - The String branch name to write this head to
80
+ # (default: "master").
81
+ # :committed_date - The Time that the commit was made.
82
+ # (Default: Time.now)
83
+ # :authored_date - The Time that the commit was authored.
84
+ # (Default: committed_date)
85
+ #
86
+ # The legacy argument style looks like:
67
87
  #
68
88
  # message - The String commit message.
69
89
  # parents - Array of String commit SHA1s or Grit::Commit objects to
@@ -77,6 +97,20 @@ module Grit
77
97
  #
78
98
  # Returns a String of the SHA1 of the new commit.
79
99
  def commit(message, parents = nil, actor = nil, last_tree = nil, head = 'master')
100
+ if parents.is_a?(Hash)
101
+ actor = parents[:actor]
102
+ committer = parents[:committer]
103
+ author = parents[:author]
104
+ last_tree = parents[:last_tree]
105
+ head = parents[:head]
106
+ committed_date = parents[:committed_date]
107
+ authored_date = parents[:authored_date]
108
+ parents = parents[:parents]
109
+ end
110
+
111
+ committer ||= actor
112
+ author ||= committer
113
+
80
114
  tree_sha1 = write_tree(self.tree, self.current_tree)
81
115
 
82
116
  # don't write identical commits
@@ -88,18 +122,16 @@ module Grit
88
122
  contents << ['parent', p].join(' ')
89
123
  end if parents
90
124
 
91
- if actor
92
- name = actor.name
93
- email = actor.email
94
- else
125
+ committer ||= begin
95
126
  config = Config.new(self.repo)
96
- name = config['user.name']
97
- email = config['user.email']
127
+ Actor.new(config['user.name'], config['user.email'])
98
128
  end
129
+ author ||= committer
130
+ committed_date ||= Time.now
131
+ authored_date ||= committed_date
99
132
 
100
- author_string = "#{name} <#{email}> #{Time.now.to_i} -0700" # !! TODO : gotta fix this
101
- contents << ['author', author_string].join(' ')
102
- contents << ['committer', author_string].join(' ')
133
+ contents << ['author', author.output(authored_date)].join(' ')
134
+ contents << ['committer', committer.output(committed_date)].join(' ')
103
135
  contents << ''
104
136
  contents << message
105
137
 
@@ -125,7 +157,8 @@ module Grit
125
157
  sha = [obj.id].pack("H*")
126
158
  k = obj.name
127
159
  k += '/' if (obj.class == Grit::Tree)
128
- tree_contents[k] = "%s %s\0%s" % [obj.mode.to_s, obj.name, sha]
160
+ tmode = obj.mode.to_i.to_s ## remove zero-padding
161
+ tree_contents[k] = "%s %s\0%s" % [tmode, obj.name, sha]
129
162
  end if now_tree
130
163
 
131
164
  # overwrite with new tree contents
@@ -0,0 +1,45 @@
1
+ require 'grit/process'
2
+
3
+ module Grit
4
+ # Override the Grit::Process class's popen4 and waitpid methods to work around
5
+ # various quirks in JRuby.
6
+ class Process
7
+ # Use JRuby's built in IO.popen4 but emulate the special spawn env
8
+ # and options arguments as best we can.
9
+ def popen4(*argv)
10
+ env = (argv.shift if argv[0].is_a?(Hash)) || {}
11
+ opt = (argv.pop if argv[-1].is_a?(Hash)) || {}
12
+
13
+ # emulate :chdir option
14
+ if opt[:chdir]
15
+ previous_dir = Dir.pwd
16
+ Dir.chdir(opt[:chdir])
17
+ else
18
+ previous_dir = nil
19
+ end
20
+
21
+ # emulate :env option
22
+ if env.size > 0
23
+ previous_env = ENV
24
+ ENV.merge!(env)
25
+ else
26
+ previous_env = nil
27
+ end
28
+
29
+ pid, stdin, stdout, stderr = IO.popen4(*argv)
30
+ ensure
31
+ ENV.replace(previous_env) if previous_env
32
+ Dir.chdir(previous_dir) if previous_dir
33
+ end
34
+
35
+ # JRuby always raises ECHILD on pids returned from its IO.popen4 method
36
+ # for some reason. Return a fake Process::Status object.
37
+ FakeStatus = Struct.new(:pid, :exitstatus, :success?, :fake?)
38
+ def waitpid(pid)
39
+ ::Process::waitpid(pid)
40
+ $?
41
+ rescue Errno::ECHILD
42
+ FakeStatus.new(pid, 0, true, true)
43
+ end
44
+ end
45
+ end
@@ -16,6 +16,10 @@
16
16
  # => 2
17
17
  #
18
18
  module Lazy
19
+ def self.extended(klass)
20
+ klass.send(:attr_writer, :lazy_source)
21
+ end
22
+
19
23
  def lazy_reader(*args)
20
24
  args.each do |arg|
21
25
  ivar = "@#{arg}"
@@ -28,6 +32,4 @@ module Lazy
28
32
  end
29
33
  end
30
34
  end
31
- end
32
-
33
- Object.extend Lazy unless Object.ancestors.include? Lazy
35
+ end
@@ -0,0 +1,294 @@
1
+ module Grit
2
+ # Grit::Process includes logic for executing child processes and
3
+ # reading/writing from their standard input, output, and error streams.
4
+ #
5
+ # Create an run a process to completion:
6
+ #
7
+ # >> process = Grit::Process.new(['git', '--help'])
8
+ #
9
+ # Retrieve stdout or stderr output:
10
+ #
11
+ # >> process.out
12
+ # => "usage: git [--version] [--exec-path[=GIT_EXEC_PATH]]\n ..."
13
+ # >> process.err
14
+ # => ""
15
+ #
16
+ # Check process exit status information:
17
+ #
18
+ # >> process.status
19
+ # => #<Process::Status: pid=80718,exited(0)>
20
+ #
21
+ # Grit::Process is designed to take all input in a single string and
22
+ # provides all output as single strings. It is therefore not well suited
23
+ # to streaming large quantities of data in and out of commands.
24
+ #
25
+ # Q: Why not use popen3 or hand-roll fork/exec code?
26
+ #
27
+ # - It's more efficient than popen3 and provides meaningful process
28
+ # hierarchies because it performs a single fork/exec. (popen3 double forks
29
+ # to avoid needing to collect the exit status and also calls
30
+ # Process::detach which creates a Ruby Thread!!!!).
31
+ #
32
+ # - It's more portable than hand rolled pipe, fork, exec code because
33
+ # fork(2) and exec(2) aren't available on all platforms. In those cases,
34
+ # Grit::Process falls back to using whatever janky substitutes the platform
35
+ # provides.
36
+ #
37
+ # - It handles all max pipe buffer hang cases, which is non trivial to
38
+ # implement correctly and must be accounted for with either popen3 or
39
+ # hand rolled fork/exec code.
40
+ class Process
41
+ # Create and execute a new process.
42
+ #
43
+ # argv - Array of [command, arg1, ...] strings to use as the new
44
+ # process's argv. When argv is a String, the shell is used
45
+ # to interpret the command.
46
+ # env - The new process's environment variables. This is merged with
47
+ # the current environment as if by ENV.merge(env).
48
+ # options - Additional options:
49
+ # :input => str to write str to the process's stdin.
50
+ # :timeout => int number of seconds before we given up.
51
+ # :max => total number of output bytes
52
+ # A subset of Process:spawn options are also supported on all
53
+ # platforms:
54
+ # :chdir => str to start the process in different working dir.
55
+ #
56
+ # Returns a new Process instance that has already executed to completion.
57
+ # The out, err, and status attributes are immediately available.
58
+ def initialize(argv, env={}, options={})
59
+ @argv = argv
60
+ @env = env
61
+
62
+ @options = options.dup
63
+ @input = @options.delete(:input)
64
+ @timeout = @options.delete(:timeout)
65
+ @max = @options.delete(:max)
66
+ @options.delete(:chdir) if @options[:chdir].nil?
67
+
68
+ exec!
69
+ end
70
+
71
+ # All data written to the child process's stdout stream as a String.
72
+ attr_reader :out
73
+
74
+ # All data written to the child process's stderr stream as a String.
75
+ attr_reader :err
76
+
77
+ # A Process::Status object with information on how the child exited.
78
+ attr_reader :status
79
+
80
+ # Total command execution time (wall-clock time)
81
+ attr_reader :runtime
82
+
83
+ # Determine if the process did exit with a zero exit status.
84
+ def success?
85
+ @status && @status.success?
86
+ end
87
+
88
+ private
89
+ # Execute command, write input, and read output. This is called
90
+ # immediately when a new instance of this object is initialized.
91
+ def exec!
92
+ # when argv is a string, use /bin/sh to interpret command
93
+ argv = @argv
94
+ argv = ['/bin/sh', '-c', argv.to_str] if argv.respond_to?(:to_str)
95
+
96
+ # spawn the process and hook up the pipes
97
+ pid, stdin, stdout, stderr = popen4(@env, *(argv + [@options]))
98
+
99
+ # async read from all streams into buffers
100
+ @out, @err = read_and_write(@input, stdin, stdout, stderr, @timeout, @max)
101
+
102
+ # grab exit status
103
+ @status = waitpid(pid)
104
+ rescue Object => boom
105
+ [stdin, stdout, stderr].each { |fd| fd.close rescue nil }
106
+ if @status.nil?
107
+ ::Process.kill('TERM', pid) rescue nil
108
+ @status = waitpid(pid) rescue nil
109
+ end
110
+ raise
111
+ end
112
+
113
+ # Exception raised when the total number of bytes output on the command's
114
+ # stderr and stdout streams exceeds the maximum output size (:max option).
115
+ class MaximumOutputExceeded < StandardError
116
+ end
117
+
118
+ # Exception raised when timeout is exceeded.
119
+ class TimeoutExceeded < StandardError
120
+ end
121
+
122
+ # Maximum buffer size for reading
123
+ BUFSIZE = (32 * 1024)
124
+
125
+ # Start a select loop writing any input on the child's stdin and reading
126
+ # any output from the child's stdout or stderr.
127
+ #
128
+ # input - String input to write on stdin. May be nil.
129
+ # stdin - The write side IO object for the child's stdin stream.
130
+ # stdout - The read side IO object for the child's stdout stream.
131
+ # stderr - The read side IO object for the child's stderr stream.
132
+ # timeout - An optional Numeric specifying the total number of seconds
133
+ # the read/write operations should occur for.
134
+ #
135
+ # Returns an [out, err] tuple where both elements are strings with all
136
+ # data written to the stdout and stderr streams, respectively.
137
+ # Raises TimeoutExceeded when all data has not been read / written within
138
+ # the duration specified in the timeout argument.
139
+ # Raises MaximumOutputExceeded when the total number of bytes output
140
+ # exceeds the amount specified by the max argument.
141
+ def read_and_write(input, stdin, stdout, stderr, timeout=nil, max=nil)
142
+ input ||= ''
143
+ max = nil if max && max <= 0
144
+ out, err = '', ''
145
+ offset = 0
146
+
147
+ timeout = nil if timeout && timeout <= 0.0
148
+ @runtime = 0.0
149
+ start = Time.now
150
+
151
+ writers = [stdin]
152
+ readers = [stdout, stderr]
153
+ t = timeout
154
+ while readers.any? || writers.any?
155
+ ready = IO.select(readers, writers, readers + writers, t)
156
+ raise TimeoutExceeded if ready.nil?
157
+
158
+ # write to stdin stream
159
+ ready[1].each do |fd|
160
+ begin
161
+ boom = nil
162
+ size = fd.write_nonblock(input)
163
+ input = input[size, input.size]
164
+ rescue Errno::EPIPE => boom
165
+ rescue Errno::EAGAIN, Errno::EINTR
166
+ end
167
+ if boom || input.size == 0
168
+ stdin.close
169
+ writers.delete(stdin)
170
+ end
171
+ end
172
+
173
+ # read from stdout and stderr streams
174
+ ready[0].each do |fd|
175
+ buf = (fd == stdout) ? out : err
176
+ begin
177
+ buf << fd.readpartial(BUFSIZE)
178
+ rescue Errno::EAGAIN, Errno::EINTR
179
+ rescue EOFError
180
+ readers.delete(fd)
181
+ fd.close
182
+ end
183
+ end
184
+
185
+ # keep tabs on the total amount of time we've spent here
186
+ @runtime = Time.now - start
187
+ if timeout
188
+ t = timeout - @runtime
189
+ raise TimeoutExceeded if t < 0.0
190
+ end
191
+
192
+ # maybe we've hit our max output
193
+ if max && ready[0].any? && (out.size + err.size) > max
194
+ raise MaximumOutputExceeded
195
+ end
196
+ end
197
+
198
+ [out, err]
199
+ end
200
+
201
+ # Spawn a child process, perform IO redirection and environment prep, and
202
+ # return the running process's pid.
203
+ #
204
+ # This method implements a limited subset of Ruby 1.9's Process::spawn.
205
+ # The idea is that we can just use that when available, since most platforms
206
+ # will eventually build in special (and hopefully good) support for it.
207
+ #
208
+ # env - Hash of { name => val } environment variables set in the child
209
+ # process.
210
+ # argv - New process's argv as an Array. When this value is a string,
211
+ # the command may be run through the system /bin/sh or
212
+ # options - Supports a subset of Process::spawn options, including:
213
+ # :chdir => str to change the directory to str in the child
214
+ # FD => :close to close a file descriptor in the child
215
+ # :in => FD to redirect child's stdin to FD
216
+ # :out => FD to redirect child's stdout to FD
217
+ # :err => FD to redirect child's stderr to FD
218
+ #
219
+ # Returns the pid of the new process as an integer. The process exit status
220
+ # must be obtained using Process::waitpid.
221
+ def spawn(env, *argv)
222
+ options = (argv.pop if argv[-1].kind_of?(Hash)) || {}
223
+ fork do
224
+ # { fd => :close } in options means close that fd
225
+ options.each { |k,v| k.close if v == :close && !k.closed? }
226
+
227
+ # reopen stdin, stdout, and stderr on provided fds
228
+ STDIN.reopen(options[:in])
229
+ STDOUT.reopen(options[:out])
230
+ STDERR.reopen(options[:err])
231
+
232
+ # setup child environment
233
+ env.each { |k, v| ENV[k] = v }
234
+
235
+ # { :chdir => '/' } in options means change into that dir
236
+ ::Dir.chdir(options[:chdir]) if options[:chdir]
237
+
238
+ # do the deed
239
+ ::Kernel::exec(*argv)
240
+ exit! 1
241
+ end
242
+ end
243
+
244
+ # Start a process with spawn options and return
245
+ # popen4([env], command, arg1, arg2, [opt])
246
+ #
247
+ # env - The child process's environment as a Hash.
248
+ # command - The command and zero or more arguments.
249
+ # options - An options hash.
250
+ #
251
+ # See Ruby 1.9 IO.popen and Process::spawn docs for more info:
252
+ # http://www.ruby-doc.org/core-1.9/classes/IO.html#M001640
253
+ #
254
+ # Returns a [pid, stdin, stderr, stdout] tuple where pid is the child
255
+ # process's pid, stdin is a writeable IO object, and stdout + stderr are
256
+ # readable IO objects.
257
+ def popen4(*argv)
258
+ # create some pipes (see pipe(2) manual -- the ruby docs suck)
259
+ ird, iwr = IO.pipe
260
+ ord, owr = IO.pipe
261
+ erd, ewr = IO.pipe
262
+
263
+ # spawn the child process with either end of pipes hooked together
264
+ opts =
265
+ ((argv.pop if argv[-1].is_a?(Hash)) || {}).merge(
266
+ # redirect fds # close other sides
267
+ :in => ird, iwr => :close,
268
+ :out => owr, ord => :close,
269
+ :err => ewr, erd => :close
270
+ )
271
+ pid = spawn(*(argv + [opts]))
272
+
273
+ [pid, iwr, ord, erd]
274
+ ensure
275
+ # we're in the parent, close child-side fds
276
+ [ird, owr, ewr].each { |fd| fd.close }
277
+ end
278
+
279
+ # Wait for the child process to exit
280
+ #
281
+ # Returns the Process::Status object obtained by reaping the process.
282
+ def waitpid(pid)
283
+ ::Process::waitpid(pid)
284
+ $?
285
+ end
286
+
287
+ # Use native Process::spawn implementation on Ruby 1.9.
288
+ if ::Process.respond_to?(:spawn)
289
+ def spawn(*argv)
290
+ ::Process.spawn(*argv)
291
+ end
292
+ end
293
+ end
294
+ end
@@ -73,4 +73,6 @@ module Grit
73
73
 
74
74
  class Remote < Ref; end
75
75
 
76
+ class Note < Ref; end
77
+
76
78
  end # Grit
@@ -2,6 +2,9 @@ module Grit
2
2
 
3
3
  class Repo
4
4
  DAEMON_EXPORT_FILE = 'git-daemon-export-ok'
5
+ BATCH_PARSERS = {
6
+ 'commit' => ::Grit::Commit
7
+ }
5
8
 
6
9
  # Public: The String path of the Git repo.
7
10
  attr_accessor :path
@@ -55,7 +58,7 @@ module Grit
55
58
 
56
59
  # Public: Initialize a git repository (create it on the filesystem). By
57
60
  # default, the newly created repository will contain a working directory.
58
- # If you would like to create a bare repo, use Gollum::Repo.init_bare.
61
+ # If you would like to create a bare repo, use Grit::Repo.init_bare.
59
62
  #
60
63
  # path - The String full path to the repo. Traditionally ends with
61
64
  # "/<name>.git".
@@ -96,6 +99,7 @@ module Grit
96
99
  git = Git.new(path)
97
100
  git.fs_mkdir('..')
98
101
  git.init(git_options)
102
+ repo_options = {:is_bare => true}.merge(repo_options)
99
103
  self.new(path, repo_options)
100
104
  end
101
105
 
@@ -155,6 +159,40 @@ module Grit
155
159
  Repo.new(self.path)
156
160
  end
157
161
 
162
+ # Public: Return the full Git objects from the given SHAs. Only Commit
163
+ # objects are parsed for now.
164
+ #
165
+ # *shas - Array of String SHAs.
166
+ #
167
+ # Returns an Array of Grit objects (Grit::Commit).
168
+ def batch(*shas)
169
+ shas.flatten!
170
+ text = git.native(:cat_file, {:batch => true, :input => (shas * "\n")})
171
+ parse_batch(text)
172
+ end
173
+
174
+ # Parses `git cat-file --batch` output, returning an array of Grit objects.
175
+ #
176
+ # text - Raw String output.
177
+ #
178
+ # Returns an Array of Grit objects (Grit::Commit).
179
+ def parse_batch(text)
180
+ io = StringIO.new(text)
181
+ objects = []
182
+ while line = io.gets
183
+ sha, type, size = line.split(" ", 3)
184
+ parser = BATCH_PARSERS[type]
185
+ if type == 'missing' || !parser
186
+ objects << nil
187
+ next
188
+ end
189
+
190
+ object = io.read(size.to_i + 1)
191
+ objects << parser.parse_batch(self, sha, size, object)
192
+ end
193
+ objects
194
+ end
195
+
158
196
  # The project's description. Taken verbatim from GIT_REPO/description
159
197
  #
160
198
  # Returns String
@@ -415,7 +453,8 @@ module Grit
415
453
  else
416
454
  # NO PARENTS:
417
455
  cmd = "-r -t #{commit_sha}"
418
- revs = self.git.method_missing('ls-tree', {:timeout => false}, "-r -t #{commit_sha}").split("\n").map{ |a| a.split("\t").first.split(' ')[2] }
456
+ revs = self.git.native(:ls_tree, {:timeout => false, :r => true, :t => true}, commit_sha).
457
+ split("\n").map{ |a| a.split("\t").first.split(' ')[2] }
419
458
  end
420
459
  revs << self.commit(commit_sha).tree.id
421
460
  Grit.no_quote = false
@@ -461,7 +500,7 @@ module Grit
461
500
  diff = self.git.native('diff', {}, a, b, '--', *paths)
462
501
 
463
502
  if diff =~ /diff --git a/
464
- diff = diff.sub(/.+?(diff --git a)/m, '\1')
503
+ diff = diff.sub(/.*?(diff --git a)/m, '\1')
465
504
  else
466
505
  diff = ''
467
506
  end
@@ -558,11 +597,9 @@ module Grit
558
597
  # Returns Array[String] (pathnames of alternates)
559
598
  def alternates
560
599
  alternates_path = "objects/info/alternates"
561
- if self.git.fs_exist?(alternates_path)
562
- self.git.fs_read(alternates_path).strip.split("\n")
563
- else
564
- []
565
- end
600
+ self.git.fs_read(alternates_path).strip.split("\n")
601
+ rescue Errno::ENOENT
602
+ []
566
603
  end
567
604
 
568
605
  # Sets the alternates
@@ -1,6 +1,8 @@
1
1
  module Grit
2
2
 
3
3
  class Tag < Ref
4
+ extend Lazy
5
+
4
6
  lazy_reader :message
5
7
  lazy_reader :tagger
6
8
  lazy_reader :tag_date
@@ -1,6 +1,8 @@
1
1
  module Grit
2
2
 
3
3
  class Tree
4
+ extend Lazy
5
+
4
6
  lazy_reader :contents
5
7
  attr_reader :id
6
8
  attr_reader :mode
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grit
3
3
  version: !ruby/object:Gem::Version
4
- hash: 3
4
+ hash: 31
5
5
  prerelease: false
6
6
  segments:
7
7
  - 2
8
- - 3
8
+ - 4
9
9
  - 0
10
- version: 2.3.0
10
+ version: 2.4.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tom Preston-Werner
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2010-09-29 00:00:00 -07:00
19
+ date: 2011-01-06 00:00:00 -08:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -105,9 +105,10 @@ files:
105
105
  - lib/grit/git-ruby/repository.rb
106
106
  - lib/grit/git.rb
107
107
  - lib/grit/index.rb
108
+ - lib/grit/jruby.rb
108
109
  - lib/grit/lazy.rb
109
110
  - lib/grit/merge.rb
110
- - lib/grit/open3_detach.rb
111
+ - lib/grit/process.rb
111
112
  - lib/grit/ref.rb
112
113
  - lib/grit/repo.rb
113
114
  - lib/grit/ruby1.9.rb
@@ -1,46 +0,0 @@
1
- module Open3
2
- extend self
3
-
4
- def popen3(*cmd)
5
- pw = IO::pipe # pipe[0] for read, pipe[1] for write
6
- pr = IO::pipe
7
- pe = IO::pipe
8
-
9
- pid = fork{
10
- # child
11
- fork{
12
- # grandchild
13
- pw[1].close
14
- STDIN.reopen(pw[0])
15
- pw[0].close
16
-
17
- pr[0].close
18
- STDOUT.reopen(pr[1])
19
- pr[1].close
20
-
21
- pe[0].close
22
- STDERR.reopen(pe[1])
23
- pe[1].close
24
-
25
- exec(*cmd)
26
- }
27
- exit!(0)
28
- }
29
-
30
- pw[0].close
31
- pr[1].close
32
- pe[1].close
33
- Process.waitpid(pid)
34
- pi = [pw[1], pr[0], pe[0]]
35
- pw[1].sync = true
36
- if defined? yield
37
- begin
38
- return yield(*pi)
39
- ensure
40
- Process.detach(pid) if pid
41
- pi.each { |p| p.close unless p.closed? }
42
- end
43
- end
44
- pi
45
- end
46
- end