/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 dulwich/dulwich/pack.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# pack.py -- For dealing wih packed git objects.
2
 
# Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
3
 
# Copryight (C) 2008 Jelmer Vernooij <jelmer@samba.org>
4
 
# The code is loosely based on that in the sha1_file.c file from git itself,
5
 
# which is Copyright (C) Linus Torvalds, 2005 and distributed under the
6
 
# GPL version 2.
7
 
8
 
# This program is free software; you can redistribute it and/or
9
 
# modify it under the terms of the GNU General Public License
10
 
# as published by the Free Software Foundation; version 2
11
 
# of the License.
12
 
13
 
# This program is distributed in the hope that it will be useful,
14
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 
# GNU General Public License for more details.
17
 
18
 
# You should have received a copy of the GNU General Public License
19
 
# along with this program; if not, write to the Free Software
20
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21
 
# MA  02110-1301, USA.
22
 
 
23
 
"""Classes for dealing with packed git objects.
24
 
 
25
 
A pack is a compact representation of a bunch of objects, stored
26
 
using deltas where possible.
27
 
 
28
 
They have two parts, the pack file, which stores the data, and an index
29
 
that tells you where the data is.
30
 
 
31
 
To find an object you look in all of the index files 'til you find a
32
 
match for the object name. You then use the pointer got from this as
33
 
a pointer in to the corresponding packfile.
34
 
"""
35
 
 
36
 
from collections import defaultdict
37
 
import hashlib
38
 
from itertools import izip
39
 
import mmap
40
 
import os
41
 
import struct
42
 
import sys
43
 
import zlib
44
 
 
45
 
from objects import (
46
 
        ShaFile,
47
 
        )
48
 
from errors import ApplyDeltaError
49
 
 
50
 
supports_mmap_offset = (sys.version_info[0] >= 3 or 
51
 
        (sys.version_info[0] == 2 and sys.version_info[1] >= 6))
52
 
 
53
 
 
54
 
def take_msb_bytes(map, offset):
55
 
    ret = []
56
 
    while len(ret) == 0 or ret[-1] & 0x80:
57
 
        ret.append(ord(map[offset]))
58
 
        offset += 1
59
 
    return ret
60
 
 
61
 
 
62
 
def read_zlib(data, offset, dec_size):
63
 
    obj = zlib.decompressobj()
64
 
    x = ""
65
 
    fed = 0
66
 
    while obj.unused_data == "":
67
 
        base = offset+fed
68
 
        add = data[base:base+1024]
69
 
        fed += len(add)
70
 
        x += obj.decompress(add)
71
 
    assert len(x) == dec_size
72
 
    comp_len = fed-len(obj.unused_data)
73
 
    return x, comp_len
74
 
 
75
 
 
76
 
def hex_to_sha(hex):
77
 
  """Convert a hex string to a binary sha string."""
78
 
  ret = ""
79
 
  for i in range(0, len(hex), 2):
80
 
    ret += chr(int(hex[i:i+2], 16))
81
 
  return ret
82
 
 
83
 
def sha_to_hex(sha):
84
 
  """Convert a binary sha string to a hex sha string."""
85
 
  ret = ""
86
 
  for i in sha:
87
 
      ret += "%02x" % ord(i)
88
 
  return ret
89
 
 
90
 
MAX_MMAP_SIZE = 256 * 1024 * 1024
91
 
 
92
 
def simple_mmap(f, offset, size, access=mmap.ACCESS_READ):
93
 
    """Simple wrapper for mmap() which always supports the offset parameter.
94
 
 
95
 
    :param f: File object.
96
 
    :param offset: Offset in the file, from the beginning of the file.
97
 
    :param size: Size of the mmap'ed area
98
 
    :param access: Access mechanism.
99
 
    :return: MMAP'd area.
100
 
    """
101
 
    if offset+size > MAX_MMAP_SIZE and not supports_mmap_offset:
