17
17
"""Tree classes, representing directory at point in time.
21
from cStringIO import StringIO
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
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
31
36
"""Abstract file tree.
33
38
There are several subclasses:
49
54
trees or versioned trees.
53
"""Get a list of the conflicts in the tree.
55
Each conflict is an instance of bzrlib.conflicts.Conflict.
59
def get_parent_ids(self):
60
"""Get the parent ids for this tree.
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.
66
raise NotImplementedError(self.get_parent_ids)
68
57
def has_filename(self, filename):
69
58
"""True if the tree has given filename."""
70
59
raise NotImplementedError()
72
61
def has_id(self, file_id):
73
62
return self.inventory.has_id(file_id)
75
def has_or_had_id(self, file_id):
76
if file_id == self.inventory.root.file_id:
78
return self.inventory.has_id(file_id)
80
64
__contains__ = has_id
67
"""Return set of all ids in this tree."""
68
return self.inventory.id_set()
82
70
def __iter__(self):
83
71
return iter(self.inventory)
85
73
def id2path(self, file_id):
86
74
return self.inventory.id2path(file_id)
88
def kind(self, file_id):
89
raise NotImplementedError("subclasses must implement kind")
91
76
def _get_inventory(self):
92
77
return self._inventory
94
def get_file_by_path(self, path):
95
return self.get_file(self._inventory.path2id(path))
97
79
inventory = property(_get_inventory,
98
80
doc="Inventory of this Tree")
100
82
def _check_retrieved(self, ie, f):
103
83
fp = fingerprint_file(f)
106
86
if ie.text_size != None:
107
87
if ie.text_size != fp['size']:
108
raise BzrError("mismatched size for file %r in %r" % (ie.file_id, self._store),
88
bailout("mismatched size for file %r in %r" % (ie.file_id, self._store),
109
89
["inventory expects %d bytes" % ie.text_size,
110
90
"file is actually %d bytes" % fp['size'],
111
91
"store is probably damaged/corrupt"])
113
93
if ie.text_sha1 != fp['sha1']:
114
raise BzrError("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
94
bailout("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
115
95
["inventory expects %s" % ie.text_sha1,
116
96
"file is actually %s" % fp['sha1'],
117
97
"store is probably damaged/corrupt"])
120
def print_file(self, file_id):
121
"""Print file with id `file_id` to stdout."""
100
def print_file(self, fileid):
101
"""Print file with id `fileid` to stdout."""
123
sys.stdout.write(self.get_file_text(file_id))
129
"""What files are present in this tree and unknown.
131
:return: an iterator over the unknown files.
138
def filter_unversioned_files(self, paths):
139
"""Filter out paths that are not versioned.
141
:return: set of paths.
143
# NB: we specifically *don't* call self.has_filename, because for
144
# WorkingTrees that can indicate files that exist on disk but that
146
pred = self.inventory.has_filename
147
return set((p for p in paths if not pred(p)))
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))
150
134
class RevisionTree(Tree):
151
135
"""Tree viewing a previous revision.
157
141
or at least passing a description to the constructor.
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
144
def __init__(self, store, inv):
168
146
self._inventory = inv
169
self._revision_id = revision_id
171
def get_parent_ids(self):
172
"""See Tree.get_parent_ids.
174
A RevisionTree's parents match the revision graph.
176
parent_ids = self._repository.get_revision(self._revision_id).parent_ids
179
def get_revision_id(self):
180
"""Return the revision id associated with this tree."""
181
return self._revision_id
183
def get_weave(self, file_id):
184
return self._weave_store.get_weave(file_id,
185
self._repository.get_transaction())
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)
192
def get_file_text(self, file_id):
193
return ''.join(self.get_file_lines(file_id))
195
148
def get_file(self, file_id):
196
return StringIO(self.get_file_text(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)
198
155
def get_file_size(self, file_id):
199
156
return self._inventory[file_id].text_size
201
def get_file_sha1(self, file_id, path=None):
202
ie = self._inventory[file_id]
203
if ie.kind == "file":
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
212
def is_executable(self, file_id, path=None):
213
ie = self._inventory[file_id]
214
if ie.kind != "file":
216
return self._inventory[file_id].executable
158
def get_file_sha1(self, file_id):
159
ie = self._inventory[file_id]
218
162
def has_filename(self, filename):
219
163
return bool(self.inventory.path2id(filename))
221
165
def list_files(self):
222
166
# The only files returned by this are those from the version
223
167
for path, entry in self.inventory.iter_entries():
224
yield path, 'V', entry.kind, entry.file_id, entry
226
def get_symlink_target(self, file_id):
227
ie = self._inventory[file_id]
228
return ie.symlink_target;
230
def kind(self, file_id):
231
return self._inventory[file_id].kind
234
self._repository.lock_read()
237
self._repository.unlock()
168
yield path, 'V', entry.kind, entry.file_id
240
171
class EmptyTree(Tree):
242
172
def __init__(self):
243
173
self._inventory = Inventory()
245
def get_parent_ids(self):
246
"""See Tree.get_parent_ids.
248
An EmptyTree always has NULL_REVISION as the only parent.
252
def get_symlink_target(self, file_id):
255
175
def has_filename(self, filename):
258
def kind(self, file_id):
259
assert self._inventory[file_id].kind == "root_directory"
260
return "root_directory"
262
178
def list_files(self):
179
if False: # just to make it a generator
265
def __contains__(self, file_id):
266
return file_id in self._inventory
268
def get_file_sha1(self, file_id, path=None):
269
assert self._inventory[file_id].kind == "root_directory"
273
184
######################################################################