summaryrefslogtreecommitdiffstats
path: root/lib/git/raw/object.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/git/raw/object.rb')
-rw-r--r--lib/git/raw/object.rb268
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