Class: Grit::Git

Inherits:
Object
  • Object
show all
Includes:
GitRuby, POSIX::Spawn
Defined in:
lib/grit/git.rb

Defined Under Namespace

Classes: CommandFailed, GitTimeout

Class Attribute Summary collapse

Instance Attribute Summary collapse

Attributes included from GitRuby

#git_file_index, #ruby_git_repo

Class Method Summary collapse

Instance Method Summary collapse

Methods included from GitRuby

#cat_file, #cat_ref, #diff, #file_size, #file_type, #init, #ls_tree, read_bytes_until, #refs, #rev_list, #rev_parse, #ruby_git, #tags

Constructor Details

#initialize(git_dir) ⇒ Git


96
97
98
99
100
# File 'lib/grit/git.rb', line 96

def initialize(git_dir)
  self.git_dir    = git_dir
  self.work_tree  = git_dir.gsub(/\/\.git$/,'')
  self.bytes_read = 0
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(cmd, options = {}, *args, &block) ⇒ Object

Methods not defined by a library implementation execute the git command using #native, passing the method name as the git command name.

Examples:

git.rev_list({:max_count => 10, :header => true}, "master")

371
372
373
# File 'lib/grit/git.rb', line 371

def method_missing(cmd, options={}, *args, &block)
  native(cmd, options, *args, &block)
end

Class Attribute Details

.git_binaryObject


75
76
77
78
79
80
# File 'lib/grit/git.rb', line 75

def git_binary
  @git_binary ||=
    ENV['PATH'].split(':').
      map  { |p| File.join(p, 'git') }.
      find { |p| File.exist?(p) }
end

.git_max_sizeObject

Returns the value of attribute git_max_size


74
75
76
# File 'lib/grit/git.rb', line 74

def git_max_size
  @git_max_size
end

.git_timeoutObject

Returns the value of attribute git_timeout


74
75
76
# File 'lib/grit/git.rb', line 74

def git_timeout
  @git_timeout
end

Instance Attribute Details

#bytes_readObject

Returns the value of attribute bytes_read


94
95
96
# File 'lib/grit/git.rb', line 94

def bytes_read
  @bytes_read
end

#git_dirObject

Returns the value of attribute git_dir


94
95
96
# File 'lib/grit/git.rb', line 94

def git_dir
  @git_dir
end

#work_treeObject

Returns the value of attribute work_tree


94
95
96
# File 'lib/grit/git.rb', line 94

def work_tree
  @work_tree
end

Class Method Details

.with_timeout(timeout = 10) ⇒ Object


87
88
89
90
91
92
# File 'lib/grit/git.rb', line 87

def self.with_timeout(timeout = 10)
  old_timeout = Grit::Git.git_timeout
  Grit::Git.git_timeout = timeout
  yield
  Grit::Git.git_timeout = old_timeout
end

Instance Method Details

#apply_patch(options = {}, head_sha = nil, patch = nil) ⇒ Object

Applies the given patch against the given SHA of the current repo.

options - grit command hash head_sha - String SHA or ref to apply the patch to. patch - The String patch to apply. Get this from #get_patch.

Returns the String Tree SHA on a successful patch application, or false.


252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/grit/git.rb', line 252

def apply_patch(options={}, head_sha=nil, patch=nil)
  options, head_sha, patch = {}, options, head_sha if !options.is_a?(Hash)
  options = options.dup
  options[:env] &&= options[:env].dup
  options[:raise] = true

  git_index = create_tempfile('index', true)
  (options[:env] ||= {}).merge!('GIT_INDEX_FILE' => git_index)

  begin
    native(:read_tree, options.dup, head_sha)
    native(:apply, options.merge(:cached => true, :input => patch))
  rescue CommandFailed
    return false
  end
  native(:write_tree, :env => options[:env]).to_s.chomp!
end

#check_applies(options = {}, head_sha = nil, applies_sha = nil) ⇒ Object

Checks if the patch of a commit can be applied to the given head.

options - grit command options hash head_sha - String SHA or ref to check the patch against. applies_sha - String SHA of the patch. The patch itself is retrieved

with #get_patch.

Returns 0 if the patch applies cleanly (according to `git apply`), or an Integer that is the sum of the failed exit statuses.


207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/grit/git.rb', line 207

def check_applies(options={}, head_sha=nil, applies_sha=nil)
  options, head_sha, applies_sha = {}, options, head_sha if !options.is_a?(Hash)
  options = options.dup
  options[:env] &&= options[:env].dup

  git_index = create_tempfile('index', true)
  (options[:env] ||= {}).merge!('GIT_INDEX_FILE' => git_index)
  options[:raise] = true

  status = 0
  begin
    native(:read_tree, options.dup, head_sha)
    stdin = native(:diff, options.dup, "#{applies_sha}^", applies_sha)
    native(:apply, options.merge(:check => true, :cached => true, :input => stdin))
  rescue CommandFailed => fail
    status += fail.exitstatus
  end
  status
