schacon-grit 0.9.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/History.txt +13 -0
- data/Manifest.txt +57 -0
- data/README.txt +213 -0
- data/Rakefile +29 -0
- data/grit.gemspec +60 -0
- data/lib/grit.rb +53 -0
- data/lib/grit/actor.rb +36 -0
- data/lib/grit/blob.rb +117 -0
- data/lib/grit/commit.rb +221 -0
- data/lib/grit/commit_stats.rb +104 -0
- data/lib/grit/config.rb +44 -0
- data/lib/grit/diff.rb +70 -0
- data/lib/grit/errors.rb +7 -0
- data/lib/grit/git-ruby.rb +175 -0
- data/lib/grit/git-ruby/commit_db.rb +52 -0
- data/lib/grit/git-ruby/file_index.rb +186 -0
- data/lib/grit/git-ruby/git_object.rb +344 -0
- data/lib/grit/git-ruby/internal/loose.rb +136 -0
- data/lib/grit/git-ruby/internal/mmap.rb +59 -0
- data/lib/grit/git-ruby/internal/pack.rb +332 -0
- data/lib/grit/git-ruby/internal/raw_object.rb +37 -0
- data/lib/grit/git-ruby/object.rb +319 -0
- data/lib/grit/git-ruby/repository.rb +675 -0
- data/lib/grit/git.rb +130 -0
- data/lib/grit/head.rb +83 -0
- data/lib/grit/index.rb +102 -0
- data/lib/grit/lazy.rb +31 -0
- data/lib/grit/ref.rb +95 -0
- data/lib/grit/repo.rb +381 -0
- data/lib/grit/status.rb +149 -0
- data/lib/grit/tag.rb +71 -0
- data/lib/grit/tree.rb +100 -0
- data/test/test_actor.rb +35 -0
- data/test/test_blob.rb +79 -0
- data/test/test_commit.rb +184 -0
- data/test/test_config.rb +58 -0
- data/test/test_diff.rb +18 -0
- data/test/test_git.rb +70 -0
- data/test/test_grit.rb +32 -0
- data/test/test_head.rb +41 -0
- data/test/test_real.rb +19 -0
- data/test/test_reality.rb +17 -0
- data/test/test_remote.rb +14 -0
- data/test/test_repo.rb +277 -0
- data/test/test_tag.rb +25 -0
- data/test/test_tree.rb +96 -0
- metadata +110 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
#
|
2
|
+
# converted from the gitrb project
|
3
|
+
#
|
4
|
+
# authors:
|
5
|
+
# Matthias Lederhofer <matled@gmx.net>
|
6
|
+
# Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
|
7
|
+
# Scott Chacon <schacon@gmail.com>
|
8
|
+
#
|
9
|
+
# provides native ruby access to git objects and pack files
|
10
|
+
#
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'mmap'
|
14
|
+
rescue LoadError
|
15
|
+
|
16
|
+
module Grit
|
17
|
+
module GitRuby
|
18
|
+
module Internal
|
19
|
+
class Mmap
|
20
|
+
def initialize(file)
|
21
|
+
@file = file
|
22
|
+
@offset = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def unmap
|
26
|
+
@file = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](*idx)
|
30
|
+
idx = idx[0] if idx.length == 1
|
31
|
+
case idx
|
32
|
+
when Range
|
33
|
+
offset = idx.first
|
34
|
+
len = idx.last - idx.first + idx.exclude_end? ? 0 : 1
|
35
|
+
when Fixnum
|
36
|
+
offset = idx
|
37
|
+
len = nil
|
38
|
+
when Array
|
39
|
+
offset, len = idx
|
40
|
+
else
|
41
|
+
raise RuntimeError, "invalid index param: #{idx.class}"
|
42
|
+
end
|
43
|
+
if @offset != offset
|
44
|
+
@file.seek(offset)
|
45
|
+
end
|
46
|
+
@offset = offset + len ? len : 1
|
47
|
+
if not len
|
48
|
+
@file.read(1)[0]
|
49
|
+
else
|
50
|
+
@file.read(len)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end # rescue LoadError
|
59
|
+
|
@@ -0,0 +1,332 @@
|
|
1
|
+
#
|
2
|
+
# converted from the gitrb project
|
3
|
+
#
|
4
|
+
# authors:
|
5
|
+
# Matthias Lederhofer <matled@gmx.net>
|
6
|
+
# Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
|
7
|
+
# Scott Chacon <schacon@gmail.com>
|
8
|
+
#
|
9
|
+
# provides native ruby access to git objects and pack files
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'zlib'
|
13
|
+
require 'grit/git-ruby/internal/raw_object'
|
14
|
+
require 'grit/git-ruby/internal/mmap'
|
15
|
+
|
16
|
+
module Grit
|
17
|
+
module GitRuby
|
18
|
+
module Internal
|
19
|
+
class PackFormatError < StandardError
|
20
|
+
end
|
21
|
+
|
22
|
+
class PackStorage
|
23
|
+
OBJ_OFS_DELTA = 6
|
24
|
+
OBJ_REF_DELTA = 7
|
25
|
+
|
26
|
+
FanOutCount = 256
|
27
|
+
SHA1Size = 20
|
28
|
+
IdxOffsetSize = 4
|
29
|
+
OffsetSize = 4
|
30
|
+
OffsetStart = FanOutCount * IdxOffsetSize
|
31
|
+
SHA1Start = OffsetStart + OffsetSize
|
32
|
+
EntrySize = OffsetSize + SHA1Size
|
33
|
+
|
34
|
+
def initialize(file)
|
35
|
+
if file =~ /\.idx$/
|
36
|
+
file = file[0...-3] + 'pack'
|
37
|
+
end
|
38
|
+
|
39
|
+
@cache = {}
|
40
|
+
@name = file
|
41
|
+
|
42
|
+
with_idx do |idx|
|
43
|
+
@offsets = [0]
|
44
|
+
FanOutCount.times do |i|
|
45
|
+
pos = idx[i * IdxOffsetSize,IdxOffsetSize].unpack('N')[0]
|
46
|
+
if pos < @offsets[i]
|
47
|
+
raise PackFormatError, "pack #@name has discontinuous index #{i}"
|
48
|
+
end
|
49
|
+
@offsets << pos
|
50
|
+
end
|
51
|
+
|
52
|
+
@size = @offsets[-1]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def with_idx(index_file = nil)
|
57
|
+
if !index_file
|
58
|
+
index_file = @name
|
59
|
+
idxfile = File.open(@name[0...-4]+'idx')
|
60
|
+
else
|
61
|
+
idxfile = File.open(index_file)
|
62
|
+
end
|
63
|
+
idx = Mmap.new(idxfile)
|
64
|
+
yield idx
|
65
|
+
idx.unmap
|
66
|
+
idxfile.close
|
67
|
+
end
|
68
|
+
|
69
|
+
def with_packfile
|
70
|
+
packfile = File.open(@name)
|
71
|
+
yield packfile
|
72
|
+
packfile.close
|
73
|
+
end
|
74
|
+
|
75
|
+
# given an index file, list out the shas that it's packfile contains
|
76
|
+
def self.get_shas(index_file)
|
77
|
+
with_idx(index_file) do |idx|
|
78
|
+
@offsets = [0]
|
79
|
+
FanOutCount.times do |i|
|
80
|
+
pos = idx[i * IdxOffsetSize,IdxOffsetSize].unpack('N')[0]
|
81
|
+
if pos < @offsets[i]
|
82
|
+
raise PackFormatError, "pack #@name has discontinuous index #{i}"
|
83
|
+
end
|
84
|
+
@offsets << pos
|
85
|
+
end
|
86
|
+
|
87
|
+
@size = @offsets[-1]
|
88
|
+
shas = []
|
89
|
+
|
90
|
+
pos = SHA1Start
|
91
|
+
@size.times do
|
92
|
+
sha1 = idx[pos,SHA1Size]
|
93
|
+
pos += EntrySize
|
94
|
+
shas << sha1.unpack("H*").first
|
95
|
+
end
|
96
|
+
shas
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def cache_objects
|
101
|
+
@cache = {}
|
102
|
+
with_packfile do |packfile|
|
103
|
+
each_entry do |sha, offset|
|
104
|
+
data, type = unpack_object(packfile, offset, {:caching => true})
|
105
|
+
if data
|
106
|
+
@cache[sha] = RawObject.new(OBJ_TYPES[type], data)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def name
|
113
|
+
@name
|
114
|
+
end
|
115
|
+
|
116
|
+
def close
|
117
|
+
# shouldnt be anything open now
|
118
|
+
end
|
119
|
+
|
120
|
+
def [](sha1)
|
121
|
+
if obj = @cache[sha1]
|
122
|
+
return obj
|
123
|
+
end
|
124
|
+
|
125
|
+
offset = find_object(sha1)
|
126
|
+
return nil if !offset
|
127
|
+
@cache[sha1] = obj = parse_object(offset)
|
128
|
+
return obj
|
129
|
+
end
|
130
|
+
|
131
|
+
def each_entry
|
132
|
+
with_idx do |idx|
|
133
|
+
pos = OffsetStart
|
134
|
+
@size.times do
|
135
|
+
offset = idx[pos,OffsetSize].unpack('N')[0]
|
136
|
+
sha1 = idx[pos+OffsetSize,SHA1Size]
|
137
|
+
pos += EntrySize
|
138
|
+
yield sha1, offset
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def each_sha1
|
144
|
+
with_idx do |idx|
|
145
|
+
# unpacking the offset is quite expensive, so
|
146
|
+
# we avoid using #each
|
147
|
+
pos = SHA1Start
|
148
|
+
@size.times do
|
149
|
+
sha1 = idx[pos,SHA1Size]
|
150
|
+
pos += EntrySize
|
151
|
+
yield sha1
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def find_object(sha1)
|
157
|
+
obj = nil
|
158
|
+
with_idx do |idx|
|
159
|
+
obj = find_object_in_index(idx, sha1)
|
160
|
+
end
|
161
|
+
obj
|
162
|
+
end
|
163
|
+
private :find_object
|
164
|
+
|
165
|
+
def find_object_in_index(idx, sha1)
|
166
|
+
slot = sha1[0]
|
167
|
+
return nil if !slot
|
168
|
+
first, last = @offsets[slot,2]
|
169
|
+
while first < last
|
170
|
+
mid = (first + last) / 2
|
171
|
+
midsha1 = idx[SHA1Start + mid * EntrySize,SHA1Size]
|
172
|
+
cmp = midsha1 <=> sha1
|
173
|
+
|
174
|
+
if cmp < 0
|
175
|
+
first = mid + 1
|
176
|
+
elsif cmp > 0
|
177
|
+
last = mid
|
178
|
+
else
|
179
|
+
pos = OffsetStart + mid * EntrySize
|
180
|
+
offset = idx[pos,OffsetSize].unpack('N')[0]
|
181
|
+
return offset
|
182
|
+
end
|
183
|
+
end
|
184
|
+
nil
|
185
|
+
end
|
186
|
+
|
187
|
+
def parse_object(offset)
|
188
|
+
obj = nil
|
189
|
+
with_packfile do |packfile|
|
190
|
+
data, type = unpack_object(packfile, offset)
|
191
|
+
obj = RawObject.new(OBJ_TYPES[type], data)
|
192
|
+
end
|
193
|
+
obj
|
194
|
+
end
|
195
|
+
protected :parse_object
|
196
|
+
|
197
|
+
def unpack_object(packfile, offset, options = {})
|
198
|
+
obj_offset = offset
|
199
|
+
packfile.seek(offset)
|
200
|
+
|
201
|
+
c = packfile.read(1)[0]
|
202
|
+
size = c & 0xf
|
203
|
+
type = (c >> 4) & 7
|
204
|
+
shift = 4
|
205
|
+
offset += 1
|
206
|
+
while c & 0x80 != 0
|
207
|
+
c = packfile.read(1)[0]
|
208
|
+
size |= ((c & 0x7f) << shift)
|
209
|
+
shift += 7
|
210
|
+
offset += 1
|
211
|
+
end
|
212
|
+
|
213
|
+
return [false, false] if !(type == OBJ_COMMIT || type == OBJ_TREE) && options[:caching]
|
214
|
+
|
215
|
+
case type
|
216
|
+
when OBJ_OFS_DELTA, OBJ_REF_DELTA
|
217
|
+
data, type = unpack_deltified(packfile, type, offset, obj_offset, size, options)
|
218
|
+
#puts type
|
219
|
+
when OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, OBJ_TAG
|
220
|
+
data = unpack_compressed(offset, size)
|
221
|
+
else
|
222
|
+
raise PackFormatError, "invalid type #{type}"
|
223
|
+
end
|
224
|
+
[data, type]
|
225
|
+
end
|
226
|
+
private :unpack_object
|
227
|
+
|
228
|
+
def unpack_deltified(packfile, type, offset, obj_offset, size, options = {})
|
229
|
+
packfile.seek(offset)
|
230
|
+
data = packfile.read(SHA1Size)
|
231
|
+
|
232
|
+
if type == OBJ_OFS_DELTA
|
233
|
+
i = 0
|
234
|
+
c = data[i]
|
235
|
+
base_offset = c & 0x7f
|
236
|
+
while c & 0x80 != 0
|
237
|
+
c = data[i += 1]
|
238
|
+
base_offset += 1
|
239
|
+
base_offset <<= 7
|
240
|
+
base_offset |= c & 0x7f
|
241
|
+
end
|
242
|
+
base_offset = obj_offset - base_offset
|
243
|
+
offset += i + 1
|
244
|
+
else
|
245
|
+
base_offset = find_object(data)
|
246
|
+
offset += SHA1Size
|
247
|
+
end
|
248
|
+
|
249
|
+
base, type = unpack_object(packfile, base_offset)
|
250
|
+
|
251
|
+
return [false, false] if !(type == OBJ_COMMIT || type == OBJ_TREE) && options[:caching]
|
252
|
+
|
253
|
+
delta = unpack_compressed(offset, size)
|
254
|
+
[patch_delta(base, delta), type]
|
255
|
+
end
|
256
|
+
private :unpack_deltified
|
257
|
+
|
258
|
+
def unpack_compressed(offset, destsize)
|
259
|
+
outdata = ""
|
260
|
+
with_packfile do |packfile|
|
261
|
+
packfile.seek(offset)
|
262
|
+
zstr = Zlib::Inflate.new
|
263
|
+
while outdata.size < destsize
|
264
|
+
indata = packfile.read(4096)
|
265
|
+
if indata.size == 0
|
266
|
+
raise PackFormatError, 'error reading pack data'
|
267
|
+
end
|
268
|
+
outdata += zstr.inflate(indata)
|
269
|
+
end
|
270
|
+
if outdata.size > destsize
|
271
|
+
raise PackFormatError, 'error reading pack data'
|
272
|
+
end
|
273
|
+
zstr.close
|
274
|
+
end
|
275
|
+
outdata
|
276
|
+
end
|
277
|
+
private :unpack_compressed
|
278
|
+
|
279
|
+
def patch_delta(base, delta)
|
280
|
+
src_size, pos = patch_delta_header_size(delta, 0)
|
281
|
+
if src_size != base.size
|
282
|
+
raise PackFormatError, 'invalid delta data'
|
283
|
+
end
|
284
|
+
|
285
|
+
dest_size, pos = patch_delta_header_size(delta, pos)
|
286
|
+
dest = ""
|
287
|
+
while pos < delta.size
|
288
|
+
c = delta[pos]
|
289
|
+
pos += 1
|
290
|
+
if c & 0x80 != 0
|
291
|
+
pos -= 1
|
292
|
+
cp_off = cp_size = 0
|
293
|
+
cp_off = delta[pos += 1] if c & 0x01 != 0
|
294
|
+
cp_off |= delta[pos += 1] << 8 if c & 0x02 != 0
|
295
|
+
cp_off |= delta[pos += 1] << 16 if c & 0x04 != 0
|
296
|
+
cp_off |= delta[pos += 1] << 24 if c & 0x08 != 0
|
297
|
+
cp_size = delta[pos += 1] if c & 0x10 != 0
|
298
|
+
cp_size |= delta[pos += 1] << 8 if c & 0x20 != 0
|
299
|
+
cp_size |= delta[pos += 1] << 16 if c & 0x40 != 0
|
300
|
+
cp_size = 0x10000 if cp_size == 0
|
301
|
+
pos += 1
|
302
|
+
dest += base[cp_off,cp_size]
|
303
|
+
elsif c != 0
|
304
|
+
dest += delta[pos,c]
|
305
|
+
pos += c
|
306
|
+
else
|
307
|
+
raise PackFormatError, 'invalid delta data'
|
308
|
+
end
|
309
|
+
end
|
310
|
+
dest
|
311
|
+
end
|
312
|
+
private :patch_delta
|
313
|
+
|
314
|
+
def patch_delta_header_size(delta, pos)
|
315
|
+
size = 0
|
316
|
+
shift = 0
|
317
|
+
begin
|
318
|
+
c = delta[pos]
|
319
|
+
if c == nil
|
320
|
+
raise PackFormatError, 'invalid delta header'
|
321
|
+
end
|
322
|
+
pos += 1
|
323
|
+
size |= (c & 0x7f) << shift
|
324
|
+
shift += 7
|
325
|
+
end while c & 0x80 != 0
|
326
|
+
[size, pos]
|
327
|
+
end
|
328
|
+
private :patch_delta_header_size
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#
|
2
|
+
# converted from the gitrb project
|
3
|
+
#
|
4
|
+
# authors:
|
5
|
+
# Matthias Lederhofer <matled@gmx.net>
|
6
|
+
# Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
|
7
|
+
#
|
8
|
+
# provides native ruby access to git objects and pack files
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'digest/sha1'
|
12
|
+
|
13
|
+
module Grit
|
14
|
+
module GitRuby
|
15
|
+
module Internal
|
16
|
+
OBJ_NONE = 0
|
17
|
+
OBJ_COMMIT = 1
|
18
|
+
OBJ_TREE = 2
|
19
|
+
OBJ_BLOB = 3
|
20
|
+
OBJ_TAG = 4
|
21
|
+
|
22
|
+
OBJ_TYPES = [nil, :commit, :tree, :blob, :tag].freeze
|
23
|
+
|
24
|
+
class RawObject
|
25
|
+
attr_accessor :type, :content
|
26
|
+
def initialize(type, content)
|
27
|
+
@type = type
|
28
|
+
@content = content
|
29
|
+
end
|
30
|
+
|
31
|
+
def sha1
|
32
|
+
Digest::SHA1.digest("%s %d\0" % [@type, @content.length] + @content)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,319 @@
|
|
1
|
+
#
|
2
|
+
# converted from the gitrb project
|
3
|
+
#
|
4
|
+
# authors:
|
5
|
+
# Matthias Lederhofer <matled@gmx.net>
|
6
|
+
# Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
|
7
|
+
# Scott Chacon <schacon@gmail.com>
|
8
|
+
#
|
9
|
+
# provides native ruby access to git objects and pack files
|
10
|
+
#
|
11
|
+
|
12
|
+
# These classes translate the raw binary data kept in the sha encoded files
|
13
|
+
# into parsed data that can then be used in another fashion
|
14
|
+
require 'stringio'
|
15
|
+
|
16
|
+
module Grit
|
17
|
+
module GitRuby
|
18
|
+
|
19
|
+
# class for author/committer/tagger lines
|
20
|
+
class UserInfo
|
21
|
+
attr_accessor :name, :email, :date, :offset
|
22
|
+
|
23
|
+
def initialize(str)
|
24
|
+
m = /^(.*?) <(.*)> (\d+) ([+-])0*(\d+?)$/.match(str)
|
25
|
+
if !m
|
26
|
+
raise RuntimeError, "invalid header '%s' in commit" % str
|
27
|
+
end
|
28
|
+
@name = m[1]
|
29
|
+
@email = m[2]
|
30
|
+
@date = Time.at(Integer(m[3]))
|
31
|
+
@offset = (m[4] == "-" ? -1 : 1)*Integer(m[5])
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
"%s <%s> %s %+05d" % [@name, @email, @date.to_i, @offset]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# base class for all git objects (blob, tree, commit, tag)
|
40
|
+
class Object
|
41
|
+
attr_accessor :repository
|
42
|
+
|
43
|
+
def Object.from_raw(rawobject, repository = nil)
|
44
|
+
case rawobject.type
|
45
|
+
when :blob
|
46
|
+
return Blob.from_raw(rawobject, repository)
|
47
|
+
when :tree
|
48
|
+
return Tree.from_raw(rawobject, repository)
|
49
|
+
when :commit
|
50
|
+
return Commit.from_raw(rawobject, repository)
|
51
|
+
when :tag
|
52
|
+
return Tag.from_raw(rawobject, repository)
|
53
|
+
else
|
54
|
+
raise RuntimeError, "got invalid object-type"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize
|
59
|
+
raise NotImplemented, "abstract class"
|
60
|
+
end
|
61
|
+
|
62
|
+
def type
|
63
|
+
raise NotImplemented, "abstract class"
|
64
|
+
end
|
65
|
+
|
66
|
+
def raw_content
|
67
|
+
raise NotImplemented, "abstract class"
|
68
|
+
end
|
69
|
+
|
70
|
+
def sha1
|
71
|
+
Digest::SHA1.hexdigest("%s %d\0" % \
|
72
|
+
[self.type, self.raw_content.length] + \
|
73
|
+
self.raw_content)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class Blob < Object
|
78
|
+
attr_accessor :content
|
79
|
+
|
80
|
+
def self.from_raw(rawobject, repository)
|
81
|
+
new(rawobject.content)
|
82
|
+
end
|
83
|
+
|
84
|
+
def initialize(content, repository=nil)
|
85
|
+
@content = content
|
86
|
+
@repository = repository
|
87
|
+
end
|
88
|
+
|
89
|
+
def type
|
90
|
+
:blob
|
91
|
+
end
|
92
|
+
|
93
|
+
def raw_content
|
94
|
+
@content
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class DirectoryEntry
|
99
|
+
S_IFMT = 00170000
|
100
|
+
S_IFLNK = 0120000
|
101
|
+
S_IFREG = 0100000
|
102
|
+
S_IFDIR = 0040000
|
103
|
+
|
104
|
+
attr_accessor :mode, :name, :sha1
|
105
|
+
def initialize(mode, filename, sha1o)
|
106
|
+
@mode = 0
|
107
|
+
mode.each_byte do |i|
|
108
|
+
@mode = (@mode << 3) | (i-'0'[0])
|
109
|
+
end
|
110
|
+
@name = filename
|
111
|
+
@sha1 = sha1o
|
112
|
+
if ![S_IFLNK, S_IFDIR, S_IFREG].include?(@mode & S_IFMT)
|
113
|
+
raise RuntimeError, "unknown type for directory entry"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def type
|
118
|
+
case @mode & S_IFMT
|
119
|
+
when S_IFLNK
|
120
|
+
@type = :link
|
121
|
+
when S_IFDIR
|
122
|
+
@type = :directory
|
123
|
+
when S_IFREG
|
124
|
+
@type = :file
|
125
|
+
else
|
126
|
+
raise RuntimeError, "unknown type for directory entry"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def type=(type)
|
131
|
+
case @type
|
132
|
+
when :link
|
133
|
+
@mode = (@mode & ~S_IFMT) | S_IFLNK
|
134
|
+
when :directory
|
135
|
+
@mode = (@mode & ~S_IFMT) | S_IFDIR
|
136
|
+
when :file
|
137
|
+
@mode = (@mode & ~S_IFMT) | S_IFREG
|
138
|
+
else
|
139
|
+
raise RuntimeError, "invalid type"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def format_type
|
144
|
+
case type
|
145
|
+
when :link
|
146
|
+
'link'
|
147
|
+
when :directory
|
148
|
+
'tree'
|
149
|
+
when :file
|
150
|
+
'blob'
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def format_mode
|
155
|
+
"%06o" % @mode
|
156
|
+
end
|
157
|
+
|
158
|
+
def raw
|
159
|
+
"%o %s\0%s" % [@mode, @name, [@sha1].pack("H*")]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
def self.read_bytes_until(io, char)
|
165
|
+
string = ''
|
166
|
+
while ((next_char = io.getc.chr) != char) && !io.eof
|
167
|
+
string += next_char
|
168
|
+
end
|
169
|
+
string
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
class Tree < Object
|
174
|
+
attr_accessor :entry
|
175
|
+
|
176
|
+
def self.from_raw(rawobject, repository=nil)
|
177
|
+
raw = StringIO.new(rawobject.content)
|
178
|
+
|
179
|
+
entries = []
|
180
|
+
while !raw.eof?
|
181
|
+
mode = Grit::GitRuby.read_bytes_until(raw, ' ')
|
182
|
+
file_name = Grit::GitRuby.read_bytes_until(raw, "\0")
|
183
|
+
raw_sha = raw.read(20)
|
184
|
+
sha = raw_sha.unpack("H*").first
|
185
|
+
|
186
|
+
entries << DirectoryEntry.new(mode, file_name, sha)
|
187
|
+
end
|
188
|
+
new(entries, repository)
|
189
|
+
end
|
190
|
+
|
191
|
+
def initialize(entries=[], repository = nil)
|
192
|
+
@entry = entries
|
193
|
+
@repository = repository
|
194
|
+
end
|
195
|
+
|
196
|
+
def type
|
197
|
+
:tree
|
198
|
+
end
|
199
|
+
|
200
|
+
def raw_content
|
201
|
+
# TODO: sort correctly
|
202
|
+
#@entry.sort { |a,b| a.name <=> b.name }.
|
203
|
+
@entry.collect { |e| [[e.format_mode, e.format_type, e.sha1].join(' '), e.name].join("\t") }.join("\n")
|
204
|
+
end
|
205
|
+
|
206
|
+
def actual_raw
|
207
|
+
#@entry.collect { |e| e.raw.join(' '), e.name].join("\t") }.join("\n")
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
class Commit < Object
|
212
|
+
attr_accessor :author, :committer, :tree, :parent, :message, :headers
|
213
|
+
|
214
|
+
def self.from_raw(rawobject, repository=nil)
|
215
|
+
parent = []
|
216
|
+
tree = author = committer = nil
|
217
|
+
|
218
|
+
headers, message = rawobject.content.split(/\n\n/, 2)
|
219
|
+
all_headers = headers.split(/\n/).map { |header| header.split(/ /, 2) }
|
220
|
+
all_headers.each do |key, value|
|
221
|
+
case key
|
222
|
+
when "tree"
|
223
|
+
tree = value
|
224
|
+
when "parent"
|
225
|
+
parent.push(value)
|
226
|
+
when "author"
|
227
|
+
author = UserInfo.new(value)
|
228
|
+
when "committer"
|
229
|
+
committer = UserInfo.new(value)
|
230
|
+
else
|
231
|
+
warn "unknown header '%s' in commit %s" % \
|
232
|
+
[key, rawobject.sha1.unpack("H*")[0]]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
if not tree && author && committer
|
236
|
+
raise RuntimeError, "incomplete raw commit object"
|
237
|
+
end
|
238
|
+
new(tree, parent, author, committer, message, headers, repository)
|
239
|
+
end
|
240
|
+
|
241
|
+
def initialize(tree, parent, author, committer, message, headers, repository=nil)
|
242
|
+
@tree = tree
|
243
|
+
@author = author
|
244
|
+
@parent = parent
|
245
|
+
@committer = committer
|
246
|
+
@message = message
|
247
|
+
@headers = headers
|
248
|
+
@repository = repository
|
249
|
+
end
|
250
|
+
|
251
|
+
def type
|
252
|
+
:commit
|
253
|
+
end
|
254
|
+
|
255
|
+
def raw_content
|
256
|
+
"tree %s\n%sauthor %s\ncommitter %s\n\n" % [
|
257
|
+
@tree,
|
258
|
+
@parent.collect { |i| "parent %s\n" % i }.join,
|
259
|
+
@author, @committer] + @message
|
260
|
+
end
|
261
|
+
|
262
|
+
def raw_log(sha)
|
263
|
+
output = "commit #{sha}\n"
|
264
|
+
output += @headers + "\n\n"
|
265
|
+
output += @message.split("\n").map { |l| ' ' + l }.join("\n") + "\n\n"
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
|
270
|
+
class Tag < Object
|
271
|
+
attr_accessor :object, :type, :tag, :tagger, :message
|
272
|
+
|
273
|
+
def self.from_raw(rawobject, repository=nil)
|
274
|
+
headers, message = rawobject.content.split(/\n\n/, 2)
|
275
|
+
headers = headers.split(/\n/).map { |header| header.split(/ /, 2) }
|
276
|
+
headers.each do |key, value|
|
277
|
+
case key
|
278
|
+
when "object"
|
279
|
+
object = value
|
280
|
+
when "type"
|
281
|
+
if !["blob", "tree", "commit", "tag"].include?(value)
|
282
|
+
raise RuntimeError, "invalid type in tag"
|
283
|
+
end
|
284
|
+
type = value.to_sym
|
285
|
+
when "tag"
|
286
|
+
tag = value
|
287
|
+
when "tagger"
|
288
|
+
tagger = UserInfo.new(value)
|
289
|
+
else
|
290
|
+
warn "unknown header '%s' in tag" % \
|
291
|
+
[key, rawobject.sha1.unpack("H*")[0]]
|
292
|
+
end
|
293
|
+
if not object && type && tag && tagger
|
294
|
+
raise RuntimeError, "incomplete raw tag object"
|
295
|
+
end
|
296
|
+
end
|
297
|
+
new(object, type, tag, tagger, repository)
|
298
|
+
end
|
299
|
+
|
300
|
+
def initialize(object, type, tag, tagger, repository=nil)
|
301
|
+
@object = object
|
302
|
+
@type = type
|
303
|
+
@tag = tag
|
304
|
+
@tagger = tagger
|
305
|
+
@repository = repository
|
306
|
+
end
|
307
|
+
|
308
|
+
def raw_content
|
309
|
+
"object %s\ntype %s\ntag %s\ntagger %s\n\n" % \
|
310
|
+
[@object, @type, @tag, @tagger] + @message
|
311
|
+
end
|
312
|
+
|
313
|
+
def type
|
314
|
+
:tag
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
end
|
319
|
+
end
|