gitrb 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +22 -0
- data/README.md +1 -0
- data/Rakefile +36 -0
- data/gitrb.gemspec +38 -0
- data/lib/gitrb/blob.rb +35 -0
- data/lib/gitrb/commit.rb +71 -0
- data/lib/gitrb/diff.rb +21 -0
- data/lib/gitrb/object.rb +59 -0
- data/lib/gitrb/pack.rb +347 -0
- data/lib/gitrb/repository.rb +373 -0
- data/lib/gitrb/tag.rb +35 -0
- data/lib/gitrb/tree.rb +163 -0
- data/lib/gitrb/trie.rb +75 -0
- data/lib/gitrb/user.rb +22 -0
- data/test/bare_repository_spec.rb +30 -0
- data/test/benchmark.rb +39 -0
- data/test/commit_spec.rb +73 -0
- data/test/repository_spec.rb +235 -0
- data/test/tree_spec.rb +75 -0
- data/test/trie_spec.rb +26 -0
- metadata +72 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2009 Daniel Mendler
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
This is a ruby git implementation based on git_store by Matthias Georgi.
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
Gem.activate 'rspec'
|
2
|
+
require 'rake'
|
3
|
+
require "rake/rdoctask"
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'spec/rake/spectask'
|
7
|
+
rescue LoadError
|
8
|
+
puts <<-EOS
|
9
|
+
To use rspec for testing you must install the rspec gem:
|
10
|
+
gem install rspec
|
11
|
+
EOS
|
12
|
+
exit(0)
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Run all specs"
|
16
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
17
|
+
t.spec_opts = ['-cfs', '--backtrace']
|
18
|
+
t.spec_files = FileList['test/**/*_spec.rb']
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "Print SpecDocs"
|
22
|
+
Spec::Rake::SpecTask.new(:doc) do |t|
|
23
|
+
t.spec_opts = ["--format", "specdoc"]
|
24
|
+
t.spec_files = FileList['test/*_spec.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "Generate the RDoc"
|
28
|
+
Rake::RDocTask.new do |rdoc|
|
29
|
+
files = ["README.md", "LICENSE", "lib/**/*.rb"]
|
30
|
+
rdoc.rdoc_files.add(files)
|
31
|
+
rdoc.main = "README.md"
|
32
|
+
rdoc.title = "Gitrb"
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Run the rspec"
|
36
|
+
task :default => :spec
|
data/gitrb.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'gitrb'
|
3
|
+
s.version = '0.0.1'
|
4
|
+
s.summary = 'Pure ruby git implementation'
|
5
|
+
s.author = 'Daniel Mendler'
|
6
|
+
s.email = 'mail@daniel-mendler.de'
|
7
|
+
s.homepage = 'https://github.com/minad/gitrb'
|
8
|
+
s.rubyforge_project = %q{gitrb}
|
9
|
+
s.description = <<END
|
10
|
+
Pure ruby git implementation similar to grit.
|
11
|
+
END
|
12
|
+
s.require_path = 'lib'
|
13
|
+
s.has_rdoc = true
|
14
|
+
s.extra_rdoc_files = ['README.md']
|
15
|
+
s.files = %w{
|
16
|
+
LICENSE
|
17
|
+
README.md
|
18
|
+
Rakefile
|
19
|
+
gitrb.gemspec
|
20
|
+
lib/gitrb/blob.rb
|
21
|
+
lib/gitrb/commit.rb
|
22
|
+
lib/gitrb/diff.rb
|
23
|
+
lib/gitrb/object.rb
|
24
|
+
lib/gitrb/pack.rb
|
25
|
+
lib/gitrb/repository.rb
|
26
|
+
lib/gitrb/tag.rb
|
27
|
+
lib/gitrb/tree.rb
|
28
|
+
lib/gitrb/trie.rb
|
29
|
+
lib/gitrb/user.rb
|
30
|
+
test/bare_repository_spec.rb
|
31
|
+
test/benchmark.rb
|
32
|
+
test/commit_spec.rb
|
33
|
+
test/repository_spec.rb
|
34
|
+
test/trie_spec.rb
|
35
|
+
test/tree_spec.rb
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
data/lib/gitrb/blob.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Gitrb
|
2
|
+
|
3
|
+
# This class stores the raw string data of a blob
|
4
|
+
class Blob < Gitrb::Object
|
5
|
+
|
6
|
+
attr_accessor :data, :mode
|
7
|
+
|
8
|
+
# Initialize a Blob
|
9
|
+
def initialize(options = {})
|
10
|
+
super(options)
|
11
|
+
@data = options[:data]
|
12
|
+
@mode = options[:mode] || "100644"
|
13
|
+
end
|
14
|
+
|
15
|
+
def type
|
16
|
+
'blob'
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(other)
|
20
|
+
Blob === other and id == other.id
|
21
|
+
end
|
22
|
+
|
23
|
+
def dump
|
24
|
+
@data
|
25
|
+
end
|
26
|
+
|
27
|
+
# Save the data to the git object repository
|
28
|
+
def save
|
29
|
+
repository.put(self)
|
30
|
+
id
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
data/lib/gitrb/commit.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
module Gitrb
|
2
|
+
|
3
|
+
class Commit < Gitrb::Object
|
4
|
+
attr_accessor :tree, :parent, :author, :committer, :message
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
super(options)
|
8
|
+
@parent = [options[:parent]].flatten.compact
|
9
|
+
@tree = options[:tree]
|
10
|
+
@author = options[:author]
|
11
|
+
@committer = options[:committer]
|
12
|
+
@message = options[:message]
|
13
|
+
parse(options[:data]) if options[:data]
|
14
|
+
end
|
15
|
+
|
16
|
+
def type
|
17
|
+
'commit'
|
18
|
+
end
|
19
|
+
|
20
|
+
def date
|
21
|
+
(committer && committer.date) || (author && author.date)
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
Commit === other and id == other.id
|
26
|
+
end
|
27
|
+
|
28
|
+
def save
|
29
|
+
repository.put(self)
|
30
|
+
id
|
31
|
+
end
|
32
|
+
|
33
|
+
def dump
|
34
|
+
[ "tree #{tree.id}",
|
35
|
+
@parent.map { |p| "parent #{p.id}" },
|
36
|
+
"author #{author.dump}",
|
37
|
+
"committer #{committer.dump}",
|
38
|
+
'',
|
39
|
+
message ].flatten.join("\n")
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
id
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def parse(data)
|
49
|
+
headers, @message = data.split("\n\n", 2)
|
50
|
+
|
51
|
+
headers.split("\n").each do |header|
|
52
|
+
key, value = header.split(' ', 2)
|
53
|
+
|
54
|
+
case key
|
55
|
+
when 'parent'
|
56
|
+
@parent << Reference.new(:repository => repository, :id => value)
|
57
|
+
when 'author'
|
58
|
+
@author = User.parse(value)
|
59
|
+
when 'committer'
|
60
|
+
@committer = User.parse(value)
|
61
|
+
when 'tree'
|
62
|
+
@tree = Reference.new(:repository => repository, :id => value)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/lib/gitrb/diff.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Gitrb
|
2
|
+
|
3
|
+
class Diff
|
4
|
+
attr_reader :from, :to, :patch, :deletions, :insertions
|
5
|
+
|
6
|
+
def initialize(from, to, patch)
|
7
|
+
@from = from
|
8
|
+
@to = to
|
9
|
+
@patch = patch
|
10
|
+
@deletions = @insertions = 0
|
11
|
+
@patch.split("\n").each do |line|
|
12
|
+
if line[0..0] == '-'
|
13
|
+
@deletions += 1
|
14
|
+
elsif line[0..0] == '+'
|
15
|
+
@insertions += 1
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/lib/gitrb/object.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
module Gitrb
|
2
|
+
class Object
|
3
|
+
attr_accessor :repository, :id
|
4
|
+
alias sha id
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@repository = options[:repository]
|
8
|
+
@id = options[:id] || options[:sha]
|
9
|
+
end
|
10
|
+
|
11
|
+
CLASS = {}
|
12
|
+
|
13
|
+
def object
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.inherited(subclass)
|
18
|
+
CLASS[subclass.name[7..-1].downcase] = subclass
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.factory(type, *args)
|
22
|
+
klass = CLASS[type] or raise NotImplementedError, "Object type not supported: #{type}"
|
23
|
+
klass.new(*args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Reference
|
28
|
+
def initialize(properties = {})
|
29
|
+
@properties = properties
|
30
|
+
@object = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def method_missing(name, *args, &block)
|
34
|
+
if @object
|
35
|
+
instance_eval "def self.#{name}(*args, &block); @object.#{name}(*args, &block); end"
|
36
|
+
@object.send(name, *args, &block)
|
37
|
+
elsif name == :type && (mode = @properties['mode'] || @properties[:mode])
|
38
|
+
mode = mode.to_i(8)
|
39
|
+
return (mode & 0x4000 == 0x4000) ? 'tree' : 'blob'
|
40
|
+
elsif @properties.include?(name)
|
41
|
+
return @properties[name]
|
42
|
+
elsif @properties.include?(name.to_s)
|
43
|
+
return @properties[name.to_s]
|
44
|
+
elsif object
|
45
|
+
method_missing(name, *args, &block)
|
46
|
+
else
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def object
|
52
|
+
@object ||= repository.get(id)
|
53
|
+
end
|
54
|
+
|
55
|
+
def resolved?
|
56
|
+
@object != nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/gitrb/pack.rb
ADDED
@@ -0,0 +1,347 @@
|
|
1
|
+
# -*- coding: us-ascii -*-
|
2
|
+
|
3
|
+
#
|
4
|
+
# converted from the gitrb project
|
5
|
+
#
|
6
|
+
# authors:
|
7
|
+
# Matthias Lederhofer <matled@gmx.net>
|
8
|
+
# Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
|
9
|
+
# Scott Chacon <schacon@gmail.com>
|
10
|
+
#
|
11
|
+
# provides native ruby access to git objects and pack files
|
12
|
+
#
|
13
|
+
|
14
|
+
require 'zlib'
|
15
|
+
|
16
|
+
class String
|
17
|
+
def getord(i); self[i].ord; end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Gitrb
|
21
|
+
PACK_SIGNATURE = "PACK"
|
22
|
+
PACK_IDX_SIGNATURE = "\377tOc"
|
23
|
+
|
24
|
+
OBJ_NONE = 0
|
25
|
+
OBJ_COMMIT = 1
|
26
|
+
OBJ_TREE = 2
|
27
|
+
OBJ_BLOB = 3
|
28
|
+
OBJ_TAG = 4
|
29
|
+
|
30
|
+
OBJ_TYPES = [nil, 'commit', 'tree', 'blob', 'tag'].freeze
|
31
|
+
|
32
|
+
class FileWindow
|
33
|
+
def initialize(file, version = 1)
|
34
|
+
@file = file
|
35
|
+
@offset = nil
|
36
|
+
if version == 2
|
37
|
+
@global_offset = 8
|
38
|
+
else
|
39
|
+
@global_offset = 0
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def [](*idx)
|
44
|
+
idx = idx[0] if idx.length == 1
|
45
|
+
case idx
|
46
|
+
when Range
|
47
|
+
offset = idx.first
|
48
|
+
len = idx.last - idx.first + idx.exclude_end? ? 0 : 1
|
49
|
+
when Fixnum
|
50
|
+
offset = idx
|
51
|
+
len = nil
|
52
|
+
when Array
|
53
|
+
offset, len = idx
|
54
|
+
else
|
55
|
+
raise RuntimeError, "invalid index param: #{idx.class}"
|
56
|
+
end
|
57
|
+
if @offset != offset
|
58
|
+
@file.seek(offset + @global_offset)
|
59
|
+
end
|
60
|
+
@offset = offset + len ? len : 1
|
61
|
+
if not len
|
62
|
+
@file.read(1).getord(0)
|
63
|
+
else
|
64
|
+
@file.read(len)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class PackFormatError < StandardError; end
|
70
|
+
|
71
|
+
class Pack
|
72
|
+
OBJ_OFS_DELTA = 6
|
73
|
+
OBJ_REF_DELTA = 7
|
74
|
+
|
75
|
+
FanOutCount = 256
|
76
|
+
SHA1Size = 20
|
77
|
+
IdxOffsetSize = 4
|
78
|
+
OffsetSize = 4
|
79
|
+
CrcSize = 4
|
80
|
+
OffsetStart = FanOutCount * IdxOffsetSize
|
81
|
+
SHA1Start = OffsetStart + OffsetSize
|
82
|
+
EntrySize = OffsetSize + SHA1Size
|
83
|
+
EntrySizeV2 = SHA1Size + CrcSize + OffsetSize
|
84
|
+
|
85
|
+
attr_reader :name
|
86
|
+
|
87
|
+
def initialize(file)
|
88
|
+
file = file[0...-3] + 'pack' if file =~ /\.idx$/
|
89
|
+
@name = file
|
90
|
+
init_pack
|
91
|
+
end
|
92
|
+
|
93
|
+
def each_object
|
94
|
+
with_idx do |idx|
|
95
|
+
if @version == 2
|
96
|
+
data = read_data_v2(idx)
|
97
|
+
data.each do |sha1, crc, offset|
|
98
|
+
sha1 = sha1.unpack("H*").first
|
99
|
+
yield sha1, offset
|
100
|
+
end
|
101
|
+
else
|
102
|
+
pos = OffsetStart
|
103
|
+
@size.times do
|
104
|
+
offset = idx[pos,OffsetSize].unpack('N')[0]
|
105
|
+
sha1 = idx[pos+OffsetSize,SHA1Size]
|
106
|
+
pos += EntrySize
|
107
|
+
sha1 = sha1.unpack("H*").first
|
108
|
+
yield sha1, offset
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def get_object(offset)
|
115
|
+
data, type = with_pack do |packfile|
|
116
|
+
unpack_object(packfile, offset)
|
117
|
+
end
|
118
|
+
[data, OBJ_TYPES[type]]
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def with_idx
|
124
|
+
idxfile = File.open(@name[0...-4]+'idx', 'rb')
|
125
|
+
|
126
|
+
sig = idxfile.read(4)
|
127
|
+
ver = idxfile.read(4).unpack("N")[0]
|
128
|
+
|
129
|
+
if sig == PACK_IDX_SIGNATURE
|
130
|
+
if(ver != 2)
|
131
|
+
raise PackFormatError, "pack #@name has unknown pack file version #{ver}"
|
132
|
+
end
|
133
|
+
@version = 2
|
134
|
+
else
|
135
|
+
@version = 1
|
136
|
+
end
|
137
|
+
|
138
|
+
idx = FileWindow.new(idxfile, @version)
|
139
|
+
result = yield idx
|
140
|
+
idxfile.close
|
141
|
+
result
|
142
|
+
end
|
143
|
+
|
144
|
+
def with_pack
|
145
|
+
packfile = File.open(@name, 'rb')
|
146
|
+
result = yield packfile
|
147
|
+
packfile.close
|
148
|
+
result
|
149
|
+
end
|
150
|
+
|
151
|
+
def init_pack
|
152
|
+
with_idx do |idx|
|
153
|
+
@offsets = [0]
|
154
|
+
FanOutCount.times do |i|
|
155
|
+
pos = idx[i * IdxOffsetSize,IdxOffsetSize].unpack('N')[0]
|
156
|
+
if pos < @offsets[i]
|
157
|
+
raise PackFormatError, "pack #@name has discontinuous index #{i}"
|
158
|
+
end
|
159
|
+
@offsets << pos
|
160
|
+
end
|
161
|
+
@size = @offsets[-1]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def read_data_v2(idx)
|
166
|
+
data = []
|
167
|
+
pos = OffsetStart
|
168
|
+
@size.times do |i|
|
169
|
+
data[i] = [idx[pos,SHA1Size], 0, 0]
|
170
|
+
pos += SHA1Size
|
171
|
+
end
|
172
|
+
@size.times do |i|
|
173
|
+
crc = idx[pos,CrcSize]
|
174
|
+
data[i][1] = crc
|
175
|
+
pos += CrcSize
|
176
|
+
end
|
177
|
+
@size.times do |i|
|
178
|
+
offset = idx[pos,OffsetSize].unpack('N')[0]
|
179
|
+
data[i][2] = offset
|
180
|
+
pos += OffsetSize
|
181
|
+
end
|
182
|
+
data
|
183
|
+
end
|
184
|
+
|
185
|
+
def find_object(sha1)
|
186
|
+
with_idx do |idx|
|
187
|
+
slot = sha1.getord(0)
|
188
|
+
return nil if !slot
|
189
|
+
first, last = @offsets[slot,2]
|
190
|
+
while first < last
|
191
|
+
mid = (first + last) / 2
|
192
|
+
if @version == 2
|
193
|
+
midsha1 = idx[OffsetStart + (mid * SHA1Size), SHA1Size]
|
194
|
+
cmp = midsha1 <=> sha1
|
195
|
+
|
196
|
+
if cmp < 0
|
197
|
+
first = mid + 1
|
198
|
+
elsif cmp > 0
|
199
|
+
last = mid
|
200
|
+
else
|
201
|
+
pos = OffsetStart + (@size * (SHA1Size + CrcSize)) + (mid * OffsetSize)
|
202
|
+
offset = idx[pos, OffsetSize].unpack('N')[0]
|
203
|
+
return offset
|
204
|
+
end
|
205
|
+
else
|
206
|
+
midsha1 = idx[SHA1Start + mid * EntrySize,SHA1Size]
|
207
|
+
cmp = midsha1 <=> sha1
|
208
|
+
|
209
|
+
if cmp < 0
|
210
|
+
first = mid + 1
|
211
|
+
elsif cmp > 0
|
212
|
+
last = mid
|
213
|
+
else
|
214
|
+
pos = OffsetStart + mid * EntrySize
|
215
|
+
offset = idx[pos,OffsetSize].unpack('N')[0]
|
216
|
+
return offset
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
nil
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def unpack_object(packfile, offset)
|
225
|
+
obj_offset = offset
|
226
|
+
packfile.seek(offset)
|
227
|
+
|
228
|
+
c = packfile.read(1).getord(0)
|
229
|
+
size = c & 0xf
|
230
|
+
type = (c >> 4) & 7
|
231
|
+
shift = 4
|
232
|
+
offset += 1
|
233
|
+
while c & 0x80 != 0
|
234
|
+
c = packfile.read(1).getord(0)
|
235
|
+
size |= ((c & 0x7f) << shift)
|
236
|
+
shift += 7
|
237
|
+
offset += 1
|
238
|
+
end
|
239
|
+
|
240
|
+
case type
|
241
|
+
when OBJ_OFS_DELTA, OBJ_REF_DELTA
|
242
|
+
data, type = unpack_deltified(packfile, type, offset, obj_offset, size)
|
243
|
+
when OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, OBJ_TAG
|
244
|
+
data = unpack_compressed(offset, size)
|
245
|
+
else
|
246
|
+
raise PackFormatError, "invalid type #{type}"
|
247
|
+
end
|
248
|
+
[data, type]
|
249
|
+
end
|
250
|
+
|
251
|
+
def unpack_deltified(packfile, type, offset, obj_offset, size)
|
252
|
+
packfile.seek(offset)
|
253
|
+
data = packfile.read(SHA1Size)
|
254
|
+
|
255
|
+
if type == OBJ_OFS_DELTA
|
256
|
+
i = 0
|
257
|
+
c = data.getord(i)
|
258
|
+
base_offset = c & 0x7f
|
259
|
+
while c & 0x80 != 0
|
260
|
+
c = data.getord(i += 1)
|
261
|
+
base_offset += 1
|
262
|
+
base_offset <<= 7
|
263
|
+
base_offset |= c & 0x7f
|
264
|
+
end
|
265
|
+
base_offset = obj_offset - base_offset
|
266
|
+
offset += i + 1
|
267
|
+
else
|
268
|
+
base_offset = find_object(data)
|
269
|
+
offset += SHA1Size
|
270
|
+
end
|
271
|
+
|
272
|
+
base, type = unpack_object(packfile, base_offset)
|
273
|
+
|
274
|
+
delta = unpack_compressed(offset, size)
|
275
|
+
[patch_delta(base, delta), type]
|
276
|
+
end
|
277
|
+
|
278
|
+
def unpack_compressed(offset, destsize)
|
279
|
+
outdata = ""
|
280
|
+
with_pack do |packfile|
|
281
|
+
packfile.seek(offset)
|
282
|
+
zstr = Zlib::Inflate.new
|
283
|
+
while outdata.size < destsize
|
284
|
+
indata = packfile.read(0xFFFF)
|
285
|
+
if indata.size == 0
|
286
|
+
raise PackFormatError, 'error reading pack data'
|
287
|
+
end
|
288
|
+
outdata << zstr.inflate(indata)
|
289
|
+
end
|
290
|
+
if outdata.size > destsize
|
291
|
+
raise PackFormatError, 'error reading pack data'
|
292
|
+
end
|
293
|
+
zstr.close
|
294
|
+
end
|
295
|
+
outdata
|
296
|
+
end
|
297
|
+
|
298
|
+
def patch_delta(base, delta)
|
299
|
+
src_size, pos = patch_delta_header_size(delta, 0)
|
300
|
+
if src_size != base.size
|
301
|
+
raise PackFormatError, 'invalid delta data'
|
302
|
+
end
|
303
|
+
|
304
|
+
dest_size, pos = patch_delta_header_size(delta, pos)
|
305
|
+
dest = ""
|
306
|
+
while pos < delta.size
|
307
|
+
c = delta.getord(pos)
|
308
|
+
pos += 1
|
309
|
+
if c & 0x80 != 0
|
310
|
+
pos -= 1
|
311
|
+
cp_off = cp_size = 0
|
312
|
+
cp_off = delta.getord(pos += 1) if c & 0x01 != 0
|
313
|
+
cp_off |= delta.getord(pos += 1) << 8 if c & 0x02 != 0
|
314
|
+
cp_off |= delta.getord(pos += 1) << 16 if c & 0x04 != 0
|
315
|
+
cp_off |= delta.getord(pos += 1) << 24 if c & 0x08 != 0
|
316
|
+
cp_size = delta.getord(pos += 1) if c & 0x10 != 0
|
317
|
+
cp_size |= delta.getord(pos += 1) << 8 if c & 0x20 != 0
|
318
|
+
cp_size |= delta.getord(pos += 1) << 16 if c & 0x40 != 0
|
319
|
+
cp_size = 0x10000 if cp_size == 0
|
320
|
+
pos += 1
|
321
|
+
dest << base[cp_off,cp_size]
|
322
|
+
elsif c != 0
|
323
|
+
dest << delta[pos,c]
|
324
|
+
pos += c
|
325
|
+
else
|
326
|
+
raise PackFormatError, 'invalid delta data'
|
327
|
+
end
|
328
|
+
end
|
329
|
+
dest
|
330
|
+
end
|
331
|
+
|
332
|
+
def patch_delta_header_size(delta, pos)
|
333
|
+
size = 0
|
334
|
+
shift = 0
|
335
|
+
begin
|
336
|
+
c = delta.getord(pos)
|
337
|
+
if c == nil
|
338
|
+
raise PackFormatError, 'invalid delta header'
|
339
|
+
end
|
340
|
+
pos += 1
|
341
|
+
size |= (c & 0x7f) << shift
|
342
|
+
shift += 7
|
343
|
+
end while c & 0x80 != 0
|
344
|
+
[size, pos]
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|