end

#commit_from_sha(id) ⇒ Object


185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/grit/git.rb', line 185

def commit_from_sha(id)
  git_ruby_repo = GitRuby::Repository.new(self.git_dir)
  object = git_ruby_repo.get_object_by_sha1(id)

  if object.type == :commit
    id
  elsif object.type == :tag
    object.object
  else
    ''
  end
end

#create_tempfile(seed, unlink = false) ⇒ Object


179
180
181
182
183
# File 'lib/grit/git.rb', line 179

def create_tempfile(seed, unlink = false)
  path = Tempfile.new(seed).path
  File.unlink(path) if unlink
  return path
end

#exist?Boolean


47
48
49
# File 'lib/grit/git.rb', line 47

def exist?
  File.exist?(self.git_dir)
end

#fs_chmod(mode, file = '/') ⇒ Object

Chmod the the file or dir and everything beneath

+file+ is the relative path from the Git dir

Returns nothing


165
166
167
# File 'lib/grit/git.rb', line 165

def fs_chmod(mode, file = '/')
  FileUtils.chmod_R(mode, File.join(self.git_dir, file))
end

#fs_delete(file) ⇒ Object

Delete a normal file from the filesystem

+file+ is the relative path from the Git dir

Returns nothing


140
141
142
# File 'lib/grit/git.rb', line 140

def fs_delete(file)
  FileUtils.rm_rf(File.join(self.git_dir, file))
end

#fs_exist?(file) ⇒ Boolean

Check if a normal file exists on the filesystem

+file+ is the relative path from the Git dir

Returns Boolean


111
112
113
# File 'lib/grit/git.rb', line 111

def fs_exist?(file)
  File.exist?(File.join(self.git_dir, file))
end

#fs_mkdir(dir) ⇒ Object

Make a directory

+dir+ is the relative path to the directory to create

Returns nothing


157
158
159
# File 'lib/grit/git.rb', line 157

def fs_mkdir(dir)
  FileUtils.mkdir_p(File.join(self.git_dir, dir))
end

#fs_move(from, to) ⇒ Object

Move a normal file

+from+ is the relative path to the current file
+to+ is the relative path to the destination file

Returns nothing


149
150
151
# File 'lib/grit/git.rb', line 149

def fs_move(from, to)
  FileUtils.mv(File.join(self.git_dir, from), File.join(self.git_dir, to))
end

#fs_read(file) ⇒ Object

Read a normal file from the filesystem.

+file+ is the relative path from the Git dir

Returns the String contents of the file


119
120
121
# File 'lib/grit/git.rb', line 119

def fs_read(file)
  File.read(File.join(self.git_dir, file))
end

#fs_write(file, contents) ⇒ Object

Write a normal file to the filesystem.

+file+ is the relative path from the Git dir
+contents+ is the String content to be written

Returns nothing


128
129
130
131
132
133
134
# File 'lib/grit/git.rb', line 128

def fs_write(file, contents)
  path = File.join(self.git_dir, file)
  FileUtils.mkdir_p(File.dirname(path))
  File.open(path, 'w') do |f|
    f.write(contents)
  end
end

#get_git_object(object_id) ⇒ Object


59
60
61
# File 'lib/grit/git.rb', line 59

def get_git_object(object_id)
  ruby_git.get_raw_object_by_sha1(object_id).to_hash
end

#get_patch(options = {}, applies_sha = nil) ⇒ Object

Gets a patch for a given SHA using `git diff`.

options - grit command options hash applies_sha - String SHA to get the patch from, using this command:

`git diff #{applies_sha}^ #{applies_sha}`

Returns the String patch from `git diff`.


234
235
236
237
238
239
240
241
242
243
# File 'lib/grit/git.rb', line 234

def get_patch(options={}, applies_sha=nil)
  options, applies_sha = {}, options if !options.is_a?(Hash)
  options = options.dup
  options[:env] &&= options[:env].dup

  git_index = create_tempfile('index', true)
  (options[:env] ||= {}).merge!('GIT_INDEX_FILE' => git_index)

  native(:diff, options, "#{applies_sha}^", applies_sha)
end

#get_raw_object(object_id) ⇒ Object


55
56
57
# File 'lib/grit/git.rb', line 55

def get_raw_object(object_id)
  ruby_git.get_raw_object_by_sha1(object_id).content
end

#list_remotesObject


169
170
171
172
173
174
175
176
177
# File 'lib/grit/git.rb', line 169

