~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: Robert Collins
  • Date: 2005-10-30 17:32:20 UTC
  • Revision ID: robertc@robertcollins.net-20051030173220-0a4a8cf5dbde6673
Bugfix: SFTP had index errors walking up to the root path.

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 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:
61
58
    def has_id(self, file_id):
62
59
        return self.inventory.has_id(file_id)
63
60
 
 
61
    def has_or_had_id(self, file_id):
 
62
        if file_id == self.inventory.root.file_id:
 
63
            return True
 
64
        return self.inventory.has_id(file_id)
 
65
 
64
66
    __contains__ = has_id
65
67
 
66
 
    def id_set(self):
67
 
        """Return set of all ids in this tree."""
68
 
        return self.inventory.id_set()
69
 
 
70
68
    def __iter__(self):
71
69
        return iter(self.inventory)
72
70
 
73
71
    def id2path(self, file_id):
74
72
        return self.inventory.id2path(file_id)
75
73
 
 
74
    def kind(self, file_id):
 
75
        raise NotImplementedError("subclasses must implement kind")
 
76
 
76
77
    def _get_inventory(self):
77
78
        return self._inventory
 
79
    
 
80
    def get_file_by_path(self, path):
 
81
        return self.get_file(self._inventory.path2id(path))
78
82
 
79
83
    inventory = property(_get_inventory,
80
84
                         doc="Inventory of this Tree")
81
85
 
82
86
    def _check_retrieved(self, ie, f):
 
87
        if not __debug__:
 
88
            return  
83
89
        fp = fingerprint_file(f)
84
90
        f.seek(0)
85
91
        
86
92
        if ie.text_size != None:
87
93
            if ie.text_size != fp['size']:
88
 
                bailout("mismatched size for file %r in %r" % (ie.file_id, self._store),
 
94
                raise BzrError("mismatched size for file %r in %r" % (ie.file_id, self._store),
89
95
                        ["inventory expects %d bytes" % ie.text_size,
90
96
                         "file is actually %d bytes" % fp['size'],
91
97
                         "store is probably damaged/corrupt"])
92
98
 
93
99
        if ie.text_sha1 != fp['sha1']:
94
 
            bailout("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
 
100
            raise BzrError("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
95
101
                    ["inventory expects %s" % ie.text_sha1,
96
102
                     "file is actually %s" % fp['sha1'],
97
103
                     "store is probably damaged/corrupt"])
98
104
 
99
105
 
100
 
    def print_file(self, fileid):
101
 
        """Print file with id `fileid` to stdout."""
 
106
    def print_file(self, file_id):
 
107
        """Print file with id `file_id` to stdout."""
102
108
        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))
 
109
        sys.stdout.write(self.get_file_text(file_id))
 
110
        
 
111
        
 
112
    def export(self, dest, format='dir', root=None):
 
113
        """Export this tree."""
 
114
        try:
 
115
            exporter = exporters[format]
 
116
        except KeyError:
 
117
            from bzrlib.errors import BzrCommandError
 
118
            raise BzrCommandError("export format %r not supported" % format)
 
119
        exporter(self, dest, root)
131
120
 
132
121
 
133
122
 
141
130
           or at least passing a description to the constructor.
142
131
    """
143
132
    
144
 
    def __init__(self, store, inv):
145
 
        self._store = store
 
133
    def __init__(self, weave_store, inv, revision_id):
 
134
        self._weave_store = weave_store
146
135
        self._inventory = inv
 
136
        self._revision_id = revision_id
 
137
 
 
138
    def get_weave(self, file_id):
 
139
        # FIXME: RevisionTree should be given a branch
 
140
        # not a store, or the store should know the branch.
 
141
        import bzrlib.transactions as transactions
 
142
        return self._weave_store.get_weave(file_id,
 
143
            transactions.PassThroughTransaction())
 
144
 
 
145
 
 
146
    def get_file_lines(self, file_id):
 
147
        ie = self._inventory[file_id]
 
148
        weave = self.get_weave(file_id)
 
149
        return weave.get(ie.revision)
 
150
        
 
151
 
 
152
    def get_file_text(self, file_id):
 
153
        return ''.join(self.get_file_lines(file_id))
 
154
 
147
155
 
148
156
    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
 
157
        return StringIO(self.get_file_text(file_id))
154
158
 
155
159
    def get_file_size(self, file_id):
156
160
        return self._inventory[file_id].text_size
157
161
 
158
162
    def get_file_sha1(self, file_id):
159
163
        ie = self._inventory[file_id]
160
 
        return ie.text_sha1
 
164
        if ie.kind == "file":
 
165
            return ie.text_sha1
 
166
 
 
167
    def is_executable(self, file_id):
 
168
        ie = self._inventory[file_id]
 
169
        if ie.kind != "file":
 
170
            return None 
 
171
        return self._inventory[file_id].executable
161
172
 
162
173
    def has_filename(self, filename):
163
174
        return bool(self.inventory.path2id(filename))
165
176
    def list_files(self):
166
177
        # The only files returned by this are those from the version
167
178
        for path, entry in self.inventory.iter_entries():
168
 
            yield path, 'V', entry.kind, entry.file_id
 
179
            yield path, 'V', entry.kind, entry.file_id, entry
 
180
 
 
181
    def get_symlink_target(self, file_id):
 
182
        ie = self._inventory[file_id]
 
183
        return ie.symlink_target;
 
184
 
 
185
    def kind(self, file_id):
 
186
        return self._inventory[file_id].kind
169
187
 
170
188
 
171
189
class EmptyTree(Tree):
172
190
    def __init__(self):
173
191
        self._inventory = Inventory()
174
192
 
 
193
    def get_symlink_target(self, file_id):
 
194
        return None
 
195
 
175
196
    def has_filename(self, filename):
176
197
        return False
177
198
 
 
199
    def kind(self, file_id):
 
200
        assert self._inventory[file_id].kind == "root_directory"
 
201
        return "root_directory"
 
202
 
178
203
    def list_files(self):
179
 
        if False:  # just to make it a generator
180
 
            yield None
 
204
        return iter([])
181
205
    
 
206
    def __contains__(self, file_id):
 
207
        return file_id in self._inventory
 
208
 
 
209
    def get_file_sha1(self, file_id):
 
210
        assert self._inventory[file_id].kind == "root_directory"
 
211
        return None
182
212
 
183
213
 
184
214
######################################################################
245
275
        if old_name != new_name:
246
276
            yield (old_name, new_name)
247
277
            
 
278
 
 
279
 
 
280
######################################################################
 
281
# export
 
282
 
 
283
def dir_exporter(tree, dest, root):
 
284
    """Export this tree to a new directory.
 
