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.
- data/History.txt +35 -0
- data/grit.gemspec +4 -3
- data/lib/grit.rb +17 -13
- data/lib/grit/actor.rb +20 -4
- data/lib/grit/blame.rb +12 -7
- data/lib/grit/commit.rb +39 -1
- data/lib/grit/git-ruby/internal/pack.rb +3 -3
- data/lib/grit/git-ruby/repository.rb +29 -21
- data/lib/grit/git.rb +181 -58
- data/lib/grit/index.rb +44 -11
- data/lib/grit/jruby.rb +45 -0
- data/lib/grit/lazy.rb +5 -3
- data/lib/grit/process.rb +294 -0
- data/lib/grit/ref.rb +2 -0
- data/lib/grit/repo.rb +45 -8
- data/lib/grit/tag.rb +2 -0
- data/lib/grit/tree.rb +2 -0
- metadata +6 -5
- data/lib/grit/open3_detach.rb +0 -46
data/History.txt
CHANGED
@@ -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.
|
data/grit.gemspec
CHANGED
@@ -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.
|
8
|
-
s.date = '
|
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/
|
65
|
+
lib/grit/process.rb
|
65
66
|
lib/grit/ref.rb
|
66
67
|
lib/grit/repo.rb
|
67
68
|
lib/grit/ruby1.9.rb
|
data/lib/grit.rb
CHANGED
@@ -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
|
-
|
13
|
+
|
22
14
|
begin
|
23
|
-
gem "mime-types", ">=0"
|
24
15
|
require 'mime/types'
|
25
|
-
|
26
|
-
|
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.
|
58
|
+
VERSION = '2.4.0'
|
55
59
|
|
56
60
|
class << self
|
57
61
|
# Set +debug+ to true to log all git calls and responses
|
data/lib/grit/actor.rb
CHANGED
@@ -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
|
-
#
|
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}>">}
|
data/lib/grit/blame.rb
CHANGED
@@ -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
|
-
|
30
|
-
|
31
|
-
|
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,
|
38
|
-
|
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
|
data/lib/grit/commit.rb
CHANGED
@@ -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(
|
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
|
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
|
354
|
+
dest << base[cp_off,cp_size]
|
355
355
|
elsif c != 0
|
356
|
-
dest
|
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
|
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
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
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
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
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
|
|
data/lib/grit/git.rb
CHANGED
@@ -3,7 +3,8 @@ module Grit
|
|
3
3
|
|
4
4
|
class Git
|
5
5
|
class GitTimeout < RuntimeError
|
6
|
-
|
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 :
|
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
|
-
#
|
211
|
-
# the
|
212
|
-
#
|
213
|
-
#
|
214
|
-
#
|
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
|
221
|
-
|
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
|
-
#
|
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
|
-
#
|
227
|
-
def
|
228
|
-
|
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
|
-
|
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
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
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
|
-
|
289
|
-
|
290
|
-
|
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
|
data/lib/grit/index.rb
CHANGED
@@ -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
|
-
|
92
|
-
name = actor.name
|
93
|
-
email = actor.email
|
94
|
-
else
|
125
|
+
committer ||= begin
|
95
126
|
config = Config.new(self.repo)
|
96
|
-
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
|
-
|
101
|
-
contents << ['
|
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
|
-
|
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
|
data/lib/grit/jruby.rb
ADDED
@@ -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
|
data/lib/grit/lazy.rb
CHANGED
@@ -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
|
data/lib/grit/process.rb
ADDED
@@ -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
|
data/lib/grit/ref.rb
CHANGED
data/lib/grit/repo.rb
CHANGED
@@ -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
|
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.
|
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(
|
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
|
-
|
562
|
-
|
563
|
-
|
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
|
data/lib/grit/tag.rb
CHANGED
data/lib/grit/tree.rb
CHANGED
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:
|
4
|
+
hash: 31
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
-
-
|
8
|
+
- 4
|
9
9
|
- 0
|
10
|
-
version: 2.
|
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:
|
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/
|
111
|
+
- lib/grit/process.rb
|
111
112
|
- lib/grit/ref.rb
|
112
113
|
- lib/grit/repo.rb
|
113
114
|
- lib/grit/ruby1.9.rb
|
data/lib/grit/open3_detach.rb
DELETED
@@ -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
|