2 require 'git/internal/mmap'
4 module Git module Internal
5 class PackFormatError < StandardError
16 OffsetStart = FanOutCount * IdxOffsetSize
17 SHA1Start = OffsetStart + OffsetSize
18 EntrySize = OffsetSize + SHA1Size
22 file = file[0...-3] + 'pack'
26 @packfile = File.open(file)
27 @idxfile = File.open(file[0...-4]+'idx')
28 @idx = Mmap.new(@idxfile)
31 FanOutCount.times do |i|
32 pos = @idx[i * IdxOffsetSize,IdxOffsetSize].unpack('N')[0]
34 raise PackFormatError, "pack #@name has discontinuous index #{i}"
43 offset = find_object(sha1)
45 return parse_object(offset)
51 offset = @idx[pos,OffsetSize].unpack('N')[0]
52 sha1 = @idx[pos+OffsetSize,SHA1Size]
59 # unpacking the offset is quite expensive, so
60 # we avoid using #each
63 sha1 = @idx[pos,SHA1Size]
71 first, last = @offsets[slot,2]
73 mid = (first + last) / 2
74 midsha1 = @idx[SHA1Start + mid * EntrySize,SHA1Size]
75 cmp = midsha1 <=> sha1
82 pos = OffsetStart + mid * EntrySize
83 offset = @idx[pos,OffsetSize].unpack('N')[0]
92 def parse_object(offset)
93 data, type = unpack_object(offset)
94 RawObject.new(OBJ_TYPES[type], data)
96 protected :parse_object
98 def unpack_object(offset)
100 @packfile.seek(offset)
102 c = @packfile.read(1)[0]
108 c = @packfile.read(1)[0]
109 size |= ((c & 0x7f) << shift)
115 when OBJ_OFS_DELTA, OBJ_REF_DELTA
116 data, type = unpack_deltified(type, offset, obj_offset, size)
117 when OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, OBJ_TAG
118 data = unpack_compressed(offset, size)
120 raise PackFormatError, "invalid type #{type}"
124 private :unpack_object
126 def unpack_deltified(type, offset, obj_offset, size)
127 @packfile.seek(offset)
128 data = @packfile.read(SHA1Size)
130 if type == OBJ_OFS_DELTA
133 base_offset = c & 0x7f
138 base_offset |= c & 0x7f
140 base_offset = obj_offset - base_offset
143 base_offset = find_object(data)
147 base, type = unpack_object(base_offset)
148 delta = unpack_compressed(offset, size)
149 [patch_delta(base, delta), type]
151 private :unpack_deltified
153 def unpack_compressed(offset, destsize)
155 @packfile.seek(offset)
156 zstr = Zlib::Inflate.new
157 while outdata.size < destsize
158 indata = @packfile.read(4096)
160 raise PackFormatError, 'error reading pack data'
162 outdata += zstr.inflate(indata)
164 if outdata.size > destsize
165 raise PackFormatError, 'error reading pack data'
170 private :unpack_compressed
172 def patch_delta(base, delta)
173 src_size, pos = patch_delta_header_size(delta, 0)
174 if src_size != base.size
175 raise PackFormatError, 'invalid delta data'
178 dest_size, pos = patch_delta_header_size(delta, pos)
180 while pos < delta.size
186 cp_off = delta[pos += 1] if c & 0x01 != 0
187 cp_off |= delta[pos += 1] << 8 if c & 0x02 != 0
188 cp_off |= delta[pos += 1] << 16 if c & 0x04 != 0
189 cp_off |= delta[pos += 1] << 24 if c & 0x08 != 0
190 cp_size = delta[pos += 1] if c & 0x10 != 0
191 cp_size |= delta[pos += 1] << 8 if c & 0x20 != 0
192 cp_size |= delta[pos += 1] << 16 if c & 0x40 != 0
193 cp_size = 0x10000 if cp_size == 0
195 dest += base[cp_off,cp_size]
200 raise PackFormatError, 'invalid delta data'
207 def patch_delta_header_size(delta, pos)
213 raise PackFormatError, 'invalid delta header'
216 size |= (c & 0x7f) << shift
218 end while c & 0x80 != 0
221 private :patch_delta_header_size
226 p = Git::Internal::PackStorage.new(ARGV[0])
229 p.each_sha1 do |sha1|
230 puts sha1.unpack('H*')
234 ARGV[1..-1].each do |sha1|
235 sha1 = [sha1].pack('H*')