/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.211.2 by James Westby
Make it more like a real project.
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.
6
# 
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
10
# of the License.
11
# 
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.
16
# 
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,
20
# MA  02110-1301, USA.
21
0.211.1 by James Westby
Start the python-git project.
22
import mmap
23
import os
24
import sha
25
import zlib
26
0.211.4 by James Westby
Add methods to repo to get objects of a certain type.
27
from errors import (NotCommitError,
28
                    NotTreeError,
29
                    NotBlobError,
30
                    )
31
0.211.1 by James Westby
Start the python-git project.
32
blob_id = "blob"
33
tree_id = "tree"
34
commit_id = "commit"
35
parent_id = "parent"
36
author_id = "author"
37
committer_id = "committer"
38
39
def _decompress(string):
40
    dcomp = zlib.decompressobj()
41
    dcomped = dcomp.decompress(string)
42
    dcomped += dcomp.flush()
43
    return dcomped
44
45
def sha_to_hex(sha):
46
  """Takes a string and returns the hex of the sha within"""
47
  hexsha = ''
48
  for c in sha:
0.211.5 by James Westby
Add support for getting the revision graph from a head.
49
    hexsha += "%02x" % ord(c)
0.211.1 by James Westby
Start the python-git project.
50
  assert len(hexsha) == 40, "Incorrect length of sha1 string: %d" % \
51
         len(hexsha)
52
  return hexsha
53
54
class ShaFile(object):
55
  """A git SHA file."""
56
57
  def _update_contents(self):
58
    """Update the _contents from the _text"""
59
    self._contents = [ord(c) for c in self._text]
60
61
  @classmethod
62
  def _parse_legacy_object(cls, map):
63
    """Parse a legacy object, creating it and setting object._text"""
64
    text = _decompress(map)
65
    object = None
66
    for posstype in type_map.keys():
67
      if text.startswith(posstype):
68
        object = type_map[posstype]()
69
        text = text[len(posstype):]
70
        break
71
    assert object is not None, "%s is not a known object type" % text[:9]
72
    assert text[0] == ' ', "%s is not a space" % text[0]
73
    text = text[1:]
74
    size = 0
75
    i = 0
76
    while text[0] >= '0' and text[0] <= '9':
77
      if i > 0 and size == 0:
78
        assert False, "Size is not in canonical format"
79
      size = (size * 10) + int(text[0])
80
      text = text[1:]
81
      i += 1
82
    object._size = size
83
    assert text[0] == "\0", "Size not followed by null"
84
    text = text[1:]
85
    object._text = text
86
    object._update_contents()
87
    return object
88
89
  @classmethod
90
  def _parse_object(cls, map):
91
    """Parse a new style object , creating it and setting object._text"""
92
    used = 0
93
    byte = ord(map[used])
94
    used += 1
95
    num_type = (byte >> 4) & 7
96
    try:
97
      object = num_type_map[num_type]()
98
    except KeyError:
99
      assert False, "Not a known type: %d" % num_type
100
    while((byte & 0x80) != 0):
101
      byte = ord(map[used])
102
      used += 1
103
    raw = map[used:]
104
    object._text = _decompress(raw)
105
    object._update_contents()
106
    return object
107
108
  @classmethod
109
  def _parse_file(cls, map):
110
    word = (ord(map[0]) << 8) + ord(map[1])
111
    if ord(map[0]) == 0x78 and (word % 31) == 0:
112
      return cls._parse_legacy_object(map)
113
    else:
114
      return cls._parse_object(map)
115
116
  def __init__(self):
117
    """Don't call this directly"""
118
119
  def _parse_text(self):
120
    """For subclasses to do initialistion time parsing"""
121
122
  @classmethod
123
  def from_file(cls, filename):
124
    """Get the contents of a SHA file on disk"""
125
    size = os.path.getsize(filename)
0.211.7 by James Westby
Drop the restriction on having objects writeable for the mmap.
126
    f = open(filename, 'rb')
0.211.1 by James Westby
Start the python-git project.
127
    try:
