~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree_4.py

Crufty but existing _iter_changes implementation for WorkingTreeFormat4.

Show diffs side-by-side

added added

removed removed

Lines of Context:
43
43
    bzrdir,
44
44
    cache_utf8,
45
45
    conflicts as _mod_conflicts,
 
46
    delta,
46
47
    dirstate,
47
48
    errors,
48
49
    generate_ids,
179
180
            state.add(f, file_id, kind, None, '')
180
181
        self._dirty = True
181
182
 
 
183
    def break_lock(self):
 
184
        """Break a lock if one is present from another instance.
 
185
 
 
186
        Uses the ui factory to ask for confirmation if the lock may be from
 
187
        an active process.
 
188
 
 
189
        This will probe the repository for its lock as well.
 
190
        """
 
191
        # if the dirstate is locked by an active process, reject the break lock
 
192
        # call.
 
193
        try:
 
194
            if self._dirstate is None:
 
195
                clear = True
 
196
            else:
 
197
                clear = False
 
198
            state = self._current_dirstate()
 
199
            if state._lock_token is not None:
 
200
                # we already have it locked. sheese, cant break our own lock.
 
201
                raise errors.LockActive(self.basedir)
 
202
            else:
 
203
                try:
 
204
                    # try for a write lock - need permission to get one anyhow
 
205
                    # to break locks.
 
206
                    state.lock_write()
 
207
                except errors.LockContention:
 
208
                    # oslocks fail when a process is still live: fail.
 
209
                    # TODO: get the locked lockdir info and give to the user to
 
210
                    # assist in debugging.
 
211
                    raise errors.LockActive(self.basedir)
 
212
                else:
 
213
                    state.unlock()
 
214
        finally:
 
215
            if clear:
 
216
                self._dirstate = None
 
217
        self._control_files.break_lock()
 
218
        self.branch.break_lock()
 
219
 
182
220
    def current_dirstate(self):
183
221
        """Return the current dirstate object. 
184
222
 
186
224
        testing.
187
225
 
188
226
        :raises errors.NotWriteLocked: when not in a lock. 
189
 
            XXX: This should probably be errors.NotLocked.
190
227
        """
191
228
        if not self._control_files._lock_count:
192
229
            raise errors.ObjectNotLocked(self)
 
230
        return self._current_dirstate()
 
231
 
 
232
    def _current_dirstate(self):
 
233
        """Internal function that does not check lock status.
 
234
        
 
235
        This is needed for break_lock which also needs the dirstate.
 
236
        """
193
237
        if self._dirstate is not None:
194
238
            return self._dirstate
