high level git repository and object classes
authorMatthias Lederhofer <[email protected]>
Tue, 23 Jan 2007 13:58:07 +0000 (23 14:58 +0100)
committerMatthias Lederhofer <[email protected]>
Tue, 23 Jan 2007 13:58:07 +0000 (23 14:58 +0100)
git.rb [new file with mode: 0644]
git/object.rb [new file with mode: 0644]

diff --git a/git.rb b/git.rb
new file mode 100644 (file)
index 0000000..ca3cb4f
--- /dev/null
+++ b/git.rb
@@ -0,0 +1,55 @@
+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
diff --git a/git/object.rb b/git/object.rb
new file mode 100644 (file)
index 0000000..1d1c1dd
--- /dev/null
@@ -0,0 +1,218 @@
+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