47
41
"""Takes a string and returns the hex of the sha within"""
50
hexsha += "%02x" % ord(c)
45
hexsha += "0%x" % ord(c)
47
hexsha += "%x" % ord(c)
51
48
assert len(hexsha) == 40, "Incorrect length of sha1 string: %d" % \
56
52
class ShaFile(object):
57
53
"""A git SHA file."""
55
def _update_contents(self):
56
"""Update the _contents from the _text"""
57
self._contents = [ord(c) for c in self._text]
60
60
def _parse_legacy_object(cls, map):
61
61
"""Parse a legacy object, creating it and setting object._text"""
81
81
assert text[0] == "\0", "Size not followed by null"
83
83
object._text = text
84
object._update_contents()
86
def as_raw_string(self):
87
return self._num_type, self._text
90
88
def _parse_object(cls, map):
91
89
"""Parse a new style object , creating it and setting object._text"""
116
115
"""Don't call this directly"""
118
117
def _parse_text(self):
119
"""For subclasses to do initialisation time parsing"""
118
"""For subclasses to do initialistion time parsing"""
122
121
def from_file(cls, filename):
123
122
"""Get the contents of a SHA file on disk"""
124
123
size = os.path.getsize(filename)
125
f = open(filename, 'rb')
124
f = open(filename, 'rb+')
127
map = mmap.mmap(f.fileno(), size, access=mmap.ACCESS_READ)
126
map = mmap.mmap(f.fileno(), size)
128
127
shafile = cls._parse_file(map)
129
128
shafile._parse_text()
135
def from_raw_string(cls, type, string):
136
"""Creates an object of the indicated type from the raw string given.
138
Type is the numeric type of an object. String is the raw uncompressed
141
real_class = num_type_map[type]
148
133
def _header(self):
149
return "%s %lu\0" % (self._type, len(self._text))
134
return "%s %lu\0" % (self._type, len(self._contents))
152
return zlib.crc32(self._text)
137
"""The raw bytes of this object"""
138
return self._contents
155
141
"""The SHA1 object that is the name of this object."""
158
144
ressha.update(self._text)
163
return self.sha().hexdigest()
166
return "<%s %s>" % (self.__class__.__name__, self.id)
168
def __eq__(self, other):
169
"""Return true id the sha of the two objects match.
171
The __le__ etc methods aren't overriden as they make no sense,
172
certainly at this level.
174
return self.sha().digest() == other.sha().digest()
177
147
class Blob(ShaFile):
178
148
"""A Git Blob object."""
184
153
"""The text contained within the blob object."""
185
154
return self._text
188
157
def from_file(cls, filename):
189
158
blob = ShaFile.from_file(filename)
190
if blob._type != cls._type:
191
raise NotBlobError(filename)
195
def from_string(cls, string):
196
"""Create a blob from a string."""
198
shafile._text = string
203
"""A Git Tag object."""
208
def from_file(cls, filename):
209
blob = ShaFile.from_file(filename)
210
if blob._type != cls._type:
211
raise NotBlobError(filename)
215
def from_string(cls, string):
216
"""Create a blob from a string."""
218
shafile._text = string
159
assert blob._type == cls._type, "%s is not a blob object" % filename
163
def from_string(cls, string):
164
"""Create a blob from a string."""
166
shafile._text = string
167
shafile._update_contents()
222
170
class Tree(ShaFile):
223
171
"""A Git tree object"""
228
176
def from_file(cls, filename):
229
177
tree = ShaFile.from_file(filename)
230
if tree._type != cls._type:
231
raise NotTreeError(filename)
178
assert tree._type == cls._type, "%s is not a tree object" % filename
234
181
def entries(self):
235
"""Return a list of tuples describing the tree entries"""
182
"""Reutrn a list of tuples describing the tree entries"""
236
183
return self._entries
238
185
def _parse_text(self):
264
211
class Commit(ShaFile):
265
212
"""A git commit object"""
270
217
def from_file(cls, filename):
271
218
commit = ShaFile.from_file(filename)
272
if commit._type != cls._type:
273
raise NotCommitError(filename)
219
assert commit._type == cls._type, "%s is not a commit object" % filename
276
222
def _parse_text(self):
277
223
text = self._text
279
assert text.startswith(TREE_ID), "Invalid commit object, " \
280
"must start with %s" % TREE_ID
281
count += len(TREE_ID)
225
assert text.startswith(tree_id), "Invlid commit object, " \
226
"must start with %s" % tree_id
227
count += len(tree_id)
282
228
assert text[count] == ' ', "Invalid commit object, " \
283
"%s must be followed by space not %s" % (TREE_ID, text[count])
229
"%s must be followed by space not %s" % (tree_id, text[count])
285
231
self._tree = text[count:count+40]
286
232
count = count + 40
288
234
"tree sha must be followed by newline"
290
236
self._parents = []
291
while text[count:].startswith(PARENT_ID):
292
count += len(PARENT_ID)
237
while text[count:].startswith(parent_id):
238
count += len(parent_id)
293
239
assert text[count] == ' ', "Invalid commit object, " \
294
"%s must be followed by space not %s" % (PARENT_ID, text[count])
240
"%s must be followed by space not %s" % (parent_id, text[count])
296
242
self._parents.append(text[count:count+40])
299
245
"parent sha must be followed by newline"
301
247
self._author = None
302
if text[count:].startswith(AUTHOR_ID):
303
count += len(AUTHOR_ID)
248
if text[count:].startswith(author_id):
249
count += len(author_id)
304
250
assert text[count] == ' ', "Invalid commit object, " \
305
"%s must be followed by space not %s" % (AUTHOR_ID, text[count])
251
"%s must be followed by space not %s" % (author_id, text[count])
307
253
self._author = ''
308
while text[count] != '>':
309
assert text[count] != '\n', "Malformed author information"
254
while text[count] != '\n':
310
255
self._author += text[count]
312
self._author += text[count]
314
while text[count] != '\n':
317
258
self._committer = None
318
if text[count:].startswith(COMMITTER_ID):
319
count += len(COMMITTER_ID)
259
if text[count:].startswith(committer_id):
260
count += len(committer_id)
320
261
assert text[count] == ' ', "Invalid commit object, " \
321
"%s must be followed by space not %s" % (COMMITTER_ID, text[count])
262
"%s must be followed by space not %s" % (committer_id, text[count])
323
264
self._committer = ''
324
while text[count] != '>':
325
assert text[count] != '\n', "Malformed committer information"
265
while text[count] != '\n':
326
266
self._committer += text[count]
328
self._committer += text[count]
330
assert text[count] == ' ', "Invalid commit object, " \
331
"commiter information must be followed by space not %s" % text[count]
333
self._commit_time = int(text[count:count+10])
334
while text[count] != '\n':
337
269
assert text[count] == '\n', "There must be a new line after the headers"
339
# XXX: There can be an encoding field.
340
271
self._message = text[count:]
344
274
"""Returns the tree that is the state of this commit"""
345
275
return self._tree
348
277
def parents(self):
349
278
"""Return a list of parents of this commit."""
350
279
return self._parents
353
281
def author(self):
354
282
"""Returns the name of the author of the commit"""
355
283
return self._author
358
285
def committer(self):
359
286
"""Returns the name of the committer of the commit"""
360
287
return self._committer
363
289
def message(self):
364
290
"""Returns the commit message"""
365
291
return self._message
368
def commit_time(self):
369
"""Returns the timestamp of the commit.
371
Returns it as the number of seconds since the epoch.
373
return self._commit_time
388
# 5 Is reserved for further expansion