102
 
        raise AssertionError("%s is larger than 256 meg, and this version "
103
 
            "of Python does not support the offset argument to mmap().")
104
 
    if supports_mmap_offset:
105
 
        return mmap.mmap(f.fileno(), size, access=access, offset=offset)
106
 
    else:
107
 
        class ArraySkipper(object):
108
 
 
109
 
            def __init__(self, array, offset):
110
 
                self.array = array
111
 
                self.offset = offset
112
 
 
113
 
            def __getslice__(self, i, j):
114
 
                return self.array[i+self.offset:j+self.offset]
115
 
 
116
 
            def __getitem__(self, i):
117
 
                return self.array[i+self.offset]
118
 
 
119
 
            def __len__(self):
120
 
                return len(self.array) - self.offset
121
 
 
122
 
            def __str__(self):
123
 
                return str(self.array[self.offset:])
124
 
 
125
 
        mem = mmap.mmap(f.fileno(), size+offset, access=access)
126
 
        if offset == 0:
127
 
            return mem
128
 
        return ArraySkipper(mem, offset)
129
 
 
130
 
 
131
 
def resolve_object(offset, type, obj, get_ref, get_offset):
132
 
  """Resolve an object, possibly resolving deltas when necessary."""
133
 
  if not type in (6, 7): # Not a delta
134
 
     return type, obj
135
 
 
136
 
  if type == 6: # offset delta
137
 
     (delta_offset, delta) = obj
138
 
     assert isinstance(delta_offset, int)
139
 
     assert isinstance(delta, str)
140
 
     offset = offset-delta_offset
141
 
     type, base_obj = get_offset(offset)
142
 
     assert isinstance(type, int)
143
 
  elif type == 7: # ref delta
144
 
     (basename, delta) = obj
145
 
     assert isinstance(basename, str) and len(basename) == 20
146
 
     assert isinstance(delta, str)
147
 
     type, base_obj= get_ref(basename)
148
 
     assert isinstance(type, int)
149
 
  type, base_text = resolve_object(offset, type, base_obj, get_ref, get_offset)
150
 
  return type, apply_delta(base_text, delta)
151
 
 
152
 
 
153
 
class PackIndex(object):
154
 
  """An index in to a packfile.
155
 
 
156
 
  Given a sha id of an object a pack index can tell you the location in the
157
 
  packfile of that object if it has it.
158
 
 
159
 
  To do the loop it opens the file, and indexes first 256 4 byte groups
160
 
  with the first byte of the sha id. The value in the four byte group indexed
161
 
  is the end of the group that shares the same starting byte. Subtract one
162
 
  from the starting byte and index again to find the start of the group.
163
 
  The values are sorted by sha id within the group, so do the math to find
164
 
  the start and end offset and then bisect in to find if the value is present.
165
 
  """
166
 
 
167
 
  def __init__(self, filename):
168
 
    """Create a pack index object.
169
 
 
170
 
    Provide it with the name of the index file to consider, and it will map
171
 
    it whenever required.
172
 
    """
173
 
    self._filename = filename
174
 
    # Take the size now, so it can be checked each time we map the file to
175
 
    # ensure that it hasn't changed.
176
 
    self._size = os.path.getsize(filename)
177
 
    self._file = open(filename, 'r')
178
 
    self._contents = simple_mmap(self._file, 0, self._size)
179
 
    if self._contents[:4] != '\377tOc':
180
 
        self.version = 1
181
 
        self._fan_out_table = self._read_fan_out_table(0)
182
 
    else:
183
 
        (self.version, ) = struct.unpack_from(">L", self._contents, 4)
184
 
        assert self.version in (2,), "Version was %d" % self.version
185
 
        self._fan_out_table = self._read_fan_out_table(8)
186
 
        self._name_table_offset = 8 + 0x100 * 4
187
 
        self._crc32_table_offset = self._name_table_offset + 20 * len(self)
188
 
        self._pack_offset_table_offset = self._crc32_table_offset + 4 * len(self)
