Class: Gash

Inherits:
SimpleDelegator
  • Object
show all
Defined in:
lib/gash.rb

Overview

What is Gash?

  • Gash lets you access a Git-repo as a Hash.

  • Gash doesn’t touch your working directory

  • Gash only cares about the data, not the commits.

  • Gash only cares about the latest data.

  • Gash can commit.

  • Gash will automatically create branches if they don’t exist.

  • Gash only loads what it needs, so it handles large repos well.

  • Gash got pretty good documentation.

  • Gash got a bug tracker.

  • Gash is being developed at GitHub.

Some of these “rules” might change it the future.

How do you install it?

The stable version can installed through RubyGems:

sudo gem install gash

The unstable version can be checked out through Git at GitHub, and installed through this command:

rake install

How do you use it?

gash = Gash.new
gash["README"] = "new content"
gash.commit("Some changes...")

It’s also important to remember that a Gash is simply a Tree, so you can also call those methods.

See also: #new, #commit, Tree

Credits

This code is based upon git-shelve, created by Michael Siebert, which is released under LGPL. However, Michael has allowed me to release this under the MIT-license as long as I keep his name here.

And, in fact: I could never create this without the code written by Michael. You should really thank him!

Older versions of Gash, which doesn’t include this section or the MIT-license, is still licensed under LGPL.

Defined Under Namespace

Modules: Errors, Helpers Classes: Blob, Tree

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(repo = ".", branch = "master") ⇒ Gash

Opens the repo with the specified branch.



350
351
352
353
354
355
356
# File 'lib/gash.rb', line 350

def initialize(repo = ".", branch = "master")
  @branch = branch
  @repository = repo
  @repository = find_repo(repo)
  __setobj__(Tree.new(:parent => self))
  update!
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args, &blk) ⇒ Object



464
465
466
467
468
469
470
# File 'lib/gash.rb', line 464

def method_missing(meth, *args, &blk)
  target = self.__getobj__
  unless target.respond_to?(meth)
    Object.instance_method(:method_missing).bind(self).call(meth, *args, &blk)
  end
  target.__send__(meth, *args, &blk)
end

Instance Attribute Details

#branchObject

Returns the value of attribute branch.



347
348
349
# File 'lib/gash.rb', line 347

def branch
  @branch
end

#repositoryObject

Returns the value of attribute repository.



347
348
349
# File 'lib/gash.rb', line 347

def repository
  @repository
end

Instance Method Details

#branch_exists?Boolean

Checks if the current branch exists

Returns:

  • (Boolean)


399
400
401
# File 'lib/gash.rb', line 399

def branch_exists?
  git_status('rev-parse', @branch) == 0
end

#cat_file(blob) ⇒ Object



418
419
420
# File 'lib/gash.rb', line 418

def cat_file(blob)
  git('cat-file', 'blob', blob)
end

#commit(msg) ⇒ Object

Commit the current changes and returns the commit-hash.

Returns nil if nothing has changed.



391
392
393
394
395
396
# File 'lib/gash.rb', line 391

def commit(msg)
  return unless changed?
  commit = commit_tree(to_tree!, msg)
  @sha1 = git_tree_sha1
  commit
end

#commit_tree(tree, msg) ⇒ Object



442
443
444
445
446
447
448
449
450
451
# File 'lib/gash.rb', line 442

def commit_tree(tree, msg)
  if branch_exists?
    commit = git('commit-tree', tree, '-p', @branch, :input => msg)
    update_head(commit)
  else
    commit = git('commit-tree', tree, :input => msg)
    git('branch', @branch, commit)
  end
  commit
end

#find_repo(dir) ⇒ Object

private



410
411
412
413
414
415
416
# File 'lib/gash.rb', line 410

def find_repo(dir)
  Dir.chdir(dir) do
    File.expand_path(git('rev-parse', '--git-dir', :git_dir => false))
  end
rescue Errno::ENOENT, Gash::Errors::Git
  raise Errors::NoGitRepo.new("No Git repository at: " + @repository)
end

#gashObject

:nodoc:



358
359
360
# File 'lib/gash.rb', line 358

def gash #:nodoc:
  self
end

#git(cmd, *rest, &block) ⇒ Object

passes the command over to git

Parameters

cmd<String>

the git command to execute

*rest

any number of String arguments to the command, followed by an options hash

&block

if you supply a block, you can communicate with git throught a pipe. NEVER even think about closing the stream!

Options

:strip<Boolean>

true to strip the output String#strip, false not to to it

Raises

Errors::Git

if git returns non-null, an Exception is raised

Returns

String