def list_remotes
  remotes = []
  Dir.chdir(File.join(self.git_dir, 'refs/remotes')) do
    remotes = Dir.glob('*')
  end
  remotes
rescue
  []
end

#native(cmd, options = {}, *args, &block) ⇒ Object

Execute a git command, bypassing any library implementation.

cmd - The name of the git command as a Symbol. Underscores are

converted to dashes as in :rev_parse => 'rev-parse'.

options - Command line option arguments passed to the git command.

Single char keys are converted to short options (:a => -a).
Multi-char keys are converted to long options (:arg => '--arg').
Underscores in keys are converted to dashes. These special options
are used to control command execution and are not passed in command
invocation:
  :timeout - Maximum amount of time the command can run for before
    being aborted. When true, use Grit::Git.git_timeout; when numeric,
    use that number of seconds; when false or 0, disable timeout.
  :base - Set false to avoid passing the --git-dir argument when
    invoking the git command.
  :env - Hash of environment variable key/values that are set on the
    child process.
  :raise - When set true, commands that exit with a non-zero status
    raise a CommandFailed exception. This option is available only on
    platforms that support fork(2).
  :process_info - By default, a single string with output written to
    the process's stdout is returned. Setting this option to true
    results in a [exitstatus, out, err] tuple being returned instead.

args - Non-option arguments passed on the command line.

Optionally yields to the block an IO object attached to the child process's STDIN.

Examples

git.native(:rev_list, {:max_count => 10, :header => true}, "master")

Returns a String with all output written to the child process's stdout

when the :process_info option is not set.

Returns a [exitstatus, out, err] tuple when the :process_info option is

set. The exitstatus is an small integer that was the process's exit
status. The out and err elements are the data written to stdout and
stderr as Strings.

Raises Grit::Git::GitTimeout when the timeout is exceeded or when more

than Grit::Git.git_max_size bytes are output.

Raises Grit::Git::CommandFailed when the :raise option is set true and the

git command exits with a non-zero exit status. The CommandFailed's #command,
#exitstatus, and #err attributes can be used to retrieve additional
detail about the error.

313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/grit/git.rb', line 313

def native(cmd, options = {}, *args, &block)
  args     = args.first if args.size == 1 && args[0].is_a?(Array)
  args.map!    { |a| a.to_s }
  args.reject! { |a| a.empty? }

  # special option arguments
  env = options.delete(:env) || {}
  raise_errors = options.delete(:raise)
  process_info = options.delete(:process_info)

  # fall back to using a shell when the last argument looks like it wants to
  # start a pipeline for compatibility with previous versions of grit.
  return run(prefix, cmd, '', options, args) if args[-1].to_s[0] == ?|

  # more options
  input    = options.delete(:input)
  timeout  = options.delete(:timeout); timeout = true if timeout.nil?
  base     = options.delete(:base);    base    = true if base.nil?
  chdir    = options.delete(:chdir)

  # build up the git process argv
  argv = []
  argv << Git.git_binary
  argv << "--git-dir=#{git_dir}" if base
  argv << cmd.to_s.tr('_', '-')
  argv.concat(options_to_argv(options))
  argv.concat(args)

  # run it and deal with fallout
  Grit.log(argv.join(' ')) if Grit.debug

  process =
    Child.new(env, *(argv + [{
      :input   => input,
      :chdir   => chdir,
      :timeout => (Grit::Git.git_timeout if timeout == true),
      :max     => (Grit::Git.git_max_size if timeout == true)
    }]))
  Grit.log(process.out) if Grit.debug
  Grit.log(process.err) if Grit.debug

  status = process.status
  if raise_errors && !status.success?
    raise CommandFailed.new(argv.join(' '), status.exitstatus, process.err)
  elsif process_info
    [status.exitstatus, process.out, process.err]
  else
    process.out
  end
rescue TimeoutExceeded, MaximumOutputExceeded
  raise GitTimeout, argv.join(' ')
end

#object_exists?(object_id) ⇒ Boolean


63
64
65
# File 'lib/grit/git.rb', line 63

def object_exists?(object_id)
  ruby_git.object_exists?(object_id)
end

#options_to_argv(options) ⇒ Object

Transform a ruby-style options hash to command-line arguments sutiable for use with Kernel::exec. No shell escaping is performed.

Returns an Array of String option arguments.


379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/grit/git.rb', line 379

def options_to_argv(options)
  argv = []
  options.each do |key, val|
    if key.to_s.size == 1
      if val == true
        argv << "-#{key}"
      elsif val == false
        # ignore
      else
        argv << "-#{key}"
        argv << val.to_s
      end
    else
      if val == true
        argv << "--#{key.to_s.tr('_', '-')}"
      elsif val == false
        # ignore
      else
        argv << "--#{key.to_s.tr('_', '-')}=#{val}"
      end
    end
  end
  argv
