~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: Martin Pool
  • Date: 2005-07-22 22:37:53 UTC
  • Revision ID: mbp@sourcefrog.net-20050722223753-7dced4e32d3ce21d
- add the start of a test for inventory file-id matching

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.errors import BzrError, BzrCheckError
25
 
from bzrlib.inventory import Inventory
26
 
from bzrlib.osutils import fingerprint_file
27
 
import bzrlib.revision
28
 
from bzrlib.trace import mutter, note
 
27
 
 
28
exporters = {}
29
29
 
30
30
class Tree(object):
31
31
    """Abstract file tree.
49
49
    trees or versioned trees.
50
50
    """
51
51
    
52
 
    def conflicts(self):
53
 
        """Get a list of the conflicts in the tree.
54
 
 
55
 
        Each conflict is an instance of bzrlib.conflicts.Conflict.
56
 
        """
57
 
        return []
58
 
 
59
 
    def get_parent_ids(self):
60
 
        """Get the parent ids for this tree. 
61
 
 
62
 
        :return: a list of parent ids. [] is returned to indicate
63
 
        a tree with no parents.
64
 
        :raises: BzrError if the parents are not known.
65
 
        """
66
 
        raise NotImplementedError(self.get_parent_ids)
67
 
    
68
52
    def has_filename(self, filename):
69
53
        """True if the tree has given filename."""
70
54
        raise NotImplementedError()
72
56
    def has_id(self, file_id):
73
57
        return self.inventory.has_id(file_id)
74
58
 
75
 
    def has_or_had_id(self, file_id):
76
 
        if file_id == self.inventory.root.file_id:
77
 
            return True
78
 
        return self.inventory.has_id(file_id)
79
 
 
80
59
    __contains__ = has_id
81
60
 
82
61
    def __iter__(self):
85
64
    def id2path(self, file_id):
86
65
        return self.inventory.id2path(file_id)
87
66
 
88
 
    def kind(self, file_id):
89
 
        raise NotImplementedError("subclasses must implement kind")
90
 
 
91
67
    def _get_inventory(self):
92
68
        return self._inventory
93
 
    
94
 
    def get_file_by_path(self, path):
95
 
        return self.get_file(self._inventory.path2id(path))
96
69
 
97
70
    inventory = property(_get_inventory,
98
71
                         doc="Inventory of this Tree")
99
72
 
100
73
    def _check_retrieved(self, ie, f):
101
 
        if not __debug__:
102
 
            return  
103
74
        fp = fingerprint_file(f)
104
75
        f.seek(0)
105
76
        
117
88
                     "store is probably damaged/corrupt"])
118
89
 
119
90
 
120
 
    def print_file(self, file_id):
121
 
        """Print file with id `file_id` to stdout."""
 
91
    def print_file(self, fileid):
 
92
        """Print file with id `fileid` to stdout."""
122
93
        import sys
123
 
        sys.stdout.write(self.get_file_text(file_id))
124
 
 
125
 
    def lock_read(self):
126
 
        pass
127
 
 
128
 
    def unknowns(self):
129
 
        """What files are present in this tree and unknown.
130
 
        
131
 
        :return: an iterator over the unknown files.
132
 
        """
133
 
        return iter([])
134
 
 
135
 
    def unlock(self):
136
 
        pass
137
 
 
138
 
    def filter_unversioned_files(self, paths):
139
 
        """Filter out paths that are not versioned.
140
 
 
141
 
        :return: set of paths.
142
 
        """
143
 
        # NB: we specifically *don't* call self.has_filename, because for
144
 
        # WorkingTrees that can indicate files that exist on disk but that 
145
 
        # are not versioned.
146
 
        pred = self.inventory.has_filename
147
 
        return set((p for p in paths if not pred(p)))
148
 
        
149
 
        
 
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
 
150
108
class RevisionTree(Tree):
151
109
    """Tree viewing a previous revision.
152
110
 
157
115
           or at least passing a description to the constructor.
158
116
    """
159
117
    
160
 
    def __init__(self, branch, inv, revision_id):
161
 
        # for compatability the 'branch' parameter has not been renamed to 
162
 
        # repository at this point. However, we should change RevisionTree's
163
 
        # construction to always be via Repository and not via direct 
164
 
        # construction - this will mean that we can change the constructor
165
 
        # with much less chance of breaking client code.
166
 
        self._repository = branch
