lib/grit/index.rb (118 lines of code) (raw):
module Grit
class Index
# Public: Gets/Sets the Grit::Repo to which this index belongs.
attr_accessor :repo
# Public: Gets/Sets the Hash tree map that holds the changes to be made
# in the next commit.
attr_accessor :tree
# Public: Gets/Sets the Grit::Tree object representing the tree upon
# which the next commit will be based.
attr_accessor :current_tree
# Public: if a tree or commit is written, this stores the size of that object
attr_reader :last_tree_size
attr_reader :last_commit_size
# Initialize a new Index object.
#
# repo - The Grit::Repo to which the index belongs.
#
# Returns the newly initialized Grit::Index.
def initialize(repo)
self.repo = repo
self.tree = {}
self.current_tree = nil
end
# Public: Add a file to the index.
#
# path - The String file path including filename (no slash prefix).
# data - The String binary contents of the file.
#
# Returns nothing.
def add(path, data)
path = path.split('/')
filename = path.pop
current = self.tree
path.each do |dir|
current[dir] ||= {}
node = current[dir]
current = node
end
current[filename] = data
end
# Public: Delete the given file from the index.
#
# path - The String file path including filename (no slash prefix).
#
# Returns nothing.
def delete(path)
add(path, false)
end
# Public: Read the contents of the given Tree into the index to use as a
# starting point for the index.
#
# tree - The String branch/tag/sha of the Git tree object.
#
# Returns nothing.
def read_tree(tree)
self.current_tree = self.repo.tree(tree)
end
# Public: Commit the contents of the index. This method supports two
# formats for arguments:
#
# message - The String commit message.
# options - An optional Hash of index options.
# :parents - Array of String commit SHA1s or Grit::Commit
# objects to attach this commit to to form a
# new head (default: nil).
# :actor - The Grit::Actor details of the user making
# the commit (default: nil).
# :last_tree - The String SHA1 of a tree to compare with
# in order to avoid making empty commits
# (default: nil).
# :head - The String branch name to write this head to
# (default: nil).
# :committed_date - The Time that the commit was made.
# (Default: Time.now)
# :authored_date - The Time that the commit was authored.
# (Default: committed_date)
#
# The legacy argument style looks like:
#
# message - The String commit message.
# parents - Array of String commit SHA1s or Grit::Commit objects to
# attach this commit to to form a new head (default: nil).
# actor - The Grit::Actor details of the user making the commit
# (default: nil).
# last_tree - The String SHA1 of a tree to compare with in order to avoid
# making empty commits (default: nil).
# head - The String branch name to write this head to
# (default: "master").
#
# Returns a String of the SHA1 of the new commit.
def commit(message, parents = nil, actor = nil, last_tree = nil, head = 'master')
commit_tree_sha = nil
if parents.is_a?(Hash)
commit_tree_sha = parents[:commit_tree_sha]
actor = parents[:actor]
committer = parents[:committer]
author = parents[:author]
last_tree = parents[:last_tree]
head = parents[:head]
committed_date = parents[:committed_date]
authored_date = parents[:authored_date]
parents = parents[:parents]
end
committer ||= actor
author ||= committer
if commit_tree_sha
tree_sha1 = commit_tree_sha
else
tree_sha1 = write_tree(self.tree, self.current_tree)
end
# don't write identical commits
return false if tree_sha1 == last_tree
contents = []
contents << ['tree', tree_sha1].join(' ')
parents.each do |p|
contents << ['parent', p].join(' ')
end if parents
committer ||= begin
config = Config.new(self.repo)
Actor.new(config['user.name'], config['user.email'])
end
author ||= committer
committed_date ||= Time.now
authored_date ||= committed_date
contents << ['author', author.output(authored_date)].join(' ')
contents << ['committer', committer.output(committed_date)].join(' ')
contents << ''
contents << message
contents = contents.join("\n")
@last_commit_size = contents.size
commit_sha1 = self.repo.git.put_raw_object(contents, 'commit')
self.repo.update_ref(head, commit_sha1) if head
commit_sha1
end
# Recursively write a tree to the index.
#
# tree - The Hash tree map:
# key - The String directory or filename.
# val - The Hash submap or the String contents of the file.
# now_tree - The Grit::Tree representing the a previous tree upon which
# this tree will be based (default: nil).
#
# Returns the String SHA1 String of the tree.
def write_tree(tree = nil, now_tree = nil)
tree = self.tree if !tree
tree_contents = {}
# fill in original tree
now_tree = read_tree(now_tree) if(now_tree && now_tree.is_a?(String))
now_tree.contents.each do |obj|
sha = [obj.id].pack("H*")
k = obj.name
k += '/' if (obj.class == Grit::Tree)
tmode = obj.mode.to_i.to_s ## remove zero-padding
tree_contents[k] = "%s %s\0%s" % [tmode, obj.name, sha]
end if now_tree
# overwrite with new tree contents
tree.each do |k, v|
case v
when Array
sha, mode = v
if sha.size == 40 # must be a sha
sha = [sha].pack("H*")
mode = mode.to_i.to_s # leading 0s not allowed
k = k.split('/').last # slashes not allowed
str = "%s %s\0%s" % [mode, k, sha]
tree_contents[k] = str
end
when String
sha = write_blob(v)
sha = [sha].pack("H*")
str = "%s %s\0%s" % ['100644', k, sha]
tree_contents[k] = str
when Hash
ctree = now_tree/k if now_tree
sha = write_tree(v, ctree)
sha = [sha].pack("H*")
str = "%s %s\0%s" % ['40000', k, sha]
tree_contents[k + '/'] = str
when false
tree_contents.delete(k)
end
end
tr = tree_contents.sort.map { |k, v| v }.join('')
@last_tree_size = tr.size
self.repo.git.put_raw_object(tr, 'tree')
end
# Write a blob to the index.
#
# data - The String data to write.
#
# Returns the String SHA1 of the new blob.
def write_blob(data)
self.repo.git.put_raw_object(data, 'blob')
end
end # Index
end # Grit