Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

receive and upload pack now works

  • Loading branch information...
commit f10df43a6de11359d0a5e5e0293e48ddf2241c81 1 parent 469b8bc
@schacon authored
Showing with 424 additions and 5 deletions.
  1. +117 −5 receive-pack.rb → git-server.rb
  2. +307 −0 objects.rb
View
122 receive-pack.rb → git-server.rb
@@ -4,6 +4,7 @@
require 'zlib'
require 'fileutils'
require 'digest'
+require 'objects'
class GitServer
@@ -141,9 +142,11 @@ def read_pack
end
def unpack_all(entries)
+ return if !entries
1.upto(entries) do |number|
unpack_object(number)
- end if entries
+ end
+ puts 'checksum:' + @session.recv(20).unpack("H*")[0]
end
def unpack_object(number)
@@ -159,12 +162,12 @@ def unpack_object(number)
case type
when OBJ_OFS_DELTA, OBJ_REF_DELTA
- puts "WRITE " + OBJ_TYPES[type].to_s
sha = unpack_deltified(type, size)
+ #puts "WRITE " + OBJ_TYPES[type].to_s + sha
return
when OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, OBJ_TAG
- #puts "WRITE " + OBJ_TYPES[type].to_s
sha = unpack_compressed(type, size)
+ #puts "WRITE " + OBJ_TYPES[type].to_s + sha
return
else
puts "invalid type #{type}"
@@ -196,8 +199,10 @@ def has_object?(sha1)
File.exists?(File.join(@git_dir, 'objects', sha1[0...2], sha1[2..39]))
end
+
def get_raw_object(sha1)
path = File.join(@git_dir, 'objects', sha1[0...2], sha1[2..39])
+ return false if !File.exists?(path)
buf = File.read(path)
if buf.length < 2
@@ -385,6 +390,10 @@ def packet_read_line
def packet_flush
@session.send('0000', 0)
end
+
+ def send_ack
+ @session.send("0007NAK", 0)
+ end
def refs
@refs = []
@@ -422,12 +431,115 @@ def write_server(data)
end
def upload_pack(path)
- puts "UPL PACK"
+ @git_dir = File.join(@path, path)
send_refs
- read_refs
+ packet_flush
+ receive_needs
+ send_ack
upload_pack_file
end
+ def receive_needs
+ @need_refs = []
+ while(data = packet_read_line) do
+ cmd, sha = data.split(' ')
+ @need_refs << [cmd, sha]
+ end
+ puts 'done:'
+ puts @session.recv(9)
+ @need_refs
+ end
+
+ def upload_pack_file
+ @send_objects = {}
+ @need_refs.each do |cmd, sha|
+ if cmd == 'want' && sha != NULL_SHA
+ @send_objects[sha] = ' commit'
+ build_object_list_from_commit(sha)
+ end
+ end
+ @send_objects = @send_objects.sort { |a, b| a[1] <=> b[1] }
+ pp @send_objects
+
+ build_pack_file
+ end
+
+ def build_pack_file
+ @digest = Digest::SHA1.new
+
+ # build_header
+ write_pack('PACK')
+ write_pack([2].pack("N"))
+ write_pack([@send_objects.length].pack("N"))
+
+ # build_pack
+ @send_objects.each do |sha, name|
+ # build pack header
+ content, type = get_raw_object(sha)
+ size = content.length
+ btype = type_to_flag(type)
+
+ c = (btype << 4) | (size & 15)
+ c |= 0x80
+ size = (size >> 4)
+ write_pack(c.chr)
+ while (size > 0) do
+ c = size & 0x7f
+ size = (size >> 7)
+ if size > 0
+ c |= 0x80;
+ end
+ write_pack(c.chr)
+ end
+
+ # pack object data
+ write_pack(Zlib::Deflate.deflate(content))
+ end
+
+ @session.send([@digest.hexdigest].pack("H*"), 0)
+ end
+
+ def type_to_flag(type)
+ case type.to_s
+ when 'commit': return OBJ_COMMIT
+ when 'tree': return OBJ_TREE
+ when 'blob': return OBJ_BLOB
+ when 'tag': return OBJ_TAG
+ end
+ end
+
+ def write_pack(bits)
+ @session.send(bits, 0)
+ @digest << bits
+ end
+
+ def object_from_sha(sha)
+ content, type = get_raw_object(sha)
+ Git::Object.from_raw(Git::RawObject.new(type.to_sym, content))
+ end
+
+ def build_object_list_from_commit(sha)
+ # go through each parent sha
+ commit = object_from_sha(sha)
+ # traverse the tree and add all the tree/blob shas
+ @send_objects[commit.tree] = '/'
+ build_object_list_from_tree(commit.tree)
+ commit.parent.each do |p|
+ @send_objects[p] = ' commit'
+ build_object_list_from_commit(p)
+ end
+ end
+
+ def build_object_list_from_tree(sha)
+ tree = object_from_sha(sha)
+ tree.entry.each do |t|
+ @send_objects[t.sha1] = t.name
+ if t.type == :tree
+ build_object_list_from_tree(t.sha1)
+ end
+ end
+ end
+
def read_header()
len = @session.recv( 4 ).hex
return false if (len == 0)
View
307 objects.rb
@@ -0,0 +1,307 @@
+#
+# converted from the gitrb project
+#
+# authors:
+# Matthias Lederhofer <matled@gmx.net>
+# Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
+#
+# provides native ruby access to git objects and pack files
+#
+
+require 'digest/sha1'
+
+module Git
+
+ class RawObject
+ attr_accessor :type, :content, :length
+ def initialize(type, content)
+ @type = type
+ @content = content
+ @length = content.length
+ end
+
+ def sha1
+ Digest::SHA1.digest("%s %d\0" % [@type, @content.length] + @content)
+ end
+ end
+
+ # 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" % str
+ 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 format_type
+ case type
+ when :link
+ 'link'
+ when :directory
+ 'tree'
+ when :file
+ 'blob'
+ end
+ end
+
+ def format_mode
+ "%06o" % @mode
+ 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.format_mode, e.format_type, e.sha1].join(' '), e.name].join("\t") }.join("\n")
+ end
+
+ def actual_raw
+ #@entry.collect { |e| e.raw.join(' '), e.name].join("\t") }.join("\n")
+ 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
Please sign in to comment.
Something went wrong with that request. Please try again.