1
# objects.py -- Acces to base git objects
2
# Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
3
# The header parsing code is based on that from git itself, which is
4
# Copyright (C) 2005 Linus Torvalds
5
# and licensed under v2 of the GPL.
7
# This program is free software; you can redistribute it and/or
8
# modify it under the terms of the GNU General Public License
9
# as published by the Free Software Foundation; version 2
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License
18
# along with this program; if not, write to the Free Software
19
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
32
committer_id = "committer"
34
def _decompress(string):
35
dcomp = zlib.decompressobj()
36
dcomped = dcomp.decompress(string)
37
dcomped += dcomp.flush()
41
"""Takes a string and returns the hex of the sha within"""
45
hexsha += "0%x" % ord(c)
47
hexsha += "%x" % ord(c)
48
assert len(hexsha) == 40, "Incorrect length of sha1 string: %d" % \
52
class ShaFile(object):
55
def _update_contents(self):
56
"""Update the _contents from the _text"""
57
self._contents = [ord(c) for c in self._text]
60
def _parse_legacy_object(cls, map):
61
"""Parse a legacy object, creating it and setting object._text"""
62
text = _decompress(map)
64
for posstype in type_map.keys():
65
if text.startswith(posstype):
66
object = type_map[posstype]()
67
text = text[len(posstype):]
69
assert object is not None, "%s is not a known object type" % text[:9]
70
assert text[0] == ' ', "%s is not a space" % text[0]
74
while text[0] >= '0' and text[0] <= '9':
75
if i > 0 and size == 0:
76
assert False, "Size is not in canonical format"
77
size = (size * 10) + int(text[0])
81
assert text[0] == "\0", "Size not followed by null"
84
object._update_contents()
88
def _parse_object(cls, map):
89
"""Parse a new style object , creating it and setting object._text"""
93
num_type = (byte >> 4) & 7
95
object = num_type_map[num_type]()
97
assert False, "Not a known type: %d" % num_type
98
while((byte & 0x80) != 0):
102
object._text = _decompress(raw)
103
object._update_contents()
107
def _parse_file(cls, map):
108
word = (ord(map[0]) << 8) + ord(map[1])
109
if ord(map[0]) == 0x78 and (word % 31) == 0:
110
return cls._parse_legacy_object(map)
112
return cls._parse_object(map)
115
"""Don't call this directly"""
117
def _parse_text(self):
118
"""For subclasses to do initialistion time parsing"""
121
def from_file(cls, filename):
122
"""Get the contents of a SHA file on disk"""
123
size = os.path.getsize(filename)
124
f = open(filename, 'rb+')
126
map = mmap.mmap(f.fileno(), size)
127
shafile = cls._parse_file(map)
128
shafile._parse_text()
134
return "%s %lu\0" % (self._type, len(self._contents))
137
"""The raw bytes of this object"""
138
return self._contents
141
"""The SHA1 object that is the name of this object."""
143
ressha.update(self._header())
144
ressha.update(self._text)
148
"""A Git Blob object."""
153
"""The text contained within the blob object."""
157
def from_file(cls, filename):
158
blob = ShaFile.from_file(filename)
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()
171
"""A Git tree object"""
176
def from_file(cls, filename):
177
tree = ShaFile.from_file(filename)
178
assert tree._type == cls._type, "%s is not a tree object" % filename
182
"""Reutrn a list of tuples describing the tree entries"""
185
def _parse_text(self):
186
"""Grab the entries in the tree"""
189
while count < len(self._text):
191
chr = self._text[count]
193
assert chr >= '0' and chr <= '7', "%s is not a valid mode char" % chr
194
mode = (mode << 3) + (ord(chr) - ord('0'))
196
chr = self._text[count]
198
chr = self._text[count]
203
chr = self._text[count]
205
chr = self._text[count]
206
sha = self._text[count:count+20]
207
hexsha = sha_to_hex(sha)
208
self._entries.append((mode, name, hexsha))
211
class Commit(ShaFile):
212
"""A git commit object"""
217
def from_file(cls, filename):
218
commit = ShaFile.from_file(filename)
219
assert commit._type == cls._type, "%s is not a commit object" % filename
222
def _parse_text(self):
225
assert text.startswith(tree_id), "Invlid commit object, " \
226
"must start with %s" % tree_id
227
count += len(tree_id)
228
assert text[count] == ' ', "Invalid commit object, " \
229
"%s must be followed by space not %s" % (tree_id, text[count])
231
self._tree = text[count:count+40]
233
assert text[count] == "\n", "Invalid commit object, " \
234
"tree sha must be followed by newline"
237
while text[count:].startswith(parent_id):
238
count += len(parent_id)
239
assert text[count] == ' ', "Invalid commit object, " \
240
"%s must be followed by space not %s" % (parent_id, text[count])
242
self._parents.append(text[count:count+40])
244
assert text[count] == "\n", "Invalid commit object, " \
245
"parent sha must be followed by newline"
248
if text[count:].startswith(author_id):
249
count += len(author_id)
250
assert text[count] == ' ', "Invalid commit object, " \
251
"%s must be followed by space not %s" % (author_id, text[count])
254
while text[count] != '\n':
255
self._author += text[count]
258
self._committer = None
259
if text[count:].startswith(committer_id):
260
count += len(committer_id)
261
assert text[count] == ' ', "Invalid commit object, " \
262
"%s must be followed by space not %s" % (committer_id, text[count])
265
while text[count] != '\n':
266
self._committer += text[count]
269
assert text[count] == '\n', "There must be a new line after the headers"
271
self._message = text[count:]
274
"""Returns the tree that is the state of this commit"""
278
"""Return a list of parents of this commit."""
282
"""Returns the name of the author of the commit"""
286
"""Returns the name of the committer of the commit"""
287
return self._committer
290
"""Returns the commit message"""