132
129
self._format = _format
133
130
self.bzrdir = _bzrdir
131
from bzrlib.trace import note, mutter
132
assert isinstance(basedir, basestring), \
133
"base directory %r is not a string" % basedir
134
134
basedir = safe_unicode(basedir)
135
135
mutter("opening working tree %r", basedir)
136
136
self._branch = branch
137
assert isinstance(self.branch, bzrlib.branch.Branch), \
138
"branch %r is not a Branch" % self.branch
137
139
self.basedir = realpath(basedir)
138
140
# if branch is at our basedir and is a format 6 or less
139
141
# assume all other formats have their own control files.
142
assert isinstance(_control_files, LockableFiles), \
143
"_control_files must be a LockableFiles, not %r" % _control_files
140
144
self._control_files = _control_files
141
self._transport = self._control_files._transport
142
145
self._dirty = None
144
147
# during a read or write lock these objects are set, and are
266
268
self._dirstate = dirstate.DirState.on_file(local_path)
267
269
return self._dirstate
271
def _directory_is_tree_reference(self, relpath):
272
# as a special case, if a directory contains control files then
273
# it's a tree reference, except that the root of the tree is not
274
return relpath and osutils.isdir(self.abspath(relpath) + u"/.bzr")
275
# TODO: We could ask all the control formats whether they
276
# recognize this directory, but at the moment there's no cheap api
277
# to do that. Since we probably can only nest bzr checkouts and
278
# they always use this name it's ok for now. -- mbp 20060306
280
# FIXME: There is an unhandled case here of a subdirectory
281
# containing .bzr but not a branch; that will probably blow up
282
# when you try to commit it. It might happen if there is a
283
# checkout in a subdirectory. This can be avoided by not adding
269
286
def filter_unversioned_files(self, paths):
270
287
"""Filter out paths that are versioned.
353
369
# add this entry to the parent map.
354
370
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
355
371
elif kind == 'tree-reference':
356
if not self._repo_supports_tree_reference:
357
raise AssertionError(
359
"doesn't support tree references "
360
"required by entry %r"
372
assert self._repo_supports_tree_reference, \
373
"repository of %r " \
374
"doesn't support tree references " \
375
"required by entry %r" \
362
377
inv_entry.reference_revision = link_or_sha1 or None
363
378
elif kind != 'symlink':
364
379
raise AssertionError("unknown kind %r" % kind)
365
380
# These checks cost us around 40ms on a 55k entry tree
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'
381
assert file_id not in inv_byid, ('file_id %s already in'
382
' inventory as %s' % (file_id, inv_byid[file_id]))
383
assert name_unicode not in parent_ie.children
372
384
inv_byid[file_id] = inv_entry
373
385
parent_ie.children[name_unicode] = inv_entry
374
386
self._inventory = inv
394
406
def get_file_sha1(self, file_id, path=None, stat_value=None):
395
407
# check file id is valid unconditionally.
396
408
entry = self._get_entry(file_id=file_id, path=path)
398
raise errors.NoSuchId(self, file_id)
409
assert entry[0] is not None, 'what error should this raise'
400
411
path = pathjoin(entry[0][0], entry[0][1]).decode('utf8')
402
413
file_abspath = self.abspath(path)
403
414
state = self.current_dirstate()
404
if stat_value is None:
406
stat_value = os.lstat(file_abspath)
408
if e.errno == errno.ENOENT:
412
415
link_or_sha1 = state.update_entry(entry, file_abspath,
413
416
stat_value=stat_value)
414
417
if entry[1][0][0] == 'f':
470
471
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
471
472
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?
479
474
if not osutils.supports_executable():
480
476
def is_executable(self, file_id, path=None):
481
"""Test if a file is executable or not.
483
Note: The caller is expected to take a read-lock before calling this.
477
file_id = osutils.safe_file_id(file_id)
485
478
entry = self._get_entry(file_id=file_id, path=path)
486
479
if entry == (None, None):
488
481
return entry[1][0][3]
490
_is_executable_from_path_and_stat = \
491
_is_executable_from_path_and_stat_from_basis
493
484
def is_executable(self, file_id, path=None):
494
"""Test if a file is executable or not.
496
Note: The caller is expected to take a read-lock before calling this.
498
self._must_be_locked()
486
file_id = osutils.safe_file_id(file_id)
500
487
path = self.id2path(file_id)
501
488
mode = os.lstat(self.abspath(path)).st_mode
502
489
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
515
492
def __iter__(self):
516
493
"""Iterate through file_ids for this tree.
541
514
# the root is not a reference.
543
relpath = pathjoin(key[0].decode('utf8'), key[1].decode('utf8'))
516
path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
545
if self._kind(relpath) == 'tree-reference':
546
yield relpath, key[2]
518
if self._kind(path) == 'tree-reference':
547
520
except errors.NoSuchFile:
548
521
# path is missing on disk.
551
525
def kind(self, file_id):
552
526
"""Return the kind of a file.
554
528
This is always the actual kind that's on disk, regardless of what it
557
Note: The caller is expected to take a read-lock before calling this.
559
531
relpath = self.id2path(file_id)
561
raise AssertionError(
562
"path for id {%s} is None!" % file_id)
532
assert relpath != None, \
533
"path for id {%s} is None!" % file_id
563
534
return self._kind(relpath)
565
536
def _kind(self, relpath):
817
794
to_block_index = state._ensure_block(
818
795
to_block_index, to_entry_index, to_dir_utf8)
819
796
to_block = state._dirblocks[to_block_index]
821
# Grab a copy since move_one may update the list.
822
for entry in from_block[1][:]:
823
if not (entry[0][0] == from_dir):
824
raise AssertionError()
797
for entry in from_block[1]:
798
assert entry[0][0] == from_dir
825
799
cur_details = entry[1][0]
826
800
to_key = (to_dir_utf8, entry[0][1], entry[0][2])
827
801
from_path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
828
802
to_path_utf8 = osutils.pathjoin(to_dir_utf8, entry[0][1])
829
803
minikind = cur_details[0]
831
# Deleted children of a renamed directory
832
# Do not need to be updated.
833
# Children that have been renamed out of this
834
# directory should also not be updated
836
804
move_one(entry, from_path_utf8=from_path_utf8,
837
805
minikind=minikind,
838
806
executable=cur_details[3],
1085
1062
parent tree - i.e. a ghost.
1087
1064
dirstate = self.current_dirstate()
1065
dirstate._validate()
1088
1066
if len(parents_list) > 0:
1089
1067
if not allow_leftmost_as_ghost and parents_list[0][1] is None:
1090
1068
raise errors.GhostRevisionUnusableHere(parents_list[0][0])
1091
1069
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()
1099
1071
# convert absent trees to the null tree, which we convert back to
1100
1072
# missing on access.
1101
1073
for rev_id, tree in parents_list:
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.
1108
_mod_revision.check_not_reserved_id(rev_id)
1074
rev_id = osutils.safe_revision_id(rev_id)
1109
1075
if tree is not None:
1110
1076
real_trees.append((rev_id, tree))
1112
1078
real_trees.append((rev_id,
1113
1079
self.branch.repository.revision_tree(None)))
1114
1080
ghosts.append(rev_id)
1115
accepted_revisions.add(rev_id)
1081
dirstate._validate()
1116
1082
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1083
dirstate._validate()
1117
1084
self._make_dirty(reset_inventory=False)
1085
dirstate._validate()
1119
1087
def _set_root_id(self, file_id):
1120
1088
"""See WorkingTree.set_root_id."""
1247
1203
self._inventory.remove_recursive_id(file_id)
1249
1205
@needs_tree_write_lock
1250
def rename_one(self, from_rel, to_rel, after=False):
1251
"""See WorkingTree.rename_one"""
1253
WorkingTree.rename_one(self, from_rel, to_rel, after)
1255
@needs_tree_write_lock
1256
def apply_inventory_delta(self, changes):
1257
"""See MutableTree.apply_inventory_delta"""
1258
state = self.current_dirstate()
1259
state.update_by_delta(changes)
1260
self._make_dirty(reset_inventory=True)
1262
def update_basis_by_delta(self, new_revid, delta):
1263
"""See MutableTree.update_basis_by_delta."""
1264
if self.last_revision() == new_revid:
1265
raise AssertionError()
1266
self.current_dirstate().update_basis_by_delta(delta, new_revid)
1269
def _validate(self):
1270
self._dirstate._validate()
1272
@needs_tree_write_lock
1273
1206
def _write_inventory(self, inv):
1274
1207
"""Write inventory as the current inventory."""
1276
raise AssertionError("attempting to write an inventory when the "
1277
"dirstate is dirty will lose pending changes")
1208
assert not self._dirty, "attempting to write an inventory when the dirstate is dirty will cause data loss"
1278
1209
self.current_dirstate().set_state_from_inventory(inv)
1279
1210
self._make_dirty(reset_inventory=False)
1280
1211
if self._inventory is not None:
1306
1233
"""See WorkingTreeFormat.get_format_description()."""
1307
1234
return "Working tree format 4"
1309
def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
1310
accelerator_tree=None, hardlink=False):
1236
def initialize(self, a_bzrdir, revision_id=None):
1311
1237
"""See WorkingTreeFormat.initialize().
1313
1239
:param revision_id: allows creating a working tree at a different
1314
1240
revision than the branch is at.
1315
:param accelerator_tree: A tree which can be used for retrieving file
1316
contents more quickly than the revision tree, i.e. a workingtree.
1317
The revision tree will be used for cases where accelerator_tree's
1318
content is different.
1319
:param hardlink: If true, hard-link files from accelerator_tree,
1322
These trees get an initial random root id, if their repository supports
1323
rich root data, TREE_ROOT otherwise.
1242
These trees get an initial random root id.
1244
revision_id = osutils.safe_revision_id(revision_id)
1325
1245
if not isinstance(a_bzrdir.transport, LocalTransport):
1326
1246
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1327
1247
transport = a_bzrdir.get_workingtree_transport(self)
1328
1248
control_files = self._open_control_files(a_bzrdir)
1329
1249
control_files.create_lock()
1330
1250
control_files.lock_write()
1331
transport.put_bytes('format', self.get_format_string(),
1332
mode=a_bzrdir._get_file_mode())
1333
if from_branch is not None:
1334
branch = from_branch
1336
branch = a_bzrdir.open_branch()
1251
control_files.put_utf8('format', self.get_format_string())
1252
branch = a_bzrdir.open_branch()
1337
1253
if revision_id is None:
1338
1254
revision_id = branch.last_revision()
1339
1255
local_path = transport.local_abspath('dirstate')
1340
1256
# write out new dirstate (must exist when we create the tree)
1341
1257
state = dirstate.DirState.initialize(local_path)
1344
wt = self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
1259
wt = WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
1347
1262
_bzrdir=a_bzrdir,
1348
1263
_control_files=control_files)
1350
1265
wt.lock_tree_write()
1352
self._init_custom_control_files(wt)
1353
1268
if revision_id in (None, NULL_REVISION):
1354
if branch.repository.supports_rich_root():
1355
wt._set_root_id(generate_ids.gen_root_id())
1357
wt._set_root_id(ROOT_ID)
1269
wt._set_root_id(generate_ids.gen_root_id())
1360
# frequently, we will get here due to branching. The accelerator
1361
# tree will be the tree from the branch, so the desired basis
1362
# tree will often be a parent of the accelerator tree.
1363
if accelerator_tree is not None:
1365
basis = accelerator_tree.revision_tree(revision_id)
1366
except errors.NoSuchRevision:
1369
basis = branch.repository.revision_tree(revision_id)
1370
if revision_id == NULL_REVISION:
1373
parents_list = [(revision_id, basis)]
1271
wt.current_dirstate()._validate()
1272
wt.set_last_revision(revision_id)
1274
basis = wt.basis_tree()
1374
1275
basis.lock_read()
1376
wt.set_parent_trees(parents_list, allow_leftmost_as_ghost=True)
1276
# if the basis has a root id we have to use that; otherwise we use
1278
basis_root_id = basis.get_root_id()
1279
if basis_root_id is not None:
1280
wt._set_root_id(basis_root_id)
1378
# if the basis has a root id we have to use that; otherwise we
1379
# use a new random one
1380
basis_root_id = basis.get_root_id()
1381
if basis_root_id is not None:
1382
wt._set_root_id(basis_root_id)
1384
# delta_from_tree is safe even for DirStateRevisionTrees,
1385
# because wt4.apply_inventory_delta does not mutate the input
1386
# inventory entries.
1387
transform.build_tree(basis, wt, accelerator_tree,
1388
hardlink=hardlink, delta_from_tree=True)
1282
transform.build_tree(basis, wt)
1392
1285
control_files.unlock()
1396
def _init_custom_control_files(self, wt):
1397
"""Subclasses with custom control files should override this method.
1399
The working tree and control files are locked for writing when this
1402
:param wt: the WorkingTree object
1405
1289
def _open(self, a_bzrdir, control_files):
1406
1290
"""Open the tree itself.
1408
1292
:param a_bzrdir: the dir for the tree.
1409
1293
:param control_files: the control files for the tree.
1411
return self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
1295
return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
1412
1296
branch=a_bzrdir.open_branch(),
1414
1298
_bzrdir=a_bzrdir,
1428
1312
def __init__(self, dirstate, revision_id, repository):
1429
1313
self._dirstate = dirstate
1430
self._revision_id = revision_id
1314
self._revision_id = osutils.safe_revision_id(revision_id)
1431
1315
self._repository = repository
1432
1316
self._inventory = None
1433
1317
self._locked = 0
1434
1318
self._dirstate_locked = False
1435
self._repo_supports_tree_reference = getattr(
1436
repository._format, "supports_tree_reference",
1439
1320
def __repr__(self):
1440
1321
return "<%s of %s in %s>" % \
1441
1322
(self.__class__.__name__, self._revision_id, self._dirstate)
1443
def annotate_iter(self, file_id,
1444
default_revision=_mod_revision.CURRENT_REVISION):
1324
def annotate_iter(self, file_id):
1445
1325
"""See Tree.annotate_iter"""
1446
text_key = (file_id, self.inventory[file_id].revision)
1447
annotations = self._repository.texts.annotate(text_key)
1448
return [(key[-1], line) for (key, line) in annotations]
1326
w = self._repository.weave_store.get_weave(file_id,
1327
self._repository.get_transaction())
1328
return w.annotate_iter(self.inventory[file_id].revision)
1450
def _get_ancestors(self, default_revision):
1451
return set(self._repository.get_ancestry(self._revision_id,
1453
1330
def _comparison_data(self, entry, path):
1454
1331
"""See Tree._comparison_data."""
1455
1332
if entry is None:
1472
1349
def get_root_id(self):
1473
1350
return self.path2id('')
1475
def id2path(self, file_id):
1476
"Convert a file-id to a path."
1477
entry = self._get_entry(file_id=file_id)
1478
if entry == (None, None):
1479
raise errors.NoSuchId(tree=self, file_id=file_id)
1480
path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
1481
return path_utf8.decode('utf8')
1483
def iter_references(self):
1484
if not self._repo_supports_tree_reference:
1485
# When the repo doesn't support references, we will have nothing to
1488
# Otherwise, fall back to the default implementation
1489
return super(DirStateRevisionTree, self).iter_references()
1491
1352
def _get_parent_index(self):
1492
1353
"""Return the index in the dirstate referenced by this tree."""
1493
1354
return self._dirstate.get_parent_ids().index(self._revision_id) + 1
1519
1381
This is relatively expensive: we have to walk the entire dirstate.
1521
if not self._locked:
1522
raise AssertionError(
1523
'cannot generate inventory of an unlocked '
1524
'dirstate revision tree')
1383
assert self._locked, 'cannot generate inventory of an unlocked '\
1384
'dirstate revision tree'
1525
1385
# separate call for profiling - makes it clear where the costs are.
1526
1386
self._dirstate._read_dirblocks_if_needed()
1527
if self._revision_id not in self._dirstate.get_parent_ids():
1528
raise AssertionError(
1529
'parent %s has disappeared from %s' % (
1530
self._revision_id, self._dirstate.get_parent_ids()))
1387
assert self._revision_id in self._dirstate.get_parent_ids(), \
1388
'parent %s has disappeared from %s' % (
1389
self._revision_id, self._dirstate.get_parent_ids())
1531
1390
parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
1532
1391
# This is identical now to the WorkingTree _generate_inventory except
1533
1392
# for the tree index use.
1534
1393
root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
1535
1394
current_id = root_key[2]
1536
if current_entry[parent_index][0] != 'd':
1537
raise AssertionError()
1395
assert current_entry[parent_index][0] == 'd'
1538
1396
inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1539
1397
inv.root.revision = current_entry[parent_index][4]
1540
1398
# Turn some things into local variables
1611
1465
return parent_details[1]
1614
def get_file(self, file_id, path=None):
1468
def get_file(self, file_id):
1615
1469
return StringIO(self.get_file_text(file_id))
1617
1471
def get_file_lines(self, file_id):
1618
return osutils.split_lines(self.get_file_text(file_id))
1472
ie = self.inventory[file_id]
1473
return self._repository.weave_store.get_weave(file_id,
1474
self._repository.get_transaction()).get_lines(ie.revision)
1620
1476
def get_file_size(self, file_id):
1621
"""See Tree.get_file_size"""
1622
1477
return self.inventory[file_id].text_size
1624
1479
def get_file_text(self, file_id):
1625
return list(self.iter_files_bytes([(file_id, None)]))[0][1]
1480
return ''.join(self.get_file_lines(file_id))
1627
1482
def get_reference_revision(self, file_id, path=None):
1628
1483
return self.inventory[file_id].reference_revision
1630
def iter_files_bytes(self, desired_files):
1631
"""See Tree.iter_files_bytes.
1633
This version is implemented on top of Repository.iter_files_bytes"""
1634
parent_index = self._get_parent_index()
1635
repo_desired_files = []
1636
for file_id, identifier in desired_files:
1637
entry = self._get_entry(file_id)
1638
if entry == (None, None):
1639
raise errors.NoSuchId(self, file_id)
1640
repo_desired_files.append((file_id, entry[1][parent_index][4],
1642
return self._repository.iter_files_bytes(repo_desired_files)
1644
1485
def get_symlink_target(self, file_id):
1645
1486
entry = self._get_entry(file_id=file_id)
1646
1487
parent_index = self._get_parent_index()
1674
1515
return bool(self.path2id(filename))
1676
1517
def kind(self, file_id):
1677
entry = self._get_entry(file_id=file_id)[1]
1679
raise errors.NoSuchId(tree=self, file_id=file_id)
1680
return dirstate.DirState._minikind_to_kind[entry[1][0]]
1682
def stored_kind(self, file_id):
1683
"""See Tree.stored_kind"""
1684
return self.kind(file_id)
1686
def path_content_summary(self, path):
1687
"""See Tree.path_content_summary."""
1688
id = self.inventory.path2id(path)
1690
return ('missing', None, None, None)
1691
entry = self._inventory[id]
1694
return (kind, entry.text_size, entry.executable, entry.text_sha1)
1695
elif kind == 'symlink':
1696
return (kind, None, None, entry.symlink_target)
1698
return (kind, None, None, None)
1518
return self.inventory[file_id].kind
1700
1520
def is_executable(self, file_id, path=None):
1701
1521
ie = self.inventory[file_id]
1813
1629
_matching_to_tree_format = WorkingTreeFormat4()
1814
1630
_test_mutable_trees_to_test_trees = make_source_parent_tree
1816
def iter_changes(self, include_unchanged=False,
1632
def _iter_changes(self, include_unchanged=False,
1817
1633
specific_files=None, pb=None, extra_trees=[],
1818
1634
require_versioned=True, want_unversioned=False):
1819
1635
"""Return the changes from source to target.
1821
:return: An iterator that yields tuples. See InterTree.iter_changes
1637
:return: An iterator that yields tuples. See InterTree._iter_changes
1823
1639
:param specific_files: An optional list of file paths to restrict the
1824
1640
comparison to. When mapping filenames to ids, all matches in all
1835
1651
output. An unversioned file is defined as one with (False, False)
1836
1652
for the versioned pair.
1838
utf8_decode = cache_utf8._utf8_decode
1654
utf8_decode_or_none = cache_utf8._utf8_decode_with_None
1839
1655
_minikind_to_kind = dirstate.DirState._minikind_to_kind
1840
cmp_by_dirs = dirstate.cmp_by_dirs
1841
1656
# NB: show_status depends on being able to pass in non-versioned files
1842
1657
# and report them as unknown
1843
1658
# TODO: handle extra trees in the dirstate.
1844
if (extra_trees or specific_files == []):
1659
# TODO: handle comparisons as an empty tree as a different special
1660
# case? mbp 20070226
1661
if extra_trees or (self.source._revision_id == NULL_REVISION):
1845
1662
# we can't fast-path these cases (yet)
1846
for f in super(InterDirStateTree, self).iter_changes(
1663
for f in super(InterDirStateTree, self)._iter_changes(
1847
1664
include_unchanged, specific_files, pb, extra_trees,
1848
1665
require_versioned, want_unversioned=want_unversioned):
1851
1668
parent_ids = self.target.get_parent_ids()
1852
if not (self.source._revision_id in parent_ids
1853
or self.source._revision_id == NULL_REVISION):
1854
raise AssertionError(
1855
"revision {%s} is not stored in {%s}, but %s "
1856
"can only be used for trees stored in the dirstate"
1857
% (self.source._revision_id, self.target, self.iter_changes))
1669
assert (self.source._revision_id in parent_ids), \
1670
"revision {%s} is not stored in {%s}, but %s " \
1671
"can only be used for trees stored in the dirstate" \
1672
% (self.source._revision_id, self.target, self._iter_changes)
1858
1673
target_index = 0
1859
1674
if self.source._revision_id == NULL_REVISION:
1860
1675
source_index = None
1861
1676
indices = (target_index,)
1863
if not (self.source._revision_id in parent_ids):
1864
raise AssertionError(
1865
"Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
1866
self.source._revision_id, parent_ids))
1678
assert (self.source._revision_id in parent_ids), \
1679
"Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
1680
self.source._revision_id, parent_ids)
1867
1681
source_index = 1 + parent_ids.index(self.source._revision_id)
1868
indices = (source_index, target_index)
1682
indices = (source_index,target_index)
1869
1683
# -- make all specific_files utf8 --
1870
1684
if specific_files:
1871
1685
specific_files_utf8 = set()
1971
1785
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1972
1786
# Using a list so that we can access the values and change them in
1973
1787
# nested scope. Each one is [path, file_id, entry]
1974
last_source_parent = [None, None]
1975
last_target_parent = [None, None]
1788
last_source_parent = [None, None, None]
1789
last_target_parent = [None, None, None]
1977
1791
use_filesystem_for_exec = (sys.platform != 'win32')
1979
# Just a sentry, so that _process_entry can say that this
1980
# record is handled, but isn't interesting to process (unchanged)
1981
uninteresting = object()
1984
old_dirname_to_file_id = {}
1985
new_dirname_to_file_id = {}
1986
# TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
1987
# keeping a cache of directories that we have seen.
1989
1793
def _process_entry(entry, path_info):
1990
1794
"""Compare an entry and real disk to generate delta information.
2096
1886
target_exec = False
2098
1888
raise Exception, "unknown kind %s" % path_info[2]
2099
if source_minikind == 'd':
2101
old_path = path = pathjoin(old_dirname, old_basename)
2102
old_dirname_to_file_id[old_path] = file_id
2103
1889
# parent id is the entry for the path in the target tree
2104
1890
if old_dirname == last_source_parent[0]:
2105
1891
source_parent_id = last_source_parent[1]
2108
source_parent_id = old_dirname_to_file_id[old_dirname]
2110
source_parent_entry = state._get_entry(source_index,
2111
path_utf8=old_dirname)
2112
source_parent_id = source_parent_entry[0][2]
1893
source_parent_entry = state._get_entry(source_index,
1894
path_utf8=old_dirname)
1895
source_parent_id = source_parent_entry[0][2]
2113
1896
if source_parent_id == entry[0][2]:
2114
1897
# This is the root, so the parent is None
2115
1898
source_parent_id = None
2117
1900
last_source_parent[0] = old_dirname
2118
1901
last_source_parent[1] = source_parent_id
1902
last_source_parent[2] = source_parent_entry
2119
1903
new_dirname = entry[0][0]
2120
1904
if new_dirname == last_target_parent[0]:
2121
1905
target_parent_id = last_target_parent[1]
2124
target_parent_id = new_dirname_to_file_id[new_dirname]
2126
# TODO: We don't always need to do the lookup, because the
2127
# parent entry will be the same as the source entry.
2128
target_parent_entry = state._get_entry(target_index,
2129
path_utf8=new_dirname)
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))
2134
target_parent_id = target_parent_entry[0][2]
1907
# TODO: We don't always need to do the lookup, because the
1908
# parent entry will be the same as the source entry.
1909
target_parent_entry = state._get_entry(target_index,
1910
path_utf8=new_dirname)
1911
target_parent_id = target_parent_entry[0][2]
2135
1912
if target_parent_id == entry[0][2]:
2136
1913
# This is the root, so the parent is None
2137
1914
target_parent_id = None
2139
1916
last_target_parent[0] = new_dirname
2140
1917
last_target_parent[1] = target_parent_id
1918
last_target_parent[2] = target_parent_entry
2142
1920
source_exec = source_details[3]
2143
if (include_unchanged
2145
or source_parent_id != target_parent_id
2146
or old_basename != entry[0][1]
2147
or source_exec != target_exec
2149
if old_path is None:
2150
old_path = path = pathjoin(old_dirname, old_basename)
2151
old_path_u = utf8_decode(old_path)[0]
2154
old_path_u = utf8_decode(old_path)[0]
2155
if old_path == path:
2158
path_u = utf8_decode(path)[0]
2159
source_kind = _minikind_to_kind[source_minikind]
2160
return (entry[0][2],
2161
(old_path_u, path_u),
2164
(source_parent_id, target_parent_id),
2165
(utf8_decode(old_basename)[0], utf8_decode(entry[0][1])[0]),
2166
(source_kind, target_kind),
2167
(source_exec, target_exec))
2169
return uninteresting
1921
return ((entry[0][2], (old_path, path), content_change,
1923
(source_parent_id, target_parent_id),
1924
(old_basename, entry[0][1]),
1925
(_minikind_to_kind[source_minikind], target_kind),
1926
(source_exec, target_exec)),)
2170
1927
elif source_minikind in 'a' and target_minikind in 'fdlt':
2171
1928
# looks like a new file
2172
1929
if path_info is not None:
2207
1962
parent_id = state._get_entry(source_index, path_utf8=entry[0][0])[0][2]
2208
1963
if parent_id == entry[0][2]:
2209
1964
parent_id = None
2210
return (entry[0][2],
2211
(utf8_decode(old_path)[0], None),
2215
(utf8_decode(entry[0][1])[0], None),
2216
(_minikind_to_kind[source_minikind], None),
2217
(source_details[3], None))
1965
return ((entry[0][2], (old_path, None), True,
1968
(entry[0][1], None),
1969
(_minikind_to_kind[source_minikind], None),
1970
(source_details[3], None)),)
2218
1971
elif source_minikind in 'fdlt' and target_minikind in 'r':
2219
1972
# a rename; could be a true rename, or a rename inherited from
2220
1973
# a renamed parent. TODO: handle this efficiently. Its not
2269
2022
path_handled = False
2270
2023
for entry in root_entries:
2271
result = _process_entry(entry, root_dir_info)
2272
if result is not None:
2024
for result in _process_entry(entry, root_dir_info):
2025
# this check should probably be outside the loop: one
2026
# 'iterate two trees' api, and then _iter_changes filters
2027
# unchanged pairs. - RBC 20070226
2273
2028
path_handled = True
2274
if result is not uninteresting:
2029
if (include_unchanged
2030
or result[2] # content change
2031
or result[3][0] != result[3][1] # versioned status
2032
or result[4][0] != result[4][1] # parent id
2033
or result[5][0] != result[5][1] # name
2034
or result[6][0] != result[6][1] # kind
2035
or result[7][0] != result[7][1] # executable
2038
(utf8_decode_or_none(result[1][0]),
2039
utf8_decode_or_none(result[1][1])),
2043
(utf8_decode_or_none(result[5][0]),
2044
utf8_decode_or_none(result[5][1])),
2276
2048
if want_unversioned and not path_handled and root_dir_info:
2277
2049
new_executable = bool(
2278
2050
stat.S_ISREG(root_dir_info[3].st_mode)
2333
2102
current_block is not None):
2334
2103
if (current_dir_info and current_block
2335
2104
and current_dir_info[0][0] != current_block[0]):
2336
if cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
2105
if current_dir_info[0][0] < current_block[0] :
2337
2106
# filesystem data refers to paths not covered by the dirblock.
2338
2107
# this has two possibilities:
2339
2108
# A) it is versioned but empty, so there is no block for it
2340
2109
# B) it is not versioned.
2342
# if (A) then we need to recurse into it to check for
2343
# new unknown files or directories.
2344
# if (B) then we should ignore it, because we don't
2345
# recurse into unknown directories.
2347
while path_index < len(current_dir_info[1]):
2348
current_path_info = current_dir_info[1][path_index]
2349
if want_unversioned:
2350
if current_path_info[2] == 'directory':
2351
if self.target._directory_is_tree_reference(
2352
current_path_info[0].decode('utf8')):
2353
current_path_info = current_path_info[:2] + \
2354
('tree-reference',) + current_path_info[3:]
2355
new_executable = bool(
2356
stat.S_ISREG(current_path_info[3].st_mode)
2357
and stat.S_IEXEC & current_path_info[3].st_mode)
2359
(None, utf8_decode(current_path_info[0])[0]),
2363
(None, utf8_decode(current_path_info[1])[0]),
2364
(None, current_path_info[2]),
2365
(None, new_executable))
2366
# dont descend into this unversioned path if it is
2368
if current_path_info[2] in ('directory',
2370
del current_dir_info[1][path_index]
2374
# This dir info has been handled, go to the next
2110
# in either case it was processed by the containing directories walk:
2111
# if it is root/foo, when we walked root we emitted it,
2112
# or if we ere given root/foo to walk specifically, we
2113
# emitted it when checking the walk-root entries
2114
# advance the iterator and loop - we dont need to emit it.
2376
2116
current_dir_info = dir_iterator.next()
2377
2117
except StopIteration:
2387
2127
for current_entry in current_block[1]:
2388
2128
# entry referring to file not present on disk.
2389
2129
# advance the entry only, after processing.
2390
result = _process_entry(current_entry, None)
2391
if result is not None:
2392
if result is not uninteresting:
2130
for result in _process_entry(current_entry, None):
2131
# this check should probably be outside the loop: one
2132
# 'iterate two trees' api, and then _iter_changes filters
2133
# unchanged pairs. - RBC 20070226
2134
if (include_unchanged
2135
or result[2] # content change
2136
or result[3][0] != result[3][1] # versioned status
2137
or result[4][0] != result[4][1] # parent id
2138
or result[5][0] != result[5][1] # name
2139
or result[6][0] != result[6][1] # kind
2140
or result[7][0] != result[7][1] # executable
2143
(utf8_decode_or_none(result[1][0]),
2144
utf8_decode_or_none(result[1][1])),
2148
(utf8_decode_or_none(result[5][0]),
2149
utf8_decode_or_none(result[5][1])),
2394
2153
block_index +=1
2395
2154
if (block_index < len(state._dirblocks) and
2396
2155
osutils.is_inside(current_root,
2426
2185
elif current_path_info is None:
2427
2186
# no path is fine: the per entry code will handle it.
2428
result = _process_entry(current_entry, current_path_info)
2429
if result is not None:
2430
if result is not uninteresting:
2432
elif (current_entry[0][1] != current_path_info[1]
2433
or current_entry[1][target_index][0] in 'ar'):
2434
# The current path on disk doesn't match the dirblock
2435
# record. Either the dirblock is marked as absent, or
2436
# the file on disk is not present at all in the
2437
# dirblock. Either way, report about the dirblock
2438
# entry, and let other code handle the filesystem one.
2440
# Compare the basename for these files to determine
2187
for result in _process_entry(current_entry, current_path_info):
2188
# this check should probably be outside the loop: one
2189
# 'iterate two trees' api, and then _iter_changes filters
2190
# unchanged pairs. - RBC 20070226
2191
if (include_unchanged
2192
or result[2] # content change
2193
or result[3][0] != result[3][1] # versioned status
2194
or result[4][0] != result[4][1] # parent id
2195
or result[5][0] != result[5][1] # name
2196
or result[6][0] != result[6][1] # kind
2197
or result[7][0] != result[7][1] # executable
2200
(utf8_decode_or_none(result[1][0]),
2201
utf8_decode_or_none(result[1][1])),
2205
(utf8_decode_or_none(result[5][0]),
2206
utf8_decode_or_none(result[5][1])),
2210
elif current_entry[0][1] != current_path_info[1]:
2442
2211
if current_path_info[1] < current_entry[0][1]:
2443
2212
# extra file on disk: pass for now, but only
2444
2213
# increment the path, not the entry
2447
2216
# entry referring to file not present on disk.
2448
2217
# advance the entry only, after processing.
2449
result = _process_entry(current_entry, None)
2450
if result is not None:
2451
if result is not uninteresting:
2218
for result in _process_entry(current_entry, None):
2219
# this check should probably be outside the loop: one
2220
# 'iterate two trees' api, and then _iter_changes filters
2221
# unchanged pairs. - RBC 20070226
2223
if (include_unchanged
2224
or result[2] # content change
2225
or result[3][0] != result[3][1] # versioned status
2226
or result[4][0] != result[4][1] # parent id
2227
or result[5][0] != result[5][1] # name
2228
or result[6][0] != result[6][1] # kind
2229
or result[7][0] != result[7][1] # executable
2232
(utf8_decode_or_none(result[1][0]),
2233
utf8_decode_or_none(result[1][1])),
2237
(utf8_decode_or_none(result[5][0]),
2238
utf8_decode_or_none(result[5][1])),
2453
2242
advance_path = False
2455
result = _process_entry(current_entry, current_path_info)
2456
if result is not None:
2244
for result in _process_entry(current_entry, current_path_info):
2245
# this check should probably be outside the loop: one
2246
# 'iterate two trees' api, and then _iter_changes filters
2247
# unchanged pairs. - RBC 20070226
2457
2248
path_handled = True
2458
if result is not uninteresting:
2249
if (include_unchanged
2250
or result[2] # content change
2251
or result[3][0] != result[3][1] # versioned status
2252
or result[4][0] != result[4][1] # parent id
2253
or result[5][0] != result[5][1] # name
2254
or result[6][0] != result[6][1] # kind
2255
or result[7][0] != result[7][1] # executable
2258
(utf8_decode_or_none(result[1][0]),
2259
utf8_decode_or_none(result[1][1])),
2263
(utf8_decode_or_none(result[5][0]),
2264
utf8_decode_or_none(result[5][1])),
2460
2268
if advance_entry and current_entry is not None:
2461
2269
entry_index += 1
2462
2270
if entry_index < len(current_block[1]):
2472
2280
new_executable = bool(
2473
2281
stat.S_ISREG(current_path_info[3].st_mode)
2474
2282
and stat.S_IEXEC & current_path_info[3].st_mode)
2476
relpath_unicode = utf8_decode(current_path_info[0])[0]
2477
except UnicodeDecodeError:
2478
raise errors.BadFilenameEncoding(
2479
current_path_info[0], osutils._fs_enc)
2481
(None, relpath_unicode),
2485
(None, utf8_decode(current_path_info[1])[0]),
2486
(None, current_path_info[2]),
2487
(None, new_executable))
2283
if want_unversioned:
2285
(None, utf8_decode_or_none(current_path_info[0])),
2289
(None, utf8_decode_or_none(current_path_info[1])),
2290
(None, current_path_info[2]),
2291
(None, new_executable))
2488
2292
# dont descend into this unversioned path if it is
2490
2294
if current_path_info[2] in ('directory'):