~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: Martin Pool
  • Date: 2005-09-22 06:28:55 UTC
  • Revision ID: mbp@sourcefrog.net-20050922062855-a29aa53982b752d6
- try to avoid checking texts repeatedly

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
"""Tree classes, representing directory at point in time.
18
18
"""
19
19
 
20
 
from sets import Set
21
 
import os.path, os, fnmatch
22
 
 
23
 
from osutils import pumpfile, filesize, quotefn, sha_file, \
24
 
     joinpath, splitpath, appendpath, isdir, isfile, file_kind, fingerprint_file
25
 
import errno
26
 
from stat import S_ISREG, S_ISDIR, ST_MODE, ST_SIZE
27
 
 
28
 
from inventory import Inventory
29
 
from trace import mutter, note
30
 
from errors import bailout
31
 
import branch
 
20
import os
 
21
from cStringIO import StringIO
32
22
 
33
23
import bzrlib
34
 
 
35
 
class Tree:
 
24
from bzrlib.trace import mutter, note
 
25
from bzrlib.errors import BzrError, BzrCheckError
 
26
from bzrlib.inventory import Inventory
 
27
from bzrlib.osutils import pumpfile, appendpath, fingerprint_file
 
28
 
 
29
 
 
30
exporters = {}
 
31
 
 
32
class Tree(object):
36
33
    """Abstract file tree.
37
34
 
38
35
    There are several subclasses:
63
60
 
64
61
    __contains__ = has_id
65
62
 
66
 
    def id_set(self):
67
 
        """Return set of all ids in this tree."""
68
 
        return self.inventory.id_set()
69
 
 
70
63
    def __iter__(self):
71
64
        return iter(self.inventory)
72
65
 
75
68
 
76
69
    def _get_inventory(self):
77
70
        return self._inventory
 
71
    
 
72
    def get_file_by_path(self, path):
 
73
        return self.get_file(self._inventory.path2id(path))
78
74
 
79
75
    inventory = property(_get_inventory,
80
76
                         doc="Inventory of this Tree")
81
77
 
82
78
    def _check_retrieved(self, ie, f):
 
79
        if not __debug__:
 
80
            return  
83
81
        fp = fingerprint_file(f)
84
82
        f.seek(0)
85
83
        
86
84
        if ie.text_size != None:
87
85
            if ie.text_size != fp['size']:
88
 
                bailout("mismatched size for file %r in %r" % (ie.file_id, self._store),
 
86
                raise BzrError("mismatched size for file %r in %r" % (ie.file_id, self._store),
89
87
                        ["inventory expects %d bytes" % ie.text_size,
90
88
                         "file is actually %d bytes" % fp['size'],
91
89
                         "store is probably damaged/corrupt"])
92
90
 
93
91
        if ie.text_sha1 != fp['sha1']:
94
 
            bailout("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
 
92
            raise BzrError("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
95
93
                    ["inventory expects %s" % ie.text_sha1,
96
94
                     "file is actually %s" % fp['sha1'],
97
95
                     "store is probably damaged/corrupt"])
98
96
 
99
97
 
100
 
    def print_file(self, fileid):
101
 
        """Print file with id `fileid` to stdout."""
 
98
    def print_file(self, file_id):
 
99
        """Print file with id `file_id` to stdout."""
102
100
        import sys
103
 
        pumpfile(self.get_file(fileid), sys.stdout)
104
 
        
105
 
        
106
 
    def export(self, dest):        
107
 
        """Export this tree to a new directory.
108
 
 
109
 
        `dest` should not exist, and will be created holding the
110
 
        contents of this tree.
111
 
 
112
 
        TODO: To handle subdirectories we need to create the
113
 
               directories first.
114
 
 
115
 
        :note: If the export fails, the destination directory will be
116
 
               left in a half-assed state.