189
 
 
190
 
  def __eq__(self, other):
191
 
    if type(self) != type(other):
192
 
        return False
193
 
 
194
 
    if self._fan_out_table != other._fan_out_table:
195
 
        return False
196
 
 
197
 
    for (name1, _, _), (name2, _, _) in izip(self.iterentries(), other.iterentries()):
198
 
        if name1 != name2:
199
 
            return False
200
 
    return True
201
 
 
202
 
  def close(self):
203
 
    self._file.close()
204
 
 
205
 
  def __len__(self):
206
 
    """Return the number of entries in this pack index."""
207
 
    return self._fan_out_table[-1]
208
 
 
209
 
  def _unpack_entry(self, i):
210
 
    """Unpack the i-th entry in the index file.
211
 
 
212
 
    :return: Tuple with object name (SHA), offset in pack file and 
213
 
          CRC32 checksum (if known)."""
214
 
    if self.version == 1:
215
 
        (offset, name) = struct.unpack_from(">L20s", self._contents, 
216
 
            (0x100 * 4) + (i * 24))
217
 
        return (name, offset, None)
218
 
    else:
219
 
        return (self._unpack_name(i), self._unpack_offset(i), 
220
 
                self._unpack_crc32_checksum(i))
221
 
 
222
 
  def _unpack_name(self, i):
223
 
    if self.version == 1:
224
 
        return self._unpack_entry(i)[0]
225
 
    else:
226
 
        return struct.unpack_from("20s", self._contents, 
227
 
                                  self._name_table_offset + i * 20)[0]
228
 
 
229
 
  def _unpack_offset(self, i):
230
 
    if self.version == 1:
231
 
        return self._unpack_entry(i)[1]
232
 
    else:
233
 
        return struct.unpack_from(">L", self._contents, 
234
 
                                  self._pack_offset_table_offset + i * 4)[0]
235
 
 
236
 
  def _unpack_crc32_checksum(self, i):
237
 
    if self.version == 1:
238
 
        return None
239
 
    else:
240
 
        return struct.unpack_from(">L", self._contents, 
241
 
                                  self._crc32_table_offset + i * 4)[0]
242
 
 
243
 
  def __iter__(self):
244
 
    for i in range(len(self)):
245
 
        yield sha_to_hex(self._unpack_name(i))
246
 
 
247
 
  def iterentries(self):
248
 
    """Iterate over the entries in this pack index.
249
 
   
250
 
    Will yield tuples with object name, offset in packfile and crc32 checksum.
251
 
    """
252
 
    for i in range(len(self)):
253
 
        yield self._unpack_entry(i)
254
 
 
255
 
  def _read_fan_out_table(self, start_offset):
256
 
    ret = []
257
 
    for i in range(0x100):
258
 
        ret.append(struct.unpack(">L", self._contents[start_offset+i*4:start_offset+(i+1)*4])[0])
259
 
    return ret
260
 
 
261
 
  def check(self):
262
 
    """Check that the stored checksum matches the actual checksum."""
263
 
    return self.calculate_checksum() == self.get_stored_checksums()[1]
264
 
 
265
 
  def calculate_checksum(self):
266
 
    f = open(self._filename, 'r')
267
 
    try:
268
 
        return hashlib.sha1(self._contents[:-20]).digest()
269
 
    finally:
270
 
        f.close()
271
 
 
272
 
  def get_stored_checksums(self):
273
 
    """Return the SHA1 checksums stored for the corresponding packfile and 
274
 
    this header file itself."""
275
 
    return str(self._contents[-40:-20]), str(self._contents[-20:])
276
 
 
277
 
  def object_index(self, sha):
278
 
    """Return the index in to the corresponding packfile for the object.
279
 
 
280
 
    Given the name of an object it will return the offset that object lives
281
 
    at within the corresponding pack file. If the pack file doesn't have the
282
 
    object then None will be returned.
283
 
    """
