diff options
author | scott Chacon <schacon@agadorsparticus.(none)> | 2007-11-27 08:21:48 -0800 |
---|---|---|
committer | scott Chacon <schacon@agadorsparticus.(none)> | 2007-11-27 08:21:48 -0800 |
commit | 779f21b307e7c119a56700fb14f88ba63a2cccc2 (patch) | |
tree | f3f03821dbe66f581ae2707e6e872395adc7afbe /lib/git/raw/internal/pack.rb | |
parent | 55c3c134c5c19103ed52fbd615d5af351fa9bb34 (diff) | |
parent | 6a9db968e8563bc27b8f56f9d413159a2e14cf67 (diff) | |
download | third_party-ruby-git-779f21b307e7c119a56700fb14f88ba63a2cccc2.tar.gz third_party-ruby-git-779f21b307e7c119a56700fb14f88ba63a2cccc2.tar.xz third_party-ruby-git-779f21b307e7c119a56700fb14f88ba63a2cccc2.zip |
Merge branches 'master' and 'internals'
Diffstat (limited to 'lib/git/raw/internal/pack.rb')
-rw-r--r-- | lib/git/raw/internal/pack.rb | 258 |
1 files changed, 258 insertions, 0 deletions
diff --git a/lib/git/raw/internal/pack.rb b/lib/git/raw/internal/pack.rb new file mode 100644 index 0000000..8d5141e --- /dev/null +++ b/lib/git/raw/internal/pack.rb @@ -0,0 +1,258 @@ +# +# 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 'zlib' +require 'git/raw/internal/object' +require 'git/raw/internal/mmap' + +module Git + module Raw + module Internal + class PackFormatError < StandardError + end + + class PackStorage + OBJ_OFS_DELTA = 6 + OBJ_REF_DELTA = 7 + + FanOutCount = 256 + SHA1Size = 20 + IdxOffsetSize = 4 + OffsetSize = 4 + OffsetStart = FanOutCount * IdxOffsetSize + SHA1Start = OffsetStart + OffsetSize + EntrySize = OffsetSize + SHA1Size + + def initialize(file) + if file =~ /\.idx$/ + file = file[0...-3] + 'pack' + end + + @name = file + @packfile = File.open(file) + @idxfile = File.open(file[0...-4]+'idx') + @idx = Mmap.new(@idxfile) + + @offsets = [0] + FanOutCount.times do |i| + pos = @idx[i * IdxOffsetSize,IdxOffsetSize].unpack('N')[0] + if pos < @offsets[i] + raise PackFormatError, "pack #@name has discontinuous index #{i}" + end + @offsets << pos + end + + @size = @offsets[-1] + end + + def name + @name + end + + def close + @packfile.close + @idx.unmap + @idxfile.close + end + + def [](sha1) + offset = find_object(sha1) + return nil if !offset + return parse_object(offset) + end + + def each_entry + pos = OffsetStart + @size.times do + offset = @idx[pos,OffsetSize].unpack('N')[0] + sha1 = @idx[pos+OffsetSize,SHA1Size] + pos += EntrySize + yield sha1, offset + end + end + + def each_sha1 + # unpacking the offset is quite expensive, so + # we avoid using #each + pos = SHA1Start + @size.times do + sha1 = @idx[pos,SHA1Size] + pos += EntrySize + yield sha1 + end + end + + def find_object(sha1) + slot = sha1[0] + first, last = @offsets[slot,2] + while first < last + mid = (first + last) / 2 + midsha1 = @idx[SHA1Start + mid * EntrySize,SHA1Size] + cmp = midsha1 <=> sha1 + + if cmp < 0 + first = mid + 1 + elsif cmp > 0 + last = mid + else + pos = OffsetStart + mid * EntrySize + offset = @idx[pos,OffsetSize].unpack('N')[0] + return offset + end + end + + nil + end + private :find_object + + def parse_object(offset) + data, type = unpack_object(offset) + RawObject.new(OBJ_TYPES[type], data) + end + protected :parse_object + + def unpack_object(offset) + obj_offset = offset + @packfile.seek(offset) + + c = @packfile.read(1)[0] + size = c & 0xf + type = (c >> 4) & 7 + shift = 4 + offset += 1 + while c & 0x80 != 0 + c = @packfile.read(1)[0] + size |= ((c & 0x7f) << shift) + shift += 7 + offset += 1 + end + + case type + when OBJ_OFS_DELTA, OBJ_REF_DELTA + data, type = unpack_deltified(type, offset, obj_offset, size) + when OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, OBJ_TAG + data = unpack_compressed(offset, size) + else + raise PackFormatError, "invalid type #{type}" + end + [data, type] + end + private :unpack_object + + def unpack_deltified(type, offset, obj_offset, size) + @packfile.seek(offset) + data = @packfile.read(SHA1Size) + + if type == OBJ_OFS_DELTA + i = 0 + c = data[i] + base_offset = c & 0x7f + while c & 0x80 != 0 + c = data[i += 1] + base_offset += 1 + base_offset <<= 7 + base_offset |= c & 0x7f + end + base_offset = obj_offset - base_offset + offset += i + 1 + else + base_offset = find_object(data) + offset += SHA1Size + end + + base, type = unpack_object(base_offset) + delta = unpack_compressed(offset, size) + [patch_delta(base, delta), type] + end + private :unpack_deltified + + def unpack_compressed(offset, destsize) + outdata = "" + @packfile.seek(offset) + zstr = Zlib::Inflate.new + while outdata.size < destsize + indata = @packfile.read(4096) + if indata.size == 0 + raise PackFormatError, 'error reading pack data' + end + outdata += zstr.inflate(indata) + end + if outdata.size > destsize + raise PackFormatError, 'error reading pack data' + end + zstr.close + outdata + end + private :unpack_compressed + + def patch_delta(base, delta) + src_size, pos = patch_delta_header_size(delta, 0) + if src_size != base.size + raise PackFormatError, 'invalid delta data' + end + + dest_size, pos = patch_delta_header_size(delta, pos) + dest = "" + while pos < delta.size + c = delta[pos] + pos += 1 + if c & 0x80 != 0 + pos -= 1 + cp_off = cp_size = 0 + cp_off = delta[pos += 1] if c & 0x01 != 0 + cp_off |= delta[pos += 1] << 8 if c & 0x02 != 0 + cp_off |= delta[pos += 1] << 16 if c & 0x04 != 0 + cp_off |= delta[pos += 1] << 24 if c & 0x08 != 0 + cp_size = delta[pos += 1] if c & 0x10 != 0 + cp_size |= delta[pos += 1] << 8 if c & 0x20 != 0 + cp_size |= delta[pos += 1] << 16 if c & 0x40 != 0 + cp_size = 0x10000 if cp_size == 0 + pos += 1 + dest += base[cp_off,cp_size] + elsif c != 0 + dest += delta[pos,c] + pos += c + else + raise PackFormatError, 'invalid delta data' + end + end + dest + end + private :patch_delta + + def patch_delta_header_size(delta, pos) + size = 0 + shift = 0 + begin + c = delta[pos] + if c == nil + raise PackFormatError, 'invalid delta header' + end + pos += 1 + size |= (c & 0x7f) << shift + shift += 7 + end while c & 0x80 != 0 + [size, pos] + end + private :patch_delta_header_size + end + end + end +end + +if $0 == __FILE__ + ARGV.each do |path| + storage = Git::Internal::PackStorage.new(path) + storage.each_sha1 do |sha1| + obj = storage[sha1] + puts "%s %s" % [obj.sha1.unpack('H*'), obj.type] + end + end +end |