117
 
        """
118
 
        os.mkdir(dest)
119
 
        mutter('export version %r' % self)
120
 
        inv = self.inventory
121
 
        for dp, ie in inv.iter_entries():
122
 
            kind = ie.kind
123
 
            fullpath = appendpath(dest, dp)
124
 
            if kind == 'directory':
125
 
                os.mkdir(fullpath)
126
 
            elif kind == 'file':
127
 
                pumpfile(self.get_file(ie.file_id), file(fullpath, 'wb'))
128
 
            else:
129
 
                bailout("don't know how to export {%s} of kind %r" % (ie.file_id, kind))
130
 
            mutter("  export {%s} kind %s to %s" % (ie.file_id, kind, fullpath))
 
101
        sys.stdout.write(self.get_file_text(file_id))
 
102
        
 
103
        
 
104
    def export(self, dest, format='dir', root=None):
 
105
        """Export this tree."""
 
106
        try:
 
107
            exporter = exporters[format]
 
108
        except KeyError:
 
109
            from bzrlib.errors import BzrCommandError
 
110
            raise BzrCommandError("export format %r not supported" % format)
 
111
        exporter(self, dest, root)
131
112
 
132
113
 
133
114
 
141
122
           or at least passing a description to the constructor.
142
123
    """
143
124
    
144
 
    def __init__(self, store, inv):
145
 
        self._store = store
 
125
    def __init__(self, weave_store, inv, revision_id):
 
126
        self._weave_store = weave_store
146
127
        self._inventory = inv
 
128
        self._revision_id = revision_id
 
129
 
 
130
    def get_weave(self, file_id):
 
131
        return self._weave_store.get_weave(file_id)
 
132
        
 
133
 
 
134
    def get_file_text(self, file_id):
 
135
        ie = self._inventory[file_id]
 
136
        weave = self.get_weave(file_id)
 
137
        idx = weave.lookup(ie.text_version)
 
138
        content = weave.get_text(idx)
 
139
        if len(content) != ie.text_size:
 
140
            raise BzrCheckError('mismatched size on revision %s of file %s: '
 
141
                                '%d vs %d bytes'
 
142
                                % (self._revision_id, file_id, len(content),
 
143
                                   ie.text_size))
 
144
        return content
147
145
 
148
146
    def get_file(self, file_id):
149
 
        ie = self._inventory[file_id]
150
 
        f = self._store[ie.text_id]
151
 
        mutter("  get fileid{%s} from %r" % (file_id, self))
152
 
        self._check_retrieved(ie, f)
153
 
        return f
 
147
        return StringIO(self.get_file_text(file_id))
154
148
 
155
149
    def get_file_size(self, file_id):
156
150
        return self._inventory[file_id].text_size
157
151
 
158
152
    def get_file_sha1(self, file_id):
159
153
        ie = self._inventory[file_id]
160
 
        return ie.text_sha1
 
154
        if ie.kind == "file":
 
155
            return ie.text_sha1
161
156
 
162
157
    def has_filename(self, filename):
163
158
        return bool(self.inventory.path2id(filename))
179
174
        if False:  # just to make it a generator
180
175
            yield None
181
176
    
 
177
    def __contains__(self, file_id):
 
178
        return file_id in self._inventory
 
179
 
 
180
    def get_file_sha1(self, file_id):
 
181
        assert self._inventory[file_id].kind == "root_directory"
 
182
        return None
 
183
 
 
184
 
182
185
 
183
186
 
184
187
######################################################################
245
248
        if old_name != new_name:
246
249
            yield (old_name, new_name)
247
250
            
 
251
 
 
252
 
 
253
######################################################################
 
254
# export
 
255
 
 
256
def dir_exporter(tree, dest, root):
 
257
    """Export this tree to a new directory.
 
258
 
 
259
    `dest` should not exist, and will be created holding the
 
260
    contents of this tree.
 
261
 
 
262
    TODO: To handle subdirectories we need to create the
 
263
           directories first.
 
264
 
 
265
    :note: If the export fails, the destination directory will be
 
266
           left in a half-assed state.
 
267
    """
 
268
    import os
 
269
    os.mkdir(dest)
 
270
    mutter('export version %r' % tree)
 
271
    inv = tree.inventory
 
272
    for dp, ie in inv.iter_entries():
 
273
        kind = ie.kind
 
274
        fullpath = appendpath(dest, dp)
 
275
        if kind == 'directory':
 
276
            os.mkdir(fullpath)
 
277
        elif kind == 'file':
 
278
            pumpfile(tree.get_file(ie.file_id), file(fullpath, 'wb'))
 
279
        else:
 