285
 
 
286
    `dest` should not exist, and will be created holding the
 
287
    contents of this tree.
 
288
 
 
289
    TODO: To handle subdirectories we need to create the
 
290
           directories first.
 
291
 
 
292
    :note: If the export fails, the destination directory will be
 
293
           left in a half-assed state.
 
294
    """
 
295
    import os
 
296
    os.mkdir(dest)
 
297
    mutter('export version %r' % tree)
 
298
    inv = tree.inventory
 
299
    for dp, ie in inv.iter_entries():
 
300
        ie.put_on_disk(dest, dp, tree)
 
301
 
 
302
exporters['dir'] = dir_exporter
 
303
 
 
304
try:
 
305
    import tarfile
 
306
except ImportError:
 
307
    pass
 
308
else:
 
309
    def get_root_name(dest):
 
310
        """Get just the root name for a tarball.
 
311
 
 
312
        >>> get_root_name('mytar.tar')
 
313
        'mytar'
 
314
        >>> get_root_name('mytar.tar.bz2')
 
315
        'mytar'
 
316
        >>> get_root_name('tar.tar.tar.tgz')
 
317
        'tar.tar.tar'
 
318
        >>> get_root_name('bzr-0.0.5.tar.gz')
 
319
        'bzr-0.0.5'
 
320
        >>> get_root_name('a/long/path/mytar.tgz')
 
321
        'mytar'
 
322
        >>> get_root_name('../parent/../dir/other.tbz2')
 
323
        'other'
 
324
        """
 
325
        endings = ['.tar', '.tar.gz', '.tgz', '.tar.bz2', '.tbz2']
 
326
        dest = os.path.basename(dest)
 
327
        for end in endings:
 
328
            if dest.endswith(end):
 
329
                return dest[:-len(end)]
 
330
 
 
331
    def tar_exporter(tree, dest, root, compression=None):
 
332
        """Export this tree to a new tar file.
 
333
 
 
334
        `dest` will be created holding the contents of this tree; if it
 
335
        already exists, it will be clobbered, like with "tar -c".
 
336
        """
 
337
        from time import time
 
338
        now = time()
 
339
        compression = str(compression or '')
 
340
        if root is None:
 
341
            root = get_root_name(dest)
 
342
        try:
 
343
            ball = tarfile.open(dest, 'w:' + compression)
 
344
        except tarfile.CompressionError, e:
 
345
            raise BzrError(str(e))
 
346
        mutter('export version %r' % tree)
 
347
        inv = tree.inventory
 
348
        for dp, ie in inv.iter_entries():
 
349
            mutter("  export {%s} kind %s to %s" % (ie.file_id, ie.kind, dest))
 
350
            item, fileobj = ie.get_tar_item(root, dp, now, tree)
 
351
            ball.addfile(item, fileobj)
 
352
        ball.close()
 
353
 
 
354
    exporters['tar'] = tar_exporter
 
355
 
 
356
    def tgz_exporter(tree, dest, root):
 
357
        tar_exporter(tree, dest, root, compression='gz')
 
358
    exporters['tgz'] = tgz_exporter
 
359
 
 
360
    def tbz_exporter(tree, dest, root):
 
361
        tar_exporter(tree, dest, root, compression='bz2')
 
362
    exporters['tbz2'] = tbz_exporter