1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
131
132
self._format = _format
132
133
self.bzrdir = _bzrdir
133
from bzrlib.trace import note, mutter
134
assert isinstance(basedir, basestring), \
135
"base directory %r is not a string" % basedir
136
134
basedir = safe_unicode(basedir)
137
135
mutter("opening working tree %r", basedir)
138
136
self._branch = branch
139
assert isinstance(self.branch, bzrlib.branch.Branch), \
140
"branch %r is not a Branch" % self.branch
141
137
self.basedir = realpath(basedir)
142
138
# if branch is at our basedir and is a format 6 or less
143
139
# assume all other formats have their own control files.
144
assert isinstance(_control_files, LockableFiles), \
145
"_control_files must be a LockableFiles, not %r" % _control_files
146
140
self._control_files = _control_files
141
self._transport = self._control_files._transport
147
142
self._dirty = None
149
144
# during a read or write lock these objects are set, and are
270
266
self._dirstate = dirstate.DirState.on_file(local_path)
271
267
return self._dirstate
273
def _directory_is_tree_reference(self, relpath):
274
# as a special case, if a directory contains control files then
275
# it's a tree reference, except that the root of the tree is not
276
return relpath and osutils.isdir(self.abspath(relpath) + u"/.bzr")
277
# TODO: We could ask all the control formats whether they
278
# recognize this directory, but at the moment there's no cheap api
279
# to do that. Since we probably can only nest bzr checkouts and
280
# they always use this name it's ok for now. -- mbp 20060306
282
# FIXME: There is an unhandled case here of a subdirectory
283
# containing .bzr but not a branch; that will probably blow up
284
# when you try to commit it. It might happen if there is a
285
# checkout in a subdirectory. This can be avoided by not adding
288
269
def filter_unversioned_files(self, paths):
289
270
"""Filter out paths that are versioned.
332
313
state._read_dirblocks_if_needed()
333
314
root_key, current_entry = self._get_entry(path='')
334
315
current_id = root_key[2]
335
assert current_entry[0][0] == 'd' # directory
316
if not (current_entry[0][0] == 'd'): # directory
317
raise AssertionError(current_entry)
336
318
inv = Inventory(root_id=current_id)
337
319
# Turn some things into local variables
338
320
minikind_to_kind = dirstate.DirState._minikind_to_kind
371
353
# add this entry to the parent map.
372
354
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
373
355
elif kind == 'tree-reference':
374
assert self._repo_supports_tree_reference, \
375
"repository of %r " \
376
"doesn't support tree references " \
377
"required by entry %r" \
356
if not self._repo_supports_tree_reference:
357
raise AssertionError(
359
"doesn't support tree references "
360
"required by entry %r"
379
362
inv_entry.reference_revision = link_or_sha1 or None
380
363
elif kind != 'symlink':
381
364
raise AssertionError("unknown kind %r" % kind)
382
365
# These checks cost us around 40ms on a 55k entry tree
383
assert file_id not in inv_byid, ('file_id %s already in'
384
' inventory as %s' % (file_id, inv_byid[file_id]))
385
assert name_unicode not in parent_ie.children
366
if file_id in inv_byid:
367
raise AssertionError('file_id %s already in'
368
' inventory as %s' % (file_id, inv_byid[file_id]))
369
if name_unicode in parent_ie.children:
370
raise AssertionError('name %r already in parent'
386
372
inv_byid[file_id] = inv_entry
387
373
parent_ie.children[name_unicode] = inv_entry
388
374
self._inventory = inv
408
394
def get_file_sha1(self, file_id, path=None, stat_value=None):
409
395
# check file id is valid unconditionally.
410
396
entry = self._get_entry(file_id=file_id, path=path)
411
assert entry[0] is not None, 'what error should this raise'
398
raise errors.NoSuchId(self, file_id)
413
400
path = pathjoin(entry[0][0], entry[0][1]).decode('utf8')
475
464
def id2path(self, file_id):
476
465
"Convert a file-id to a path."
477
file_id = osutils.safe_file_id(file_id)
478
466
state = self.current_dirstate()
479
467
entry = self._get_entry(file_id=file_id)
480
468
if entry == (None, None):
482
470
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
483
471
return path_utf8.decode('utf8')
473
def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
474
entry = self._get_entry(path=path)
475
if entry == (None, None):
476
return False # Missing entries are not executable
477
return entry[1][0][3] # Executable?
485
479
if not osutils.supports_executable():
486
480
def is_executable(self, file_id, path=None):
487
481
"""Test if a file is executable or not.
489
483
Note: The caller is expected to take a read-lock before calling this.
491
file_id = osutils.safe_file_id(file_id)
492
485
entry = self._get_entry(file_id=file_id, path=path)
493
486
if entry == (None, None):
495
488
return entry[1][0][3]
490
_is_executable_from_path_and_stat = \
491
_is_executable_from_path_and_stat_from_basis
497
493
def is_executable(self, file_id, path=None):
498
494
"""Test if a file is executable or not.
500
496
Note: The caller is expected to take a read-lock before calling this.
498
self._must_be_locked()
503
file_id = osutils.safe_file_id(file_id)
504
500
path = self.id2path(file_id)
505
501
mode = os.lstat(self.abspath(path)).st_mode
506
502
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
504
def all_file_ids(self):
505
"""See Tree.iter_all_file_ids"""
506
self._must_be_locked()
508
for key, tree_details in self.current_dirstate()._iter_entries():
509
if tree_details[0][0] in ('a', 'r'): # relocated
509
515
def __iter__(self):
510
516
"""Iterate through file_ids for this tree.
523
529
return iter(result)
525
531
def iter_references(self):
532
if not self._repo_supports_tree_reference:
533
# When the repo doesn't support references, we will have nothing to
526
536
for key, tree_details in self.current_dirstate()._iter_entries():
527
537
if tree_details[0][0] in ('a', 'r'): # absent, relocated
528
538
# not relevant to the working tree
531
541
# the root is not a reference.
533
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
543
relpath = pathjoin(key[0].decode('utf8'), key[1].decode('utf8'))
535
if self._kind(path) == 'tree-reference':
545
if self._kind(relpath) == 'tree-reference':
546
yield relpath, key[2]
537
547
except errors.NoSuchFile:
538
548
# path is missing on disk.
547
557
Note: The caller is expected to take a read-lock before calling this.
549
559
relpath = self.id2path(file_id)
550
assert relpath != None, \
551
"path for id {%s} is None!" % file_id
561
raise AssertionError(
562
"path for id {%s} is None!" % file_id)
552
563
return self._kind(relpath)
554
565
def _kind(self, relpath):
627
638
if not from_paths:
630
640
state = self.current_dirstate()
632
assert not isinstance(from_paths, basestring)
641
if isinstance(from_paths, basestring):
633
643
to_dir_utf8 = to_dir.encode('utf8')
634
644
to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
635
645
id_index = state._get_id_index()
791
800
if minikind == 'd':
792
801
def update_dirblock(from_dir, to_key, to_dir_utf8):
793
802
"""Recursively update all entries in this dirblock."""
794
assert from_dir != '', "renaming root not supported"
804
raise AssertionError("renaming root not supported")
795
805
from_key = (from_dir, '')
796
806
from_block_idx, present = \
797
807
state._find_block_index_from_key(from_key)
811
821
# Grab a copy since move_one may update the list.
812
822
for entry in from_block[1][:]:
813
assert entry[0][0] == from_dir
823
if not (entry[0][0] == from_dir):
824
raise AssertionError()
814
825
cur_details = entry[1][0]
815
826
to_key = (to_dir_utf8, entry[0][1], entry[0][2])
816
827
from_path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
941
952
if not all_versioned:
942
953
raise errors.PathsNotVersionedError(paths)
943
954
# -- remove redundancy in supplied paths to prevent over-scanning --
946
other_paths = paths.difference(set([path]))
947
if not osutils.is_inside_any(other_paths, path):
948
# this is a top level path, we must check it.
949
search_paths.add(path)
955
search_paths = osutils.minimum_path_selection(paths)
951
957
# for all search_indexs in each path at or under each element of
952
958
# search_paths, if the detail is relocated: add the id, and add the
1033
1038
@needs_tree_write_lock
1034
1039
def set_last_revision(self, new_revision):
1035
1040
"""Change the last revision in the working tree."""
1036
new_revision = osutils.safe_revision_id(new_revision)
1037
1041
parents = self.get_parent_ids()
1038
1042
if new_revision in (NULL_REVISION, None):
1039
assert len(parents) < 2, (
1040
"setting the last parent to none with a pending merge is "
1043
if len(parents) >= 2:
1044
raise AssertionError(
1045
"setting the last parent to none with a pending merge is "
1042
1047
self.set_parent_ids([])
1044
1049
self.set_parent_ids([new_revision] + parents[1:],
1086
1090
raise errors.GhostRevisionUnusableHere(parents_list[0][0])
1087
1091
real_trees = []
1094
parent_ids = [rev_id for rev_id, tree in parents_list]
1095
graph = self.branch.repository.get_graph()
1096
heads = graph.heads(parent_ids)
1097
accepted_revisions = set()
1089
1099
# convert absent trees to the null tree, which we convert back to
1090
1100
# missing on access.
1091
1101
for rev_id, tree in parents_list:
1092
rev_id = osutils.safe_revision_id(rev_id)
1102
if len(accepted_revisions) > 0:
1103
# we always accept the first tree
1104
if rev_id in accepted_revisions or rev_id not in heads:
1105
# We have already included either this tree, or its
1106
# descendent, so we skip it.
1093
1108
_mod_revision.check_not_reserved_id(rev_id)
1094
1109
if tree is not None:
1095
1110
real_trees.append((rev_id, tree))
1097
1112
real_trees.append((rev_id,
1098
self.branch.repository.revision_tree(None)))
1113
self.branch.repository.revision_tree(
1114
_mod_revision.NULL_REVISION)))
1099
1115
ghosts.append(rev_id)
1116
accepted_revisions.add(rev_id)
1100
1117
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1101
1118
self._make_dirty(reset_inventory=False)
1107
1124
if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
1108
1125
self._make_dirty(reset_inventory=True)
1127
def _sha_from_stat(self, path, stat_result):
1128
"""Get a sha digest from the tree's stat cache.
1130
The default implementation assumes no stat cache is present.
1132
:param path: The path.
1133
:param stat_result: The stat result being looked up.
1135
return self.current_dirstate().sha1_from_stat(path, stat_result)
1110
1137
@needs_read_lock
1111
1138
def supports_tree_reference(self):
1112
1139
return self._repo_supports_tree_reference
1114
1141
def unlock(self):
1115
1142
"""Unlock in format 4 trees needs to write the entire dirstate."""
1143
# do non-implementation specific cleanup
1116
1146
if self._control_files._lock_count == 1:
1117
1147
# eventually we should do signature checking during read locks for
1118
1148
# dirstate updates.
1150
1180
state = self.current_dirstate()
1151
1181
state._read_dirblocks_if_needed()
1152
ids_to_unversion = set()
1153
for file_id in file_ids:
1154
ids_to_unversion.add(osutils.safe_file_id(file_id))
1182
ids_to_unversion = set(file_ids)
1155
1183
paths_to_unversion = set()
1157
1185
# check if the root is to be unversioned, if so, assert for now.
1187
1215
# Mark this file id as having been removed
1188
1216
entry = block[1][entry_index]
1189
1217
ids_to_unversion.discard(entry[0][2])
1190
if (entry[1][0][0] == 'a'
1218
if (entry[1][0][0] in 'ar' # don't remove absent or renamed
1191
1220
or not state._make_absent(entry)):
1192
1221
entry_index += 1
1193
1222
# go to the next block. (At the moment we dont delete empty
1218
1247
for file_id in file_ids:
1219
1248
self._inventory.remove_recursive_id(file_id)
1250
@needs_tree_write_lock
1251
def rename_one(self, from_rel, to_rel, after=False):
1252
"""See WorkingTree.rename_one"""
1254
WorkingTree.rename_one(self, from_rel, to_rel, after)
1256
@needs_tree_write_lock
1257
def apply_inventory_delta(self, changes):
1258
"""See MutableTree.apply_inventory_delta"""
1259
state = self.current_dirstate()
1260
state.update_by_delta(changes)
1261
self._make_dirty(reset_inventory=True)
1263
def update_basis_by_delta(self, new_revid, delta):
1264
"""See MutableTree.update_basis_by_delta."""
1265
if self.last_revision() == new_revid:
1266
raise AssertionError()
1267
self.current_dirstate().update_basis_by_delta(delta, new_revid)
1221
1269
@needs_read_lock
1222
1270
def _validate(self):
1223
1271
self._dirstate._validate()
1225
1273
@needs_tree_write_lock
1226
1274
def _write_inventory(self, inv):
1227
1275
"""Write inventory as the current inventory."""
1228
assert not self._dirty, "attempting to write an inventory when the dirstate is dirty will cause data loss"
1277
raise AssertionError("attempting to write an inventory when the "
1278
"dirstate is dirty will lose pending changes")
1229
1279
self.current_dirstate().set_state_from_inventory(inv)
1230
1280
self._make_dirty(reset_inventory=False)
1231
1281
if self._inventory is not None:
1255
1307
"""See WorkingTreeFormat.get_format_description()."""
1256
1308
return "Working tree format 4"
1258
def initialize(self, a_bzrdir, revision_id=None):
1310
def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
1311
accelerator_tree=None, hardlink=False):
1259
1312
"""See WorkingTreeFormat.initialize().
1261
1314
:param revision_id: allows creating a working tree at a different
1262
1315
revision than the branch is at.
1316
:param accelerator_tree: A tree which can be used for retrieving file
1317
contents more quickly than the revision tree, i.e. a workingtree.
1318
The revision tree will be used for cases where accelerator_tree's
1319
content is different.
1320
:param hardlink: If true, hard-link files from accelerator_tree,
1264
1323
These trees get an initial random root id, if their repository supports
1265
1324
rich root data, TREE_ROOT otherwise.
1267
revision_id = osutils.safe_revision_id(revision_id)
1268
1326
if not isinstance(a_bzrdir.transport, LocalTransport):
1269
1327
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1270
1328
transport = a_bzrdir.get_workingtree_transport(self)
1271
1329
control_files = self._open_control_files(a_bzrdir)
1272
1330
control_files.create_lock()
1273
1331
control_files.lock_write()
1274
control_files.put_utf8('format', self.get_format_string())
1275
branch = a_bzrdir.open_branch()
1332
transport.put_bytes('format', self.get_format_string(),
1333
mode=a_bzrdir._get_file_mode())
1334
if from_branch is not None:
1335
branch = from_branch
1337
branch = a_bzrdir.open_branch()
1276
1338
if revision_id is None:
1277
1339
revision_id = branch.last_revision()
1278
1340
local_path = transport.local_abspath('dirstate')
1289
1351
wt.lock_tree_write()
1353
self._init_custom_control_files(wt)
1291
1354
if revision_id in (None, NULL_REVISION):
1292
1355
if branch.repository.supports_rich_root():
1293
1356
wt._set_root_id(generate_ids.gen_root_id())
1295
1358
wt._set_root_id(ROOT_ID)
1297
wt.set_last_revision(revision_id)
1299
basis = wt.basis_tree()
1361
# frequently, we will get here due to branching. The accelerator
1362
# tree will be the tree from the branch, so the desired basis
1363
# tree will often be a parent of the accelerator tree.
1364
if accelerator_tree is not None:
1366
basis = accelerator_tree.revision_tree(revision_id)
1367
except errors.NoSuchRevision:
1370
basis = branch.repository.revision_tree(revision_id)
1371
if revision_id == NULL_REVISION:
1374
parents_list = [(revision_id, basis)]
1300
1375
basis.lock_read()
1301
# if the basis has a root id we have to use that; otherwise we use
1303
basis_root_id = basis.get_root_id()
1304
if basis_root_id is not None:
1305
wt._set_root_id(basis_root_id)
1377
wt.set_parent_trees(parents_list, allow_leftmost_as_ghost=True)
1307
transform.build_tree(basis, wt)
1379
# if the basis has a root id we have to use that; otherwise we
1380
# use a new random one
1381
basis_root_id = basis.get_root_id()
1382
if basis_root_id is not None:
1383
wt._set_root_id(basis_root_id)
1385
# delta_from_tree is safe even for DirStateRevisionTrees,
1386
# because wt4.apply_inventory_delta does not mutate the input
1387
# inventory entries.
1388
transform.build_tree(basis, wt, accelerator_tree,
1389
hardlink=hardlink, delta_from_tree=True)
1310
1393
control_files.unlock()
1397
def _init_custom_control_files(self, wt):
1398
"""Subclasses with custom control files should override this method.
1400
The working tree and control files are locked for writing when this
1403
:param wt: the WorkingTree object
1314
1406
def _open(self, a_bzrdir, control_files):
1315
1407
"""Open the tree itself.
1317
1409
:param a_bzrdir: the dir for the tree.
1318
1410
:param control_files: the control files for the tree.
1320
return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
1412
return self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
1321
1413
branch=a_bzrdir.open_branch(),
1323
1415
_bzrdir=a_bzrdir,
1337
1429
def __init__(self, dirstate, revision_id, repository):
1338
1430
self._dirstate = dirstate
1339
self._revision_id = osutils.safe_revision_id(revision_id)
1431
self._revision_id = revision_id
1340
1432
self._repository = repository
1341
1433
self._inventory = None
1342
1434
self._locked = 0
1343
1435
self._dirstate_locked = False
1436
self._repo_supports_tree_reference = getattr(
1437
repository._format, "supports_tree_reference",
1345
1440
def __repr__(self):
1346
1441
return "<%s of %s in %s>" % \
1347
1442
(self.__class__.__name__, self._revision_id, self._dirstate)
1349
def annotate_iter(self, file_id):
1444
def annotate_iter(self, file_id,
1445
default_revision=_mod_revision.CURRENT_REVISION):
1350
1446
"""See Tree.annotate_iter"""
1351
w = self._repository.weave_store.get_weave(file_id,
1352
self._repository.get_transaction())
1353
return w.annotate_iter(self.inventory[file_id].revision)
1447
text_key = (file_id, self.inventory[file_id].revision)
1448
annotations = self._repository.texts.annotate(text_key)
1449
return [(key[-1], line) for (key, line) in annotations]
1451
def _get_ancestors(self, default_revision):
1452
return set(self._repository.get_ancestry(self._revision_id,
1355
1454
def _comparison_data(self, entry, path):
1356
1455
"""See Tree._comparison_data."""
1357
1456
if entry is None:
1374
1473
def get_root_id(self):
1375
1474
return self.path2id('')
1476
def id2path(self, file_id):
1477
"Convert a file-id to a path."
1478
entry = self._get_entry(file_id=file_id)
1479
if entry == (None, None):
1480
raise errors.NoSuchId(tree=self, file_id=file_id)
1481
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
1482
return path_utf8.decode('utf8')
1484
def iter_references(self):
1485
if not self._repo_supports_tree_reference:
1486
# When the repo doesn't support references, we will have nothing to
1489
# Otherwise, fall back to the default implementation
1490
return super(DirStateRevisionTree, self).iter_references()
1377
1492
def _get_parent_index(self):
1378
1493
"""Return the index in the dirstate referenced by this tree."""
1379
1494
return self._dirstate.get_parent_ids().index(self._revision_id) + 1
1406
1520
This is relatively expensive: we have to walk the entire dirstate.
1408
assert self._locked, 'cannot generate inventory of an unlocked '\
1409
'dirstate revision tree'
1522
if not self._locked:
1523
raise AssertionError(
1524
'cannot generate inventory of an unlocked '
1525
'dirstate revision tree')
1410
1526
# separate call for profiling - makes it clear where the costs are.
1411
1527
self._dirstate._read_dirblocks_if_needed()
1412
assert self._revision_id in self._dirstate.get_parent_ids(), \
1413
'parent %s has disappeared from %s' % (
1414
self._revision_id, self._dirstate.get_parent_ids())
1528
if self._revision_id not in self._dirstate.get_parent_ids():
1529
raise AssertionError(
1530
'parent %s has disappeared from %s' % (
1531
self._revision_id, self._dirstate.get_parent_ids()))
1415
1532
parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
1416
1533
# This is identical now to the WorkingTree _generate_inventory except
1417
1534
# for the tree index use.
1418
1535
root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
1419
1536
current_id = root_key[2]
1420
assert current_entry[parent_index][0] == 'd'
1537
if current_entry[parent_index][0] != 'd':
1538
raise AssertionError()
1421
1539
inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1422
1540
inv.root.revision = current_entry[parent_index][4]
1423
1541
# Turn some things into local variables
1463
1581
raise AssertionError("cannot convert entry %r into an InventoryEntry"
1465
1583
# These checks cost us around 40ms on a 55k entry tree
1466
assert file_id not in inv_byid
1467
assert name_unicode not in parent_ie.children
1584
if file_id in inv_byid:
1585
raise AssertionError('file_id %s already in'
1586
' inventory as %s' % (file_id, inv_byid[file_id]))
1587
if name_unicode in parent_ie.children:
1588
raise AssertionError('name %r already in parent'
1468
1590
inv_byid[file_id] = inv_entry
1469
1591
parent_ie.children[name_unicode] = inv_entry
1470
1592
self._inventory = inv
1490
1612
return parent_details[1]
1493
def get_weave(self, file_id):
1494
return self._repository.weave_store.get_weave(file_id,
1495
self._repository.get_transaction())
1497
def get_file(self, file_id):
1615
def get_file(self, file_id, path=None):
1498
1616
return StringIO(self.get_file_text(file_id))
1500
1618
def get_file_lines(self, file_id):
1501
ie = self.inventory[file_id]
1502
return self._repository.weave_store.get_weave(file_id,
1503
self._repository.get_transaction()).get_lines(ie.revision)
1619
return osutils.split_lines(self.get_file_text(file_id))
1505
1621
def get_file_size(self, file_id):
1622
"""See Tree.get_file_size"""
1506
1623
return self.inventory[file_id].text_size
1508
1625
def get_file_text(self, file_id):
1509
return ''.join(self.get_file_lines(file_id))
1626
return list(self.iter_files_bytes([(file_id, None)]))[0][1]
1511
1628
def get_reference_revision(self, file_id, path=None):
1512
1629
return self.inventory[file_id].reference_revision
1631
def iter_files_bytes(self, desired_files):
1632
"""See Tree.iter_files_bytes.
1634
This version is implemented on top of Repository.iter_files_bytes"""
1635
parent_index = self._get_parent_index()
1636
repo_desired_files = []
1637
for file_id, identifier in desired_files:
1638
entry = self._get_entry(file_id)
1639
if entry == (None, None):
1640
raise errors.NoSuchId(self, file_id)
1641
repo_desired_files.append((file_id, entry[1][parent_index][4],
1643
return self._repository.iter_files_bytes(repo_desired_files)
1514
1645
def get_symlink_target(self, file_id):
1515
1646
entry = self._get_entry(file_id=file_id)
1516
1647
parent_index = self._get_parent_index()
1544
1675
return bool(self.path2id(filename))
1546
1677
def kind(self, file_id):
1547
return self.inventory[file_id].kind
1678
entry = self._get_entry(file_id=file_id)[1]
1680
raise errors.NoSuchId(tree=self, file_id=file_id)
1681
return dirstate.DirState._minikind_to_kind[entry[1][0]]
1683
def stored_kind(self, file_id):
1684
"""See Tree.stored_kind"""
1685
return self.kind(file_id)
1687
def path_content_summary(self, path):
1688
"""See Tree.path_content_summary."""
1689
id = self.inventory.path2id(path)
1691
return ('missing', None, None, None)
1692
entry = self._inventory[id]
1695
return (kind, entry.text_size, entry.executable, entry.text_sha1)
1696
elif kind == 'symlink':
1697
return (kind, None, None, entry.symlink_target)
1699
return (kind, None, None, None)
1549
1701
def is_executable(self, file_id, path=None):
1550
1702
ie = self.inventory[file_id]
1596
1748
self._dirstate_locked = False
1597
1749
self._repository.unlock()
1752
def supports_tree_reference(self):
1753
return self._repo_supports_tree_reference
1599
1755
def walkdirs(self, prefix=""):
1600
1756
# TODO: jam 20070215 This is the lazy way by using the RevisionTree
1601
1757
# implementation based on an inventory.
1658
1814
_matching_to_tree_format = WorkingTreeFormat4()
1659
1815
_test_mutable_trees_to_test_trees = make_source_parent_tree
1661
def _iter_changes(self, include_unchanged=False,
1817
def iter_changes(self, include_unchanged=False,
1662
1818
specific_files=None, pb=None, extra_trees=[],
1663
1819
require_versioned=True, want_unversioned=False):
1664
1820
"""Return the changes from source to target.
1666
:return: An iterator that yields tuples. See InterTree._iter_changes
1822
:return: An iterator that yields tuples. See InterTree.iter_changes
1668
1824
:param specific_files: An optional list of file paths to restrict the
1669
1825
comparison to. When mapping filenames to ids, all matches in all
1683
1839
utf8_decode = cache_utf8._utf8_decode
1684
1840
_minikind_to_kind = dirstate.DirState._minikind_to_kind
1841
cmp_by_dirs = dirstate.cmp_by_dirs
1685
1842
# NB: show_status depends on being able to pass in non-versioned files
1686
1843
# and report them as unknown
1687
1844
# TODO: handle extra trees in the dirstate.
1688
# TODO: handle comparisons as an empty tree as a different special
1689
# case? mbp 20070226
1690
if extra_trees or (self.source._revision_id == NULL_REVISION):
1845
if (extra_trees or specific_files == []):
1691
1846
# we can't fast-path these cases (yet)
1692
for f in super(InterDirStateTree, self)._iter_changes(
1847
for f in super(InterDirStateTree, self).iter_changes(
1693
1848
include_unchanged, specific_files, pb, extra_trees,
1694
1849
require_versioned, want_unversioned=want_unversioned):
1697
1852
parent_ids = self.target.get_parent_ids()
1698
assert (self.source._revision_id in parent_ids), \
1699
"revision {%s} is not stored in {%s}, but %s " \
1700
"can only be used for trees stored in the dirstate" \
1701
% (self.source._revision_id, self.target, self._iter_changes)
1853
if not (self.source._revision_id in parent_ids
1854
or self.source._revision_id == NULL_REVISION):
1855
raise AssertionError(
1856
"revision {%s} is not stored in {%s}, but %s "
1857
"can only be used for trees stored in the dirstate"
1858
% (self.source._revision_id, self.target, self.iter_changes))
1702
1859
target_index = 0
1703
1860
if self.source._revision_id == NULL_REVISION:
1704
1861
source_index = None
1705
1862
indices = (target_index,)
1707
assert (self.source._revision_id in parent_ids), \
1708
"Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
1709
self.source._revision_id, parent_ids)
1864
if not (self.source._revision_id in parent_ids):
1865
raise AssertionError(
1866
"Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
1867
self.source._revision_id, parent_ids))
1710
1868
source_index = 1 + parent_ids.index(self.source._revision_id)
1711
indices = (source_index,target_index)
1869
indices = (source_index, target_index)
1712
1870
# -- make all specific_files utf8 --
1713
1871
if specific_files:
1714
1872
specific_files_utf8 = set()
1850
2007
target_details = entry[1][target_index]
1851
2008
target_minikind = target_details[0]
1852
2009
if path_info is not None and target_minikind in 'fdlt':
1853
assert target_index == 0
2010
if not (target_index == 0):
2011
raise AssertionError()
1854
2012
link_or_sha1 = state.update_entry(entry, abspath=path_info[4],
1855
2013
stat_value=path_info[3])
1856
2014
# The entry may have been modified by update_entry
1881
2039
path_utf8=old_path)
1882
2040
# update the source details variable to be the real
2042
if old_entry == (None, None):
2043
raise errors.CorruptDirstate(state._filename,
2044
"entry '%s/%s' is considered renamed from %r"
2045
" but source does not exist\n"
2046
"entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
1884
2047
source_details = old_entry[1][source_index]
1885
2048
source_minikind = source_details[0]
1964
2127
# parent entry will be the same as the source entry.
1965
2128
target_parent_entry = state._get_entry(target_index,
1966
2129
path_utf8=new_dirname)
1967
assert target_parent_entry != (None, None), (
1968
"Could not find target parent in wt: %s\nparent of: %s"
1969
% (new_dirname, entry))
2130
if target_parent_entry == (None, None):
2131
raise AssertionError(
2132
"Could not find target parent in wt: %s\nparent of: %s"
2133
% (new_dirname, entry))
1970
2134
target_parent_id = target_parent_entry[0][2]
1971
2135
if target_parent_id == entry[0][2]:
1972
2136
# This is the root, so the parent is None
2005
2169
return uninteresting
2006
2170
elif source_minikind in 'a' and target_minikind in 'fdlt':
2007
2171
# looks like a new file
2172
path = pathjoin(entry[0][0], entry[0][1])
2173
# parent id is the entry for the path in the target tree
2174
# TODO: these are the same for an entire directory: cache em.
2175
parent_id = state._get_entry(target_index,
2176
path_utf8=entry[0][0])[0][2]
2177
if parent_id == entry[0][2]:
2008
2179
if path_info is not None:
2009
path = pathjoin(entry[0][0], entry[0][1])
2010
# parent id is the entry for the path in the target tree
2011
# TODO: these are the same for an entire directory: cache em.
2012
parent_id = state._get_entry(target_index,
2013
path_utf8=entry[0][0])[0][2]
2014
if parent_id == entry[0][2]:
2016
2181
if use_filesystem_for_exec:
2017
2182
# We need S_ISREG here, because we aren't sure if this
2018
2183
# is a file or not.
2030
2195
(None, path_info[2]),
2031
2196
(None, target_exec))
2033
# but its not on disk: we deliberately treat this as just
2034
# never-present. (Why ?! - RBC 20070224)
2198
# Its a missing file, report it as such.
2199
return (entry[0][2],
2200
(None, utf8_decode(path)[0]),
2204
(None, utf8_decode(entry[0][1])[0]),
2036
2207
elif source_minikind in 'fdlt' and target_minikind in 'a':
2037
2208
# unversioned, possibly, or possibly not deleted: we dont care.
2038
2209
# if its still on disk, *and* theres no other entry at this
2155
2326
if current_dir_info[0][0] == '':
2156
2327
# remove .bzr from iteration
2157
2328
bzr_index = bisect_left(current_dir_info[1], ('.bzr',))
2158
assert current_dir_info[1][bzr_index][0] == '.bzr'
2329
if current_dir_info[1][bzr_index][0] != '.bzr':
2330
raise AssertionError()
2159
2331
del current_dir_info[1][bzr_index]
2160
2332
# walk until both the directory listing and the versioned metadata
2161
2333
# are exhausted.
2168
2340
current_block is not None):
2169
2341
if (current_dir_info and current_block
2170
2342
and current_dir_info[0][0] != current_block[0]):
2171
if current_dir_info[0][0].split('/') < current_block[0].split('/'):
2343
if cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
2172
2344
# filesystem data refers to paths not covered by the dirblock.
2173
2345
# this has two possibilities:
2174
2346
# A) it is versioned but empty, so there is no block for it
2307
2479
new_executable = bool(
2308
2480
stat.S_ISREG(current_path_info[3].st_mode)
2309
2481
and stat.S_IEXEC & current_path_info[3].st_mode)
2483
relpath_unicode = utf8_decode(current_path_info[0])[0]
2484
except UnicodeDecodeError:
2485
raise errors.BadFilenameEncoding(
2486
current_path_info[0], osutils._fs_enc)
2311
(None, utf8_decode(current_path_info[0])[0]),
2488
(None, relpath_unicode),
2313
2490
(False, False),
2413
2589
def update_format(self, tree):
2414
2590
"""Change the format marker."""
2415
tree._control_files.put_utf8('format',
2416
self.target_format.get_format_string())
2591
tree._transport.put_bytes('format',
2592
self.target_format.get_format_string(),
2593
mode=tree.bzrdir._get_file_mode())