2 # converted from the gitrb project
5 # Matthias Lederhofer <matled@gmx.net>
6 # Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
8 # provides native ruby access to git objects and pack files
12 require 'git/raw/internal/object'
13 require 'git/raw/internal/mmap'
18 class PackFormatError < StandardError
29 OffsetStart = FanOutCount * IdxOffsetSize
30 SHA1Start = OffsetStart + OffsetSize
31 EntrySize = OffsetSize + SHA1Size
35 file = file[0...-3] + 'pack'
39 @packfile = File.open(file)
40 @idxfile = File.open(file[0...-4]+'idx')
41 @idx = Mmap.new(@idxfile)
44 FanOutCount.times do |i|
45 pos = @idx[i * IdxOffsetSize,IdxOffsetSize].unpack('N')[0]
47 raise PackFormatError, "pack #@name has discontinuous index #{i}"
66 offset = find_object(sha1)
68 return parse_object(offset)
74 offset = @idx[pos,OffsetSize].unpack('N')[0]
75 sha1 = @idx[pos+OffsetSize,SHA1Size]
82 # unpacking the offset is quite expensive, so
83 # we avoid using #each
86 sha1 = @idx[pos,SHA1Size]
94 first, last = @offsets[slot,2]
96 mid = (first + last) / 2
97 midsha1 = @idx[SHA1Start + mid * EntrySize,SHA1Size]
98 cmp = midsha1 <=> sha1
105 pos = OffsetStart + mid * EntrySize
106 offset = @idx[pos,OffsetSize].unpack('N')[0]
115 def parse_object(offset)
116 data, type = unpack_object(offset)
117 RawObject.new(OBJ_TYPES[type], data)
119 protected :parse_object
121 def unpack_object(offset)
123 @packfile.seek(offset)
125 c = @packfile.read(1)[0]
131 c = @packfile.read(1)[0]
132 size |= ((c & 0x7f) << shift)
138 when OBJ_OFS_DELTA, OBJ_REF_DELTA
139 data, type = unpack_deltified(type, offset, obj_offset, size)
140 when OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, OBJ_TAG
141 data = unpack_compressed(offset, size)
143 raise PackFormatError, "invalid type #{type}"
147 private :unpack_object
149 def unpack_deltified(type, offset, obj_offset, size)
150 @packfile.seek(offset)
151 data = @packfile.read(SHA1Size)
153 if type == OBJ_OFS_DELTA
156 base_offset = c & 0x7f
161 base_offset |= c & 0x7f
163 base_offset = obj_offset - base_offset
166 base_offset = find_object(data)
170 base, type = unpack_object(base_offset)
171 delta = unpack_compressed(offset, size)
172 [patch_delta(base, delta), type]
174 private :unpack_deltified
176 def unpack_compressed(offset, destsize)
178 @packfile.seek(offset)
179 zstr = Zlib::Inflate.new
180 while outdata.size < destsize
181 indata = @packfile.read(4096)
183 raise PackFormatError, 'error reading pack data'
185 outdata += zstr.inflate(indata)
187 if outdata.size > destsize
188 raise PackFormatError, 'error reading pack data'
193 private :unpack_compressed
195 def patch_delta(base, delta)
196 src_size, pos = patch_delta_header_size(delta, 0)
197 if src_size != base.size
198 raise PackFormatError, 'invalid delta data'
201 dest_size, pos = patch_delta_header_size(delta, pos)
203 while pos < delta.size
209 cp_off = delta[pos += 1] if c & 0x01 != 0
210 cp_off |= delta[pos += 1] << 8 if c & 0x02 != 0
211 cp_off |= delta[pos += 1] << 16 if c & 0x04 != 0
212 cp_off |= delta[pos += 1] << 24 if c & 0x08 != 0
213 cp_size = delta[pos += 1] if c & 0x10 != 0
214 cp_size |= delta[pos += 1] << 8 if c & 0x20 != 0
215 cp_size |= delta[pos += 1] << 16 if c & 0x40 != 0
216 cp_size = 0x10000 if cp_size == 0
218 dest += base[cp_off,cp_size]
223 raise PackFormatError, 'invalid delta data'
230 def patch_delta_header_size(delta, pos)
236 raise PackFormatError, 'invalid delta header'
239 size |= (c & 0x7f) << shift
241 end while c & 0x80 != 0
244 private :patch_delta_header_size
252 storage = Git::Internal::PackStorage.new(path)
253 storage.each_sha1 do |sha1|
255 puts "%s %s" % [obj.sha1.unpack('H*'), obj.type]