# -*- coding: utf-8 -*- # https://github.com/git/git/blob/master/sha1_file.c # https://github.com/git/git/blob/master/patch-delta.c # https://github.com/git/git/blob/master/builtin/unpack-objects.c # https://github.com/mojombo/grit/blob/master/lib/grit/git-ruby/internal/pack.rb module GitObjectBrowser module Models class PackedObject < Bindata attr_reader :header, :raw_data attr_reader :object TYPES = %w{ undefined commit tree blob tag undefined ofs_delta ref_delta } def initialize(index, input) super(input) @index = index end def parse(offset) parse_raw(offset) input = "#{ @object_type } #{ @object_size }\0" + @raw_data @object = GitObject.new(nil).parse_inflated(input) self end def parse_raw(offset) @offset = offset @header = parse_header(offset) if @header[:type] == 'ofs_delta' obj_ofs_delta elsif @header[:type] == 'ref_delta' obj_ref_delta else @object_type = @header[:type] @object_size = @header[:size] @raw_data = zlib_inflate end [@object_type, @object_size] end def parse_header(offset) seek(offset) (type, size, header_size) = parse_type_and_size type = TYPES[type] { :type => type, :size => size, :header_size => header_size } end def parse_ofs_delta_header(offset, header) (delta_offset, delta_header_size) = parse_delta_offset header[:base_offset] = offset - delta_offset header[:header_size] += delta_header_size header end def parse_ref_delta_header(header) header[:base_sha1] = hex(20) header[:header_size] += 20 header end def to_hash return { :offset => @offset, :type => @header[:type], # commit, tree, blob, tag, ofs_delta, ref_delta :size => @header[:size], :header_size => @header[:header_size], :base_offset => @header[:base_offset], :delta_commands => @delta_commands, :base_size => @base_size, :object => @object.to_hash, } end def self.path?(relpath) return relpath =~ %r{\Aobjects/pack/pack-[0-9a-f]{40}\.pack\z} end private def obj_ofs_delta parse_ofs_delta_header(@offset, @header) load_base_and_patch_delta end def obj_ref_delta parse_ref_delta_header(@header) index_entry = @index.find(@header[:base_sha1]) @header[:base_offset] = index_entry[:offset] load_base_and_patch_delta end def load_base_and_patch_delta begin pack = PackedObject.new(@index, @in) (@object_type, _) = pack.parse_raw(@header[:base_offset]) @base = pack.raw_data ensure seek(@offset + @header[:header_size]) end switch_source(StringIO.new(zlib_inflate)) { patch_delta } end def patch_delta @base_size = parse_size if @base.size != @base_size raise 'incollect base size' end @object_size = parse_size @delta_commands = [] @raw_data = '' while ! @in.eof? delta_command end if @object_size != @raw_data.size raise 'incollect delta size' end end def delta_command cmd = byte data = nil if cmd & 0b10000000 != 0 (offset, size) = parse_base_offset_and_size(cmd) data = @base[offset, size] @raw_data << data @delta_commands << { :source => :base, :offset => offset, :size => size } elsif cmd != 0 size = cmd data = raw(size) @raw_data << data @delta_commands << { :source => :delta, :size => size } else raise 'delta command is 0' end @delta_commands.last[:data] = shorten_utf8(data, 2000) end def shorten_utf8(bin, length) str = bin.force_encoding('UTF-8') str = '(not UTF-8)' unless str.valid_encoding? str = str[0, length] + '...' if str.length > length str end def zlib_inflate store = Zlib::Inflate.new buffer = '' while buffer.size < @header[:size] rawdata = raw(4096) if rawdata.size == 0 raise 'inflate error' end buffer << store.inflate(rawdata) end store.close buffer end # sha1_file.c unpack_object_header_buffer # unpack-objects.c unpack_one def parse_type_and_size hdr = byte hdr_size = 1 continue = (hdr & 0b10000000) type = (hdr & 0b01110000) >> 4 size = (hdr & 0b00001111) size_len = 4 while continue != 0 hdr = byte hdr_size += 1 continue = (hdr & 0b10000000) size += (hdr & 0b01111111) << size_len size_len += 7 end return [type, size, hdr_size] end # delta.h get_delta_hdr_size def parse_size size = 0 size_len = 0 begin hdr = byte continue = (hdr & 0b10000000) size += (hdr & 0b01111111) << size_len size_len += 7 end while continue != 0 return size end # sha1_file.c get_delta_base # https://github.com/git/git/blob/master/sha1_file.c # unpack-objects.c unpack_delta_entry # https://github.com/git/git/blob/master/builtin/unpack-objects.c def parse_delta_offset offset = -1 hdr_size = 0 begin hdr = byte hdr_size += 1 continue = hdr & 0b10000000 low_offset = hdr & 0b01111111 offset = ((offset + 1) << 7) | low_offset end while continue != 0 return [offset, hdr_size] end # patch-delta.c def parse_base_offset_and_size(cmd) offset = size = 0 offset = byte if cmd & 0b00000001 != 0 offset |= byte << 8 if cmd & 0b00000010 != 0 offset |= byte << 16 if cmd & 0b00000100 != 0 offset |= byte << 24 if cmd & 0b00001000 != 0 size = byte if cmd & 0b00010000 != 0 size |= byte << 8 if cmd & 0b00100000 != 0 size |= byte << 16 if cmd & 0b01000000 != 0 size = 0x10000 if size == 0 return [offset, size] end end end end