~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree_4.py

  • Committer: Ian Clatworthy
  • Date: 2008-03-27 07:51:10 UTC
  • mto: (3311.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 3312.
  • Revision ID: ian.clatworthy@canonical.com-20080327075110-afgd7x03ybju06ez
Reduce evangelism in the User Guide

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
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
69
69
from bzrlib import symbol_versioning
70
70
from bzrlib.decorators import needs_read_lock, needs_write_lock
71
71
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID, entry_factory
 
72
from bzrlib.lockable_files import LockableFiles, TransportLock
 
73
from bzrlib.lockdir import LockDir
72
74
import bzrlib.mutabletree
73
75
from bzrlib.mutabletree import needs_tree_write_lock
74
76
from bzrlib.osutils import (
96
98
from bzrlib.workingtree import WorkingTree, WorkingTree3, WorkingTreeFormat3
97
99
 
98
100
 
 
101
# This is the Windows equivalent of ENOTDIR
 
102
# It is defined in pywin32.winerror, but we don't want a strong dependency for
 
103
# just an error code.
 
104
ERROR_PATH_NOT_FOUND = 3
 
105
ERROR_DIRECTORY = 267
 
106
 
 
107
 
99
108
class WorkingTree4(WorkingTree3):
100
109
    """This is the Format 4 working tree.
101
110
 
122
131
        """
123
132
        self._format = _format
124
133
        self.bzrdir = _bzrdir
 
134
        assert isinstance(basedir, basestring), \
 
135
            "base directory %r is not a string" % basedir
125
136
        basedir = safe_unicode(basedir)
126
137
        mutter("opening working tree %r", basedir)
127
138
        self._branch = branch
 
139
        assert isinstance(self.branch, bzrlib.branch.Branch), \
 
140
            "branch %r is not a Branch" % self.branch
128
141
        self.basedir = realpath(basedir)
129
142
        # if branch is at our basedir and is a format 6 or less
130
143
        # 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
131
146
        self._control_files = _control_files
132
 
        self._transport = self._control_files._transport
133
147
        self._dirty = None
134
148
        #-------------
135
149
        # during a read or write lock these objects are set, and are
139
153
        #-------------
140
154
        self._setup_directory_is_tree_reference()
141
155
        self._detect_case_handling()
142
 
        self._rules_searcher = None
143
 
        #--- allow tests to select the dirstate iter_changes implementation
144
 
        self._iter_changes = dirstate._process_entry
145
156
 
146
157
    @needs_tree_write_lock
147
158
    def _add(self, files, ids, kinds):
306
317
        state._read_dirblocks_if_needed()
307
318
        root_key, current_entry = self._get_entry(path='')
308
319
        current_id = root_key[2]
309
 
        if not (current_entry[0][0] == 'd'): # directory
310
 
            raise AssertionError(current_entry)
 
320
        assert current_entry[0][0] == 'd' # directory
311
321
        inv = Inventory(root_id=current_id)
312
322
        # Turn some things into local variables
313
323
        minikind_to_kind = dirstate.DirState._minikind_to_kind
346
356
                    # add this entry to the parent map.
347
357
                    parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
348
358
                elif kind == 'tree-reference':
349
 
                    if not self._repo_supports_tree_reference:
350
 
                        raise AssertionError(
351
 
                            "repository of %r "
352
 
                            "doesn't support tree references "
353
 
                            "required by entry %r"
354
 
                            % (self, name))
 
359
                    assert self._repo_supports_tree_reference, \
 
360
                        "repository of %r " \
 
361
                        "doesn't support tree references " \
 
362
                        "required by entry %r" \
 
363
                        % (self, name)
355
364
                    inv_entry.reference_revision = link_or_sha1 or None
356
365
                elif kind != 'symlink':
357
366
                    raise AssertionError("unknown kind %r" % kind)
358
367
                # These checks cost us around 40ms on a 55k entry tree
359
 
                if file_id in inv_byid:
360
 
                    raise AssertionError('file_id %s already in'
361
 
                        ' inventory as %s' % (file_id, inv_byid[file_id]))
362
 
                if name_unicode in parent_ie.children:
363
 
                    raise AssertionError('name %r already in parent'
364
 
                        % (name_unicode,))
 
368
                assert file_id not in inv_byid, ('file_id %s already in'
 
369
                    ' inventory as %s' % (file_id, inv_byid[file_id]))
 
370
                assert name_unicode not in parent_ie.children
365
371
                inv_byid[file_id] = inv_entry
366
372
                parent_ie.children[name_unicode] = inv_entry
367
373
        self._inventory = inv
402
408
                    return None
403
409
                else:
404
410
                    raise
405
 
        link_or_sha1 = dirstate.update_entry(state, entry, file_abspath,
406
 
            stat_value=stat_value)
 
411
        link_or_sha1 = state.update_entry(entry, file_abspath,
 
412
                                          stat_value=stat_value)
407
413
        if entry[1][0][0] == 'f':
408
 
            if link_or_sha1 is None:
409
 
                file_obj, statvalue = self.get_file_with_stat(file_id, path)
410
 
                try:
411
 
                    sha1 = osutils.sha_file(file_obj)
412
 
                finally:
413
 
                    file_obj.close()
414
 
                self._observed_sha1(file_id, path, (sha1, statvalue))
415
 
                return sha1
416
 
            else:
417
 
                return link_or_sha1
 
414
            return link_or_sha1
418
415
        return None
419
416
 
420
417
    def _get_inventory(self):
531
528
        return iter(result)
532
529
 
533
530
    def iter_references(self):
534
 
        if not self._repo_supports_tree_reference:
535
 
            # When the repo doesn't support references, we will have nothing to
536
 
            # return
537
 
            return
538
531
        for key, tree_details in self.current_dirstate()._iter_entries():
539
532
            if tree_details[0][0] in ('a', 'r'): # absent, relocated
540
533
                # not relevant to the working tree
542
535
            if not key[1]:
543
536
                # the root is not a reference.
544
537
                continue
545
 
            relpath = pathjoin(key[0].decode('utf8'), key[1].decode('utf8'))
 
538
            path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
546
539
            try:
547
 
                if self._kind(relpath) == 'tree-reference':
548
 
                    yield relpath, key[2]
 
540
                if self._kind(path) == 'tree-reference':
 
541
                    yield path, key[2]
549
542
            except errors.NoSuchFile:
550
543
                # path is missing on disk.
551
544
                continue
552
545
 
553
 
    def _observed_sha1(self, file_id, path, (sha1, statvalue)):
554
 
        """See MutableTree._observed_sha1."""
555
 
        state = self.current_dirstate()
556
 
        entry = self._get_entry(file_id=file_id, path=path)
557
 
        state._observed_sha1(entry, sha1, statvalue)
558
 
 
559
546
    def kind(self, file_id):
560
547
        """Return the kind of a file.
561
548
 
565
552
        Note: The caller is expected to take a read-lock before calling this.
566
553
        """
567
554
        relpath = self.id2path(file_id)
568
 
        if relpath is None:
569
 
            raise AssertionError(
570
 
                "path for id {%s} is None!" % file_id)
 
555
        assert relpath != None, \
 
556
            "path for id {%s} is None!" % file_id
571
557
        return self._kind(relpath)
572
558
 
573
559
    def _kind(self, relpath):
645
631
        result = []
646
632
        if not from_paths:
647
633
            return result
 
634
 
648
635
        state = self.current_dirstate()
649
 
        if isinstance(from_paths, basestring):
650
 
            raise ValueError()
 
636
 
 
637
        assert not isinstance(from_paths, basestring)
651
638
        to_dir_utf8 = to_dir.encode('utf8')
652
639
        to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
653
640
        id_index = state._get_id_index()
808
795
                if minikind == 'd':
809
796
                    def update_dirblock(from_dir, to_key, to_dir_utf8):
810
797
                        """Recursively update all entries in this dirblock."""
811
 
                        if from_dir == '':
812
 
                            raise AssertionError("renaming root not supported")
 
798
                        assert from_dir != '', "renaming root not supported"
813
799
                        from_key = (from_dir, '')
814
800
                        from_block_idx, present = \
815
801
                            state._find_block_index_from_key(from_key)
828
814
 
829
815
                        # Grab a copy since move_one may update the list.
830
816
                        for entry in from_block[1][:]:
831
 
                            if not (entry[0][0] == from_dir):
832
 
                                raise AssertionError()
 
817
                            assert entry[0][0] == from_dir
833
818
                            cur_details = entry[1][0]
834
819
                            to_key = (to_dir_utf8, entry[0][1], entry[0][2])
835
820
                            from_path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
1048
1033
        """Change the last revision in the working tree."""
1049
1034
        parents = self.get_parent_ids()
1050
1035
        if new_revision in (NULL_REVISION, None):
1051
 
            if len(parents) >= 2:
1052
 
                raise AssertionError(
1053
 
                    "setting the last parent to none with a pending merge is "
1054
 
                    "unsupported.")
 
1036
            assert len(parents) < 2, (
 
1037
                "setting the last parent to none with a pending merge is "
 
1038
                "unsupported.")
1055
1039
            self.set_parent_ids([])
1056
1040
        else:
1057
1041
            self.set_parent_ids([new_revision] + parents[1:],
1098
1082
                raise errors.GhostRevisionUnusableHere(parents_list[0][0])
1099
1083
        real_trees = []
1100
1084
        ghosts = []
1101
 
 
1102
 
        parent_ids = [rev_id for rev_id, tree in parents_list]
1103
 
        graph = self.branch.repository.get_graph()
1104
 
        heads = graph.heads(parent_ids)
1105
 
        accepted_revisions = set()
1106
 
 
1107
1085
        # convert absent trees to the null tree, which we convert back to
1108
1086
        # missing on access.
1109
1087
        for rev_id, tree in parents_list:
1110
 
            if len(accepted_revisions) > 0:
1111
 
                # we always accept the first tree
1112
 
                if rev_id in accepted_revisions or rev_id not in heads:
1113
 
                    # We have already included either this tree, or its
1114
 
                    # descendent, so we skip it.
1115
 
                    continue
1116
1088
            _mod_revision.check_not_reserved_id(rev_id)
1117
1089
            if tree is not None:
1118
1090
                real_trees.append((rev_id, tree))
1119
1091
            else:
1120
1092
                real_trees.append((rev_id,
1121
 
                    self.branch.repository.revision_tree(
1122
 
                        _mod_revision.NULL_REVISION)))
 
1093
                    self.branch.repository.revision_tree(None)))
1123
1094
                ghosts.append(rev_id)
1124
 
            accepted_revisions.add(rev_id)
1125
1095
        dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1126
1096
        self._make_dirty(reset_inventory=False)
1127
1097
 
1270
1240
 
1271
1241
    def update_basis_by_delta(self, new_revid, delta):
1272
1242
        """See MutableTree.update_basis_by_delta."""
1273
 
        if self.last_revision() == new_revid:
1274
 
            raise AssertionError()
 
1243
        assert self.last_revision() != new_revid
1275
1244
        self.current_dirstate().update_basis_by_delta(delta, new_revid)
1276
1245
 
1277
1246
    @needs_read_lock
1281
1250
    @needs_tree_write_lock
1282
1251
    def _write_inventory(self, inv):
1283
1252
        """Write inventory as the current inventory."""
1284
 
        if self._dirty:
1285
 
            raise AssertionError("attempting to write an inventory when the "
1286
 
                "dirstate is dirty will lose pending changes")
 
1253
        assert not self._dirty, ("attempting to write an inventory when the "
 
1254
            "dirstate is dirty will cause data loss")
1287
1255
        self.current_dirstate().set_state_from_inventory(inv)
1288
1256
        self._make_dirty(reset_inventory=False)
1289
1257
        if self._inventory is not None:
1305
1273
 
1306
1274
    upgrade_recommended = False
1307
1275
 
1308
 
    _tree_class = WorkingTree4
1309
 
 
1310
1276
    def get_format_string(self):
1311
1277
        """See WorkingTreeFormat.get_format_string()."""
1312
1278
        return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
1337
1303
        control_files = self._open_control_files(a_bzrdir)
1338
1304
        control_files.create_lock()
1339
1305
        control_files.lock_write()
1340
 
        transport.put_bytes('format', self.get_format_string(),
1341
 
            mode=a_bzrdir._get_file_mode())
 
1306
        control_files.put_utf8('format', self.get_format_string())
1342
1307
        if from_branch is not None:
1343
1308
            branch = from_branch
1344
1309
        else:
1350
1315
        state = dirstate.DirState.initialize(local_path)
1351
1316
        state.unlock()
1352
1317
        del state
1353
 
        wt = self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
 
1318
        wt = WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
1354
1319
                         branch,
1355
1320
                         _format=self,
1356
1321
                         _bzrdir=a_bzrdir,
1358
1323
        wt._new_tree()
1359
1324
        wt.lock_tree_write()
1360
1325
        try:
1361
 
            self._init_custom_control_files(wt)
1362
1326
            if revision_id in (None, NULL_REVISION):
1363
1327
                if branch.repository.supports_rich_root():
1364
1328
                    wt._set_root_id(generate_ids.gen_root_id())
1390
1354
                if basis_root_id is not None:
1391
1355
                    wt._set_root_id(basis_root_id)
1392
1356
                    wt.flush()
1393
 
                # delta_from_tree is safe even for DirStateRevisionTrees,
1394
 
                # because wt4.apply_inventory_delta does not mutate the input
1395
 
                # inventory entries.
1396
1357
                transform.build_tree(basis, wt, accelerator_tree,
1397
 
                                     hardlink=hardlink, delta_from_tree=True)
 
1358
                                     hardlink=hardlink)
