~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revfile.py

  • Committer: mbp at sourcefrog
  • Date: 2005-04-09 06:21:44 UTC
  • Revision ID: mbp@sourcefrog.net-20050409062144-e47a4b64106e4c21af99beaf
debugĀ output

Show diffs side-by-side

added added

removed removed

Lines of Context:
52
52
is that sequence numbers are stable references.  But not every
53
53
repository in the world will assign the same sequence numbers,
54
54
therefore the SHA-1 is the only universally unique reference.
55
 
The iter method here will generally read through the whole index file
56
 
in one go.  With readahead in the kernel and python/libc (typically
57
 
128kB) this means that there should be no seeks and often only one
58
 
read() call to get everything into memory.
 
55
 
 
56
This is meant to scale to hold 100,000 revisions of a single file, by
 
57
which time the index file will be ~4.8MB and a bit big to read
 
58
sequentially.
 
59
 
 
60
Some of the reserved fields could be used to implement a (semi?)
 
61
balanced tree indexed by SHA1 so we can much more efficiently find the
 
62
index associated with a particular hash.  For 100,000 revs we would be
 
63
able to find it in about 17 random reads, which is not too bad.
 
64
 
 
65
This performs pretty well except when trying to calculate deltas of
 
66
really large files.  For that the main thing would be to plug in
 
67
something faster than difflib, which is after all pure Python.
 
68
Another approach is to just store the gzipped full text of big files,
 
69
though perhaps that's too perverse?
59
70
"""
60
71
 
61
72
 
96
107
FL_GZIP = 1
97
108
 
98
109
# maximum number of patches in a row before recording a whole text.
99
 
CHAIN_LIMIT = 50
 
110
# intentionally pretty low for testing purposes.
 
111
CHAIN_LIMIT = 2
100
112
 
101
113
 
102
114
class RevfileError(Exception):
105
117
class LimitHitException(Exception):
106
118
    pass
107
119
 
108
 
class Revfile(object):
109
 
    def __init__(self, basename, mode):
 
120
class Revfile:
 
121
    def __init__(self, basename):
 
122
        # TODO: Option to open readonly
 
123
 
110
124
        # TODO: Lock file  while open
111
125
 
112
126
        # TODO: advise of random access
113
127
 
114
128
        self.basename = basename
115
 
 
116
 
        if mode not in ['r', 'w']:
117
 
            raise RevfileError("invalid open mode %r" % mode)
118
 
        self.mode = mode
119
129
        
120
130
        idxname = basename + '.irev'
121
131
        dataname = basename + '.drev'
127
137
            raise RevfileError("half-assed revfile")
128
138
        
129
139
        if not idx_exists:
130
 
            if mode == 'r':
131
 
                raise RevfileError("Revfile %r does not exist" % basename)
132
 
            
133
140
            self.idxfile = open(idxname, 'w+b')
134
141
            self.datafile = open(dataname, 'w+b')
135
142
            
137
144
            self.idxfile.write(_HEADER)
138
145
            self.idxfile.flush()
139
146
        else:
140
 
            if mode == 'r':
141
 
                diskmode = 'rb'
142
 
            else:
143
 
                diskmode = 'r+b'
144
 
                
145
 
            self.idxfile = open(idxname, diskmode)
146
 
            self.datafile = open(dataname, diskmode)
 
147
            self.idxfile = open(idxname, 'r+b')
 
148
            self.datafile = open(dataname, 'r+b')
147
149
            
148
150
            h = self.idxfile.read(_RECORDSIZE)
149
151
            if h != _HEADER:
155
157
        if idx < 0 or idx > len(self):
156
158
            raise RevfileError("invalid index %r" % idx)
157
159
 
158
 
    def _check_write(self):
159
 
        if self.mode != 'w':
160
 
            raise RevfileError("%r is open readonly" % self.basename)
161
 
 
162
160
 
163
161
    def find_sha(self, s):
164
162
        assert isinstance(s, str)
201
199
        assert self.idxfile.tell() == _RECORDSIZE * (idx + 1)
202
200
        data_offset = self.datafile.tell()
203
201
 
204
 
        assert isinstance(data, str) # not unicode or anything weird
 
202
        assert isinstance(data, str) # not unicode or anything wierd
205
203
 
206
204
        self.datafile.write(data)
207
205
        self.datafile.flush()
262
260
        only be used if it would be a size win and if the existing
263
261
        base is not at too long of a delta chain already.
264
262
        """