280
            raise BzrError("don't know how to export {%s} of kind %r" % (ie.file_id, kind))
 
281
        mutter("  export {%s} kind %s to %s" % (ie.file_id, kind, fullpath))
 
282
exporters['dir'] = dir_exporter
 
283
 
 
284
try:
 
285
    import tarfile
 
286
except ImportError:
 
287
    pass
 
288
else:
 
289
    def get_root_name(dest):
 
290
        """Get just the root name for a tarball.
 
291
 
 
292
        >>> get_root_name('mytar.tar')
 
293
        'mytar'
 
294
        >>> get_root_name('mytar.tar.bz2')
 
295
        'mytar'
 
296
        >>> get_root_name('tar.tar.tar.tgz')
 
297
        'tar.tar.tar'
 
298
        >>> get_root_name('bzr-0.0.5.tar.gz')
 
299
        'bzr-0.0.5'
 
300
        >>> get_root_name('a/long/path/mytar.tgz')
 
301
        'mytar'
 
302
        >>> get_root_name('../parent/../dir/other.tbz2')
 
303
        'other'
 
304
        """
 
305
        endings = ['.tar', '.tar.gz', '.tgz', '.tar.bz2', '.tbz2']
 
306
        dest = os.path.basename(dest)
 
307
        for end in endings:
 
308
            if dest.endswith(end):
 
309
                return dest[:-len(end)]
 
310
 
 
311
    def tar_exporter(tree, dest, root, compression=None):
 
312
        """Export this tree to a new tar file.
 
313
 
 
314
        `dest` will be created holding the contents of this tree; if it
 
315
        already exists, it will be clobbered, like with "tar -c".
 
316
        """
 
317
        from time import time
 
318
        now = time()
 
319
        compression = str(compression or '')
 
320
        if root is None:
 
321
            root = get_root_name(dest)
 
322
        try:
 
323
            ball = tarfile.open(dest, 'w:' + compression)
 
324
        except tarfile.CompressionError, e:
 
325
            raise BzrError(str(e))
 
326
        mutter('export version %r' % tree)
 
327
        inv = tree.inventory
 
328
        for dp, ie in inv.iter_entries():
 
329
            mutter("  export {%s} kind %s to %s" % (ie.file_id, ie.kind, dest))
 
330
            item = tarfile.TarInfo(os.path.join(root, dp))
 
331
            # TODO: would be cool to actually set it to the timestamp of the
 
332
            # revision it was last changed
 
333
            item.mtime = now
 
334
            if ie.kind == 'directory':
 
335
                item.type = tarfile.DIRTYPE
 
336
                fileobj = None
 
337
                item.name += '/'
 
338
                item.size = 0
 
339
                item.mode = 0755
 
340
            elif ie.kind == 'file':
 
341
                item.type = tarfile.REGTYPE
 
342
                fileobj = tree.get_file(ie.file_id)
 
343
                item.size = _find_file_size(fileobj)
 
344
                item.mode = 0644
 
345
            else:
 
346
                raise BzrError("don't know how to export {%s} of kind %r" %
 
347
                        (ie.file_id, ie.kind))
 
348
 
 
349
            ball.addfile(item, fileobj)
 
350
        ball.close()
 
351
    exporters['tar'] = tar_exporter
 
352
 
 
353
    def tgz_exporter(tree, dest, root):
 
354
        tar_exporter(tree, dest, root, compression='gz')
 
355
    exporters['tgz'] = tgz_exporter
 
356
 
 
357
    def tbz_exporter(tree, dest, root):
 
358
        tar_exporter(tree, dest, root, compression='bz2')
 
359
    exporters['tbz2'] = tbz_exporter
 
360
 
 
361
 
 
362
def _find_file_size(fileobj):
 
363
    offset = fileobj.tell()
 
364
    try:
 
365
        fileobj.seek(0, 2)
 
366
        size = fileobj.tell()
 
367
    except TypeError:
 
368
        # gzip doesn't accept second argument to seek()
 
369
        fileobj.seek(0)
 
370
        size = 0
 
371
        while True:
 
372
            nread = len(fileobj.read())
 
373
            if nread == 0:
 
374
                break
 
375
            size += nread
 
376
    fileobj.seek(offset)
 
377
    return size