1398
1359
            finally:
1399
1360
                basis.unlock()
1400
1361
        finally:
1402
1363
            wt.unlock()
1403
1364
        return wt
1404
1365
 
1405
 
    def _init_custom_control_files(self, wt):
1406
 
        """Subclasses with custom control files should override this method.
1407
 
        
1408
 
        The working tree and control files are locked for writing when this
1409
 
        method is called.
1410
 
        
1411
 
        :param wt: the WorkingTree object
1412
 
        """
1413
 
 
1414
1366
    def _open(self, a_bzrdir, control_files):
1415
1367
        """Open the tree itself.
1416
1368
 
1417
1369
        :param a_bzrdir: the dir for the tree.
1418
1370
        :param control_files: the control files for the tree.
1419
1371
        """
1420
 
        return self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
 
1372
        return WorkingTree4(a_bzrdir.root_transport.local_abspath('.'),
1421
1373
                           branch=a_bzrdir.open_branch(),
1422
1374
                           _format=self,
1423
1375
                           _bzrdir=a_bzrdir,
1441
1393
        self._inventory = None
1442
1394
        self._locked = 0
1443
1395
        self._dirstate_locked = False
1444
 
        self._repo_supports_tree_reference = getattr(
1445
 
            repository._format, "supports_tree_reference",
1446
 
            False)
1447
1396
 
1448
1397
    def __repr__(self):
1449
1398
        return "<%s of %s in %s>" % \
1452
1401
    def annotate_iter(self, file_id,
1453
1402
                      default_revision=_mod_revision.CURRENT_REVISION):
1454
1403
        """See Tree.annotate_iter"""
1455
 
        text_key = (file_id, self.inventory[file_id].revision)
1456
 
        annotations = self._repository.texts.annotate(text_key)
1457
 
        return [(key[-1], line) for (key, line) in annotations]
 
1404
        w = self._get_weave(file_id)
 
1405
        return w.annotate_iter(self.inventory[file_id].revision)
1458
1406
 
1459
1407
    def _get_ancestors(self, default_revision):
1460
1408
        return set(self._repository.get_ancestry(self._revision_id,
1489
1437
        path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
1490
1438
        return path_utf8.decode('utf8')
1491
1439
 
1492
 
    def iter_references(self):
1493
 
        if not self._repo_supports_tree_reference:
1494
 
            # When the repo doesn't support references, we will have nothing to
1495
 
            # return
1496
 
            return iter([])
1497
 
        # Otherwise, fall back to the default implementation
1498
 
        return super(DirStateRevisionTree, self).iter_references()
1499
 
 
1500
1440
    def _get_parent_index(self):
1501
1441
        """Return the index in the dirstate referenced by this tree."""
1502
1442
        return self._dirstate.get_parent_ids().index(self._revision_id) + 1
1527
1467
 
1528
1468
        This is relatively expensive: we have to walk the entire dirstate.
1529
1469
        """
1530
 
        if not self._locked:
1531
 
            raise AssertionError(
1532
 
                'cannot generate inventory of an unlocked '
1533
 
                'dirstate revision tree')
 
1470
        assert self._locked, 'cannot generate inventory of an unlocked '\
 
1471
            'dirstate revision tree'
1534
1472
        # separate call for profiling - makes it clear where the costs are.
1535
1473
        self._dirstate._read_dirblocks_if_needed()
1536
 
        if self._revision_id not in self._dirstate.get_parent_ids():
1537
 
            raise AssertionError(
1538
 
                'parent %s has disappeared from %s' % (
1539
 
                self._revision_id, self._dirstate.get_parent_ids()))
 
1474
        assert self._revision_id in self._dirstate.get_parent_ids(), \
 
1475
            'parent %s has disappeared from %s' % (
 
1476
            self._revision_id, self._dirstate.get_parent_ids())
1540
1477
        parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
1541
1478
        # This is identical now to the WorkingTree _generate_inventory except
1542
1479
        # for the tree index use.
1543
1480
        root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
1544
1481
        current_id = root_key[2]
1545
 
        if current_entry[parent_index][0] != 'd':
1546
 
            raise AssertionError()
 
1482
        assert current_entry[parent_index][0] == 'd'
1547
1483
        inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1548
1484
        inv.root.revision = current_entry[parent_index][4]
1549
1485
        # Turn some things into local variables
1589
1525
                    raise AssertionError("cannot convert entry %r into an InventoryEntry"
1590
1526
                            % entry)
1591
1527
                # These checks cost us around 40ms on a 55k entry tree
1592
 
                if file_id in inv_byid:
1593
 
                    raise AssertionError('file_id %s already in'
1594
 
                        ' inventory as %s' % (file_id, inv_byid[file_id]))
1595
 
                if name_unicode in parent_ie.children:
1596
 
                    raise AssertionError('name %r already in parent'
1597
 
                        % (name_unicode,))
 
1528
                assert file_id not in inv_byid
 
1529
                assert name_unicode not in parent_ie.children
1598
1530
                inv_byid[file_id] = inv_entry
1599
1531
                parent_ie.children[name_unicode] = inv_entry
1600
1532
        self._inventory = inv
1620
1552
            return parent_details[1]
1621
1553
        return None
1622
1554
 
 
1555
    @symbol_versioning.deprecated_method(symbol_versioning.zero_ninety)
 
1556
    def get_weave(self, file_id):
 
1557
        return self._get_weave(file_id)
 
1558
 
 
1559
    def _get_weave(self, file_id):
 
1560
        return self._repository.weave_store.get_weave(file_id,
 
1561
                self._repository.get_transaction())
 
1562
 
1623
1563
    def get_file(self, file_id, path=None):
1624
1564
        return StringIO(self.get_file_text(file_id))
1625
1565
 
 
1566
    def get_file_lines(self, file_id):
 
1567
        entry = self._get_entry(file_id=file_id)[1]
 
1568
        if entry == None:
 
1569
            raise errors.NoSuchId(tree=self, file_id=file_id)
 
1570
        return self._get_weave(file_id).get_lines(entry[1][4])
 
1571
 
1626
1572
    def get_file_size(self, file_id):
1627
 
        """See Tree.get_file_size"""
1628
1573
        return self.inventory[file_id].text_size
1629
1574
 
1630
 
    def get_file_text(self, file_id, path=None):
1631
 
        return list(self.iter_files_bytes([(file_id, None)]))[0][1]
 
1575
    def get_file_text(self, file_id):
 
1576
        return ''.join(self.get_file_lines(file_id))
1632
1577
 
1633
1578
    def get_reference_revision(self, file_id, path=None):
1634
1579
        return self.inventory[file_id].reference_revision
1681
1626
 
1682
1627
    def kind(self, file_id):
1683
1628
        entry = self._get_entry(file_id=file_id)[1]
1684
 
        if entry is None:
 
1629
        if entry == None:
1685
1630
            raise errors.NoSuchId(tree=self, file_id=file_id)
1686
1631
        return dirstate.DirState._minikind_to_kind[entry[1][0]]
1687
1632
 
1753
1698
                self._dirstate_locked = False
1754
1699
            self._repository.unlock()
1755
1700
 
1756
 
    @needs_read_lock
1757
 
    def supports_tree_reference(self):
1758
 
        return self._repo_supports_tree_reference
1759
 
 
1760
1701
    def walkdirs(self, prefix=""):
1761
1702
        # TODO: jam 20070215 This is the lazy way by using the RevisionTree
1762
1703
        # implementation based on an inventory.  
1815
1756
        target.set_parent_ids([revid])
1816
1757
        return target.basis_tree(), target
1817
1758
 
1818
 
    @classmethod
1819
 
    def make_source_parent_tree_python_dirstate(klass, test_case, source, target):
1820
 
        result = klass.make_source_parent_tree(source, target)
1821
 
        result[1]._iter_changes = dirstate.ProcessEntryPython
1822
 
        return result
1823
 
 
1824
 
    @classmethod
1825
 
    def make_source_parent_tree_compiled_dirstate(klass, test_case, source, target):
1826
 
        from bzrlib.tests.test__dirstate_helpers import \
1827
 
            CompiledDirstateHelpersFeature
1828
 
        if not CompiledDirstateHelpersFeature.available():
1829
 
            from bzrlib.tests import UnavailableFeature
1830
 
            raise UnavailableFeature(CompiledDirstateHelpersFeature)
1831
 
        from bzrlib._dirstate_helpers_c import ProcessEntryC
1832
 
        result = klass.make_source_parent_tree(source, target)
1833
 
        result[1]._iter_changes = ProcessEntryC
1834
 
        return result
1835
 
 
1836
1759
    _matching_from_tree_format = WorkingTreeFormat4()
1837
1760
    _matching_to_tree_format = WorkingTreeFormat4()
1838
 
 
1839
 
    @classmethod
1840
 
    def _test_mutable_trees_to_test_trees(klass, test_case, source, target):
1841
 
        # This method shouldn't be called, because we have python and C
1842
 
        # specific flavours.
1843
 
        raise NotImplementedError
 
1761
    _test_mutable_trees_to_test_trees = make_source_parent_tree
1844
1762
 
1845
1763
    def iter_changes(self, include_unchanged=False,
1846
1764
                      specific_files=None, pb=None, extra_trees=[],
1864
1782
            output. An unversioned file is defined as one with (False, False)
1865
1783
            for the versioned pair.
1866
1784
        """
 
1785
        utf8_decode = cache_utf8._utf8_decode
 
1786
        _minikind_to_kind = dirstate.DirState._minikind_to_kind
 
1787
        cmp_by_dirs = dirstate.cmp_by_dirs
1867
1788
        # NB: show_status depends on being able to pass in non-versioned files
1868
1789
        # and report them as unknown
1869
1790
        # TODO: handle extra trees in the dirstate.
1870
1791
        if (extra_trees or specific_files == []):
1871
1792
            # we can't fast-path these cases (yet)
1872
 
            return super(InterDirStateTree, self).iter_changes(
 
1793
            for f in super(InterDirStateTree, self).iter_changes(
1873
1794
                include_unchanged, specific_files, pb, extra_trees,
1874
 
                require_versioned, want_unversioned=want_unversioned)
 
1795
                require_versioned, want_unversioned=want_unversioned):
 
1796
                yield f
 
1797
            return
1875
1798
        parent_ids = self.target.get_parent_ids()
1876
 
        if not (self.source._revision_id in parent_ids
1877
 
                or self.source._revision_id == NULL_REVISION):
1878
 
            raise AssertionError(
1879
 
                "revision {%s} is not stored in {%s}, but %s "
1880
 
                "can only be used for trees stored in the dirstate"
1881
 
                % (self.source._revision_id, self.target, self.iter_changes))
 
1799
        assert (self.source._revision_id in parent_ids
 
1800
                or self.source._revision_id == NULL_REVISION), \
 
1801
                "revision {%s} is not stored in {%s}, but %s " \
 
1802
                "can only be used for trees stored in the dirstate" \
 
1803
                % (self.source._revision_id, self.target, self.iter_changes)
1882
1804
        target_index = 0
1883
1805
        if self.source._revision_id == NULL_REVISION:
1884
1806
            source_index = None
1885
1807
            indices = (target_index,)
1886
1808
        else:
1887
 
            if not (self.source._revision_id in parent_ids):
1888
 
                raise AssertionError(
1889
 
                    "Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
1890
 
                    self.source._revision_id, parent_ids))
 
1809
            assert (self.source._revision_id in parent_ids), \
 
1810
                "Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
 
1811
                self.source._revision_id, parent_ids)
1891
1812
            source_index = 1 + parent_ids.index(self.source._revision_id)
1892
1813
            indices = (source_index, target_index)
1893
1814
        # -- make all specific_files utf8 --
1894
1815
        if specific_files:
1895
1816
            specific_files_utf8 = set()
1896
1817
            for path in specific_files:
1897
 
                # Note, if there are many specific files, using cache_utf8
1898
 
                # would be good here.
1899
1818
                specific_files_utf8.add(path.encode('utf8'))
1900
1819
            specific_files = specific_files_utf8
1901
1820
        else:
1902
1821
            specific_files = set([''])
1903
1822
        # -- specific_files is now a utf8 path set --
1904
 
        search_specific_files = set()
1905
1823
        # -- get the state object and prepare it.
1906
1824
        state = self.target.current_dirstate()
1907
1825
        state._read_dirblocks_if_needed()
 
1826
        def _entries_for_path(path):
 
1827
            """Return a list with all the entries that match path for all ids.
 
1828
            """
 
1829
            dirname, basename = os.path.split(path)
 
1830
            key = (dirname, basename, '')
 
1831
            block_index, present = state._find_block_index_from_key(key)
 
1832
            if not present:
 
1833
                # the block which should contain path is absent.
 
1834
                return []
 
1835
            result = []
 
1836
            block = state._dirblocks[block_index][1]
 
1837
            entry_index, _ = state._find_entry_index(key, block)
 
1838
            # we may need to look at multiple entries at this path: walk while the specific_files match.
 
1839
            while (entry_index < len(block) and
 
1840
                block[entry_index][0][0:2] == key[0:2]):
 
1841
                result.append(block[entry_index])
 
1842
                entry_index += 1
 
1843
            return result
1908
1844
        if require_versioned:
1909
1845
            # -- check all supplied paths are versioned in a search tree. --
1910
1846
            all_versioned = True
1911
1847
            for path in specific_files:
1912
 
                path_entries = state._entries_for_path(path)
 
1848
                path_entries = _entries_for_path(path)
1913
1849
                if not path_entries:
1914
1850
                    # this specified path is not present at all: error
1915
1851
                    all_versioned = False
1931
1867
            if not all_versioned:
1932
1868
                raise errors.PathsNotVersionedError(specific_files)
1933
1869
        # -- remove redundancy in supplied specific_files to prevent over-scanning --
 
1870
        search_specific_files = set()
1934
1871
        for path in specific_files:
1935
1872
            other_specific_files = specific_files.difference(set([path]))
1936
1873
            if not osutils.is_inside_any(other_specific_files, path):
1937
1874
                # this is a top level path, we must check it.
1938
1875
                search_specific_files.add(path)
 
1876
        # sketch: 
 
1877
        # compare source_index and target_index at or under each element of search_specific_files.
 
1878
        # follow the following comparison table. Note that we only want to do diff operations when
 
1879
        # the target is fdl because thats when the walkdirs logic will have exposed the pathinfo 
 
1880
        # for the target.
 
1881
        # cases:
 
1882
        # 
 
1883
        # Source | Target | disk | action
 
1884
        #   r    | fdlt   |      | add source to search, add id path move and perform
 
1885
        #        |        |      | diff check on source-target
 
1886
        #   r    | fdlt   |  a   | dangling file that was present in the basis. 
 
1887
        #        |        |      | ???
 
1888
        #   r    |  a     |      | add source to search
 
1889
        #   r    |  a     |  a   | 
 
1890
        #   r    |  r     |      | this path is present in a non-examined tree, skip.
 
1891
        #   r    |  r     |  a   | this path is present in a non-examined tree, skip.
 
1892
        #   a    | fdlt   |      | add new id
 
1893
        #   a    | fdlt   |  a   | dangling locally added file, skip
 
1894
        #   a    |  a     |      | not present in either tree, skip
 
1895
        #   a    |  a     |  a   | not present in any tree, skip
 
1896
        #   a    |  r     |      | not present in either tree at this path, skip as it
 
1897
        #        |        |      | may not be selected by the users list of paths.
 
1898
        #   a    |  r     |  a   | not present in either tree at this path, skip as it
 
1899
        #        |        |      | may not be selected by the users list of paths.
 
1900
        #  fdlt  | fdlt   |      | content in both: diff them
 
1901
        #  fdlt  | fdlt   |  a   | deleted locally, but not unversioned - show as deleted ?
 
1902
        #  fdlt  |  a     |      | unversioned: output deleted id for now
 
1903
        #  fdlt  |  a     |  a   | unversioned and deleted: output deleted id
 
1904
        #  fdlt  |  r     |      | relocated in this tree, so add target to search.
 
1905
        #        |        |      | Dont diff, we will see an r,fd; pair when we reach
 
1906
        #        |        |      | this id at the other path.
 
1907
        #  fdlt  |  r     |  a   | relocated in this tree, so add target to search.
 
1908
        #        |        |      | Dont diff, we will see an r,fd; pair when we reach
 
1909
        #        |        |      | this id at the other path.
 
1910
 
 
1911
        # for all search_indexs in each path at or under each element of
 
1912
        # search_specific_files, if the detail is relocated: add the id, and add the
 
1913
        # relocated path as one to search if its not searched already. If the
 
1914
        # detail is not relocated, add the id.
 
1915
        searched_specific_files = set()
 
1916
        NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
 
1917
        # Using a list so that we can access the values and change them in
 
1918
        # nested scope. Each one is [path, file_id, entry]
 
1919
        last_source_parent = [None, None]
 
1920
        last_target_parent = [None, None]
1939
1921
 
1940
1922
        use_filesystem_for_exec = (sys.platform != 'win32')
1941
 
        iter_changes = self.target._iter_changes(include_unchanged,
1942
 
            use_filesystem_for_exec, search_specific_files, state,
1943
 
            source_index, target_index, want_unversioned, self.target)
1944
 
        return iter_changes.iter_changes()
 
1923
 
 
1924
        # Just a sentry, so that _process_entry can say that this
 
1925
        # record is handled, but isn't interesting to process (unchanged)
 
1926
        uninteresting = object()
 
1927
 
 
1928
 
 
1929
        old_dirname_to_file_id = {}
 
1930
        new_dirname_to_file_id = {}
 
1931
        # TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
 
1932
        #       keeping a cache of directories that we have seen.
 
1933
 
 
1934
        def _process_entry(entry, path_info):
 
1935
            """Compare an entry and real disk to generate delta information.
 
1936
 
 
1937
            :param path_info: top_relpath, basename, kind, lstat, abspath for
 
1938
                the path of entry. If None, then the path is considered absent.
 
1939
                (Perhaps we should pass in a concrete entry for this ?)
 
1940
                Basename is returned as a utf8 string because we expect this
 
1941
                tuple will be ignored, and don't want to take the time to
 
1942
                decode.
 
1943
            :return: None if these don't match
 
1944
                     A tuple of information about the change, or
 
1945
                     the object 'uninteresting' if these match, but are
 
1946
                     basically identical.
 
1947
            """
 
1948
            if source_index is None:
 
1949
                source_details = NULL_PARENT_DETAILS
 
1950
            else:
 
1951
                source_details = entry[1][source_index]
 
1952
            target_details = entry[1][target_index]
 
1953
            target_minikind = target_details[0]
 
1954
            if path_info is not None and target_minikind in 'fdlt':
 
1955
                assert target_index == 0
 
1956
                link_or_sha1 = state.update_entry(entry, abspath=path_info[4],
 
1957
                                                  stat_value=path_info[3])
 
1958
                # The entry may have been modified by update_entry
 
1959
                target_details = entry[1][target_index]
 
1960
                target_minikind = target_details[0]
 
1961
            else:
 
1962
                link_or_sha1 = None
 
1963
            file_id = entry[0][2]
 
1964
            source_minikind = source_details[0]
 
1965
            if source_minikind in 'fdltr' and target_minikind in 'fdlt':
 
1966
                # claimed content in both: diff
 
1967
                #   r    | fdlt   |      | add source to search, add id path move and perform
 
1968
                #        |        |      | diff check on source-target
 
1969
                #   r    | fdlt   |  a   | dangling file that was present in the basis.
 
1970
                #        |        |      | ???
 
1971
                if source_minikind in 'r':
 
1972
                    # add the source to the search path to find any children it
 
1973
                    # has.  TODO ? : only add if it is a container ?
 
1974
                    if not osutils.is_inside_any(searched_specific_files,
 
1975
                                                 source_details[1]):
 
1976
                        search_specific_files.add(source_details[1])
 
1977
                    # generate the old path; this is needed for stating later
 
1978
                    # as well.
 
1979
                    old_path = source_details[1]
 
1980
                    old_dirname, old_basename = os.path.split(old_path)
 
1981
                    path = pathjoin(entry[0][0], entry[0][1])
 
1982
                    old_entry = state._get_entry(source_index,
 
1983
                                                 path_utf8=old_path)
 
1984
                    # update the source details variable to be the real
 
1985
                    # location.
 
1986
                    if old_entry == (None, None):
 
1987
                        raise errors.CorruptDirstate(state._filename,
 
1988
                            "entry '%s/%s' is considered renamed from %r"
 
1989
                            " but source does not exist\n"
 
1990
                            "entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
 
1991
                    source_details = old_entry[1][source_index]
 
1992
                    source_minikind = source_details[0]
 
1993
                else:
 
1994
                    old_dirname = entry[0][0]
 
1995
                    old_basename = entry[0][1]
 
1996
                    old_path = path = None
 
1997
                if path_info is None:
 
1998
                    # the file is missing on disk, show as removed.
 
1999
                    content_change = True
 
2000
                    target_kind = None
 
2001
                    target_exec = False
 
2002
                else:
 
2003
                    # source and target are both versioned and disk file is present.
 
2004
                    target_kind = path_info[2]
 
2005
                    if target_kind == 'directory':
 
2006
                        if path is None:
 
2007
                            old_path = path = pathjoin(old_dirname, old_basename)
 
2008
                        new_dirname_to_file_id[path] = file_id
 
2009
                        if source_minikind != 'd':
 
2010
                            content_change = True
 
2011
                        else:
 
2012
                            # directories have no fingerprint
 
2013
                            content_change = False
 
2014
                        target_exec = False
 
2015
                    elif target_kind == 'file':
 
2016
                        if source_minikind != 'f':
 
2017
                            content_change = True
 
2018
                        else:
 
2019
                            # We could check the size, but we already have the
 
2020
                            # sha1 hash.
 
2021
                            content_change = (link_or_sha1 != source_details[1])
 
2022
                        # Target details is updated at update_entry time
 
2023
                        if use_filesystem_for_exec:
 
2024
                            # We don't need S_ISREG here, because we are sure
 
2025
                            # we are dealing with a file.
 
2026
                            target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
 
2027
                        else:
 
2028
                            target_exec = target_details[3]
 
2029
                    elif target_kind == 'symlink':
 
2030
                        if source_minikind != 'l':
 
2031
                            content_change = True
 
2032
                        else:
 
2033
                            content_change = (link_or_sha1 != source_details[1])
 
2034
                        target_exec = False
 
2035
                    elif target_kind == 'tree-reference':
 
2036
                        if source_minikind != 't':
 
2037
                            content_change = True
 
2038
                        else:
 
2039
                            content_change = False
 
2040
                        target_exec = False
 
2041
                    else:
 
2042
                        raise Exception, "unknown kind %s" % path_info[2]
 
2043
                if source_minikind == 'd':
 
2044
                    if path is None:
 
2045
                        old_path = path = pathjoin(old_dirname, old_basename)
 
2046
                    old_dirname_to_file_id[old_path] = file_id
 
2047
                # parent id is the entry for the path in the target tree
 
2048
                if old_dirname == last_source_parent[0]:
 
2049
                    source_parent_id = last_source_parent[1]
 
2050
                else:
 
2051
                    try:
 
2052
                        source_parent_id = old_dirname_to_file_id[old_dirname]
 
2053
                    except KeyError:
 
2054
                        source_parent_entry = state._get_entry(source_index,
 
2055
                                                               path_utf8=old_dirname)
 
2056
                        source_parent_id = source_parent_entry[0][2]
 
2057
                    if source_parent_id == entry[0][2]:
 
2058
                        # This is the root, so the parent is None
 
2059
                        source_parent_id = None
 
2060
                    else:
 
2061
                        last_source_parent[0] = old_dirname
 
2062
                        last_source_parent[1] = source_parent_id
 
2063
                new_dirname = entry[0][0]
 
2064
                if new_dirname == last_target_parent[0]:
 
2065
                    target_parent_id = last_target_parent[1]
 
2066
                else:
 
2067
                    try:
 
2068
                        target_parent_id = new_dirname_to_file_id[new_dirname]
 
2069
                    except KeyError:
 
2070
                        # TODO: We don't always need to do the lookup, because the
 
2071
                        #       parent entry will be the same as the source entry.
 
2072
                        target_parent_entry = state._get_entry(target_index,
 
2073
                                                               path_utf8=new_dirname)
 
2074
                        assert target_parent_entry != (None, None), (
 
2075
                            "Could not find target parent in wt: %s\nparent of: %s"
 
2076
                            % (new_dirname, entry))
 
2077
                        target_parent_id = target_parent_entry[0][2]
 
2078
                    if target_parent_id == entry[0][2]:
 
2079
                        # This is the root, so the parent is None
 
2080
                        target_parent_id = None
 
2081
                    else:
 
2082
                        last_target_parent[0] = new_dirname
 
2083
                        last_target_parent[1] = target_parent_id
 
2084
 
 
2085
                source_exec = source_details[3]
 
2086
                if (include_unchanged
 
2087
                    or content_change
 
2088
                    or source_parent_id != target_parent_id
 
2089
                    or old_basename != entry[0][1]
 
2090
                    or source_exec != target_exec
 
2091
                    ):
 
2092
                    if old_path is None:
 
2093
                        old_path = path = pathjoin(old_dirname, old_basename)
 
2094
                        old_path_u = utf8_decode(old_path)[0]
 
2095
                        path_u = old_path_u
 
2096
                    else:
 
2097
                        old_path_u = utf8_decode(old_path)[0]
 
2098
                        if old_path == path:
 
2099
                            path_u = old_path_u
 
2100
                        else:
 
2101
                            path_u = utf8_decode(path)[0]
 
2102
                    source_kind = _minikind_to_kind[source_minikind]
 
2103
                    return (entry[0][2],
 
2104
                           (old_path_u, path_u),
 
2105
                           content_change,
 
2106
                           (True, True),
 
2107
                           (source_parent_id, target_parent_id),
 
2108
                           (utf8_decode(old_basename)[0], utf8_decode(entry[0][1])[0]),
 
2109
                           (source_kind, target_kind),
 
2110
                           (source_exec, target_exec))
 
2111
                else:
 
2112
                    return uninteresting
 
2113
            elif source_minikind in 'a' and target_minikind in 'fdlt':
 
2114
                # looks like a new file
 
2115
                if path_info is not None:
 
2116
                    path = pathjoin(entry[0][0], entry[0][1])
 
2117
                    # parent id is the entry for the path in the target tree
 
2118
                    # TODO: these are the same for an entire directory: cache em.
 
2119
                    parent_id = state._get_entry(target_index,
 
2120
                                                 path_utf8=entry[0][0])[0][2]
 
2121
                    if parent_id == entry[0][2]:
 
2122
                        parent_id = None
 
2123
                    if use_filesystem_for_exec:
 
2124
                        # We need S_ISREG here, because we aren't sure if this
 
2125
                        # is a file or not.
 
2126
                        target_exec = bool(
 
2127
                            stat.S_ISREG(path_info[3].st_mode)
 
2128
                            and stat.S_IEXEC & path_info[3].st_mode)
 
2129
                    else:
 
2130
                        target_exec = target_details[3]
 
2131
                    return (entry[0][2],
 
2132
                           (None, utf8_decode(path)[0]),
 
2133
                           True,
 
2134
                           (False, True),
 
2135
                           (None, parent_id),
 
2136
                           (None, utf8_decode(entry[0][1])[0]),
 
2137
                           (None, path_info[2]),
 
2138
                           (None, target_exec))
 
2139
                else:
 
2140
                    # but its not on disk: we deliberately treat this as just
 
2141
                    # never-present. (Why ?! - RBC 20070224)
 
2142
                    pass
 
2143
            elif source_minikind in 'fdlt' and target_minikind in 'a':
 
2144
                # unversioned, possibly, or possibly not deleted: we dont care.
 
2145
                # if its still on disk, *and* theres no other entry at this
 
2146
                # path [we dont know this in this routine at the moment -
 
2147
                # perhaps we should change this - then it would be an unknown.
 
2148
                old_path = pathjoin(entry[0][0], entry[0][1])
 
2149
                # parent id is the entry for the path in the target tree
 
2150
                parent_id = state._get_entry(source_index, path_utf8=entry[0][0])[0][2]
 
2151
                if parent_id == entry[0][2]:
 
2152
                    parent_id = None
 
2153
                return (entry[0][2],
 
2154
                       (utf8_decode(old_path)[0], None),
 
2155
                       True,
 
2156
                       (True, False),
 
2157
                       (parent_id, None),
 
2158
                       (utf8_decode(entry[0][1])[0], None),
 
2159
                       (_minikind_to_kind[source_minikind], None),
 
2160
                       (source_details[3], None))
 
2161
            elif source_minikind in 'fdlt' and target_minikind in 'r':
 
2162
                # a rename; could be a true rename, or a rename inherited from
 
2163
                # a renamed parent. TODO: handle this efficiently. Its not
 
2164
                # common case to rename dirs though, so a correct but slow
 
2165
                # implementation will do.
 
2166
                if not osutils.is_inside_any(searched_specific_files, target_details[1]):
 
2167
                    search_specific_files.add(target_details[1])
 
2168
            elif source_minikind in 'ra' and target_minikind in 'ra':
 
2169
                # neither of the selected trees contain this file,
 
2170
                # so skip over it. This is not currently directly tested, but
 
2171
                # is indirectly via test_too_much.TestCommands.test_conflicts.
 
2172
                pass
 
2173
            else:
 
2174
                raise AssertionError("don't know how to compare "
 
2175
                    "source_minikind=%r, target_minikind=%r"
 
2176
                    % (source_minikind, target_minikind))
 
2177
                ## import pdb;pdb.set_trace()
 
2178
            return None
 
2179
 
 
2180
        while search_specific_files:
 
2181
            # TODO: the pending list should be lexically sorted?  the
 
2182
            # interface doesn't require it.
 
2183
            current_root = search_specific_files.pop()
 
2184
            current_root_unicode = current_root.decode('utf8')
 
2185
            searched_specific_files.add(current_root)
 
2186
            # process the entries for this containing directory: the rest will be
 
2187
            # found by their parents recursively.
 
2188
            root_entries = _entries_for_path(current_root)
 
2189
            root_abspath = self.target.abspath(current_root_unicode)
 
2190
            try:
 
2191
                root_stat = os.lstat(root_abspath)
 
2192
            except OSError, e:
 
2193
                if e.errno == errno.ENOENT:
 
2194
                    # the path does not exist: let _process_entry know that.
 
2195
                    root_dir_info = None
 
2196
                else:
 
2197
                    # some other random error: hand it up.
 
2198
                    raise
 
2199
            else:
 
2200
                root_dir_info = ('', current_root,
 
2201
                    osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
 
2202
                    root_abspath)
 
2203
                if root_dir_info[2] == 'directory':
 
2204
                    if self.target._directory_is_tree_reference(
 
2205
                        current_root.decode('utf8')):
 
2206
                        root_dir_info = root_dir_info[:2] + \
 
2207
                            ('tree-reference',) + root_dir_info[3:]
 
2208
 
 
2209
            if not root_entries and not root_dir_info:
 
2210
                # this specified path is not present at all, skip it.
 
2211
                continue
 
2212
            path_handled = False
 
2213
            for entry in root_entries:
 
2214
                result = _process_entry(entry, root_dir_info)
 
2215
                if result is not None:
 
2216
                    path_handled = True
 
2217
                    if result is not uninteresting:
 
2218
                        yield result
 
2219
            if want_unversioned and not path_handled and root_dir_info:
 
2220
                new_executable = bool(
 
2221
                    stat.S_ISREG(root_dir_info[3].st_mode)
 
2222
                    and stat.S_IEXEC & root_dir_info[3].st_mode)
 
2223
                yield (None,
 
2224
                       (None, current_root_unicode),
 
2225
                       True,
 
2226
                       (False, False),
 
2227
                       (None, None),
 
2228
                       (None, splitpath(current_root_unicode)[-1]),
 
2229
                       (None, root_dir_info[2]),
 
2230
                       (None, new_executable)
 
2231
                      )
 
2232
            initial_key = (current_root, '', '')
 
2233
            block_index, _ = state._find_block_index_from_key(initial_key)
 
2234
            if block_index == 0:
 
2235
                # we have processed the total root already, but because the
 
2236
                # initial key matched it we should skip it here.
 
2237
                block_index +=1
 
2238
            if root_dir_info and root_dir_info[2] == 'tree-reference':
 
2239
                current_dir_info = None
 
2240
            else:
 
2241
                dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
 
2242
                try:
 
2243
                    current_dir_info = dir_iterator.next()
 
2244
                except OSError, e:
 
2245
                    # on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
 
2246
                    # python 2.5 has e.errno == EINVAL,
 
2247
                    #            and e.winerror == ERROR_DIRECTORY
 
2248
                    e_winerror = getattr(e, 'winerror', None)
 
2249
                    win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
 
2250
                    # there may be directories in the inventory even though
 
2251
                    # this path is not a file on disk: so mark it as end of
 
2252
                    # iterator
 
2253
                    if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
 
2254
                        current_dir_info = None
 
2255
                    elif (sys.platform == 'win32'
 
2256
                          and (e.errno in win_errors
 
2257
                               or e_winerror in win_errors)):
 
2258
                        current_dir_info = None
 
2259
                    else:
 
2260
                        raise
 
2261
                else:
 
2262
                    if current_dir_info[0][0] == '':
 
2263
                        # remove .bzr from iteration
 
2264
                        bzr_index = bisect_left(current_dir_info[1], ('.bzr',))
 
2265
                        assert current_dir_info[1][bzr_index][0] == '.bzr'
 
2266
                        del current_dir_info[1][bzr_index]
 
2267
            # walk until both the directory listing and the versioned metadata
 
2268
            # are exhausted. 
 
2269
            if (block_index < len(state._dirblocks) and
 
2270
                osutils.is_inside(current_root, state._dirblocks[block_index][0])):
 
2271
                current_block = state._dirblocks[block_index]
 
2272
            else:
 
2273
                current_block = None
 
2274
            while (current_dir_info is not None or
 
2275
                   current_block is not None):
 
2276
                if (current_dir_info and current_block
 
2277
                    and current_dir_info[0][0] != current_block[0]):
 
2278
                    if cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
 
2279
                        # filesystem data refers to paths not covered by the dirblock.
 
2280
                        # this has two possibilities:
 
2281
                        # A) it is versioned but empty, so there is no block for it
 
2282
                        # B) it is not versioned.
 
2283
 
 
2284
                        # if (A) then we need to recurse into it to check for
 
2285
                        # new unknown files or directories.
 
2286
                        # if (B) then we should ignore it, because we don't
 
2287
                        # recurse into unknown directories.
 
2288
                        path_index = 0
 
2289
                        while path_index < len(current_dir_info[1]):
 
2290
                                current_path_info = current_dir_info[1][path_index]
 
2291
                                if want_unversioned:
 
2292
                                    if current_path_info[2] == 'directory':
 
2293
                                        if self.target._directory_is_tree_reference(
 
2294
                                            current_path_info[0].decode('utf8')):
 
2295
                                            current_path_info = current_path_info[:2] + \
 
2296
                                                ('tree-reference',) + current_path_info[3:]
 
2297
                                    new_executable = bool(
 
2298
                                        stat.S_ISREG(current_path_info[3].st_mode)
 
2299
                                        and stat.S_IEXEC & current_path_info[3].st_mode)
 
2300
                                    yield (None,
 
2301
                                        (None, utf8_decode(current_path_info[0])[0]),
 
2302
                                        True,
 
2303
                                        (False, False),
 
2304
                                        (None, None),
 
2305
                                        (None, utf8_decode(current_path_info[1])[0]),
 
2306
                                        (None, current_path_info[2]),
 
2307
                                        (None, new_executable))
 
2308
                                # dont descend into this unversioned path if it is
 
2309
                                # a dir
 
2310
                                if current_path_info[2] in ('directory',
 
2311
                                                            'tree-reference'):
 
2312
                                    del current_dir_info[1][path_index]
 
2313
                                    path_index -= 1
 
2314
                                path_index += 1
 
2315
 
 
2316
                        # This dir info has been handled, go to the next
 
2317
                        try:
 
2318
                            current_dir_info = dir_iterator.next()
 
2319
                        except StopIteration:
 
2320
                            current_dir_info = None
 
2321
                    else:
 
2322
                        # We have a dirblock entry for this location, but there
 
2323
                        # is no filesystem path for this. This is most likely
 
2324
                        # because a directory was removed from the disk.
 
2325
                        # We don't have to report the missing directory,
 
2326
                        # because that should have already been handled, but we
 
2327
                        # need to handle all of the files that are contained
 
2328
                        # within.
 
2329
                        for current_entry in current_block[1]:
 
2330
                            # entry referring to file not present on disk.
 
2331
                            # advance the entry only, after processing.
 
2332
                            result = _process_entry(current_entry, None)
 
2333
                            if result is not None:
 
2334
                                if result is not uninteresting:
 
2335
                                    yield result
 
2336
                        block_index +=1
 
2337
                        if (block_index < len(state._dirblocks) and
 
2338
                            osutils.is_inside(current_root,
 
2339
                                              state._dirblocks[block_index][0])):
 
2340
                            current_block = state._dirblocks[block_index]
 
2341
                        else:
 
2342
                            current_block = None
 
2343
                    continue
 
2344
                entry_index = 0
 
2345
                if current_block and entry_index < len(current_block[1]):
 
2346
                    current_entry = current_block[1][entry_index]
 
2347
                else:
 
2348
                    current_entry = None
 
2349
                advance_entry = True
 
2350
                path_index = 0
 
2351
                if current_dir_info and path_index < len(current_dir_info[1]):
 
2352
                    current_path_info = current_dir_info[1][path_index]
 
2353
                    if current_path_info[2] == 'directory':
 
2354
                        if self.target._directory_is_tree_reference(
 
2355
                            current_path_info[0].decode('utf8')):
 
2356
                            current_path_info = current_path_info[:2] + \
 
2357
                                ('tree-reference',) + current_path_info[3:]
 
2358
                else:
 
2359
                    current_path_info = None
 
2360
                advance_path = True
 
2361
                path_handled = False
 
2362
                while (current_entry is not None or
 
2363
                    current_path_info is not None):
 
2364
                    if current_entry is None:
 
2365
                        # the check for path_handled when the path is adnvaced
 
2366
                        # will yield this path if needed.
 
2367
                        pass
 
2368
                    elif current_path_info is None:
 
2369
                        # no path is fine: the per entry code will handle it.
 
2370
                        result = _process_entry(current_entry, current_path_info)
 
2371
                        if result is not None:
 
2372
                            if result is not uninteresting:
 
2373
                                yield result
 
2374
                    elif (current_entry[0][1] != current_path_info[1]
 
2375
                          or current_entry[1][target_index][0] in 'ar'):
 
2376
                        # The current path on disk doesn't match the dirblock
 
2377
                        # record. Either the dirblock is marked as absent, or
 
2378
                        # the file on disk is not present at all in the
 
2379
                        # dirblock. Either way, report about the dirblock
 
2380
                        # entry, and let other code handle the filesystem one.
 
2381
 
 
2382
                        # Compare the basename for these files to determine
 
2383
                        # which comes first
 
2384
                        if current_path_info[1] < current_entry[0][1]:
 
2385
                            # extra file on disk: pass for now, but only
 
2386
                            # increment the path, not the entry
 
2387
                            advance_entry = False
 
2388
                        else:
 
2389
                            # entry referring to file not present on disk.
 
2390
                            # advance the entry only, after processing.
 
2391
                            result = _process_entry(current_entry, None)
 
2392
                            if result is not None:
 
2393
                                if result is not uninteresting:
 
2394
                                    yield result
 
2395
                            advance_path = False
 
2396
                    else:
 
2397
                        result = _process_entry(current_entry, current_path_info)
 
2398
                        if result is not None:
 
2399
                            path_handled = True
 
2400
                            if result is not uninteresting:
 
2401
                                yield result
 
2402
                    if advance_entry and current_entry is not None:
 
2403
                        entry_index += 1
 
2404
                        if entry_index < len(current_block[1]):
 
2405
                            current_entry = current_block[1][entry_index]
 
2406
                        else:
 
2407
                            current_entry = None
 
2408
                    else:
 
2409
                        advance_entry = True # reset the advance flaga
 
2410
                    if advance_path and current_path_info is not None:
 
2411
                        if not path_handled:
 
2412
                            # unversioned in all regards
 
2413
                            if want_unversioned:
 
2414
                                new_executable = bool(
 
2415
                                    stat.S_ISREG(current_path_info[3].st_mode)
 
2416
                                    and stat.S_IEXEC & current_path_info[3].st_mode)
 
2417
                                yield (None,
 
2418
                                    (None, utf8_decode(current_path_info[0])[0]),
 
2419
                                    True,
 
2420
                                    (False, False),
 
2421
                                    (None, None),
 
2422
                                    (None, utf8_decode(current_path_info[1])[0]),
 
2423
                                    (None, current_path_info[2]),
 
2424
                                    (None, new_executable))
 
2425
                            # dont descend into this unversioned path if it is
 
2426
                            # a dir
 
2427
                            if current_path_info[2] in ('directory'):
 
2428
                                del current_dir_info[1][path_index]
 
2429
                                path_index -= 1
 
2430
                        # dont descend the disk iterator into any tree 
 
2431
                        # paths.
 
2432
                        if current_path_info[2] == 'tree-reference':
 
2433
                            del current_dir_info[1][path_index]
 
2434
                            path_index -= 1
 
2435
                        path_index += 1
 
2436
                        if path_index < len(current_dir_info[1]):
 
2437
                            current_path_info = current_dir_info[1][path_index]
 
2438
                            if current_path_info[2] == 'directory':
 
2439
                                if self.target._directory_is_tree_reference(
 
2440
                                    current_path_info[0].decode('utf8')):
 
2441
                                    current_path_info = current_path_info[:2] + \
 
2442
                                        ('tree-reference',) + current_path_info[3:]
 
2443
                        else:
 
2444
                            current_path_info = None
 
2445
                        path_handled = False
 
2446
                    else:
 
2447
                        advance_path = True # reset the advance flagg.
 
2448
                if current_block is not None:
 
2449
                    block_index += 1
 
2450
                    if (block_index < len(state._dirblocks) and
 
2451
                        osutils.is_inside(current_root, state._dirblocks[block_index][0])):
 
2452
                        current_block = state._dirblocks[block_index]
 
2453
                    else:
 
2454
                        current_block = None
 
2455
                if current_dir_info is not None:
 
2456
                    try:
 
2457
                        current_dir_info = dir_iterator.next()
 
2458
                    except StopIteration:
 
2459
                        current_dir_info = None
 
2460
 
1945
2461
 
1946
2462
    @staticmethod
1947
2463
    def is_compatible(source, target):
2003
2519
 
2004
2520
    def update_format(self, tree):
2005
2521
        """Change the format marker."""
2006
 
        tree._transport.put_bytes('format',
2007
 
            self.target_format.get_format_string(),
2008
 
            mode=tree.bzrdir._get_file_mode())
 
2522
        tree._control_files.put_utf8('format',
 
2523
            self.target_format.get_format_string())