/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to git/objects.py

  • Committer: James Westby
  • Date: 2007-03-25 13:28:11 UTC
  • mto: (0.215.1 trunk)
  • mto: This revision was merged to the branch mainline in revision 6960.
  • Revision ID: jw+debian@jameswestby.net-20070325132811-j9n1036d8ziqhvs9
Make it more like a real project.

Add copyright statements, and license the code under the GPLv2.

Also add a README file.

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
import sha
25
25
import zlib
26
26
 
27
 
from errors import (NotCommitError,
28
 
                    NotTreeError,
29
 
                    NotBlobError,
30
 
                    )
31
 
 
32
 
BLOB_ID = "blob"
33
 
TAG_ID = "tag"
34
 
TREE_ID = "tree"
35
 
COMMIT_ID = "commit"
36
 
PARENT_ID = "parent"
37
 
AUTHOR_ID = "author"
38
 
COMMITTER_ID = "committer"
 
27
blob_id = "blob"
 
28
tree_id = "tree"
 
29
commit_id = "commit"
 
30
parent_id = "parent"
 
31
author_id = "author"
 
32
committer_id = "committer"
39
33
 
40
34
def _decompress(string):
41
35
    dcomp = zlib.decompressobj()
47
41
  """Takes a string and returns the hex of the sha within"""
48
42
  hexsha = ''
49
43
  for c in sha:
50
 
    hexsha += "%02x" % ord(c)
 
44
    if ord(c) < 16:
 
45
      hexsha += "0%x" % ord(c)
 
46
    else:
 
47
      hexsha += "%x" % ord(c)
51
48
  assert len(hexsha) == 40, "Incorrect length of sha1 string: %d" % \
52
49
         len(hexsha)
53
50
  return hexsha
54
51
 
55
 
 
56
52
class ShaFile(object):
57
53
  """A git SHA file."""
58
54
 
 
55
  def _update_contents(self):
 
56
    """Update the _contents from the _text"""
 
57
    self._contents = [ord(c) for c in self._text]
 
58
 
59
59
  @classmethod
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"
82
82
    text = text[1:]
83
83
    object._text = text
 
84
    object._update_contents()
84
85
    return object
85
86
 
86
 
  def as_raw_string(self):
87
 
    return self._num_type, self._text
88
 
 
89
87
  @classmethod
90
88
  def _parse_object(cls, map):
91
89
    """Parse a new style object , creating it and setting object._text"""
102
100
      used += 1
103
101
    raw = map[used:]
104
102
    object._text = _decompress(raw)
 
103
    object._update_contents()
105
104
    return object
106
105
 
107
106
  @classmethod
116
115
    """Don't call this directly"""
117
116
 
118
117
  def _parse_text(self):
119
 
    """For subclasses to do initialisation time parsing"""
 
118
    """For subclasses to do initialistion time parsing"""
120
119
 
121
120
  @classmethod
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+')
126
125
    try:
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()
130
129
      return shafile
131
130
    finally:
132
131
      f.close()
133
132
 
134
 
  @classmethod
135
 
  def from_raw_string(cls, type, string):
136
 
    """Creates an object of the indicated type from the raw string given.
137
 
 
138
 
    Type is the numeric type of an object. String is the raw uncompressed
139
 
    contents.
140
 
    """
141
 
    real_class = num_type_map[type]
142
 
    obj = real_class()
143
 
    obj._num_type = type
144
 
    obj._text = string
145
 
    obj._parse_text()
146
 
    return obj
147
 
 
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))
150
135
 
151
 
  def crc32(self):
152
 
    return zlib.crc32(self._text)
 
136
  def contents(self):
 
137
    """The raw bytes of this object"""
 
138
    return self._contents
153
139
 
154
140
  def sha(self):
155
141
    """The SHA1 object that is the name of this object."""
158
144
    ressha.update(self._text)
159
145
    return ressha
160
146
 
161
 
  @property
162
 
  def id(self):
163
 
      return self.sha().hexdigest()
164
 
 
165
 
  def __repr__(self):
166
 
    return "<%s %s>" % (self.__class__.__name__, self.id)
167
 
 
168
 
  def __eq__(self, other):
169
 
    """Return true id the sha of the two objects match.
170
 
 
171
 
    The __le__ etc methods aren't overriden as they make no sense,
172
 
    certainly at this level.
173
 
    """
174
 
    return self.sha().digest() == other.sha().digest()
175
 
 
176
 
 
177
147
class Blob(ShaFile):
178
148
  """A Git Blob object."""
179
149
 
180
 
  _type = BLOB_ID
 
150
  _type = blob_id
181
151
 
182
 
  @property
183
 
  def data(self):
 
152
  def text(self):
184
153
    """The text contained within the blob object."""
185
154
    return self._text
186
155
 
187
156
  @classmethod
188
157
  def from_file(cls, filename):
189
158
    blob = ShaFile.from_file(filename)