284
 
    size = os.path.getsize(self._filename)
285
 
    assert size == self._size, "Pack index %s has changed size, I don't " \
286
 
         "like that" % self._filename
287
 
    if len(sha) == 40:
288
 
        sha = hex_to_sha(sha)
289
 
    return self._object_index(sha)
290
 
 
291
 
  def _object_index(self, sha):
292
 
      """See object_index"""
293
 
      idx = ord(sha[0])
294
 
      if idx == 0:
295
 
          start = 0
296
 
      else:
297
 
          start = self._fan_out_table[idx-1]
298
 
      end = self._fan_out_table[idx]
299
 
      assert start <= end
300
 
      while start <= end:
301
 
        i = (start + end)/2
302
 
        file_sha = self._unpack_name(i)
303
 
        if file_sha < sha:
304
 
          start = i + 1
305
 
        elif file_sha > sha:
306
 
          end = i - 1
307
 
        else:
308
 
          return self._unpack_offset(i)
309
 
      return None
310
 
 
311
 
 
312
 
class PackData(object):
313
 
  """The data contained in a packfile.
314
 
 
315
 
  Pack files can be accessed both sequentially for exploding a pack, and
316
 
  directly with the help of an index to retrieve a specific object.
317
 
 
318
 
  The objects within are either complete or a delta aginst another.
319
 
 
320
 
  The header is variable length. If the MSB of each byte is set then it
321
 
  indicates that the subsequent byte is still part of the header.
322
 
  For the first byte the next MS bits are the type, which tells you the type
323
 
  of object, and whether it is a delta. The LS byte is the lowest bits of the
324
 
  size. For each subsequent byte the LS 7 bits are the next MS bits of the
325
 
  size, i.e. the last byte of the header contains the MS bits of the size.
326
 
 
327
 
  For the complete objects the data is stored as zlib deflated data.
328
 
  The size in the header is the uncompressed object size, so to uncompress
329
 
  you need to just keep feeding data to zlib until you get an object back,
330
 
  or it errors on bad data. This is done here by just giving the complete
331
 
  buffer from the start of the deflated object on. This is bad, but until I
332
 
  get mmap sorted out it will have to do.
333
 
 
334
 
  Currently there are no integrity checks done. Also no attempt is made to try
335
 
  and detect the delta case, or a request for an object at the wrong position.
336
 
  It will all just throw a zlib or KeyError.
337
 
  """
338
 
 
339
 
  def __init__(self, filename):
340
 
    """Create a PackData object that represents the pack in the given filename.
341
 
 
342
 
    The file must exist and stay readable until the object is disposed of. It
343
 
    must also stay the same size. It will be mapped whenever needed.
344
 
 
345
 
    Currently there is a restriction on the size of the pack as the python
346
 
    mmap implementation is flawed.
347
 
    """
348
 
    self._filename = filename
349
 
    assert os.path.exists(filename), "%s is not a packfile" % filename
350
 
    self._size = os.path.getsize(filename)
351
 
    self._header_size = self._read_header()
352
 
 
353
 
  def _read_header(self):
354
 
    f = open(self._filename, 'rb')
355
 
    try:
356
 
        header = f.read(12)
357
 
        f.seek(self._size-20)
358
 
        self._stored_checksum = f.read(20)
359
 
    finally:
360
 
        f.close()
361
 
    assert header[:4] == "PACK"
362
 
    (version,) = struct.unpack_from(">L", header, 4)
363
 
    assert version in (2, 3), "Version was %d" % version
364
 
    (self._num_objects,) = struct.unpack_from(">L", header, 8)
365
 
    return 12 # Header size
366
 
 
367
 
  def __len__(self):
368
 
      """Returns the number of objects in this pack."""
369
 
      return self._num_objects
370
 
 
371
 
  def calculate_checksum(self):
372
 
    f = open(self._filename, 'rb')
373
 
    try:
374
 
        map = simple_mmap(f, 0, self._size)
