/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
0.211.9 by James Westby
Add some basic pack handling code.
135
  @classmethod
136
  def from_raw_string(cls, type, string):
137
    """Creates an object of the indicated type from the raw string given.
138
139
    Type is the numeric type of an object. String is the raw uncompressed
140
    contents.
141
    """
142
    real_class = num_type_map[type]
143
    obj = real_class()
144
    obj._text = string
145
    obj._update_contents()
146
    return obj
147
0.211.1 by James Westby
Start the python-git project.
148
  def _header(self):
149
    return "%s %lu\0" % (self._type, len(self._contents))
150
151
  def contents(self):
152
    """The raw bytes of this object"""
153
    return self._contents
154
155
  def sha(self):
156
    """The SHA1 object that is the name of this object."""
157
    ressha = sha.new()
158
    ressha.update(self._header())
159
    ressha.update(self._text)
160
    return ressha
161
0.211.5 by James Westby
Add support for getting the revision graph from a head.
162
  def __eq__(self, other):
163
    """Return true id the sha of the two objects match.
164
165
    The __le__ etc methods aren't overriden as they make no sense,
166
    certainly at this level.
167
    """
168
    return self.sha().digest() == other.sha().digest()
169
0.211.1 by James Westby
Start the python-git project.
170
class Blob(ShaFile):
171
  """A Git Blob object."""
172
173
  _type = blob_id
174
175
  def text(self):
176
    """The text contained within the blob object."""
177
    return self._text
178
179
  @classmethod
180
  def from_file(cls, filename):
181
    blob = ShaFile.from_file(filename)
0.211.4 by James Westby
Add methods to repo to get objects of a certain type.
182
    if blob._type != cls._type:
183
      raise NotBlobError(filename)
0.211.1 by James Westby
Start the python-git project.
184
    return blob
185
186
  @classmethod
187
  def from_string(cls, string):
188
    """Create a blob from a string."""
189
    shafile = cls()
190
    shafile._text = string
191
    shafile._update_contents()
192
    return shafile
193
194
class Tree(ShaFile):
195
  """A Git tree object"""
196
197
  _type = tree_id
198
199
  @classmethod
200
  def from_file(cls, filename):
201
    tree = ShaFile.from_file(filename)
0.211.4 by James Westby
Add methods to repo to get objects of a certain type.
202
    if tree._type != cls._type:
203
      raise NotTreeError(filename)
0.211.1 by James Westby
Start the python-git project.
204
    return tree
205
206
  def entries(self):
207
    """Reutrn a list of tuples describing the tree entries"""
208
    return self._entries
209
210
  def _parse_text(self):
211
    """Grab the entries in the tree"""
212
    self._entries = []
213
    count = 0
214
    while count < len(self._text):
215
      mode = 0
216
      chr = self._text[count]
217
      while chr != ' ':
218
        assert chr >= '0' and chr <= '7', "%s is not a valid mode char" % chr
219
        mode = (mode << 3) + (ord(chr) - ord('0'))
220
        count += 1
221
        chr = self._text[count]
222
      count += 1
223
      chr = self._text[count]
224
      name = ''
225
      while chr != '\0':
226
        name += chr
227
        count += 1
228
        chr = self._text[count]
229
      count += 1
230
      chr = self._text[count]
231
      sha = self._text[count:count+20]
232
      hexsha = sha_to_hex(sha)
233
      self._entries.append((mode, name, hexsha))
234
      count = count + 20
235
236
class Commit(ShaFile):
237
  """A git commit object"""
238
239
  _type = commit_id
240
241
  @classmethod
242
  def from_file(cls, filename):
243
    commit = ShaFile.from_file(filename)
0.211.4 by James Westby
Add methods to repo to get objects of a certain type.
244
    if commit._type != cls._type:
245
      raise NotCommitError(filename)
0.211.1 by James Westby
Start the python-git project.
246
    return commit
247
248
  def _parse_text(self):
249
    text = self._text
250
    count = 0
251
    assert text.startswith(tree_id), "Invlid commit object, " \
