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.
Files changed (47) hide show
  1. data/History.txt +13 -0
  2. data/Manifest.txt +57 -0
  3. data/README.txt +213 -0
  4. data/Rakefile +29 -0
  5. data/grit.gemspec +60 -0
  6. data/lib/grit.rb +53 -0
  7. data/lib/grit/actor.rb +36 -0
  8. data/lib/grit/blob.rb +117 -0
  9. data/lib/grit/commit.rb +221 -0
  10. data/lib/grit/commit_stats.rb +104 -0
  11. data/lib/grit/config.rb +44 -0
  12. data/lib/grit/diff.rb +70 -0
  13. data/lib/grit/errors.rb +7 -0
  14. data/lib/grit/git-ruby.rb +175 -0
  15. data/lib/grit/git-ruby/commit_db.rb +52 -0
  16. data/lib/grit/git-ruby/file_index.rb +186 -0
  17. data/lib/grit/git-ruby/git_object.rb +344 -0
  18. data/lib/grit/git-ruby/internal/loose.rb +136 -0
  19. data/lib/grit/git-ruby/internal/mmap.rb +59 -0
  20. data/lib/grit/git-ruby/internal/pack.rb +332 -0
  21. data/lib/grit/git-ruby/internal/raw_object.rb +37 -0
  22. data/lib/grit/git-ruby/object.rb +319 -0
  23. data/lib/grit/git-ruby/repository.rb +675 -0
  24. data/lib/grit/git.rb +130 -0
  25. data/lib/grit/head.rb +83 -0
  26. data/lib/grit/index.rb +102 -0
  27. data/lib/grit/lazy.rb +31 -0
  28. data/lib/grit/ref.rb +95 -0
  29. data/lib/grit/repo.rb +381 -0
  30. data/lib/grit/status.rb +149 -0
  31. data/lib/grit/tag.rb +71 -0
  32. data/lib/grit/tree.rb +100 -0
  33. data/test/test_actor.rb +35 -0
  34. data/test/test_blob.rb +79 -0
  35. data/test/test_commit.rb +184 -0
  36. data/test/test_config.rb +58 -0
  37. data/test/test_diff.rb +18 -0
  38. data/test/test_git.rb +70 -0
  39. data/test/test_grit.rb +32 -0
  40. data/test/test_head.rb +41 -0
  41. data/test/test_real.rb +19 -0
  42. data/test/test_reality.rb +17 -0
  43. data/test/test_remote.rb +14 -0
  44. data/test/test_repo.rb +277 -0
  45. data/test/test_tag.rb +25 -0
  46. data/test/test_tree.rb +96 -0
  47. 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