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.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
35
30
class Tree(object):
36
31
"""Abstract file tree.
54
49
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)
57
68
def has_filename(self, filename):
58
69
"""True if the tree has given filename."""
59
70
raise NotImplementedError()
69
85
def id2path(self, file_id):
70
86
return self.inventory.id2path(file_id)
88
def kind(self, file_id):
89
raise NotImplementedError("subclasses must implement kind")
72
91
def _get_inventory(self):
73
92
return self._inventory
94
def get_file_by_path(self, path):
95
return self.get_file(self._inventory.path2id(path))
75
97
inventory = property(_get_inventory,
76
98
doc="Inventory of this Tree")
78
100
def _check_retrieved(self, ie, f):
79
103
fp = fingerprint_file(f)
82
106
if ie.text_size != None:
83
107
if ie.text_size != fp['size']:
84
bailout("mismatched size for file %r in %r" % (ie.file_id, self._store),
108
raise BzrError("mismatched size for file %r in %r" % (ie.file_id, self._store),
85
109
["inventory expects %d bytes" % ie.text_size,
86
110
"file is actually %d bytes" % fp['size'],
87
111
"store is probably damaged/corrupt"])
89
113
if ie.text_sha1 != fp['sha1']:
90
bailout("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
114
raise BzrError("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
91
115
["inventory expects %s" % ie.text_sha1,
92
116
"file is actually %s" % fp['sha1'],
93
117
"store is probably damaged/corrupt"])
96
def print_file(self, fileid):
97
"""Print file with id `fileid` to stdout."""
120
def print_file(self, file_id):
121
"""Print file with id `file_id` to stdout."""
99
pumpfile(self.get_file(fileid), sys.stdout)
102
def export(self, dest):
103
"""Export this tree to a new directory.
105
`dest` should not exist, and will be created holding the
106
contents of this tree.
108
TODO: To handle subdirectories we need to create the
111
:note: If the export fails, the destination directory will be
112
left in a half-assed state.
115
mutter('export version %r' % self)
117
for dp, ie in inv.iter_entries():
119
fullpath = appendpath(dest, dp)
120
if kind == 'directory':
123
pumpfile(self.get_file(ie.file_id), file(fullpath, 'wb'))
125
bailout("don't know how to export {%s} of kind %r" % (ie.file_id, kind))
126
mutter(" export {%s} kind %s to %s" % (ie.file_id, kind, fullpath))
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)))
130
150
class RevisionTree(Tree):
131
151
"""Tree viewing a previous revision.
137
157
or at least passing a description to the constructor.
140
def __init__(self, store, inv):
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
142
168
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))
144
195
def get_file(self, file_id):
145
ie = self._inventory[file_id]
146
f = self._store[ie.text_id]
147
mutter(" get fileid{%s} from %r" % (file_id, self))
148
self._check_retrieved(ie, f)
196
return StringIO(self.get_file_text(file_id))
151
198
def get_file_size(self, file_id):
152
199
return self._inventory[file_id].text_size
154
def get_file_sha1(self, file_id):
155
ie = self._inventory[file_id]
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
218
def has_filename(self, filename):
159
219
return bool(self.inventory.path2id(filename))
161
221
def list_files(self):
162
222
# The only files returned by this are those from the version
163
223
for path, entry in self.inventory.iter_entries():
164
yield path, 'V', entry.kind, entry.file_id
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()
167
240
class EmptyTree(Tree):
168
242
def __init__(self):
169
243
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):
171
255
def has_filename(self, filename):
258
def kind(self, file_id):
259
assert self._inventory[file_id].kind == "root_directory"
260
return "root_directory"
174
262
def list_files(self):
175
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"
180
273
######################################################################