--- /dev/null
+require 'git/internal/object.rb'
+require 'git/internal/pack.rb'
+require 'git/internal/loose.rb'
+
+module Git
+ class Repository
+ def initialize(git_dir)
+ @git_dir = git_dir
+ end
+
+ def get_object_by_sha1(sha1)
+ r = get_raw_object_by_sha1(sha1)
+ return nil if !r
+
+ case r.type
+ when :blob
+ return Blob.new(r, self)
+ when :tree
+ return Tree.new(r, self)
+ when :commit
+ return Commit.new(r, self)
+ when :tag
+ return Tag.new(r, self)
+ else
+ raise RuntimeError, "got invalid object-type"
+ end
+ end
+
+ def get_raw_object_by_sha1(sha1)
+ sha1 = [sha1].pack("H*")
+ packs = Dir.glob(@git_dir + '/objects/pack/pack-*.pack')
+
+ # try packs
+ packs.each do |pack|
+ storage = Git::Internal::PackStorage.new(pack)
+ o = storage[sha1]
+ return o if o
+ end
+
+ # try loose storage
+ storage = Git::Internal::LooseStorage.new(@git_dir+'/objects')
+ o = storage[sha1]
+ return o if o
+
+ # try packs again, maybe the object got packed in the meantime
+ packs.each do |pack|
+ storage = Git::Internal::PackStorage.new(pack)
+ o = storage[sha1]
+ return o if o
+ end
+
+ return nil
+ end
+ end
+end
--- /dev/null
+require 'digest/sha1'
+
+module Git
+ # class for author/committer/tagger lines
+ class UserInfo
+ attr_accessor :name, :email, :date, :offset
+
+ def initialize(str)
+ m = /^(.*?) <(.*)> (\d+) ([+-])0*(\d+?)$/.match(str)
+ if !m
+ raise RuntimeError, "invalid %s header in commit" % key
+ end
+ @name = m[1]
+ @email = m[2]
+ @date = Time.at(Integer(m[3]))
+ @offset = (m[4] == "-" ? -1 : 1)*Integer(m[5])
+ end
+
+ def to_s
+ "%s <%s> %s %+05d" % [@name, @email, @date.to_i, @offset]
+ end
+ end
+
+ # base class for all git objects (blob, tree, commit, tag)
+ class Object
+ def initialize
+ raise NotImplemented, "abstract class"
+ end
+
+ def type
+ raise NotImplemented, "abstract class"
+ end
+
+ def raw_content
+ raise NotImplemented, "abstract class"
+ end
+
+ def sha1
+ Digest::SHA1.hexdigest("%s %d\0" % \
+ [self.type, self.raw_content.length] + \
+ self.raw_content)
+ end
+ end
+
+ class Blob < Object
+ attr_accessor :content
+
+ def initialize(rawobject, repository = nil)
+ @repository = repository
+ @content = rawobject.content
+ end
+
+ def type
+ :blob
+ end
+
+ def raw_content
+ @content
+ end
+ end
+
+ class DirectoryEntry
+ S_IFMT = 00170000
+ S_IFLNK = 0120000
+ S_IFREG = 0100000
+ S_IFDIR = 0040000
+
+ attr_accessor :mode, :name, :sha1
+ def initialize(buf)
+ m = /^(\d+) (.*)\0(.{20})$/m.match(buf)
+ if !m
+ raise RuntimeError, "invalid directory entry"
+ end
+ @mode = 0
+ m[1].each_byte do |i|
+ @mode = (@mode << 3) | (i-'0'[0])
+ end
+ @name = m[2]
+ @sha1 = m[3].unpack("H*")[0]
+
+ if ![S_IFLNK, S_IFDIR, S_IFREG].include?(@mode & S_IFMT)
+ raise RuntimeError, "unknown type for directory entry"
+ end
+ end
+
+ def type
+ case @mode & S_IFMT
+ when S_IFLNK
+ @type = :link
+ when S_IFDIR
+ @type = :directory
+ when S_IFREG
+ @type = :file
+ else
+ raise RuntimeError, "unknown type for directory entry"
+ end
+ end
+
+ def type=(type)
+ case @type
+ when :link
+ @mode = (@mode & ~S_IFMT) | S_IFLNK
+ when :directory
+ @mode = (@mode & ~S_IFMT) | S_IFDIR
+ when :file
+ @mode = (@mode & ~S_IFMT) | S_IFREG
+ else
+ raise RuntimeError, "invalid type"
+ end
+ end
+
+ def raw
+ "%o %s\0%s" % [@mode, @name, [@sha1].pack("H*")]
+ end
+ end
+
+ class Tree < Object
+ attr_accessor :entry
+
+ def initialize(rawobject, repository = nil)
+ @repository = repository
+ @entry = []
+ rawobject.content.scan(/\d+ .*?\0.{20}/m) do |i|
+ @entry.push(DirectoryEntry.new(i))
+ end
+ end
+
+ def type
+ :tree
+ end
+
+ def raw_content
+ # TODO: sort correctly
+ #@entry.sort { |a,b| a.name <=> b.name }.
+ @entry.
+ collect { |e| e.raw }.join
+ end
+ end
+
+ class Commit < Object
+ attr_accessor :author, :committer, :tree, :parent, :message
+
+ def initialize(rawobject, repository = nil)
+ @repository = repository
+ @parent = []
+
+ headers, @message = rawobject.content.split(/\n\n/, 2)
+
+ headers = headers.split(/\n/).map { |header| header.split(/ /, 2) }
+ headers.each do |header|
+ key, value = header
+ case key
+ when "tree"
+ @tree = value
+ when "parent"
+ @parent.push(value)
+ when "author"
+ @author = UserInfo.new(value)
+ when "committer"
+ @committer = UserInfo.new(value)
+ else
+ raise RuntimeError, "invalid header %s in commit" % key
+ end
+ end
+ end
+
+ def type
+ :commit
+ end
+
+ def raw_content
+ "tree %s\n%sauthor %s\ncommitter %s\n\n" % [
+ @tree,
+ @parent.collect { |i| "parent %s\n" % i }.join,
+ @author, @committer] + @message
+ end
+ end
+
+ class Tag < Object
+ attr_accessor :object, :type, :tag, :tagger, :message
+
+ def initialize(rawobject, repository = nil)
+ @repository = repository
+ @parent = []
+
+ headers, @message = rawobject.content.split(/\n\n/, 2)
+
+ headers = headers.split(/\n/).map { |header| header.split(/ /, 2) }
+ headers.each do |header|
+ key, value = header
+ case key
+ when "object"
+ @object = value
+ when "type"
+ if !["blob", "tree", "commit", "tag"].include?(value)
+ raise RuntimeError, "invalid type in tag"
+ end
+ @type = value.to_sym
+ when "tag"
+ @tag = value
+ when "tagger"
+ @tagger = UserInfo.new(value)
+ else
+ raise RuntimeError, "invalid header %s in commit" % key
+ end
+ end
+ end
+
+ def raw_content
+ "object %s\ntype %s\ntag %s\ntagger %s\n\n" % \
+ [@object, @type, @tag, @tagger] + @message
+ end
+
+ def type
+ :tag
+ end
+ end
+end