190
 
    if blob._type != cls._type:
191
 
      raise NotBlobError(filename)
192
 
    return blob
193
 
 
194
 
  @classmethod
195
 
  def from_string(cls, string):
196
 
    """Create a blob from a string."""
197
 
    shafile = cls()
198
 
    shafile._text = string
199
 
    return shafile
200
 
 
201
 
 
202
 
class Tag(ShaFile):
203
 
  """A Git Tag object."""
204
 
 
205
 
  _type = TAG_ID
206
 
 
207
 
  @classmethod
208
 
  def from_file(cls, filename):
209
 
    blob = ShaFile.from_file(filename)
210
 
    if blob._type != cls._type:
211
 
      raise NotBlobError(filename)
212
 
    return blob
213
 
 
214
 
  @classmethod
215
 
  def from_string(cls, string):
216
 
    """Create a blob from a string."""
217
 
    shafile = cls()
218
 
    shafile._text = string
219
 
    return shafile
220
 
 
 
159
    assert blob._type == cls._type, "%s is not a blob object" % filename
 
160
    return blob
 
161
 
 
162
  @classmethod
 
163
  def from_string(cls, string):
 
164
    """Create a blob from a string."""
 
165
    shafile = cls()
 
166
    shafile._text = string
 
167
    shafile._update_contents()
 
168
    return shafile
221
169
 
222
170
class Tree(ShaFile):
223
171
  """A Git tree object"""
224
172
 
225
 
  _type = TREE_ID
 
173
  _type = tree_id
226
174
 
227
175
  @classmethod
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
232
179
    return tree
233
180
 
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
237
184
 
238
185
  def _parse_text(self):
264
211
class Commit(ShaFile):
265
212
  """A git commit object"""
266
213
 
267
 
  _type = COMMIT_ID
 
214
  _type = commit_id
268
215
 
269
216
  @classmethod
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
274
220
    return commit
275
221
 
276
222
  def _parse_text(self):
277
223
    text = self._text
278
224
    count = 0
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])
284
230
    count += 1
285
231
    self._tree = text[count:count+40]
286
232
    count = count + 40
288
234
         "tree sha must be followed by newline"
289
235
    count += 1
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])
295
241
      count += 1
296
242
      self._parents.append(text[count:count+40])
297
243
      count += 40
299
245
           "parent sha must be followed by newline"
300
246
      count += 1
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])
306
252
      count += 1
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]
311
256
        count += 1
312
 
      self._author += text[count]
313
 
      count += 1
314
 
      while text[count] != '\n':
315
 
        count += 1
316
257
      count += 1
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])
322
263
      count += 1
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]
327
267
        count += 1
328
 
      self._committer += text[count]
329
 
      count += 1
330
 
      assert text[count] == ' ', "Invalid commit object, " \
331
 
           "commiter information must be followed by space not %s" % text[count]
332
 
      count += 1
333
 
      self._commit_time = int(text[count:count+10])
334
 
      while text[count] != '\n':
335
 
        count += 1
336
268
      count += 1
337
269
    assert text[count] == '\n', "There must be a new line after the headers"
338
270
    count += 1
339
 
    # XXX: There can be an encoding field.
340
271
    self._message = text[count:]
341
272
 
342
 
  @property
343
273
  def tree(self):
344
274
    """Returns the tree that is the state of this commit"""
345
275
    return self._tree
346
276
 
347
 
  @property
348
277
  def parents(self):
349
278
    """Return a list of parents of this commit."""
350
279
    return self._parents
351
280
 
352
 
  @property
353
281
  def author(self):
354
282
    """Returns the name of the author of the commit"""
355
283
    return self._author
356
284
 
357
 
  @property
358
285
  def committer(self):
359
286
    """Returns the name of the committer of the commit"""
360
287
    return self._committer
361
288
 
362
 
  @property
363
289
  def message(self):
364
290
    """Returns the commit message"""
365
291
    return self._message
366
292
 
367
 
  @property
368
 
  def commit_time(self):
369
 
    """Returns the timestamp of the commit.
370
 
    
371
 
    Returns it as the number of seconds since the epoch.
372
 
    """
373
 
    return self._commit_time
374
 
 
375
293
type_map = {
376
 
  BLOB_ID : Blob,
377
 
  TREE_ID : Tree,
378
 
  COMMIT_ID : Commit,
379
 
  TAG_ID: Tag,
 
294
  blob_id : Blob,
 
295
  tree_id : Tree,
 
296
  commit_id : Commit,
380
297
}
381
298
 
382
299
num_type_map = {
383
 
  0: None,
384
 
  1: Commit,
385
 
  2: Tree,
386
 
  3: Blob,
387
 
  4: Tag,
388
 
  # 5 Is reserved for further expansion
 
300
  1 : Commit,
 
301
  2 : Tree,
 
302
  3 : Blob,
389
303
}
390
304