375
 
        return hashlib.sha1(map[:-20]).digest()
376
 
    finally:
377
 
        f.close()
378
 
 
379
 
  def iterobjects(self):
380
 
    offset = self._header_size
381
 
    f = open(self._filename, 'rb')
382
 
    for i in range(len(self)):
383
 
        map = simple_mmap(f, offset, self._size-offset)
384
 
        (type, obj, total_size) = self._unpack_object(map)
385
 
        yield offset, type, obj
386
 
        offset += total_size
387
 
    f.close()
388
 
 
389
 
  def iterentries(self):
390
 
    found = {}
391
 
    postponed = list(self.iterobjects())
392
 
    while postponed:
393
 
      (offset, type, obj) = postponed.pop(0)
394
 
      assert isinstance(offset, int)
395
 
      assert isinstance(type, int)
396
 
      assert isinstance(obj, tuple) or isinstance(obj, str)
397
 
      try:
398
 
        type, obj = resolve_object(offset, type, obj, found.__getitem__, 
399
 
            self.get_object_at)
400
 
      except KeyError:
401
 
        postponed.append((offset, type, obj))
402
 
      else:
403
 
        shafile = ShaFile.from_raw_string(type, obj)
404
 
        sha = shafile.sha().digest()
405
 
        found[sha] = (type, obj)
406
 
        yield sha, offset, shafile.crc32()
407
 
 
408
 
  def create_index_v1(self, filename):
409
 
    entries = list(self.iterentries())
410
 
    write_pack_index_v1(filename, entries, self.calculate_checksum())
411
 
 
412
 
  def create_index_v2(self, filename):
413
 
    entries = list(self.iterentries())
414
 
    write_pack_index_v1(filename, entries, self.calculate_checksum())
415
 
 
416
 
  def get_stored_checksum(self):
417
 
    return self._stored_checksum
418
 
 
419
 
  def check(self):
420
 
    return (self.calculate_checksum() == self.get_stored_checksum())
421
 
 
422
 
  def get_object_at(self, offset):
423
 
    """Given an offset in to the packfile return the object that is there.
424
 
 
425
 
    Using the associated index the location of an object can be looked up, and
426
 
    then the packfile can be asked directly for that object using this
427
 
    function.
428
 
    """
429
 
    assert isinstance(offset, long) or isinstance(offset, int),\
430
 
            "offset was %r" % offset
431
 
    assert offset >= self._header_size
432
 
    size = os.path.getsize(self._filename)
433
 
    assert size == self._size, "Pack data %s has changed size, I don't " \
434
 
         "like that" % self._filename
435
 
    f = open(self._filename, 'rb')
436
 
    try:
437
 
      map = simple_mmap(f, offset, size-offset)
438
 
      return self._unpack_object(map)[:2]
439
 
    finally:
440
 
      f.close()
441
 
 
442
 
  def _unpack_object(self, map):
443
 
    bytes = take_msb_bytes(map, 0)
444
 
    type = (bytes[0] >> 4) & 0x07
445
 
    size = bytes[0] & 0x0f
446
 
    for i, byte in enumerate(bytes[1:]):
447
 
      size += (byte & 0x7f) << ((i * 7) + 4)
448
 
    raw_base = len(bytes)
449
 
    if type == 6: # offset delta
450
 
        bytes = take_msb_bytes(map, raw_base)
451
 
        assert not (bytes[-1] & 0x80)
452
 
        delta_base_offset = bytes[0] & 0x7f
453
 
        for byte in bytes[1:]:
454
 
            delta_base_offset += 1
455
 
            delta_base_offset <<= 7
456
 
            delta_base_offset += (byte & 0x7f)
457
 
        raw_base+=len(bytes)
458
 
        uncomp, comp_len = read_zlib(map, raw_base, size)
459
 
        assert size == len(uncomp)
460
 
        return type, (delta_base_offset, uncomp), comp_len+raw_base