if you didn’t supply a block, the things git said on STDOUT, otherwise noting



487
488
489
490
491
492
493
494
# File 'lib/gash.rb', line 487

def git(cmd, *rest, &block)
  result, reserr, status = run_git(cmd, *rest, &block)

  if status != 0
    raise Errors::Git.new("Error: #{cmd} returned #{status}. STDERR: #{reserr}")
  end
  result
end

#git_status(cmd, *rest, &block) ⇒ Object

passes the command over to git and returns its status ($?)

Parameters

cmd<String>

the git command to execute

*rest

any number of String arguments to the command, followed by an options hash

&block

if you supply a block, you can communicate with git throught a pipe. NEVER even think about closing the stream!

Returns

Integer

the return status of git



506
507
508
# File 'lib/gash.rb', line 506

def git_status(cmd, *rest, &block)
  run_git(cmd, *rest, &block)[2]
end

#git_tree(&blk) ⇒ Object



453
454
455
456
457
# File 'lib/gash.rb', line 453

def git_tree(&blk)
  git('ls-tree', '-r', '-t', '-z', @branch).split("\0").each(&blk)
rescue Errors::Git
  ""
end

#git_tree_sha1(from = @branch) ⇒ Object



459
460
461
462
# File 'lib/gash.rb', line 459

def git_tree_sha1(from = @branch)
  git('rev-parse', @branch + '^{tree}')
rescue Errors::Git
end

#inspectObject

:nodoc:



403
404
405
# File 'lib/gash.rb', line 403

def inspect #:nodoc:
  __getobj__.inspect
end

#run_git(cmd, *args, &block) ⇒ Object

passes the command over to git (you should not call this directly)

Parameters

cmd<String>

the git command to execute

*rest

any number of String arguments to the command, followed by an options hash

&block

if you supply a block, you can communicate with git throught a pipe. NEVER even think about closing the stream!

Options

:strip<Boolean>

true to strip the output String#strip, false not to to it

:git_dir<Boolean>

true to automatically use @repository as git-dir, false to not use anything.

Raises

Errors::Git

if git returns non-null, an Exception is raised

Returns

Array[String, String, Integer]

the first item is the STDOUT of git, the second is the STDERR, the third is the return-status



526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# File 'lib/gash.rb', line 526

def run_git(cmd, *args, &block)
  options = if args.last.kind_of?(Hash)
    args.pop
  else
    {}
  end
  options[:strip] = true unless options.key?(:strip)
  
  git_cmd = ["git"]
  
  unless options[:git_dir] == false
    git_cmd.push("--git-dir", @repository)
  end

  git_cmd.push(cmd, *args)
  
  result = ""
  reserr = ""
  status = Open4.popen4(*git_cmd) do |pid, stdin, stdout, stderr|
    if input = options.delete(:input)
      stdin.write(input.join)
    elsif block_given?
      yield stdin
    end
    stdin.close_write

    result = ""
    reserr = ""

    while !stdout.eof
      result << stdout.read
    end
    
    while !stderr.eof
      reserr << stderr.read
    end
  end

  result.strip! if options[:strip] == true
  
  [result, reserr, status]
end

#to_tree!(from = self) ⇒ Object



422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/gash.rb', line 422

def to_tree!(from = self)
  input = []
  from.each do |key, value|
    if value.tree?
      value.sha1 ||= to_tree!(value)
      value.mode ||= "040000"
      input << "#{value.mode} tree #{value.sha1}\t#{key}\0"
    else
      value.sha1 ||= git('hash-object', '-w', '--stdin', :input => value.to_s)
      value.mode ||= "100644" 
      input << "#{value.mode} blob #{value.sha1}\t#{key}\0"
    end
  end
  git('mktree', '-z', :input => input)
end

#update!Object

Fetch the latest data from Git; you can use this as a clear-method.



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
# File 'lib/gash.rb', line 363

def update!
  clear
  self.sha1 = git_tree_sha1
  git_tree do |line|
    line.strip!
    mode = line[0, 6]
    type = line[7]
    sha1 = line[12, 40]
    name = line[53..-1]
    name = name[/[^\/]+$/]
    parent = if $`.empty?
      self
    else
      self[$`.chomp("/")]
    end
    parent[name, true] = case type
    when ?b
      Blob.new(:sha1 => sha1, :mode => mode)
    when ?t
      Tree.new(:sha1 => sha1, :mode => mode)
    end
  end
  self
end

#update_head(new_head) ⇒ Object



438
439
440
# File 'lib/gash.rb', line 438

def update_head(new_head)
  git('update-ref', 'refs/heads/%s' % @branch, new_head)
end