265
 
        self._check_write()
266
 
        
267
263
        text_sha = sha.new(text).digest()
268
264
 
269
265
        idx = self.find_sha(text_sha)
370
366
        """Index by sequence id returns the index field"""
371
367
        ## TODO: Can avoid seek if we just moved there...
372
368
        self._seek_index(idx)
373
 
        idxrec = self._read_next_index()
374
 
        if idxrec == None:
375
 
            raise IndexError()
376
 
        else:
377
 
            return idxrec
 
369
        return self._read_next_index()
378
370
 
379
371
 
380
372
    def _seek_index(self, idx):
381
373
        if idx < 0:
382
374
            raise RevfileError("invalid index %r" % idx)
383
375
        self.idxfile.seek((idx + 1) * _RECORDSIZE)
384
 
 
385
 
 
386
 
 
387
 
    def __iter__(self):
388
 
        """Read back all index records.
389
 
 
390
 
        Do not seek the index file while this is underway!"""
391
 
        sys.stderr.write(" ** iter called ** \n")
392
 
        self._seek_index(0)
393
 
        while True:
394
 
            idxrec = self._read_next_index()
395
 
            if not idxrec:
396
 
                break
397
 
            yield idxrec
398
376
        
399
377
 
400
378
    def _read_next_index(self):
401
379
        rec = self.idxfile.read(_RECORDSIZE)
402
380
        if not rec:
403
 
            return None
 
381
            raise IndexError("end of index file")
404
382
        elif len(rec) != _RECORDSIZE:
405
383
            raise RevfileError("short read of %d bytes getting index %d from %r"
406
384
                               % (len(rec), idx, self.basename))
440
418
 
441
419
 
442
420
def main(argv):
 
421
    r = Revfile("testrev")
 
422
 
443
423
    try:
444
424
        cmd = argv[1]
445
425
    except IndexError:
448
428
                         "       revfile add-delta BASE\n"
449
429
                         "       revfile get IDX\n"
450
430
                         "       revfile find-sha HEX\n"
451
 
                         "       revfile total-text-size\n"
452
 
                         "       revfile last\n")
 
431
                         "       revfile total-text-size\n")
453
432
        return 1
454
433
 
455
 
    def rw():
456
 
        return Revfile('testrev', 'w')
457
 
 
458
 
    def ro():
459
 
        return Revfile('testrev', 'r')
460
 
 
461
434
    if cmd == 'add':
462
 
        print rw().add(sys.stdin.read())
 
435
        new_idx = r.add(sys.stdin.read())
 
436
        print new_idx
463
437
    elif cmd == 'add-delta':
464
 
        print rw().add(sys.stdin.read(), int(argv[2]))
 
438
        new_idx = r.add(sys.stdin.read(), int(argv[2]))
 
439
        print new_idx
465
440
    elif cmd == 'dump':
466
 
        ro().dump()
 
441
        r.dump()
467
442
    elif cmd == 'get':
468
443
        try:
469
444
            idx = int(argv[2])
475
450
            sys.stderr.write("invalid index %r\n" % idx)
476
451
            return 1
477
452
 
478
 
        sys.stdout.write(ro().get(idx))
 
453
        sys.stdout.write(r.get(idx))
479
454
    elif cmd == 'find-sha':
480
455
        try:
481
456
            s = unhexlify(argv[2])
483
458
            sys.stderr.write("usage: revfile find-sha HEX\n")
484
459
            return 1
485
460
 
486
 
        idx = ro().find_sha(s)
 
461
        idx = r.find_sha(s)
487
462
        if idx == _NO_RECORD:
488
463
            sys.stderr.write("no such record\n")
489
464
            return 1
490
465
        else:
491
466
            print idx
492
467
    elif cmd == 'total-text-size':
493
 
        print ro().total_text_size()
494
 
    elif cmd == 'last':
495
 
        print len(ro())-1
 
468
        print r.total_text_size()
496
469
    else:
497
470
        sys.stderr.write("unknown command %r\n" % cmd)
498
471
        return 1