461
 
    elif type == 7: # ref delta
462
 
        basename = map[raw_base:raw_base+20]
463
 
        uncomp, comp_len = read_zlib(map, raw_base+20, size)
464
 
        assert size == len(uncomp)
465
 
        return type, (basename, uncomp), comp_len+raw_base+20
466
 
    else:
467
 
        uncomp, comp_len = read_zlib(map, raw_base, size)
468
 
        assert len(uncomp) == size
469
 
        return type, uncomp, comp_len+raw_base
470
 
 
471
 
 
472
 
class SHA1Writer(object):
473
 
    
474
 
    def __init__(self, f):
475
 
        self.f = f
476
 
        self.sha1 = hashlib.sha1("")
477
 
 
478
 
    def write(self, data):
479
 
        self.sha1.update(data)
480
 
        self.f.write(data)
481
 
 
482
 
    def close(self):
483
 
        sha = self.sha1.digest()
484
 
        assert len(sha) == 20
485
 
        self.f.write(sha)
486
 
        self.f.close()
487
 
        return sha
488
 
 
489
 
    def tell(self):
490
 
        return self.f.tell()
491
 
 
492
 
 
493
 
def write_pack_object(f, type, object):
494
 
    """Write pack object to a file.
495
 
 
496
 
    :param f: File to write to
497
 
    :param o: Object to write
498
 
    """
499
 
    ret = f.tell()
500
 
    if type == 6: # ref delta
501
 
        (delta_base_offset, object) = object
502
 
    elif type == 7: # offset delta
503
 
        (basename, object) = object
504
 
    size = len(object)
505
 
    c = (type << 4) | (size & 15)
506
 
    size >>= 4
507
 
    while size:
508
 
        f.write(chr(c | 0x80))
509
 
        c = size & 0x7f
510
 
        size >>= 7
511
 
    f.write(chr(c))
512
 
    if type == 6: # offset delta
513
 
        ret = [delta_base_offset & 0x7f]
514
 
        delta_base_offset >>= 7
515
 
        while delta_base_offset:
516
 
            delta_base_offset -= 1
517
 
            ret.insert(0, 0x80 | (delta_base_offset & 0x7f))
518
 
            delta_base_offset >>= 7
519
 
        f.write("".join([chr(x) for x in ret]))
520
 
    elif type == 7: # ref delta
521
 
        assert len(basename) == 20
522
 
        f.write(basename)
523
 
    f.write(zlib.compress(object))
524
 
    return f.tell()
525
 
 
526
 
 
527
 
def write_pack(filename, objects):
528
 
    entries, data_sum = write_pack_data(filename + ".pack", objects)
529
 
    write_pack_index_v2(filename + ".idx", entries, data_sum)
530
 
 
531
 
 
532
 
def write_pack_data(filename, objects):
533
 
    """Write a new pack file.
534
 
 
535
 
    :param filename: The filename of the new pack file.
536
 
    :param objects: List of objects to write.
537
 
    :return: List with (name, offset, crc32 checksum) entries, pack checksum
538
 
    """
539
 
    f = open(filename, 'w')
540
 
    entries = []
541
 
    f = SHA1Writer(f)
542
 
    f.write("PACK")               # Pack header
543
 
    f.write(struct.pack(">L", 2)) # Pack version
544
 
    f.write(struct.pack(">L", len(objects))) # Number of objects in pack
545
 
    for o in objects:
546
 
        sha1 = o.sha().digest()
547
 
        crc32 = o.crc32()
548
 
        # FIXME: Delta !
549
 
        t, o = o.as_raw_string()
550
 
        offset = write_pack_object(f, t, o)
551
 
        entries.append((sha1, offset, crc32))
552
 
    return entries, f.close()
553
 
 
554
 
 
555
 
