17
17
"""Tree classes, representing directory at point in time.
21
import os.path, os, fnmatch, time
23
from osutils import pumpfile, filesize, quotefn, sha_file, \
24
joinpath, splitpath, appendpath, isdir, isfile, file_kind, fingerprint_file
26
from stat import S_ISREG, S_ISDIR, ST_MODE, ST_SIZE
28
from inventory import Inventory
29
from trace import mutter, note
30
from errors import bailout
21
from cStringIO import StringIO
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 fingerprint_file
37
29
class Tree(object):
38
30
"""Abstract file tree.
71
68
def id2path(self, file_id):
72
69
return self.inventory.id2path(file_id)
71
def kind(self, file_id):
72
raise NotImplementedError("subclasses must implement kind")
74
74
def _get_inventory(self):
75
75
return self._inventory
77
def get_file_by_path(self, path):
78
return self.get_file(self._inventory.path2id(path))
77
80
inventory = property(_get_inventory,
78
81
doc="Inventory of this Tree")
80
83
def _check_retrieved(self, ie, f):
81
86
fp = fingerprint_file(f)
84
89
if ie.text_size != None:
85
90
if ie.text_size != fp['size']:
86
bailout("mismatched size for file %r in %r" % (ie.file_id, self._store),
91
raise BzrError("mismatched size for file %r in %r" % (ie.file_id, self._store),
87
92
["inventory expects %d bytes" % ie.text_size,
88
93
"file is actually %d bytes" % fp['size'],
89
94
"store is probably damaged/corrupt"])
91
96
if ie.text_sha1 != fp['sha1']:
92
bailout("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
97
raise BzrError("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
93
98
["inventory expects %s" % ie.text_sha1,
94
99
"file is actually %s" % fp['sha1'],
95
100
"store is probably damaged/corrupt"])
98
def print_file(self, fileid):
99
"""Print file with id `fileid` to stdout."""
103
def print_file(self, file_id):
104
"""Print file with id `file_id` to stdout."""
101
pumpfile(self.get_file(fileid), sys.stdout)
104
def export(self, dest, format='dir'):
105
"""Export this tree."""
107
exporter = exporters[format]
109
raise BzrCommandError("export format %r not supported" % format)
106
sys.stdout.write(self.get_file_text(file_id))
114
def filter_unversioned_files(self, paths):
115
"""Filter out paths that are not versioned.
117
:return: set of paths.
119
# NB: we specifically *don't* call self.has_filename, because for
120
# WorkingTrees that can indicate files that exist on disk but that
122
pred = self.inventory.has_filename
123
return set((p for p in paths if not pred(p)))
114
126
class RevisionTree(Tree):
115
127
"""Tree viewing a previous revision.
121
133
or at least passing a description to the constructor.
124
def __init__(self, store, inv):
136
def __init__(self, branch, inv, revision_id):
137
self._branch = branch
138
self._weave_store = branch.weave_store
126
139
self._inventory = inv
140
self._revision_id = revision_id
142
def get_revision_id(self):
143
"""Return the revision id associated with this tree."""
144
return self._revision_id
146
def get_weave(self, file_id):
147
return self._weave_store.get_weave(file_id,
148
self._branch.get_transaction())
150
def get_file_lines(self, file_id):
151
ie = self._inventory[file_id]
152
weave = self.get_weave(file_id)
153
return weave.get_lines(ie.revision)
155
def get_file_text(self, file_id):
156
return ''.join(self.get_file_lines(file_id))
128
158
def get_file(self, file_id):
129
ie = self._inventory[file_id]
130
f = self._store[ie.text_id]
131
mutter(" get fileid{%s} from %r" % (file_id, self))
132
self._check_retrieved(ie, f)
159
return StringIO(self.get_file_text(file_id))
135
161
def get_file_size(self, file_id):
136
162
return self._inventory[file_id].text_size
138
def get_file_sha1(self, file_id):
139
ie = self._inventory[file_id]
164
def get_file_sha1(self, file_id, path=None):
165
ie = self._inventory[file_id]
166
if ie.kind == "file":
170
def get_file_mtime(self, file_id, path=None):
171
ie = self._inventory[file_id]
172
revision = self._branch.get_revision(ie.revision)
173
return revision.timestamp
175
def is_executable(self, file_id, path=None):
176
ie = self._inventory[file_id]
177
if ie.kind != "file":
179
return self._inventory[file_id].executable
142
181
def has_filename(self, filename):
143
182
return bool(self.inventory.path2id(filename))
145
184
def list_files(self):
146
185
# The only files returned by this are those from the version
147
186
for path, entry in self.inventory.iter_entries():
148
yield path, 'V', entry.kind, entry.file_id
187
yield path, 'V', entry.kind, entry.file_id, entry
189
def get_symlink_target(self, file_id):
190
ie = self._inventory[file_id]
191
return ie.symlink_target;
193
def kind(self, file_id):
194
return self._inventory[file_id].kind
197
self._branch.lock_read()
200
self._branch.unlock()
151
203
class EmptyTree(Tree):
152
204
def __init__(self):
153
205
self._inventory = Inventory()
207
def get_symlink_target(self, file_id):
155
210
def has_filename(self, filename):
213
def kind(self, file_id):
214
assert self._inventory[file_id].kind == "root_directory"
215
return "root_directory"
158
217
def list_files(self):
159
if False: # just to make it a generator
220
def __contains__(self, file_id):
221
return file_id in self._inventory
223
def get_file_sha1(self, file_id, path=None):
224
assert self._inventory[file_id].kind == "root_directory"
164
228
######################################################################
230
######################################################################
233
def dir_exporter(tree, dest):
234
"""Export this tree to a new directory.
236
`dest` should not exist, and will be created holding the
237
contents of this tree.
239
TODO: To handle subdirectories we need to create the
242
:note: If the export fails, the destination directory will be
243
left in a half-assed state.
246
mutter('export version %r' % tree)
248
for dp, ie in inv.iter_entries():
250
fullpath = appendpath(dest, dp)
251
if kind == 'directory':
254
pumpfile(tree.get_file(ie.file_id), file(fullpath, 'wb'))
256
bailout("don't know how to export {%s} of kind %r" % (ie.file_id, kind))
257
mutter(" export {%s} kind %s to %s" % (ie.file_id, kind, fullpath))
258
exporters['dir'] = dir_exporter
265
def tar_exporter(tree, dest, compression=None):
266
"""Export this tree to a new tar file.
268
`dest` will be created holding the contents of this tree; if it
269
already exists, it will be clobbered, like with "tar -c".
272
compression = str(compression or '')
274
ball = tarfile.open(dest, 'w:' + compression)
275
except tarfile.CompressionError, e:
277
mutter('export version %r' % tree)
279
for dp, ie in inv.iter_entries():
280
mutter(" export {%s} kind %s to %s" % (ie.file_id, ie.kind, dest))
281
item = tarfile.TarInfo(dp)
282
# TODO: would be cool to actually set it to the timestamp of the
283
# revision it was last changed
285
if ie.kind == 'directory':
286
item.type = tarfile.DIRTYPE
291
elif ie.kind == 'file':
292
item.type = tarfile.REGTYPE
293
fileobj = tree.get_file(ie.file_id)
294
item.size = _find_file_size(fileobj)
297
bailout("don't know how to export {%s} of kind %r" %
298
(ie.file_id, ie.kind))
300
ball.addfile(item, fileobj)
302
exporters['tar'] = tar_exporter
304
def tgz_exporter(tree, dest):
305
tar_exporter(tree, dest, compression='gz')
306
exporters['tgz'] = tgz_exporter
308
def tbz_exporter(tree, dest):
309
tar_exporter(tree, dest, compression='bz2')
310
exporters['tbz2'] = tbz_exporter
313
def _find_file_size(fileobj):
314
offset = fileobj.tell()
317
size = fileobj.tell()
319
# gzip doesn't accept second argument to seek()
323
nread = len(fileobj.read())