195
239
        local_path = self.bzrdir.get_workingtree_transport(None
678
722
                entry_index += 1
679
723
            return result
680
724
        if require_versioned:
681
 
            # -- check all supplied paths are versioned in all search trees. --
 
725
            # -- check all supplied paths are versioned in a search tree. --
682
726
            all_versioned = True
683
727
            for path in paths:
684
728
                path_entries = _entries_for_path(path)
1332
1376
    _matching_to_tree_format = WorkingTreeFormat4()
1333
1377
    _test_mutable_trees_to_test_trees = make_source_parent_tree
1334
1378
 
1335
 
    @needs_read_lock
1336
 
    def compare(self, want_unchanged=False, specific_files=None,
1337
 
        extra_trees=None, require_versioned=False, include_root=False):
 
1379
    def _iter_changes(self, include_unchanged=False,
 
1380
                      specific_files=None, pb=None, extra_trees=[],
 
1381
                      require_versioned=True):
1338
1382
        """Return the changes from source to target.
1339
1383
 
1340
 
        :return: A TreeDelta.
 
1384
        :return: An iterator that yields tuples. See InterTree._iter_changes
 
1385
            for details.
1341
1386
        :param specific_files: An optional list of file paths to restrict the
1342
1387
            comparison to. When mapping filenames to ids, all matches in all
1343
1388
            trees (including optional extra_trees) are used, and all children of
1344
1389
            matched directories are included.
1345
 
        :param want_unchanged: An optional boolean requesting the inclusion of
 
1390
        :param include_unchanged: An optional boolean requesting the inclusion of
1346
1391
            unchanged entries in the result.
1347
1392
        :param extra_trees: An optional list of additional trees to use when
1348
1393
            mapping the contents of specific_files (paths) to file_ids.
1349
 
        :param require_versioned: An optional boolean (defaults to False). When
1350
 
            supplied and True all the 'specific_files' must be versioned, or
1351
 
            a PathsNotVersionedError will be thrown.
 
1394
        :param require_versioned: If True, all files in specific_files must be
 
1395
            versioned in one of source, target, extra_trees or
 
1396
            PathsNotVersionedError is raised.
1352
1397
        """
1353
1398
        # NB: show_status depends on being able to pass in non-versioned files
1354
1399
        # and report them as unknown
1355
 
        trees = (self.source,)
1356
 
        if extra_trees is not None:
1357
 
            trees = trees + tuple(extra_trees)
1358
 
        # target is usually the newer tree:
1359
 
        specific_file_ids = self.target.paths2ids(specific_files, trees,
1360
 
            require_versioned=require_versioned)
1361
 
        from bzrlib import delta
1362
 
        if specific_files and not specific_file_ids:
1363
 
            # All files are unversioned, so just return an empty delta
1364
 
            # _compare_trees would think we want a complete delta
1365
 
            return delta.TreeDelta()
1366
 
        return delta._compare_trees(self.source, self.target, want_unchanged,
1367
 
            specific_file_ids, include_root)
 
1400
            # TODO: handle extra trees in the dirstate.
 
1401
        if (extra_trees or
 
1402
            # TODO: handle specific files
 
1403
            specific_files):
 
1404
            for f in super(InterDirStateTree, self)._iter_changes(
 
1405
                include_unchanged, specific_files, pb, extra_trees,
 
1406
                require_versioned):
 
1407
                yield f
 
1408
            return
 
1409
        assert (self.source._revision_id in self.target.get_parent_ids())
 
1410
        parents = self.target.get_parent_ids()
 
1411
        target_index = 0
 
1412
        source_index = 1 + parents.index(self.source._revision_id)
 
1413
        # -- make all specific_files utf8 --
 
1414
        if specific_files:
 
1415
            specific_files_utf8 = set()
 
1416
            for path in specific_files:
 
1417
                specific_files_utf8.add(path.encode('utf8'))
 
1418
            specific_files = specific_files_utf8
 
1419
        else:
 
1420
            specific_files = set([''])
 
1421
        # -- specific_files is now a utf8 path set --
 
1422
        # -- get the state object and prepare it.
 
1423
        state = self.target.current_dirstate()
 
1424
        state._read_dirblocks_if_needed()
 
1425
        def _entries_for_path(path):
 
1426
            """Return a list with all the entries that match path for all ids.
 
1427
            """
 
1428
            dirname, basename = os.path.split(path)
 
1429
            key = (dirname, basename, '')
 
1430
            block_index, present = state._find_block_index_from_key(key)
 
1431
            if not present:
 
1432
                # the block which should contain path is absent.
 
1433
                return []
 
1434
            result = []
 
1435
            block = state._dirblocks[block_index][1]
 
1436
            entry_index, _ = state._find_entry_index(key, block)
 
1437
            # we may need to look at multiple entries at this path: walk while the specific_files match.
 
1438
            while (entry_index < len(block) and
 
1439
                block[entry_index][0][0:2] == key[0:2]):
 
1440
                result.append(block[entry_index])
 
1441
                entry_index += 1
 
1442
            return result
 
1443
        if require_versioned:
 
1444
            # -- check all supplied paths are versioned in a search tree. --
 
1445
            all_versioned = True
 
1446
            for path in specific_files:
 
1447
                path = path.encode('utf8')
 
1448
                path_entries = _entries_for_path(path)
 
1449
                if not path_entries:
 
1450
                    # this specified path is not present at all: error
 
1451
                    all_versioned = False
 
1452
                    break
 
1453
                found_versioned = False
 
1454
                # for each id at this path
 
1455
                for entry in path_entries:
 
1456
                    # for each tree.
 
1457
                    for index in source_index, target_index:
 
1458
                        if entry[1][index][0] != 'a': # absent
 
1459
                            found_versioned = True
 
1460
                            # all good: found a versioned cell
 
1461
                            break
 
1462
                if not found_versioned:
 
1463
                    # none of the indexes was not 'absent' at all ids for this
 
1464
                    # path.
 
1465
                    all_versioned = False
 
1466
                    break
 
1467
            if not all_versioned:
 
1468
                raise errors.PathsNotVersionedError(paths)
 
1469
        # -- remove redundancy in supplied specific_files to prevent over-scanning --
 
1470
        search_specific_files = set()
 
1471
        for path in specific_files:
 
1472
            other_specific_files = specific_files.difference(set([path]))
 
1473
            if not osutils.is_inside_any(other_specific_files, path):
 
1474
                # this is a top level path, we must check it.
 
1475
                search_specific_files.add(path)
 
1476
        # sketch: 
 
1477
        # compare source_index and target_index at or under each element of search_specific_files.
 
1478
        # follow the following comparison table. Note that we only want to do diff operations when
 
1479
        # the target is fdl because thats when the walkdirs logic will have exposed the pathinfo 
 
1480
        # for the target.
 
1481
        # cases:
 
1482
        # 
 
1483
        # Source | Target | disk | action
 
1484
        #   r    | fdl    |      | add source to search, add id path move and perform
 
1485
        #        |        |      | diff check on source-target
 
1486
        #   r    | fdl    |  a   | dangling file that was present in the basis. 
 
1487
        #        |        |      | ???
 
1488
        #   r    |  a     |      | add source to search
 
1489
        #   r    |  a     |  a   | 
 
1490
        #   r    |  r     |      | this path is present in a non-examined tree, skip.
 
1491
        #   r    |  r     |  a   | this path is present in a non-examined tree, skip.
 
1492
        #   a    | fdl    |      | add new id
 
1493
        #   a    | fdl    |  a   | dangling locally added file, skip
 
1494
        #   a    |  a     |      | not present in either tree, skip
 
1495
        #   a    |  a     |  a   | not present in any tree, skip
 
1496
        #   a    |  r     |      | not present in either tree at this path, skip as it
 
1497
        #        |        |      | may not be selected by the users list of paths.
 
1498
        #   a    |  r     |  a   | not present in either tree at this path, skip as it
 
1499
        #        |        |      | may not be selected by the users list of paths.
 
1500
        #  fdl   | fdl    |      | content in both: diff them
 
1501
        #  fdl   | fdl    |  a   | deleted locally, but not unversioned - show as deleted ?
 
1502
        #  fdl   |  a     |      | unversioned: output deleted id for now
 
1503
        #  fdl   |  a     |  a   | unversioned and deleted: output deleted id
 
1504
        #  fdl   |  r     |      | relocated in this tree, so add target to search.
 
1505
        #        |        |      | Dont diff, we will see an r,fd; pair when we reach
 
1506
        #        |        |      | this id at the other path.
 
1507
        #  fdl   |  r     |  a   | relocated in this tree, so add target to search.
 
1508
        #        |        |      | Dont diff, we will see an r,fd; pair when we reach
 
1509
        #        |        |      | this id at the other path.
 
1510
 
 
1511
        # for all search_indexs in each path at or under each element of
 
1512
        # search_specific_files, if the detail is relocated: add the id, and add the
 
1513
        # relocated path as one to search if its not searched already. If the
 
1514
        # detail is not relocated, add the id.
 
1515
        searched_specific_files = set()
 
1516
        def _process_entry(entry, path_info):
 
1517
            """Compare an entry and real disk to generate delta information.
 
1518
 
 
1519
            :param path_info: top_relpath, basename, kind, lstat, abspath for
 
1520
                the path of entry. If None, then the path is considered absent.
 
1521
                (Perhaps we should pass in a concrete entry for this ?)
 
1522
            """
 
1523
            # TODO: when a parent has been renamed, dont emit path renames for children,
 
1524
            source_details = entry[1][source_index]
 
1525
            target_details = entry[1][target_index]
 
1526
            if source_details[0] in 'rfdl' and target_details[0] in 'fdl':
 
1527
                # claimed content in both: diff
 
1528
                #   r    | fdl    |      | add source to search, add id path move and perform
 
1529
                #        |        |      | diff check on source-target
 
1530
                #   r    | fdl    |  a   | dangling file that was present in the basis. 
 
1531
                #        |        |      | ???
 
1532
                if source_details[0] in 'r':
 
1533
                    # add the source to the search path to find any children it
 
1534
                    # has.  TODO ? : only add if it is a container ?
 
1535
                    if not osutils.is_inside_any(searched_specific_files, source_details[1]):
 
1536
                        search_specific_files.add(source_details[1])
 
1537
                    # generate the old path; this is needed for stating later
 
1538
                    # as well.
 
1539
                    old_path = source_details[1]
 
1540
                    old_dirname, old_basename = os.path.split(old_path)
 
1541
                    path = os.path.join(*entry[0][0:2])
 
1542
                    old_entry = state._get_entry(source_index, path_utf8=old_path)
 
1543
                    # update the source details variable to be the real
 
1544
                    # location.
 
1545
                    source_details = old_entry[1][source_index]
 
1546
                else:
 
1547
                    old_path = path = os.path.join(*entry[0][0:2])
 
1548
                    old_dirname, old_basename = entry[0][0:2]
 
1549
                if path_info is None:
 
1550
                    # the file is missing on disk, show as removed.
 
1551
                    print "missing file"
 
1552
                    old_path = os.path.join(*entry[0][0:2])
 
1553
                    result.removed.append((old_path, entry[0][2], dirstate.DirState._minikind_to_kind[source_details[0]]))
 
1554
                # use the kind from disk.
 
1555
                elif source_details[0] != path_info[2][0]:
 
1556
                    # different kind
 
1557
                    import pdb;pdb.set_trace()
 
1558
                    print "kind change"
 
1559
                else:
 
1560
                    # same kind
 
1561
                    if path_info[2][0] == 'd':
 
1562
                        # directories have no fingerprint
 
1563
                        content_change = False
 
1564
                        executable_change = False
 
1565
                    elif path_info[2][0] == 'f':
 
1566
                        # has it changed? fast path: size, slow path: sha1.
 
1567
                        executable_change = source_details[3] != bool(
 
1568
                            stat.S_ISREG(path_info[3].st_mode)
 
1569
                            and stat.S_IEXEC & path_info[3].st_mode)
 
1570
                        if source_details[2] != path_info[3].st_size:
 
1571
                            content_change = True
 
1572
                        else:
 
1573
                            # maybe the same. Get the hash
 
1574
                            new_hash = self.target._hashcache.get_sha1(path, path_info[3])
 
1575
                            content_change = (new_hash != source_details[1])
 
1576
                    elif path_info[2][0] == 'l':
 
1577
                        import pdb;pdb.set_trace()
 
1578
                        print "link"
 
1579
                    else:
 
1580
                        raise Exception, "unknown minikind"
 
1581
                    # parent id is the entry for the path in the target tree
 
1582
                    # TODO: the target is the same for an entire directory: cache em.
 
1583
                    source_parent_id = state._get_entry(source_index, path_utf8=old_dirname)[0][2]
 
1584
                    if source_parent_id == entry[0][2]:
 
1585
                        source_parent_id = None
 
1586
                    target_parent_id = state._get_entry(target_index, path_utf8=entry[0][0])[0][2]
 
1587
                    if target_parent_id == entry[0][2]:
 
1588
                        target_parent_id = None
 
1589
                    source_exec = source_details[3]
 
1590
                    target_exec = bool(
 
1591
                        stat.S_ISREG(path_info[3].st_mode)
 
1592
                        and stat.S_IEXEC & path_info[3].st_mode)
 
1593
                    return ((entry[0][2], path, content_change, (True, True), (source_parent_id, target_parent_id), (old_basename, entry[0][1]), (dirstate.DirState._minikind_to_kind[source_details[0]], path_info[2]), (source_exec, target_exec)),)
 
1594
            elif source_details[0] in 'a' and target_details[0] in 'fdl':
 
1595
                # looks like a new file
 
1596
                if path_info is not None:
 
1597
                    path = os.path.join(*entry[0][0:2])
 
1598
                    # parent id is the entry for the path in the target tree
 
1599
                    # TODO: these are the same for an entire directory: cache em.
 
1600
                    parent_id = state._get_entry(target_index, path_utf8=entry[0][0])[0][2]
 
1601
                    if parent_id == entry[0][2]:
 
1602
                        parent_id = None
 
1603
                    # basename
 
1604
                    new_executable = bool(
 
1605
                        stat.S_ISREG(path_info[3].st_mode)
 
1606
                        and stat.S_IEXEC & path_info[3].st_mode)
 
1607
                    return ((entry[0][2], path, True, (False, True), (None, parent_id), (None, entry[0][1]), (None, path_info[2]), (None, new_executable)),)
 
1608
                else:
 
1609
                    # but its not on disk: we deliberately treat this as just
 
1610
                    # never-present. (Why ?! - RBC 20070224)
 
1611
                    pass
 
1612
            elif source_details[0] in 'fdl' and target_details[0] in 'a':
 
1613
                # unversioned, possibly, or possibly not deleted: we dont care.
 
1614
                # if its still on disk, *and* theres no other entry at this
 
1615
                # path [we dont know this in this routine at the moment -
 
1616
                # perhaps we should change this - then it would be an unknown.
 
1617
                old_path = os.path.join(*entry[0][0:2])
 
1618
                # parent id is the entry for the path in the target tree
 
1619
                parent_id = state._get_entry(source_index, path_utf8=entry[0][0])[0][2]
 
1620
                if parent_id == entry[0][2]:
 
1621
                    parent_id = None
 
1622
                return ((entry[0][2], old_path, True, (True, False), (parent_id, None), (entry[0][1], None), (dirstate.DirState._minikind_to_kind[source_details[0]], None), (source_details[3], None)),)
 
1623
            elif source_details[0] in 'fdl' and target_details[0] in 'r':
 
1624
                # a rename; could be a true rename, or a rename inherited from
 
1625
                # a renamed parent. TODO: handle this efficiently. Its not
 
1626
                # common case to rename dirs though, so a correct but slow
 
1627
                # implementation will do.
 
1628
                if not osutils.is_inside_any(searched_specific_files, target_details[1]):
 
1629
                    search_specific_files.add(target_details[1])
 
1630
            else:
 
1631
                import pdb;pdb.set_trace()
 
1632
            return ()
 
1633
        while search_specific_files:
 
1634
            # TODO: the pending list should be lexically sorted?
 
1635
            current_root = search_specific_files.pop()
 
1636
            searched_specific_files.add(current_root)
 
1637
            # process the entries for this containing directory: the rest will be
 
1638
            # found by their parents recursively.
 
1639
            root_entries = _entries_for_path(current_root)
 
1640
            root_abspath = self.target.abspath(current_root)
 
1641
            try:
 
1642
                root_stat = os.lstat(root_abspath)
 
1643
            except OSError, e:
 
1644
                if e.errno == errno.ENOENT:
 
1645
                    # TODO: this directory does not exist in target. Should we
 
1646
                    # consider it missing and diff, or should we just skip? For
 
1647
                    # now, skip.
 
1648
                    continue
 
1649
                else:
 
1650
                    # some other random error: hand it up.
 
1651
                    raise
 
1652
            root_dir_info = ('', current_root,
 
1653
                osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
 
1654
                root_abspath)
 
1655
            #
 
1656
            if not root_entries:
 
1657
                # this specified path is not present at all, skip it.
 
1658
                continue
 
1659
            for entry in root_entries:
 
1660
                for result in _process_entry(entry, root_dir_info):
 
1661
                    # this check should probably be outside the loop: one
 
1662
                    # 'iterate two trees' api, and then _iter_changes filters
 
1663
                    # unchanged pairs. - RBC 20070226
 
1664
                    if include_unchanged or result[2] or True in map(lambda x:x[0]!=x[1], result[3:8]):
 
1665
                        yield result
 
1666
            dir_iterator = osutils.walkdirs(root_abspath, prefix=current_root)
 
1667
            initial_key = (current_root, '', '')
 
1668
            block_index, _ = state._find_block_index_from_key(initial_key)
 
1669
            if block_index == 0:
 
1670
                # we have processed the total root already, but because the
 
1671
                # initial key matched it we sould skip it here.
 
1672
                block_index +=1
 
1673
            current_dir_info = dir_iterator.next()
 
1674
            if current_dir_info[0][0] == '':
 
1675
                # remove .bzr from iteration
 
1676
                bzr_index = bisect_left(current_dir_info[1], ('.bzr',))
 
1677
                assert current_dir_info[1][bzr_index][0] == '.bzr'
 
1678
                del current_dir_info[1][bzr_index]
 
1679
            # convert the unicode relpaths in the dir index to uf8 for
 
1680
            # comparison with dirstate data.
 
1681
            # TODO: keep the utf8 version around for giving to the caller.
 
1682
            current_dir_info = ((current_dir_info[0][0].encode('utf8'), current_dir_info[0][1]),
 
1683
                [(line[0].encode('utf8'), line[1].encode('utf8')) + line[2:] for line in current_dir_info[1]])
 
1684
            # walk until both the directory listing and the versioned metadata
 
1685
            # are exhausted. TODO: reevaluate this, perhaps we should stop when
 
1686
            # the versioned data runs out.
 
1687
            if (block_index < len(state._dirblocks) and
 
1688
                osutils.is_inside(current_root, state._dirblocks[block_index][0])):
 
1689
                current_block = state._dirblocks[block_index]
 
1690
            else:
 
1691
                current_block = None
 
1692
            while (current_dir_info is not None or
 
1693
                current_block is not None):
 
1694
                if current_dir_info and current_block and current_dir_info[0][0] != current_block[0]:
 
1695
                    if current_block[0] < current_dir_info[0][0]:
 
1696
                        # extra dir on disk: pass for now? should del from info ?
 
1697
                        import pdb;pdb.set_trace()
 
1698
                        print 'unversioned dir'
 
1699
                    else:
 
1700
                        # entry referring to missing dir.
 
1701
                        import pdb;pdb.set_trace()
 
1702
                        print 'missing dir'
 
1703
                entry_index = 0
 
1704
                if current_block and entry_index < len(current_block[1]):
 
1705
                    current_entry = current_block[1][entry_index]
 
1706
                else:
 
1707
                    current_entry = None
 
1708
                advance_entry = True
 
1709
                path_index = 0
 
1710
                if current_dir_info and path_index < len(current_dir_info[1]):
 
1711
                    current_path_info = current_dir_info[1][path_index]
 
1712
                else:
 
1713
                    current_path_info = None
 
1714
                advance_path = True
 
1715
                while (current_entry is not None or
 
1716
                    current_path_info is not None):
 
1717
                    if current_entry is None:
 
1718
                        # no more entries: yield current_pathinfo as an
 
1719
                        # unversioned file: its not the same as a path in any
 
1720
                        # tree in the dirstate.
 
1721
                        new_executable = bool(
 
1722
                            stat.S_ISREG(current_path_info[3].st_mode)
 
1723
                            and stat.S_IEXEC & current_path_info[3].st_mode)
 
1724
                        yield (None, current_path_info[0], True, (False, False), (None, None), (None, current_path_info[1]), (None, current_path_info[2]), (None, new_executable))
 
1725
                    elif current_path_info is None:
 
1726
                        # no path is fine: the per entry code will handle it.
 
1727
                        for result in _process_entry(current_entry, current_path_info):
 
1728
                            # this check should probably be outside the loop: one
 
1729
                            # 'iterate two trees' api, and then _iter_changes filters
 
1730
                            # unchanged pairs. - RBC 20070226
 
1731
                            if include_unchanged or result[2] or True in map(lambda x:x[0]!=x[1], result[3:8]):
 
1732
                                yield result
 
1733
                    elif current_entry[0][1] != current_path_info[1]:
 
1734
                        if current_path_info[1] < current_entry[0][1]:
 
1735
                            # extra file on disk: pass for now
 
1736
                            import pdb;pdb.set_trace()
 
1737
                            print 'unversioned file'
 
1738
                        else:
 
1739
                            # entry referring to file not present on disk.
 
1740
                            # advance the entry only, after processing.
 
1741
                            for result in _process_entry(current_entry, None):
 
1742
                                # this check should probably be outside the loop: one
 
1743
                                # 'iterate two trees' api, and then _iter_changes filters
 
1744
                                # unchanged pairs. - RBC 20070226
 
1745
                                if include_unchanged or result[2] or True in map(lambda x:x[0]!=x[1], result[3:8]):
 
1746
                                    yield result
 
1747
                            advance_path = False
 
1748
                    else:
 
1749
                        for result in _process_entry(current_entry, current_path_info):
 
1750
                            # this check should probably be outside the loop: one
 
1751
                            # 'iterate two trees' api, and then _iter_changes filters
 
1752
                            # unchanged pairs. - RBC 20070226
 
1753
                            if include_unchanged or result[2] or True in map(lambda x:x[0]!=x[1], result[3:8]):
 
1754
                                yield result
 
1755
                    if advance_entry and current_entry is not None:
 
1756
                        entry_index += 1
 
1757
                        if entry_index < len(current_block[1]):
 
1758
                            current_entry = current_block[1][entry_index]
 
1759
                        else:
 
1760
                            current_entry = None
 
1761
                    else:
 
1762
                        advance_entry = True # reset the advance flaga
 
1763
                    if advance_path and current_path_info is not None:
 
1764
                        path_index += 1
 
1765
                        if path_index < len(current_dir_info[1]):
 
1766
                            current_path_info = current_dir_info[1][path_index]
 
1767
                        else:
 
1768
                            current_path_info = None
 
1769
                    else:
 
1770
                        advance_path = True # reset the advance flagg.
 
1771
                if current_block is not None:
 
1772
                    block_index += 1
 
1773
                    if (block_index < len(state._dirblocks) and
 
1774
                        osutils.is_inside(current_root, state._dirblocks[block_index][0])):
 
1775
                        current_block = state._dirblocks[block_index]
 
1776
                    else:
 
1777
                        current_block = None
 
1778
                if current_dir_info is not None:
 
1779
                    try:
 
1780
                        current_dir_info = dir_iterator.next()
 
1781
                        # convert the unicode relpaths in the dir index to uf8 for
 
1782
                        # comparison with dirstate data.
 
1783
                        # TODO: keep the utf8 version around for giving to the caller.
 
1784
                        current_dir_info = ((current_dir_info[0][0].encode('utf8'), current_dir_info[0][1]),
 
1785
                            [(line[0].encode('utf8'), line[1].encode('utf8')) + line[2:] for line in current_dir_info[1]])
 
1786
                    except StopIteration:
 
1787
                        current_dir_info = None
 
1788
 
1368
1789
 
1369
1790
    @staticmethod
1370
1791
    def is_compatible(source, target):