def write_pack_index_v1(filename, entries, pack_checksum):
556
 
    """Write a new pack index file.
557
 
 
558
 
    :param filename: The filename of the new pack index file.
559
 
    :param entries: List of tuples with object name (sha), offset_in_pack,  and
560
 
            crc32_checksum.
561
 
    :param pack_checksum: Checksum of the pack file.
562
 
    """
563
 
    # Sort entries first
564
 
 
565
 
    entries = sorted(entries)
566
 
    f = open(filename, 'w')
567
 
    f = SHA1Writer(f)
568
 
    fan_out_table = defaultdict(lambda: 0)
569
 
    for (name, offset, entry_checksum) in entries:
570
 
        fan_out_table[ord(name[0])] += 1
571
 
    # Fan-out table
572
 
    for i in range(0x100):
573
 
        f.write(struct.pack(">L", fan_out_table[i]))
574
 
        fan_out_table[i+1] += fan_out_table[i]
575
 
    for (name, offset, entry_checksum) in entries:
576
 
        f.write(struct.pack(">L20s", offset, name))
577
 
    assert len(pack_checksum) == 20
578
 
    f.write(pack_checksum)
579
 
    f.close()
580
 
 
581
 
 
582
 
def apply_delta(src_buf, delta):
583
 
    """Based on the similar function in git's patch-delta.c."""
584
 
    assert isinstance(src_buf, str), "was %r" % (src_buf,)
585
 
    assert isinstance(delta, str)
586
 
    out = ""
587
 
    def pop(delta):
588
 
        ret = delta[0]
589
 
        delta = delta[1:]
590
 
        return ord(ret), delta
591
 
    def get_delta_header_size(delta):
592
 
        size = 0
593
 
        i = 0
594
 
        while delta:
595
 
            cmd, delta = pop(delta)
596
 
            size |= (cmd & ~0x80) << i
597
 
            i += 7
598
 
            if not cmd & 0x80:
599
 
                break
600
 
        return size, delta
601
 
    src_size, delta = get_delta_header_size(delta)
602
 
    dest_size, delta = get_delta_header_size(delta)
603
 
    assert src_size == len(src_buf)
604
 
    while delta:
605
 
        cmd, delta = pop(delta)
606
 
        if cmd & 0x80:
607
 
            cp_off = 0
608
 
            for i in range(4):
609
 
                if cmd & (1 << i): 
610
 
                    x, delta = pop(delta)
611
 
                    cp_off |= x << (i * 8)
612
 
            cp_size = 0
613
 
            for i in range(3):
614
 
                if cmd & (1 << (4+i)): 
615
 
                    x, delta = pop(delta)
616
 
                    cp_size |= x << (i * 8)
617
 
            if cp_size == 0: 
618
 
                cp_size = 0x10000
619
 
            if (cp_off + cp_size < cp_size or
620
 
                cp_off + cp_size > src_size or
621
 
                cp_size > dest_size):
622
 
                break
623
 
            out += src_buf[cp_off:cp_off+cp_size]
624
 
        elif cmd != 0:
625
 
            out += delta[:cmd]
626
 
            delta = delta[cmd:]
627
 
        else:
628
 
            raise ApplyDeltaError("Invalid opcode 0")
629
 
    
630
 
    if delta != "":
631
 
        raise ApplyDeltaError("delta not empty: %r" % delta)
632
 
 
633
 
    if dest_size != len(out):
634
 
        raise ApplyDeltaError("dest size incorrect")
635
 
 
636
 
    return out
637
 
 
638
 
 
639
 
def write_pack_index_v2(filename, entries, pack_checksum):
640
 
    """Write a new pack index file.
641
 
 
642
 
    :param filename: The filename of the new pack index file.
643
 
    :param entries: List of tuples with object name (sha), offset_in_pack,  and
644
 
            crc32_checksum.
645
 
    :param pack_checksum: Checksum of the pack file.
646
 
    """
647
 
    # Sort entries first
648
 
    entries = sorted(entries)
649
 
    f = open(filename, 'w')
650
 
    f = SHA1Writer(f)
