c40ed1559ca1fa7510080529828ba3477c65ceeb
[git/ruby-binding.git] / git / object.rb
blobc40ed1559ca1fa7510080529828ba3477c65ceeb
1 require 'digest/sha1'
3 module Git
4   # class for author/committer/tagger lines
5   class UserInfo
6     attr_accessor :name, :email, :date, :offset
8     def initialize(str)
9       m = /^(.*?) <(.*)> (\d+) ([+-])0*(\d+?)$/.match(str)
10       if !m
11         raise RuntimeError, "invalid %s header in commit" % key
12       end
13       @name = m[1]
14       @email = m[2]
15       @date = Time.at(Integer(m[3]))
16       @offset = (m[4] == "-" ? -1 : 1)*Integer(m[5])
17     end
19     def to_s
20       "%s <%s> %s %+05d" % [@name, @email, @date.to_i, @offset]
21     end
22   end
24   # base class for all git objects (blob, tree, commit, tag)
25   class Object
26     attr_accessor :repository
28     def Object.from_raw(rawobject, repository = nil)
29       case rawobject.type
30       when :blob
31         return Blob.from_raw(rawobject, repository)
32       when :tree
33         return Tree.from_raw(rawobject, repository)
34       when :commit
35         return Commit.from_raw(rawobject, repository)
36       when :tag
37         return Tag.from_raw(rawobject, repository)
38       else
39         raise RuntimeError, "got invalid object-type"
40       end
41     end
43     def initialize
44       raise NotImplemented, "abstract class"
45     end
47     def type
48       raise NotImplemented, "abstract class"
49     end
51     def raw_content
52       raise NotImplemented, "abstract class"
53     end
55     def sha1
56       Digest::SHA1.hexdigest("%s %d\0" % \
57                              [self.type, self.raw_content.length] + \
58                              self.raw_content)
59     end
60   end
62   class Blob < Object
63     attr_accessor :content
65     def self.from_raw(rawobject, repository)
66       new(rawobject.content)
67     end
69     def initialize(content, repository=nil)
70       @content = content
71       @repository = repository
72     end
74     def type
75       :blob
76     end
78     def raw_content
79       @content
80     end
81   end
83   class DirectoryEntry
84     S_IFMT  = 00170000
85     S_IFLNK =  0120000
86     S_IFREG =  0100000
87     S_IFDIR =  0040000
89     attr_accessor :mode, :name, :sha1
90     def initialize(buf)
91       m = /^(\d+) (.*)\0(.{20})$/m.match(buf)
92       if !m
93         raise RuntimeError, "invalid directory entry"
94       end
95       @mode = 0
96       m[1].each_byte do |i|
97         @mode = (@mode << 3) | (i-'0'[0])
98       end
99       @name = m[2]
100       @sha1 = m[3].unpack("H*")[0]
102       if ![S_IFLNK, S_IFDIR, S_IFREG].include?(@mode & S_IFMT)
103         raise RuntimeError, "unknown type for directory entry"
104       end
105     end
107     def type
108       case @mode & S_IFMT
109       when S_IFLNK
110         @type = :link
111       when S_IFDIR
112         @type = :directory
113       when S_IFREG
114         @type = :file
115       else
116         raise RuntimeError, "unknown type for directory entry"
117       end
118     end
120     def type=(type)
121       case @type
122       when :link
123         @mode = (@mode & ~S_IFMT) | S_IFLNK
124       when :directory
125         @mode = (@mode & ~S_IFMT) | S_IFDIR
126       when :file
127         @mode = (@mode & ~S_IFMT) | S_IFREG
128       else
129         raise RuntimeError, "invalid type"
130       end
131     end
133     def raw
134       "%o %s\0%s" % [@mode, @name, [@sha1].pack("H*")]
135     end
136   end
138   class Tree < Object
139     attr_accessor :entry
141     def self.from_raw(rawobject, repository=nil)
142       entries = []
143       rawobject.content.scan(/\d+ .*?\0.{20}/m) do |raw|
144         entries << DirectoryEntry.new(raw)
145       end
146       new(entries, repository)
147     end
149     def initialize(entries=[], repository = nil)
150       @entry = entries
151       @repository = repository
152     end
154     def type
155       :tree
156     end
158     def raw_content
159       # TODO: sort correctly
160       #@entry.sort { |a,b| a.name <=> b.name }.
161       @entry.
162         collect { |e| e.raw }.join
163     end
164   end
166   class Commit < Object
167     attr_accessor :author, :committer, :tree, :parent, :message
169     def self.from_raw(rawobject, repository=nil)
170       parent = []
171       tree = author = committer = nil
173       headers, message = rawobject.content.split(/\n\n/, 2)
174       headers = headers.split(/\n/).map { |header| header.split(/ /, 2) }
175       headers.each do |key, value|
176         case key
177         when "tree"
178           tree = value
179         when "parent"
180           parent.push(value)
181         when "author"
182           author = UserInfo.new(value)
183         when "committer"
184           committer = UserInfo.new(value)
185         else
186           raise RuntimeError, "invalid header %s in commit" % key
187         end
188       end
189       if not tree && author && committer
190         raise RuntimeError, "incomplete raw commit object"
191       end
192       new(tree, parent, author, committer, message, repository)
193     end
195     def initialize(tree, parent, author, committer, message, repository=nil)
196       @tree = tree
197       @author = author
198       @parent = parent
199       @committer = committer
200       @message = message
201       @repository = repository
202     end
204     def type
205       :commit
206     end
208     def raw_content
209       "tree %s\n%sauthor %s\ncommitter %s\n\n" % [
210         @tree,
211         @parent.collect { |i| "parent %s\n" % i }.join,
212         @author, @committer] + @message
213     end
214   end
216   class Tag < Object
217     attr_accessor :object, :type, :tag, :tagger, :message
219     def self.from_raw(rawobject, repository=nil)
220       headers, message = rawobject.content.split(/\n\n/, 2)
221       headers = headers.split(/\n/).map { |header| header.split(/ /, 2) }
222       headers.each do |key, value|
223         case key
224         when "object"
225           object = value
226         when "type"
227           if !["blob", "tree", "commit", "tag"].include?(value)
228             raise RuntimeError, "invalid type in tag"
229           end
230           type = value.to_sym
231         when "tag"
232           tag = value
233         when "tagger"
234           tagger = UserInfo.new(value)
235         else
236           raise RuntimeError, "invalid header %s in commit" % key
237         end
238       end
239       new(object, type, tag, tagger, repository)
240     end
242     def initialize(object, type, tag, tagger, repository=nil)
243       @object = object
244       @type = type
245       @tag = tag
246       @tagger = tagger
247       @repository = repository
248     end
250     def raw_content
251       "object %s\ntype %s\ntag %s\ntagger %s\n\n" % \
252         [@object, @type, @tag, @tagger] + @message
253     end
255     def type
256       :tag
257     end
258   end