diff options
Diffstat (limited to 'lib/git/raw/object.rb')
-rw-r--r-- | lib/git/raw/object.rb | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/lib/git/raw/object.rb b/lib/git/raw/object.rb new file mode 100644 index 0000000..7e3e618 --- /dev/null +++ b/lib/git/raw/object.rb @@ -0,0 +1,268 @@ +require 'digest/sha1' + +module Git + module Raw + + # 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 + attr_accessor :repository + + def Object.from_raw(rawobject, repository = nil) + case rawobject.type + when :blob + return Blob.from_raw(rawobject, repository) + when :tree + return Tree.from_raw(rawobject, repository) + when :commit + return Commit.from_raw(rawobject, repository) + when :tag + return Tag.from_raw(rawobject, repository) + else + raise RuntimeError, "got invalid object-type" + end + end + + 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 self.from_raw(rawobject, repository) + new(rawobject.content) + end + + def initialize(content, repository=nil) + @content = content + @repository = repository + 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 self.from_raw(rawobject, repository=nil) + entries = [] + rawobject.content.scan(/\d+ .*?\0.{20}/m) do |raw| + entries << DirectoryEntry.new(raw) + end + new(entries, repository) + end + + def initialize(entries=[], repository = nil) + @entry = entries + @repository = repository + 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 self.from_raw(rawobject, repository=nil) + parent = [] + tree = author = committer = nil + + headers, message = rawobject.content.split(/\n\n/, 2) + headers = headers.split(/\n/).map { |header| header.split(/ /, 2) } + headers.each do |key, value| + case key + when "tree" + tree = value + when "parent" + parent.push(value) + when "author" + author = UserInfo.new(value) + when "committer" + committer = UserInfo.new(value) + else + warn "unknown header '%s' in commit %s" % \ + [key, rawobject.sha1.unpack("H*")[0]] + end + end + if not tree && author && committer + raise RuntimeError, "incomplete raw commit object" + end + new(tree, parent, author, committer, message, repository) + end + + def initialize(tree, parent, author, committer, message, repository=nil) + @tree = tree + @author = author + @parent = parent + @committer = committer + @message = message + @repository = repository + 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 self.from_raw(rawobject, repository=nil) + headers, message = rawobject.content.split(/\n\n/, 2) + headers = headers.split(/\n/).map { |header| header.split(/ /, 2) } + headers.each do |key, value| + 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 + warn "unknown header '%s' in tag" % \ + [key, rawobject.sha1.unpack("H*")[0]] + end + if not object && type && tag && tagger + raise RuntimeError, "incomplete raw tag object" + end + end + new(object, type, tag, tagger, repository) + end + + def initialize(object, type, tag, tagger, repository=nil) + @object = object + @type = type + @tag = tag + @tagger = tagger + @repository = repository + 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 +end
\ No newline at end of file |