651
 
    f.write('\377tOc')
652
 
    f.write(struct.pack(">L", 2))
653
 
    fan_out_table = defaultdict(lambda: 0)
654
 
    for (name, offset, entry_checksum) in entries:
655
 
        fan_out_table[ord(name[0])] += 1
656
 
    # Fan-out table
657
 
    for i in range(0x100):
658
 
        f.write(struct.pack(">L", fan_out_table[i]))
659
 
        fan_out_table[i+1] += fan_out_table[i]
660
 
    for (name, offset, entry_checksum) in entries:
661
 
        f.write(name)
662
 
    for (name, offset, entry_checksum) in entries:
663
 
        f.write(struct.pack(">L", entry_checksum))
664
 
    for (name, offset, entry_checksum) in entries:
665
 
        # FIXME: handle if MSBit is set in offset
666
 
        f.write(struct.pack(">L", offset))
667
 
    # FIXME: handle table for pack files > 8 Gb
668
 
    assert len(pack_checksum) == 20
669
 
    f.write(pack_checksum)
670
 
    f.close()
671
 
 
672
 
 
673
 
class Pack(object):
674
 
 
675
 
    def __init__(self, basename):
676
 
        self._basename = basename
677
 
        self._idx = PackIndex(basename + ".idx")
678
 
        self._data = None
679
 
 
680
 
    def _get_data(self):
681
 
        if self._data is None:
682
 
            self._data = PackData(self._basename + ".pack")
683
 
            assert len(self._idx) == len(self._data)
684
 
            assert self._idx.get_stored_checksums()[0] == self._data.get_stored_checksum()
685
 
        return self._data
686
 
 
687
 
    def close(self):
688
 
        if self._data is not None:
689
 
            self._data.close()
690
 
        self._idx.close()
691
 
 
692
 
    def __eq__(self, other):
693
 
        return type(self) == type(other) and self._idx == other._idx
694
 
 
695
 
    def __len__(self):
696
 
        """Number of entries in this pack."""
697
 
        return len(self._idx)
698
 
 
699
 
    def __repr__(self):
700
 
        return "Pack(%r)" % self._basename
701
 
 
702
 
    def __iter__(self):
703
 
        """Iterate over all the sha1s of the objects in this pack."""
704
 
        return iter(self._idx)
705
 
 
706
 
    def check(self):
707
 
        return self._idx.check() and self._get_data().check()
708
 
 
709
 
    def get_stored_checksum(self):
710
 
        return self._get_data().get_stored_checksum()
711
 
 
712
 
    def __contains__(self, sha1):
713
 
        """Check whether this pack contains a particular SHA1."""
714
 
        return (self._idx.object_index(sha1) is not None)
715
 
 
716
 
    def _get_text(self, sha1):
717
 
        offset = self._idx.object_index(sha1)
718
 
        if offset is None:
719
 
            raise KeyError(sha1)
720
 
 
721
 
        type, obj = self._get_data().get_object_at(offset)
722
 
        assert isinstance(offset, int)
723
 
        return resolve_object(offset, type, obj, self._get_text, 
724
 
            self._get_data().get_object_at)
725
 
 
726
 
    def __getitem__(self, sha1):
727
 
        """Retrieve the specified SHA1."""
728
 
        type, uncomp = self._get_text(sha1)
729
 
        return ShaFile.from_raw_string(type, uncomp)
730
 
 
731
 
    def iterobjects(self):
732
 
        for offset, type, obj in self._get_data().iterobjects():
733
 
            assert isinstance(offset, int)
734
 
            yield ShaFile.from_raw_string(
735
 
                    *resolve_object(offset, type, obj, self._get_text, 
736
 
                self._get_data().get_object_at))
737
 
 
738
 
 
739
 
def load_packs(path):
740
 
    for name in os.listdir(path):
741
 
        if name.endswith(".pack"):
742
 
            yield Pack(os.path.join(path, name.rstrip(".pack")))