252
         "must start with %s" % tree_id
253
    count += len(tree_id)
254
    assert text[count] == ' ', "Invalid commit object, " \
255
         "%s must be followed by space not %s" % (tree_id, text[count])
256
    count += 1
257
    self._tree = text[count:count+40]
258
    count = count + 40
259
    assert text[count] == "\n", "Invalid commit object, " \
260
         "tree sha must be followed by newline"
261
    count += 1
262
    self._parents = []
263
    while text[count:].startswith(parent_id):
264
      count += len(parent_id)
265
      assert text[count] == ' ', "Invalid commit object, " \
266
           "%s must be followed by space not %s" % (parent_id, text[count])
267
      count += 1
268
      self._parents.append(text[count:count+40])
269
      count += 40
270
      assert text[count] == "\n", "Invalid commit object, " \
271
           "parent sha must be followed by newline"
272
      count += 1
273
    self._author = None
274
    if text[count:].startswith(author_id):
275
      count += len(author_id)
276
      assert text[count] == ' ', "Invalid commit object, " \
277
           "%s must be followed by space not %s" % (author_id, text[count])
278
      count += 1
279
      self._author = ''
0.211.5 by James Westby
Add support for getting the revision graph from a head.
280
      while text[count] != '>':
281
        assert text[count] != '\n', "Malformed author information"
0.211.1 by James Westby
Start the python-git project.
282
        self._author += text[count]
283
        count += 1
0.211.5 by James Westby
Add support for getting the revision graph from a head.
284
      self._author += text[count]
285
      count += 1
286
      while text[count] != '\n':
287
        count += 1
0.211.1 by James Westby
Start the python-git project.
288
      count += 1
289
    self._committer = None
290
    if text[count:].startswith(committer_id):
291
      count += len(committer_id)
292
      assert text[count] == ' ', "Invalid commit object, " \
293
           "%s must be followed by space not %s" % (committer_id, text[count])
294
      count += 1
295
      self._committer = ''
0.211.5 by James Westby
Add support for getting the revision graph from a head.
296
      while text[count] != '>':
297
        assert text[count] != '\n', "Malformed committer information"
0.211.1 by James Westby
Start the python-git project.
298
        self._committer += text[count]
299
        count += 1
0.211.5 by James Westby
Add support for getting the revision graph from a head.
300
      self._committer += text[count]
301
      count += 1
302
      assert text[count] == ' ', "Invalid commit object, " \
303
           "commiter information must be followed by space not %s" % text[count]
304
      count += 1
305
      self._commit_time = int(text[count:count+10])
306
      while text[count] != '\n':
307
        count += 1
0.211.1 by James Westby
Start the python-git project.
308
      count += 1
309
    assert text[count] == '\n', "There must be a new line after the headers"
310
    count += 1
0.211.5 by James Westby
Add support for getting the revision graph from a head.
311
    # XXX: There can be an encoding field.
0.211.1 by James Westby
Start the python-git project.
312
    self._message = text[count:]
313
314
  def tree(self):
315
    """Returns the tree that is the state of this commit"""
316
    return self._tree
317
318
  def parents(self):
319
    """Return a list of parents of this commit."""
320
    return self._parents
321
322
  def author(self):
323
    """Returns the name of the author of the commit"""
324
    return self._author
325
326
  def committer(self):
327
    """Returns the name of the committer of the commit"""
328
    return self._committer
329
330
  def message(self):
331
    """Returns the commit message"""
332
    return self._message
333
0.211.5 by James Westby
Add support for getting the revision graph from a head.
334
  def commit_time(self):
335
    """Returns the timestamp of the commit.
336
    
337
    Returns it as the number of seconds since the epoch.
338
    """
339
    return self._commit_time
340
0.211.1 by James Westby
Start the python-git project.
341
type_map = {
342
  blob_id : Blob,
343
  tree_id : Tree,
344
  commit_id : Commit,
345
}
346
347
num_type_map = {
348
  1 : Commit,
349
  2 : Tree,
350
  3 : Blob,
351
}
352