17
17
"""Tree classes, representing directory at point in time.
21
import os.path, os, fnmatch
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 pumpfile, appendpath, fingerprint_file
36
33
"""Abstract file tree.
38
35
There are several subclasses:
76
69
def _get_inventory(self):
77
70
return self._inventory
72
def get_file_by_path(self, path):
73
return self.get_file(self._inventory.path2id(path))
79
75
inventory = property(_get_inventory,
80
76
doc="Inventory of this Tree")
82
78
def _check_retrieved(self, ie, f):
83
81
fp = fingerprint_file(f)
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"])
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"])
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."""
103
pumpfile(self.get_file(fileid), sys.stdout)
106
def export(self, dest):
107
"""Export this tree to a new directory.
109
`dest` should not exist, and will be created holding the
110
contents of this tree.
112
TODO: To handle subdirectories we need to create the
115
:note: If the export fails, the destination directory will be
116
left in a half-assed state.
119
mutter('export version %r' % self)
121
for dp, ie in inv.iter_entries():
123
fullpath = appendpath(dest, dp)
124
if kind == 'directory':
127
pumpfile(self.get_file(ie.file_id), file(fullpath, 'wb'))
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))
104
def export(self, dest, format='dir', root=None):
105
"""Export this tree."""
107
exporter = exporters[format]
109
from bzrlib.errors import BzrCommandError
110
raise BzrCommandError("export format %r not supported" % format)
111
exporter(self, dest, root)
141
122
or at least passing a description to the constructor.
144
def __init__(self, store, inv):
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
130
def get_weave(self, file_id):
131
return self._weave_store.get_weave(file_id)
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: '
142
% (self._revision_id, file_id, len(content),
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)
147
return StringIO(self.get_file_text(file_id))
155
149
def get_file_size(self, file_id):
156
150
return self._inventory[file_id].text_size
158
152
def get_file_sha1(self, file_id):
159
153
ie = self._inventory[file_id]
154
if ie.kind == "file":
162
157
def has_filename(self, filename):
163
158
return bool(self.inventory.path2id(filename))
245
248
if old_name != new_name:
246
249
yield (old_name, new_name)
253
######################################################################
256
def dir_exporter(tree, dest, root):
257
"""Export this tree to a new directory.
259
`dest` should not exist, and will be created holding the
260
contents of this tree.
262
TODO: To handle subdirectories we need to create the
265
:note: If the export fails, the destination directory will be
266
left in a half-assed state.
270
mutter('export version %r' % tree)
272
for dp, ie in inv.iter_entries():
274
fullpath = appendpath(dest, dp)
275
if kind == 'directory':
278
pumpfile(tree.get_file(ie.file_id), file(fullpath, 'wb'))
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
289
def get_root_name(dest):
290
"""Get just the root name for a tarball.
292
>>> get_root_name('mytar.tar')
294
>>> get_root_name('mytar.tar.bz2')
296
>>> get_root_name('tar.tar.tar.tgz')
298
>>> get_root_name('bzr-0.0.5.tar.gz')
300
>>> get_root_name('a/long/path/mytar.tgz')
302
>>> get_root_name('../parent/../dir/other.tbz2')
305
endings = ['.tar', '.tar.gz', '.tgz', '.tar.bz2', '.tbz2']
306
dest = os.path.basename(dest)
308
if dest.endswith(end):
309
return dest[:-len(end)]
311
def tar_exporter(tree, dest, root, compression=None):
312
"""Export this tree to a new tar file.
314
`dest` will be created holding the contents of this tree; if it
315
already exists, it will be clobbered, like with "tar -c".
317
from time import time
319
compression = str(compression or '')
321
root = get_root_name(dest)
323
ball = tarfile.open(dest, 'w:' + compression)
324
except tarfile.CompressionError, e:
325
raise BzrError(str(e))
326
mutter('export version %r' % tree)
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
334
if ie.kind == 'directory':
335
item.type = tarfile.DIRTYPE
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)
346
raise BzrError("don't know how to export {%s} of kind %r" %
347
(ie.file_id, ie.kind))
349
ball.addfile(item, fileobj)
351
exporters['tar'] = tar_exporter
353
def tgz_exporter(tree, dest, root):
354
tar_exporter(tree, dest, root, compression='gz')
355
exporters['tgz'] = tgz_exporter
357
def tbz_exporter(tree, dest, root):
358
tar_exporter(tree, dest, root, compression='bz2')
359
exporters['tbz2'] = tbz_exporter
362
def _find_file_size(fileobj):
363
offset = fileobj.tell()
366
size = fileobj.tell()
368
# gzip doesn't accept second argument to seek()
372
nread = len(fileobj.read())