end

#put_raw_object(content, type) ⇒ Object


51
52
53
# File 'lib/grit/git.rb', line 51

def put_raw_object(content, type)
  ruby_git.put_raw_object(content, type)
end

#run(prefix, cmd, postfix, options, args, &block) ⇒ Object

DEPRECATED OPEN3-BASED COMMAND EXECUTION


422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/grit/git.rb', line 422

def run(prefix, cmd, postfix, options, args, &block)
  timeout  = options.delete(:timeout) rescue nil
  timeout  = true if timeout.nil?

  base     = options.delete(:base) rescue nil
  base     = true if base.nil?

  if input = options.delete(:input)
    block = lambda { |stdin| stdin.write(input) }
  end

  opt_args = transform_options(options)

  if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin/
    ext_args = args.reject { |a| a.empty? }.map { |a| (a == '--' || a[0].chr == '|' || Grit.no_quote) ? a : "\"#{e(a)}\"" }
    gitdir = base ? "--git-dir=\"#{self.git_dir}\"" : ""
    call = "#{prefix}#{Git.git_binary} #{gitdir} #{cmd.to_s.gsub(/_/, '-')} #{(opt_args + ext_args).join(' ')}#{e(postfix)}"
  else
    ext_args = args.reject { |a| a.empty? }.map { |a| (a == '--' || a[0].chr == '|' || Grit.no_quote) ? a : "'#{e(a)}'" }
    gitdir = base ? "--git-dir='#{self.git_dir}'" : ""
    call = "#{prefix}#{Git.git_binary} #{gitdir} #{cmd.to_s.gsub(/_/, '-')} #{(opt_args + ext_args).join(' ')}#{e(postfix)}"
  end

  Grit.log(call) if Grit.debug
  response, err = timeout ? sh(call, &block) : wild_sh(call, &block)
  Grit.log(response) if Grit.debug
  Grit.log(err) if Grit.debug
  response
end

#select_existing_objects(object_ids) ⇒ Object


67
68
69
70
71
# File 'lib/grit/git.rb', line 67

def select_existing_objects(object_ids)
  object_ids.select do |object_id|
    object_exists?(object_id)
  end
end

#sh(command, &block) ⇒ Object


452
453
454
455
456
457
458
459
460
461
462
# File 'lib/grit/git.rb', line 452

def sh(command, &block)
  process =
    Child.new(
      command,
      :timeout => Git.git_timeout,
      :max     => Git.git_max_size
    )
  [process.out, process.err]
rescue TimeoutExceeded, MaximumOutputExceeded
  raise GitTimeout, command
end

#shell_escape(str) ⇒ Object Also known as: e


102
103
104
# File 'lib/grit/git.rb', line 102

def shell_escape(str)
  str.to_s.gsub("'", "\\\\'").gsub(";", '\\;')
end

#timeout_after(seconds) ⇒ Object

Simple wrapper around Timeout::timeout.

seconds - Float number of seconds before a Timeout::Error is raised. When

true, the Grit::Git.git_timeout value is used. When the timeout is less
than or equal to 0, no timeout is established.

Raises Timeout::Error when the timeout has elapsed.


411
412
413
414
415
416
417
418
# File 'lib/grit/git.rb', line 411

def timeout_after(seconds)
  seconds = self.class.git_timeout if seconds == true
  if seconds && seconds > 0
    Timeout.timeout(seconds) { yield }
  else
    yield
  end
end

#transform_options(options) ⇒ Object

Transform Ruby style options into git command line options

+options+ is a hash of Ruby style options

Returns String[]

e.g. ["--max-count=10", "--header"]

474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
# File 'lib/grit/git.rb', line 474

def transform_options(options)
  args = []
  options.keys.each do |opt|
    if opt.to_s.size == 1
      if options[opt] == true
        args << "-#{opt}"
      elsif options[opt] == false
        # ignore
      else
        val = options.delete(opt)
        args << "-#{opt.to_s} '#{e(val)}'"
      end
    else
      if options[opt] == true
        args << "--#{opt.to_s.gsub(/_/, '-')}"
      elsif options[opt] == false
        # ignore
      else
        val = options.delete(opt)
        args << "--#{opt.to_s.gsub(/_/, '-')}='#{e(val)}'"
      end
    end
  end
  args
end

#wild_sh(command, &block) ⇒ Object


464
465
466
467
# File 'lib/grit/git.rb', line 464

def wild_sh(command, &block)
  process = Child.new(command)
  [process.out, process.err]
end