gitrb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
@@ -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