167
 
        self._weave_store = branch.weave_store
 
118
    def __init__(self, store, inv):
 
119
        self._store = store
168
120
        self._inventory = inv
169
 
        self._revision_id = revision_id
170
 
 
171
 
    def get_parent_ids(self):
172
 
        """See Tree.get_parent_ids.
173
 
 
174
 
        A RevisionTree's parents match the revision graph.
175
 
        """
176
 
        parent_ids = self._repository.get_revision(self._revision_id).parent_ids
177
 
        return parent_ids
178
 
        
179
 
    def get_revision_id(self):
180
 
        """Return the revision id associated with this tree."""
181
 
        return self._revision_id
182
 
 
183
 
    def get_weave(self, file_id):
184
 
        return self._weave_store.get_weave(file_id,
185
 
                self._repository.get_transaction())
186
 
 
187
 
    def get_file_lines(self, file_id):
188
 
        ie = self._inventory[file_id]
189
 
        weave = self.get_weave(file_id)
190
 
        return weave.get_lines(ie.revision)
191
 
 
192
 
    def get_file_text(self, file_id):
193
 
        return ''.join(self.get_file_lines(file_id))
194
121
 
195
122
    def get_file(self, file_id):
196
 
        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
197
128
 
198
129
    def get_file_size(self, file_id):
199
130
        return self._inventory[file_id].text_size
200
131
 
201
 
    def get_file_sha1(self, file_id, path=None):
202
 
        ie = self._inventory[file_id]
203
 
        if ie.kind == "file":
204
 
            return ie.text_sha1
205
 
        return None
206
 
 
207
 
    def get_file_mtime(self, file_id, path=None):
208
 
        ie = self._inventory[file_id]
209
 
        revision = self._repository.get_revision(ie.revision)
210
 
        return revision.timestamp
211
 
 
212
 
    def is_executable(self, file_id, path=None):
213
 
        ie = self._inventory[file_id]
214
 
        if ie.kind != "file":
215
 
            return None 
216
 
        return self._inventory[file_id].executable
 
132
    def get_file_sha1(self, file_id):
 
133
        ie = self._inventory[file_id]
 
134
        return ie.text_sha1
217
135
 
218
136
    def has_filename(self, filename):
219
137
        return bool(self.inventory.path2id(filename))
221
139
    def list_files(self):
222
140
        # The only files returned by this are those from the version
223
141
        for path, entry in self.inventory.iter_entries():
224
 
            yield path, 'V', entry.kind, entry.file_id, entry
225
 
 
226
 
    def get_symlink_target(self, file_id):
227
 
        ie = self._inventory[file_id]
228
 
        return ie.symlink_target;
229
 
 
230
 
    def kind(self, file_id):
231
 
        return self._inventory[file_id].kind
232
 
 
233
 
    def lock_read(self):
234
 
        self._repository.lock_read()
235
 
 
236
 
    def unlock(self):
237
 
        self._repository.unlock()
 
142
            yield path, 'V', entry.kind, entry.file_id
238
143
 
239
144
 
240
145
class EmptyTree(Tree):
241
 
 
242
 
    def __init__(self):
243
 
        self._inventory = Inventory()
244
 
 
245
 
    def get_parent_ids(self):
246
 
        """See Tree.get_parent_ids.
247
 
 
248
 
        An EmptyTree always has NULL_REVISION as the only parent.
249
 
        """
250
 
        return []
251
 
 
252
 
    def get_symlink_target(self, file_id):
253
 
        return None
 
146
    def __init__(self, root_id):
 
147
        from bzrlib.inventory import Inventory
 
148
        self._inventory = Inventory(root_id)
254
149
 
255
150
    def has_filename(self, filename):
256
151
        return False
257
152
 
258
 
    def kind(self, file_id):
259
 
        assert self._inventory[file_id].kind == "root_directory"
260
 
        return "root_directory"
261
 
 
262
153
    def list_files(self):
263
 
        return iter([])
 
154
        if False:  # just to make it a generator
 
155
            yield None
264
156
    
265
 
    def __contains__(self, file_id):
266
 
        return file_id in self._inventory
267
 
 
268
 
    def get_file_sha1(self, file_id, path=None):
269
 
        assert self._inventory[file_id].kind == "root_directory"
270
 
        return None
271
157
 
272
158
 
273
159
######################################################################
336
222
            
337
223
 
338
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