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 appendpath, 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
109
class RevisionTree(Tree):
115
110
"""Tree viewing a previous revision.
121
116
or at least passing a description to the constructor.
124
def __init__(self, store, inv):
119
def __init__(self, branch, inv, revision_id):
120
self._branch = branch
121
self._weave_store = branch.weave_store
126
122
self._inventory = inv
123
self._revision_id = revision_id
125
def get_weave(self, file_id):
126
import bzrlib.transactions as transactions
127
return self._weave_store.get_weave(file_id,
128
self._branch.get_transaction())
130
def get_weave_prelude(self, file_id):
131
import bzrlib.transactions as transactions
132
return self._weave_store.get_weave_prelude(file_id,
133
self._branch.get_transaction())
135
def get_file_lines(self, file_id):
136
ie = self._inventory[file_id]
137
weave = self.get_weave(file_id)
138
return weave.get(ie.revision)
140
def get_file_text(self, file_id):
141
return ''.join(self.get_file_lines(file_id))
128
143
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)
144
return StringIO(self.get_file_text(file_id))
135
146
def get_file_size(self, file_id):
136
147
return self._inventory[file_id].text_size
138
149
def get_file_sha1(self, file_id):
139
150
ie = self._inventory[file_id]
151
if ie.kind == "file":
154
def is_executable(self, file_id):
155
ie = self._inventory[file_id]
156
if ie.kind != "file":
158
return self._inventory[file_id].executable
142
160
def has_filename(self, filename):
143
161
return bool(self.inventory.path2id(filename))
145
163
def list_files(self):
146
164
# The only files returned by this are those from the version
147
165
for path, entry in self.inventory.iter_entries():
148
yield path, 'V', entry.kind, entry.file_id
166
yield path, 'V', entry.kind, entry.file_id, entry
168
def get_symlink_target(self, file_id):
169
ie = self._inventory[file_id]
170
return ie.symlink_target;
172
def kind(self, file_id):
173
return self._inventory[file_id].kind
151
176
class EmptyTree(Tree):
152
177
def __init__(self):
153
178
self._inventory = Inventory()
180
def get_symlink_target(self, file_id):
155
183
def has_filename(self, filename):
186
def kind(self, file_id):
187
assert self._inventory[file_id].kind == "root_directory"
188
return "root_directory"
158
190
def list_files(self):
159
if False: # just to make it a generator
193
def __contains__(self, file_id):
194
return file_id in self._inventory
196
def get_file_sha1(self, file_id):
197
assert self._inventory[file_id].kind == "root_directory"
164
201
######################################################################
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())