0.211.7 by James Westby
Drop the restriction on having objects writeable for the mmap.
128
      map = mmap.mmap(f.fileno(), size, access=mmap.ACCESS_READ)
0.211.1 by James Westby
Start the python-git project.
129
      shafile = cls._parse_file(map)
130
      shafile._parse_text()
131
      return shafile
132
    finally:
133
      f.close()
134
135
  def _header(self):
136
    return "%s %lu\0" % (self._type, len(self._contents))
137
138
  def contents(self):
139
    """The raw bytes of this object"""
140
    return self._contents
141
142
  def sha(self):
143
    """The SHA1 object that is the name of this object."""
144
    ressha = sha.new()
145
    ressha.update(self._header())
146
    ressha.update(self._text)
147
    return ressha
148
0.211.5 by James Westby
Add support for getting the revision graph from a head.
149
  def __eq__(self, other):
150
    """Return true id the sha of the two objects match.
151
152
    The __le__ etc methods aren't overriden as they make no sense,
153
    certainly at this level.
154
    """
155
    return self.sha().digest() == other.sha().digest()
156
0.211.1 by James Westby
Start the python-git project.
157
class Blob(ShaFile):
158
  """A Git Blob object."""
159
160
  _type = blob_id
161
162
  def text(self):
163
    """The text contained within the blob object."""
164
    return self._text
165
166
  @classmethod
167
  def from_file(cls, filename):
168
    blob = ShaFile.from_file(filename)
0.211.4 by James Westby
Add methods to repo to get objects of a certain type.
169
    if blob._type != cls._type:
170
      raise NotBlobError(filename)
0.211.1 by James Westby
Start the python-git project.
171
    return blob
172
173
  @classmethod
174
  def from_string(cls, string):
175
    """Create a blob from a string."""
176
    shafile = cls()
177
    shafile._text = string
178
    shafile._update_contents()
179
    return shafile
180
181
class Tree(ShaFile):
182
  """A Git tree object"""
183
184
  _type = tree_id
185
186
  @classmethod
187
  def from_file(cls, filename):
188
    tree = ShaFile.from_file(filename)
0.211.4 by James Westby
Add methods to repo to get objects of a certain type.
189
    if tree._type != cls._type:
190
      raise NotTreeError(filename)
0.211.1 by James Westby
Start the python-git project.
191
    return tree
192
193
  def entries(self):
194
    """Reutrn a list of tuples describing the tree entries"""
195
    return self._entries
196
197
  def _parse_text(self):
198
    """Grab the entries in the tree"""
199
    self._entries = []
200
    count = 0
201
    while count < len(self._text):
202
      mode = 0
203
      chr = self._text[count]
204
      while chr != ' ':
205
        assert chr >= '0' and chr <= '7', "%s is not a valid mode char" % chr
206
        mode = (mode << 3) + (ord(chr) - ord('0'))
207
        count += 1
208
        chr = self._text[count]
209
      count += 1
210
      chr = self._text[count]
211
      name = ''
212
      while chr != '\0':
213
        name += chr
214
        count += 1
215
        chr = self._text[count]
216
      count += 1
217
      chr = self._text[count]
218
      sha = self._text[count:count+20]
219
      hexsha = sha_to_hex(sha)
220
      self._entries.append((mode, name, hexsha))
221
      count = count + 20
222
223
class Commit(ShaFile):
224
  """A git commit object"""
225
226
  _type = commit_id
227
228
  @classmethod
229
  def from_file(cls, filename):
230
    commit = ShaFile.from_file(filename)
0.211.4 by James Westby
Add methods to repo to get objects of a certain type.
231
    if commit._type != cls._type:
232
      raise NotCommitError(filename)
0.211.1 by James Westby
Start the python-git project.
233
    return commit
234
235
  def _parse_text(self):
236
    text = self._text
237
    count = 0
238
    assert text.startswith(tree_id), "Invlid commit object, " \
239
         "must start with %s" % tree_id
