~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: Martin Pool
  • Date: 2005-07-11 04:53:07 UTC
  • Revision ID: mbp@sourcefrog.net-20050711045307-2b38378d043dc25c
- Refactor weave calculation of inclusions

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 osutils import pumpfile, appendpath, fingerprint_file
20
21
import os
21
 
from cStringIO import StringIO
 
22
 
 
23
from bzrlib.trace import mutter, note
 
24
from bzrlib.errors import BzrError
22
25
 
23
26
import bzrlib
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
 
27
 
 
28
exporters = {}
28
29
 
29
30
class Tree(object):
30
31
    """Abstract file tree.
55
56
    def has_id(self, file_id):
56
57
        return self.inventory.has_id(file_id)
57
58
 
58
 
    def has_or_had_id(self, file_id):
59
 
        if file_id == self.inventory.root.file_id:
60
 
            return True
61
 
        return self.inventory.has_id(file_id)
62
 
 
63
59
    __contains__ = has_id
64
60
 
65
61
    def __iter__(self):
68
64
    def id2path(self, file_id):
69
65
        return self.inventory.id2path(file_id)
70
66
 
71
 
    def kind(self, file_id):
72
 
        raise NotImplementedError("subclasses must implement kind")
73
 
 
74
67
    def _get_inventory(self):
75
68
        return self._inventory
76
 
    
77
 
    def get_file_by_path(self, path):
78
 
        return self.get_file(self._inventory.path2id(path))
79
69
 
80
70
    inventory = property(_get_inventory,
81
71
                         doc="Inventory of this Tree")
82
72
 
83
73
    def _check_retrieved(self, ie, f):
84
 
        if not __debug__:
85
 
            return  
86
74
        fp = fingerprint_file(f)
87
75
        f.seek(0)
88
76
        
100
88
                     "store is probably damaged/corrupt"])
101
89
 
102
90
 
103
 
    def print_file(self, file_id):
104
 
        """Print file with id `file_id` to stdout."""
 
91
    def print_file(self, fileid):
 
92
        """Print file with id `fileid` to stdout."""
105
93
        import sys
106
 
        sys.stdout.write(self.get_file_text(file_id))
107
 
 
108
 
    def lock_read(self):
109
 
        pass
110
 
 
111
 
    def unlock(self):
112
 
        pass
113
 
 
114
 
    def filter_unversioned_files(self, paths):
115
 
        """Filter out paths that are not versioned.
116
 
 
117
 
        :return: set of paths.
118
 
        """
119
 
        # NB: we specifically *don't* call self.has_filename, because for
120
 
        # WorkingTrees that can indicate files that exist on disk but that 
121
 
        # are not versioned.
122
 
        pred = self.inventory.has_filename
123
 
        return set((p for p in paths if not pred(p)))
124
 
        
125
 
        
 
94
        pumpfile(self.get_file(fileid), sys.stdout)
 
95
        
 
96
        
 
97
    def export(self, dest, format='dir', root=None):
 
98
        """Export this tree."""
 
99
        try:
 
100
            exporter = exporters[format]
 
101
        except KeyError:
 
102
            from bzrlib.errors import BzrCommandError
 
103
            raise BzrCommandError("export format %r not supported" % format)
 
104
        exporter(self, dest, root)
 
105
 
 
106
 
 
107
 
126
108
class RevisionTree(Tree):
127
109
    """Tree viewing a previous revision.
128
110
 
133
115
           or at least passing a description to the constructor.
