150
151
class TreeEntry(object):
151
"""An entry that implements the minium interface used by commands.
152
"""An entry that implements the minimum interface used by commands.
153
154
This needs further inspection, it may be better to have
154
155
InventoryEntries without ids - though that seems wrong. For now,
279
280
# if needed, or, when the cache sees a change, append it to the hash
280
281
# cache file, and have the parser take the most recent entry for a
281
282
# given path only.
282
cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
283
cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
283
284
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
285
286
# is this scan needed ? it makes things kinda slow.
288
289
if hc.needs_write:
289
290
mutter("write hc")
349
350
run into /. If there isn't one, raises NotBranchError.
350
351
TODO: give this a new exception.
351
352
If there is one, it is returned, along with the unused portion of path.
354
:return: The WorkingTree that contains 'path', and the rest of path
354
357
path = os.getcwdu()
355
358
control, relpath = bzrdir.BzrDir.open_containing(path)
356
360
return control.open_workingtree(), relpath
530
534
return os.path.getsize(self.id2abspath(file_id))
533
def get_file_sha1(self, file_id):
534
path = self._inventory.id2path(file_id)
537
def get_file_sha1(self, file_id, path=None):
539
path = self._inventory.id2path(file_id)
535
540
return self._hashcache.get_sha1(path)
537
def is_executable(self, file_id):
538
if not supports_executable():
542
def get_file_mtime(self, file_id, path=None):
544
path = self._inventory.id2path(file_id)
545
return os.lstat(self.abspath(path)).st_mtime
547
if not supports_executable():
548
def is_executable(self, file_id, path=None):
539
549
return self._inventory[file_id].executable
541
path = self._inventory.id2path(file_id)
551
def is_executable(self, file_id, path=None):
553
path = self._inventory.id2path(file_id)
542
554
mode = os.lstat(self.abspath(path)).st_mode
543
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
555
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
545
557
@needs_write_lock
546
558
def add(self, files, ids=None):
692
704
def list_files(self):
693
"""Recursively list all files as (path, class, kind, id).
705
"""Recursively list all files as (path, class, kind, id, entry).
695
707
Lists, but does not descend into unversioned directories.
700
712
Skips the control directory.
702
714
inv = self._inventory
704
def descend(from_dir_relpath, from_dir_id, dp):
715
# Convert these into local objects to save lookup times
716
pathjoin = bzrlib.osutils.pathjoin
717
file_kind = bzrlib.osutils.file_kind
719
# transport.base ends in a slash, we want the piece
720
# between the last two slashes
721
transport_base_dir = self.bzrdir.transport.base.rsplit('/', 2)[1]
723
fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
725
# directory file_id, relative path, absolute path, reverse sorted children
726
children = os.listdir(self.basedir)
728
# jam 20060527 The kernel sized tree seems equivalent whether we
729
# use a deque and popleft to keep them sorted, or if we use a plain
730
# list and just reverse() them.
731
children = collections.deque(children)
732
stack = [(inv.root.file_id, u'', self.basedir, children)]
734
from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
737
f = children.popleft()
708
738
## TODO: If we find a subdirectory with its own .bzr
709
739
## directory, then that is a separate tree and we
710
740
## should exclude it.
712
742
# the bzrdir for this tree
713
if self.bzrdir.transport.base.endswith(f + '/'):
743
if transport_base_dir == f:
717
fp = appendpath(from_dir_relpath, f)
746
# we know that from_dir_relpath and from_dir_abspath never end in a slash
747
# and 'f' doesn't begin with one, we can do a string op, rather
748
# than the checks of pathjoin(), all relative paths will have an extra slash
750
fp = from_dir_relpath + '/' + f
720
fap = appendpath(dp, f)
753
fap = from_dir_abspath + '/' + f
722
755
f_ie = inv.get_child(from_dir_id, f)
725
elif self.is_ignored(fp):
758
elif self.is_ignored(fp[1:]):
738
771
# make a last minute entry
773
yield fp[1:], c, fk, f_ie.file_id, f_ie
742
if fk == 'directory':
743
entry = TreeDirectory()
746
elif fk == 'symlink':
776
yield fp[1:], c, fk, None, fk_entries[fk]()
778
yield fp[1:], c, fk, None, TreeEntry()
751
yield fp, c, fk, (f_ie and f_ie.file_id), entry
753
781
if fk != 'directory':
757
# don't descend unversioned directories
760
for ff in descend(fp, f_ie.file_id, fap):
784
# But do this child first
785
new_children = os.listdir(fap)
787
new_children = collections.deque(new_children)
788
stack.append((f_ie.file_id, fp, fap, new_children))
789
# Break out of inner loop, so that we start outer loop with child
792
# if we finished all children, pop it off the stack
763
for f in descend(u'', inv.root.file_id, self.basedir):
766
796
@needs_write_lock
767
797
def move(self, from_paths, to_name):
804
834
raise BzrError("%r is not versioned" % f)
805
835
name_tail = splitpath(f)[-1]
806
dest_path = appendpath(to_name, name_tail)
836
dest_path = pathjoin(to_name, name_tail)
807
837
if self.has_filename(dest_path):
808
838
raise BzrError("destination %r already exists" % dest_path)
809
839
if f_id in to_idpath:
817
847
for f in from_paths:
818
848
name_tail = splitpath(f)[-1]
819
dest_path = appendpath(to_name, name_tail)
849
dest_path = pathjoin(to_name, name_tail)
820
850
result.append((f, dest_path))
821
851
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1054
1085
l = bzrlib.DEFAULT_IGNORE[:]
1055
1086
if self.has_filename(bzrlib.IGNORE_FILENAME):
1056
1087
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1057
l.extend([line.rstrip("\n\r") for line in f.readlines()])
1088
l.extend([line.rstrip("\n\r").decode('utf-8')
1089
for line in f.readlines()])
1058
1090
self._ignorelist = l
1059
1091
self._ignore_regex = self._combine_ignore_rules(l)
1167
1199
def _cache_basis_inventory(self, new_revision):
1168
1200
"""Cache new_revision as the basis inventory."""
1201
# TODO: this should allow the ready-to-use inventory to be passed in,
1202
# as commit already has that ready-to-use [while the format is the
1170
1205
# this double handles the inventory - unpack and repack -
1171
1206
# but is easier to understand. We can/should put a conditional
1172
1207
# in here based on whether the inventory is in the latest format
1173
1208
# - perhaps we should repack all inventories on a repository
1175
inv = self.branch.repository.get_inventory(new_revision)
1176
inv.revision_id = new_revision
1177
xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1210
# the fast path is to copy the raw xml from the repository. If the
1211
# xml contains 'revision_id="', then we assume the right
1212
# revision_id is set. We must check for this full string, because a
1213
# root node id can legitimately look like 'revision_id' but cannot
1215
xml = self.branch.repository.get_inventory_xml(new_revision)
1216
if not 'revision_id="' in xml.split('\n', 1)[0]:
1217
inv = self.branch.repository.deserialise_inventory(
1219
inv.revision_id = new_revision
1220
xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1221
assert isinstance(xml, str), 'serialised xml must be bytestring.'
1179
1222
path = self._basis_inventory_name()
1180
self._control_files.put_utf8(path, xml)
1224
self._control_files.put(path, sio)
1181
1225
except WeaveRevisionNotPresent:
1184
1228
def read_basis_inventory(self):
1185
1229
"""Read the cached basis inventory."""
1186
1230
path = self._basis_inventory_name()
1187
return self._control_files.get_utf8(path).read()
1231
return self._control_files.get(path).read()
1189
1233
@needs_read_lock
1190
1234
def read_working_inventory(self):
1199
1243
@needs_write_lock
1200
def remove(self, files, verbose=False):
1244
def remove(self, files, verbose=False, to_file=None):
1201
1245
"""Remove nominated files from the working inventory..
1203
1247
This does not remove their text. This does not run on XXX on what? RBC
1232
1276
new_status = 'I'
1234
1278
new_status = '?'
1235
show_status(new_status, inv[fid].kind, quotefn(f))
1279
show_status(new_status, inv[fid].kind, quotefn(f), to_file=to_file)
1238
1282
self._write_inventory(inv)
1305
1349
# of a nasty hack; probably it's better to have a transaction object,
1306
1350
# which can do some finalization when it's either successfully or
1307
1351
# unsuccessfully completed. (Denys's original patch did that.)
1308
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1309
# wrongly. Hookinh into unllock on the control files object is fine though.
1352
# RBC 20060206 hooking into transaction will couple lock and transaction
1353
# wrongly. Hooking into unlock on the control files object is fine though.
1311
1355
# TODO: split this per format so there is no ugly if block
1312
1356
if self._hashcache.needs_write and (
1358
1402
this_tree=self)
1359
1403
self.set_last_revision(self.branch.last_revision())
1360
1404
if old_tip and old_tip != self.last_revision():
1361
# our last revision was not the prior branch last reivison
1405
# our last revision was not the prior branch last revision
1362
1406
# and we have converted that last revision to a pending merge.
1363
1407
# base is somewhere between the branch tip now
1364
1408
# and the now pending merge
1401
1445
if file_kind(self.abspath(conflicted)) != "file":
1404
if e.errno == errno.ENOENT:
1447
except errors.NoSuchFile:
1408
1449
if text is True:
1409
1450
for suffix in ('.THIS', '.OTHER'):
1411
1452
kind = file_kind(self.abspath(conflicted+suffix))
1413
if e.errno == errno.ENOENT:
1455
except errors.NoSuchFile:
1421
1459
ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1422
1460
conflicts.append(Conflict.factory(ctype, path=conflicted,
1643
1681
raise NotImplementedError
1644
1682
if not isinstance(a_bzrdir.transport, LocalTransport):
1645
1683
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1646
return WorkingTree(a_bzrdir.root_transport.base,
1684
return WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
1647
1685
_internal=True,
1649
1687
_bzrdir=a_bzrdir)
1680
1718
def initialize(self, a_bzrdir, revision_id=None):
1681
1719
"""See WorkingTreeFormat.initialize().
1683
revision_id allows creating a working tree at a differnet
1721
revision_id allows creating a working tree at a different
1684
1722
revision than the branch is at.
1686
1724
if not isinstance(a_bzrdir.transport, LocalTransport):
1729
1767
if not isinstance(a_bzrdir.transport, LocalTransport):
1730
1768
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1731
1769
control_files = self._open_control_files(a_bzrdir)
1732
return WorkingTree3(a_bzrdir.root_transport.base,
1770
return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1733
1771
_internal=True,
1735
1773
_bzrdir=a_bzrdir,