240
    count += len(tree_id)
241
    assert text[count] == ' ', "Invalid commit object, " \
242
         "%s must be followed by space not %s" % (tree_id, text[count])
243
    count += 1
244
    self._tree = text[count:count+40]
245
    count = count + 40
246
    assert text[count] == "\n", "Invalid commit object, " \
247
         "tree sha must be followed by newline"
248
    count += 1
249
    self._parents = []
250
    while text[count:].startswith(parent_id):
251
      count += len(parent_id)
252
      assert text[count] == ' ', "Invalid commit object, " \
253
           "%s must be followed by space not %s" % (parent_id, text[count])
254
      count += 1
255
      self._parents.append(text[count:count+40])
256
      count += 40
257
      assert text[count] == "\n", "Invalid commit object, " \
258
           "parent sha must be followed by newline"
259
      count += 1
260
    self._author = None
261
    if text[count:].startswith(author_id):
262
      count += len(author_id)
263
      assert text[count] == ' ', "Invalid commit object, " \
264
           "%s must be followed by space not %s" % (author_id, text[count])
265
      count += 1
266
      self._author = ''
0.211.5 by James Westby
Add support for getting the revision graph from a head.
267
      while text[count] != '>':
268
        assert text[count] != '\n', "Malformed author information"
0.211.1 by James Westby
Start the python-git project.
269
        self._author += text[count]
270
        count += 1
0.211.5 by James Westby
Add support for getting the revision graph from a head.
271
      self._author += text[count]
272
      count += 1
273
      while text[count] != '\n':
274
        count += 1
0.211.1 by James Westby
Start the python-git project.
275
      count += 1
276
    self._committer = None
277
    if text[count:].startswith(committer_id):
278
      count += len(committer_id)
279
      assert text[count] == ' ', "Invalid commit object, " \
280
           "%s must be followed by space not %s" % (committer_id, text[count])
281
      count += 1
282
      self._committer = ''
0.211.5 by James Westby
Add support for getting the revision graph from a head.
283
      while text[count] != '>':
284
        assert text[count] != '\n', "Malformed committer information"
0.211.1 by James Westby
Start the python-git project.
285
        self._committer += text[count]
286
        count += 1
0.211.5 by James Westby
Add support for getting the revision graph from a head.
287
      self._committer += text[count]
288
      count += 1
289
      assert text[count] == ' ', "Invalid commit object, " \
290
           "commiter information must be followed by space not %s" % text[count]
291
      count += 1
292
      self._commit_time = int(text[count:count+10])
293
      while text[count] != '\n':
294
        count += 1
0.211.1 by James Westby
Start the python-git project.
295
      count += 1
296
    assert text[count] == '\n', "There must be a new line after the headers"
297
    count += 1
0.211.5 by James Westby
Add support for getting the revision graph from a head.
298
    # XXX: There can be an encoding field.
0.211.1 by James Westby
Start the python-git project.
299
    self._message = text[count:]
300
301
  def tree(self):
302
    """Returns the tree that is the state of this commit"""
303
    return self._tree
304
305
  def parents(self):
306
    """Return a list of parents of this commit."""
307
    return self._parents
308
309
  def author(self):
310
    """Returns the name of the author of the commit"""
311
    return self._author
312
313
  def committer(self):
314
    """Returns the name of the committer of the commit"""
315
    return self._committer
316
317
  def message(self):
318
    """Returns the commit message"""
319
    return self._message
320
0.211.5 by James Westby
Add support for getting the revision graph from a head.
321
  def commit_time(self):
322
    """Returns the timestamp of the commit.
323
    
324
    Returns it as the number of seconds since the epoch.
325
    """
326
    return self._commit_time
327
0.211.1 by James Westby
Start the python-git project.
328
type_map = {
329
  blob_id : Blob,
330
  tree_id : Tree,
331
  commit_id : Commit,
332
}
333
334
num_type_map = {
335
  1 : Commit,
336
  2 : Tree,
337
  3 : Blob,
338
}
339