134
116
    """
135
117
    
136
 
    def __init__(self, branch, inv, revision_id):
137
 
        self._branch = branch
138
 
        self._weave_store = branch.weave_store
 
118
    def __init__(self, store, inv):
 
119
        self._store = store
139
120
        self._inventory = inv
140
 
        self._revision_id = revision_id
141
 
 
142
 
    def get_weave(self, file_id):
143
 
        return self._weave_store.get_weave(file_id,
144
 
                self._branch.get_transaction())
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_lines(ie.revision)
150
 
 
151
 
    def get_file_text(self, file_id):
152
 
        return ''.join(self.get_file_lines(file_id))
153
121
 
154
122
    def get_file(self, file_id):
155
 
        return StringIO(self.get_file_text(file_id))
 
123
        ie = self._inventory[file_id]
 
124
        f = self._store[ie.text_id]
 
125
        mutter("  get fileid{%s} from %r" % (file_id, self))
 
126
        self._check_retrieved(ie, f)
 
127
        return f
156
128
 
157
129
    def get_file_size(self, file_id):
158
130
        return self._inventory[file_id].text_size
159
131
 
160
132
    def get_file_sha1(self, file_id):
161
133
        ie = self._inventory[file_id]
162
 
        if ie.kind == "file":
163
 
            return ie.text_sha1
164
 
 
165
 
    def is_executable(self, file_id):
166
 
        ie = self._inventory[file_id]
167
 
        if ie.kind != "file":
168
 
            return None 
169
 
        return self._inventory[file_id].executable
 
134
        return ie.text_sha1
170
135
 
171
136
    def has_filename(self, filename):
172
137
        return bool(self.inventory.path2id(filename))
174
139
    def list_files(self):
175
140
        # The only files returned by this are those from the version
176
141
        for path, entry in self.inventory.iter_entries():
177
 
            yield path, 'V', entry.kind, entry.file_id, entry
178
 
 
179
 
    def get_symlink_target(self, file_id):
180
 
        ie = self._inventory[file_id]
181
 
        return ie.symlink_target;
182
 
 
183
 
    def kind(self, file_id):
184
 
        return self._inventory[file_id].kind
185
 
 
186
 
    def lock_read(self):
187
 
        self._branch.lock_read()
188
 
 
189
 
    def unlock(self):
190
 
        self._branch.unlock()
 
142
            yield path, 'V', entry.kind, entry.file_id
191
143
 
192
144
 
193
145
class EmptyTree(Tree):
194
146
    def __init__(self):
 
147
        from bzrlib.inventory import Inventory
195
148
        self._inventory = Inventory()
196
149
 
197
 
    def get_symlink_target(self, file_id):
198
 
        return None
199
 
 
200
150
    def has_filename(self, filename):
201
151
        return False
202
152
 
203
 
    def kind(self, file_id):
204
 
        assert self._inventory[file_id].kind == "root_directory"
205
 
        return "root_directory"
206
 
 
207
153
    def list_files(self):
208
 
        return iter([])
 
154
        if False:  # just to make it a generator
 
155
            yield None
209
156
    
210
 
    def __contains__(self, file_id):
211
 
        return file_id in self._inventory
212
 
 
213
 
    def get_file_sha1(self, file_id):
214
 
        assert self._inventory[file_id].kind == "root_directory"
215
 
        return None
216
157
 
217
158
 
218
159
######################################################################
281
222
            
282
223
 
283
224
 
 
225
######################################################################
 
226
# export
 
227
 
 
228
def dir_exporter(tree, dest, root):
 
229
    """Export this tree to a new directory.
 
230
 
 
231
    `dest` should not exist, and will be created holding the
 
232
    contents of this tree.
 
233
 
 
234
    TODO: To handle subdirectories we need to create the
 
235
           directories first.
 
236
 
 
237
    :note: If the export fails, the destination directory will be
 
238
           left in a half-assed state.
 
239
    """
 
240
    import os
 
241
    os.mkdir(dest)
 
242
    mutter('export version %r' % tree)
 
243
    inv = tree.inventory
 
244
    for dp, ie in inv.iter_entries():
 
245
        kind = ie.kind
 
246
        fullpath = appendpath(dest, dp)
 
247
        if kind == 'directory':
 
248
            os.mkdir(fullpath)
 
249
        elif kind == 'file':
 
250
            pumpfile(tree.get_file(ie.file_id), file(fullpath, 'wb'))
 
251
        else:
 
252
            raise BzrError("don't know how to export {%s} of kind %r" % (ie.file_id, kind))
 
253
        mutter("  export {%s} kind %s to %s" % (ie.file_id, kind, fullpath))
 
254
exporters['dir'] = dir_exporter
 
255
 
 
256
try:
 
257
    import tarfile
 
258
except ImportError:
 
259
    pass
 
260
else:
 
261
    def get_root_name(dest):
 
262
        """Get just the root name for a tarball.
 
263
 
 
264
        >>> get_root_name('mytar.tar')
 
265
        'mytar'
 
266
        >>> get_root_name('mytar.tar.bz2')
 
267
        'mytar'
 
268
        >>> get_root_name('tar.tar.tar.tgz')
 
269
        'tar.tar.tar'
 
270
        >>> get_root_name('bzr-0.0.5.tar.gz')
 
271
        'bzr-0.0.5'
 
272
        >>> get_root_name('a/long/path/mytar.tgz')
 
273
        'mytar'
 
274
        >>> get_root_name('../parent/../dir/other.tbz2')
 
275
        'other'
 
276
        """
 
277
        endings = ['.tar', '.tar.gz', '.tgz', '.tar.bz2', '.tbz2']
 
278
        dest = os.path.basename(dest)
 
279
        for end in endings:
 
280
            if dest.endswith(end):
 
281
                return dest[:-len(end)]
 
282
 
 
283
    def tar_exporter(tree, dest, root, compression=None):
 
284
        """Export this tree to a new tar file.
 
285
 
 
286
        `dest` will be created holding the contents of this tree; if it
 
287
        already exists, it will be clobbered, like with "tar -c".
 
288
        """
 
289
        from time import time
 
290
        now = time()
 
291
        compression = str(compression or '')
 
292
        if root is None:
 
293
            root = get_root_name(dest)
 
294
        try:
 
295
            ball = tarfile.open(dest, 'w:' + compression)
 
296
        except tarfile.CompressionError, e:
 
297
            raise BzrError(str(e))
 
298
        mutter('export version %r' % tree)
 
299
        inv = tree.inventory
 
300
        for dp, ie in inv.iter_entries():
 
301
            mutter("  export {%s} kind %s to %s" % (ie.file_id, ie.kind, dest))
 
302
            item = tarfile.TarInfo(os.path.join(root, dp))
 
303
            # TODO: would be cool to actually set it to the timestamp of the
 
304
            # revision it was last changed
 
305
            item.mtime = now
 
306
            if ie.kind == 'directory':
 
307
                item.type = tarfile.DIRTYPE
 
308
                fileobj = None
 
309
                item.name += '/'
 
310
                item.size = 0
 
311
                item.mode = 0755
 
312
            elif ie.kind == 'file':
 
313
                item.type = tarfile.REGTYPE
 
314
                fileobj = tree.get_file(ie.file_id)
 
315
                item.size = _find_file_size(fileobj)
 
316
                item.mode = 0644
 
317
            else:
 
318
                raise BzrError("don't know how to export {%s} of kind %r" %
 
319
                        (ie.file_id, ie.kind))
 
320
 
 
321
            ball.addfile(item, fileobj)
 
322
        ball.close()
 
323
    exporters['tar'] = tar_exporter
 
324
 
 
325
    def tgz_exporter(tree, dest, root):
 
326
        tar_exporter(tree, dest, root, compression='gz')
 
327
    exporters['tgz'] = tgz_exporter
 
328
 
 
329
    def tbz_exporter(tree, dest, root):
 
330
        tar_exporter(tree, dest, root, compression='bz2')
 
331
    exporters['tbz2'] = tbz_exporter
 
332
 
 
333
 
 
334
def _find_file_size(fileobj):
 
335
    offset = fileobj.tell()
 
336
    try:
 
337
        fileobj.seek(0, 2)
 
338
        size = fileobj.tell()
 
339
    except TypeError:
 
340
        # gzip doesn't accept second argument to seek()
 
341
        fileobj.seek(0)
 
342
        size = 0
 
343
        while True:
 
344
            nread = len(fileobj.read())
 
345
            if nread == 0:
 
346
                break
 
347
            size += nread
 
348
    fileobj.seek(offset)
 
349
    return size