~jelmer/ubuntu/maverick/bzr/2.2.5

« back to all changes in this revision

Viewing changes to bzrlib/dirstate.py

  • Committer: Bazaar Package Importer
  • Author(s): Jelmer Vernooij
  • Date: 2009-03-10 14:11:59 UTC
  • mfrom: (1.2.1 upstream) (3.1.68 jaunty)
  • Revision ID: james.westby@ubuntu.com-20090310141159-lwzzo5c1fwrtzgj4
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
99
99
 
100
100
--- Format 1 had the following different definition: ---
101
101
rows = dirname, NULL, basename, NULL, MINIKIND, NULL, fileid_utf8, NULL,
102
 
    WHOLE NUMBER (* size *), NULL, packed stat, NULL, sha1|symlink target, 
 
102
    WHOLE NUMBER (* size *), NULL, packed stat, NULL, sha1|symlink target,
103
103
    {PARENT ROW}
104
104
PARENT ROW = NULL, revision_utf8, NULL, MINIKIND, NULL, dirname, NULL,
105
105
    basename, NULL, WHOLE NUMBER (* size *), NULL, "y" | "n", NULL,
130
130
operations for the less common but still occurs a lot status/diff/commit
131
131
on specific files). Operations on specific files involve a scan for all
132
132
the children of a path, *in every involved tree*, which the current
133
 
format did not accommodate. 
 
133
format did not accommodate.
134
134
----
135
135
 
136
136
Design priorities:
148
148
 
149
149
Memory representation:
150
150
 vector of all directories, and vector of the childen ?
151
 
   i.e. 
152
 
     root_entrie = (direntry for root, [parent_direntries_for_root]), 
 
151
   i.e.
 
152
     root_entrie = (direntry for root, [parent_direntries_for_root]),
153
153
     dirblocks = [
154
154
     ('', ['data for achild', 'data for bchild', 'data for cchild'])
155
155
     ('dir', ['achild', 'cchild', 'echild'])
158
158
    - in-order for serialisation - this is 'dirblock' grouping.
159
159
    - insertion of a file '/a' affects only the '/' child-vector, that is, to
160
160
      insert 10K elements from scratch does not generates O(N^2) memoves of a
161
 
      single vector, rather each individual, which tends to be limited to a 
162
 
      manageable number. Will scale badly on trees with 10K entries in a 
 
161
      single vector, rather each individual, which tends to be limited to a
 
162
      manageable number. Will scale badly on trees with 10K entries in a
163
163
      single directory. compare with Inventory.InventoryDirectory which has
164
164
      a dictionary for the children. No bisect capability, can only probe for
165
165
      exact matches, or grab all elements and sort.
166
166
    - What's the risk of error here? Once we have the base format being processed
167
 
      we should have a net win regardless of optimality. So we are going to 
 
167
      we should have a net win regardless of optimality. So we are going to
168
168
      go with what seems reasonable.
169
169
open questions:
170
170
 
222
222
    )
223
223
 
224
224
 
225
 
def pack_stat(st, _encode=binascii.b2a_base64, _pack=struct.pack):
226
 
    """Convert stat values into a packed representation."""
227
 
    # jam 20060614 it isn't really worth removing more entries if we
228
 
    # are going to leave it in packed form.
229
 
    # With only st_mtime and st_mode filesize is 5.5M and read time is 275ms
230
 
    # With all entries, filesize is 5.9M and read time is maybe 280ms
231
 
    # well within the noise margin
232
 
 
233
 
    # base64 encoding always adds a final newline, so strip it off
234
 
    # The current version
235
 
    return _encode(_pack('>LLLLLL'
236
 
        , st.st_size, int(st.st_mtime), int(st.st_ctime)
237
 
        , st.st_dev, st.st_ino & 0xFFFFFFFF, st.st_mode))[:-1]
238
 
    # This is 0.060s / 1.520s faster by not encoding as much information
239
 
    # return _encode(_pack('>LL', int(st.st_mtime), st.st_mode))[:-1]
240
 
    # This is not strictly faster than _encode(_pack())[:-1]
241
 
    # return '%X.%X.%X.%X.%X.%X' % (
242
 
    #      st.st_size, int(st.st_mtime), int(st.st_ctime),
243
 
    #      st.st_dev, st.st_ino, st.st_mode)
244
 
    # Similar to the _encode(_pack('>LL'))
245
 
    # return '%X.%X' % (int(st.st_mtime), st.st_mode)
 
225
# This is the Windows equivalent of ENOTDIR
 
226
# It is defined in pywin32.winerror, but we don't want a strong dependency for
 
227
# just an error code.
 
228
ERROR_PATH_NOT_FOUND = 3
 
229
ERROR_DIRECTORY = 267
 
230
 
 
231
 
 
232
if not getattr(struct, '_compile', None):
 
233
    # Cannot pre-compile the dirstate pack_stat
 
234
    def pack_stat(st, _encode=binascii.b2a_base64, _pack=struct.pack):
 
235
        """Convert stat values into a packed representation."""
 
236
        return _encode(_pack('>LLLLLL', st.st_size, int(st.st_mtime),
 
237
            int(st.st_ctime), st.st_dev, st.st_ino & 0xFFFFFFFF,
 
238
            st.st_mode))[:-1]
 
239
else:
 
240
    # compile the struct compiler we need, so as to only do it once
 
241
    from _struct import Struct
 
242
    _compiled_pack = Struct('>LLLLLL').pack
 
243
    def pack_stat(st, _encode=binascii.b2a_base64, _pack=_compiled_pack):
 
244
        """Convert stat values into a packed representation."""
 
245
        # jam 20060614 it isn't really worth removing more entries if we
 
246
        # are going to leave it in packed form.
 
247
        # With only st_mtime and st_mode filesize is 5.5M and read time is 275ms
 
248
        # With all entries, filesize is 5.9M and read time is maybe 280ms
 
249
        # well within the noise margin
 
250
 
 
251
        # base64 encoding always adds a final newline, so strip it off
 
252
        # The current version
 
253
        return _encode(_pack(st.st_size, int(st.st_mtime), int(st.st_ctime),
 
254
            st.st_dev, st.st_ino & 0xFFFFFFFF, st.st_mode))[:-1]
 
255
        # This is 0.060s / 1.520s faster by not encoding as much information
 
256
        # return _encode(_pack('>LL', int(st.st_mtime), st.st_mode))[:-1]
 
257
        # This is not strictly faster than _encode(_pack())[:-1]
 
258
        # return '%X.%X.%X.%X.%X.%X' % (
 
259
        #      st.st_size, int(st.st_mtime), int(st.st_ctime),
 
260
        #      st.st_dev, st.st_ino, st.st_mode)
 
261
        # Similar to the _encode(_pack('>LL'))
 
262
        # return '%X.%X' % (int(st.st_mtime), st.st_mode)
246
263
 
247
264
 
248
265
class DirState(object):
314
331
        # IN_MEMORY_UNMODIFIED indicates that what we have in memory
315
332
        #   is the same as is on disk
316
333
        # IN_MEMORY_MODIFIED indicates that we have a modified version
317
 
        #   of what is on disk. 
 
334
        #   of what is on disk.
318
335
        # In future we will add more granularity, for instance _dirblock_state
319
336
        # will probably support partially-in-memory as a separate variable,
320
337
        # allowing for partially-in-memory unmodified and partially-in-memory
321
338
        # modified states.
322
339
        self._header_state = DirState.NOT_IN_MEMORY
323
340
        self._dirblock_state = DirState.NOT_IN_MEMORY
324
 
        # If true, an error has been detected while updating the dirstate, and 
 
341
        # If true, an error has been detected while updating the dirstate, and
325
342
        # for safety we're not going to commit to disk.
326
343
        self._changes_aborted = False
327
344
        self._dirblocks = []
357
374
        """Add a path to be tracked.
358
375
 
359
376
        :param path: The path within the dirstate - '' is the root, 'foo' is the
360
 
            path foo within the root, 'foo/bar' is the path bar within foo 
 
377
            path foo within the root, 'foo/bar' is the path bar within foo
361
378
            within the root.
362
379
        :param file_id: The file id of the path being added.
363
 
        :param kind: The kind of the path, as a string like 'file', 
 
380
        :param kind: The kind of the path, as a string like 'file',
364
381
            'directory', etc.
365
382
        :param stat: The output of os.lstat for the path.
366
383
        :param fingerprint: The sha value of the file,
369
386
            or '' for directories.
370
387
        """
371
388
        # adding a file:
372
 
        # find the block its in. 
 
389
        # find the block its in.
373
390
        # find the location in the block.
374
391
        # check its not there
375
392
        # add it.
388
405
        # in the parent, or according to the special treatment for the root
389
406
        if basename == '.' or basename == '..':
390
407
            raise errors.InvalidEntryName(path)
391
 
        # now that we've normalised, we need the correct utf8 path and 
 
408
        # now that we've normalised, we need the correct utf8 path and
392
409
        # dirname and basename elements. This single encode and split should be
393
410
        # faster than three separate encodes.
394
411
        utf8path = (dirname + '/' + basename).strip('/').encode('utf8')
398
415
            raise AssertionError(
399
416
                "must be a utf8 file_id not %s" % (type(file_id), ))
400
417
        # Make sure the file_id does not exist in this tree
401
 
        file_id_entry = self._get_entry(0, fileid_utf8=file_id)
 
418
        rename_from = None
 
419
        file_id_entry = self._get_entry(0, fileid_utf8=file_id, include_deleted=True)
402
420
        if file_id_entry != (None, None):
403
 
            path = osutils.pathjoin(file_id_entry[0][0], file_id_entry[0][1])
404
 
            kind = DirState._minikind_to_kind[file_id_entry[1][0][0]]
405
 
            info = '%s:%s' % (kind, path)
406
 
            raise errors.DuplicateFileId(file_id, info)
 
421
            if file_id_entry[1][0][0] == 'a':
 
422
                if file_id_entry[0] != (dirname, basename, file_id):
 
423
                    # set the old name's current operation to rename
 
424
                    self.update_minimal(file_id_entry[0],
 
425
                        'r',
 
426
                        path_utf8='',
 
427
                        packed_stat='',
 
428
                        fingerprint=utf8path
 
429
                    )
 
430
                    rename_from = file_id_entry[0][0:2]
 
431
            else:
 
432
                path = osutils.pathjoin(file_id_entry[0][0], file_id_entry[0][1])
 
433
                kind = DirState._minikind_to_kind[file_id_entry[1][0][0]]
 
434
                info = '%s:%s' % (kind, path)
 
435
                raise errors.DuplicateFileId(file_id, info)
407
436
        first_key = (dirname, basename, '')
408
437
        block_index, present = self._find_block_index_from_key(first_key)
409
438
        if present:
410
439
            # check the path is not in the tree
411
440
            block = self._dirblocks[block_index][1]
412
441
            entry_index, _ = self._find_entry_index(first_key, block)
413
 
            while (entry_index < len(block) and 
 
442
            while (entry_index < len(block) and
414
443
                block[entry_index][0][0:2] == first_key[0:2]):
415
444
                if block[entry_index][1][0][0] not in 'ar':
416
445
                    # this path is in the dirstate in the current tree.
436
465
            packed_stat = pack_stat(stat)
437
466
        parent_info = self._empty_parent_info()
438
467
        minikind = DirState._kind_to_minikind[kind]
 
468
        if rename_from is not None:
 
469
            if rename_from[0]:
 
470
                old_path_utf8 = '%s/%s' % rename_from
 
471
            else:
 
472
                old_path_utf8 = rename_from[1]
 
473
            parent_info[0] = ('r', old_path_utf8, 0, False, '')
439
474
        if kind == 'file':
440
475
            entry_data = entry_key, [
441
476
                (minikind, fingerprint, size, False, packed_stat),
900
935
 
901
936
    def _discard_merge_parents(self):
902
937
        """Discard any parents trees beyond the first.
903
 
        
 
938
 
904
939
        Note that if this fails the dirstate is corrupted.
905
940
 
906
941
        After this function returns the dirstate contains 2 trees, neither of
976
1011
                raise AssertionError("bad dirname %r" % dirname)
977
1012
        block_index, present = self._find_block_index_from_key((dirname, '', ''))
978
1013
        if not present:
979
 
            ## In future, when doing partial parsing, this should load and 
 
1014
            ## In future, when doing partial parsing, this should load and
980
1015
            # populate the entire block.
981
1016
            self._dirblocks.insert(block_index, (dirname, []))
982
1017
        return block_index
994
1029
        if new_entries[0][0][0:2] != ('', ''):
995
1030
            raise AssertionError(
996
1031
                "Missing root row %r" % (new_entries[0][0],))
997
 
        # The two blocks here are deliberate: the root block and the 
 
1032
        # The two blocks here are deliberate: the root block and the
998
1033
        # contents-of-root block.
999
1034
        self._dirblocks = [('', []), ('', [])]
1000
1035
        current_block = self._dirblocks[0][1]
1033
1068
        self._dirblocks[0] = ('', root_block)
1034
1069
        self._dirblocks[1] = ('', contents_of_root_block)
1035
1070
 
 
1071
    def _entries_for_path(self, path):
 
1072
        """Return a list with all the entries that match path for all ids."""
 
1073
        dirname, basename = os.path.split(path)
 
1074
        key = (dirname, basename, '')
 
1075
        block_index, present = self._find_block_index_from_key(key)
 
1076
        if not present:
 
1077
            # the block which should contain path is absent.
 
1078
            return []
 
1079
        result = []
 
1080
        block = self._dirblocks[block_index][1]
 
1081
        entry_index, _ = self._find_entry_index(key, block)
 
1082
        # we may need to look at multiple entries at this path: walk while the specific_files match.
 
1083
        while (entry_index < len(block) and
 
1084
            block[entry_index][0][0:2] == key[0:2]):
 
1085
            result.append(block[entry_index])
 
1086
            entry_index += 1
 
1087
        return result
 
1088
 
1036
1089
    def _entry_to_line(self, entry):
1037
1090
        """Serialize entry to a NULL delimited line ready for _get_output_lines.
1038
1091
 
1106
1159
        # one to use it. we use _right here because there are two
1107
1160
        # '' blocks - the root, and the contents of root
1108
1161
        # we always have a minimum of 2 in self._dirblocks: root and
1109
 
        # root-contents, and for '', we get 2 back, so this is 
 
1162
        # root-contents, and for '', we get 2 back, so this is
1110
1163
        # simple and correct:
1111
1164
        present = (block_index < len(self._dirblocks) and
1112
1165
            self._dirblocks[block_index][0] == key[0])
1199
1252
                    fingerprint = ''
1200
1253
                insertions[file_id] = (key, minikind, inv_entry.executable,
1201
1254
                                       fingerprint, new_path)
 
1255
            # Transform moves into delete+add pairs
1202
1256
            if None not in (old_path, new_path):
1203
1257
                for child in self._iter_child_entries(0, old_path):
1204
1258
                    if child[0][2] in insertions or child[0][2] in removals:
1228
1282
                self._get_block_entry_index(dirname, basename, 0)
1229
1283
            entry = self._dirblocks[block_i][1][entry_i]
1230
1284
            self._make_absent(entry)
 
1285
            # See if we have a malformed delta: deleting a directory must not
 
1286
            # leave crud behind. This increases the number of bisects needed
 
1287
            # substantially, but deletion or renames of large numbers of paths
 
1288
            # is rare enough it shouldn't be an issue (famous last words?) RBC
 
1289
            # 20080730.
 
1290
            block_i, entry_i, d_present, f_present = \
 
1291
                self._get_block_entry_index(path, '', 0)
 
1292
            if d_present:
 
1293
                # The dir block is still present in the dirstate; this could
 
1294
                # be due to it being in a parent tree, or a corrupt delta.
 
1295
                for child_entry in self._dirblocks[block_i][1]:
 
1296
                    if child_entry[1][0][0] not in ('r', 'a'):
 
1297
                        raise errors.InconsistentDelta(path, entry[0][2],
 
1298
                            "The file id was deleted but its children were "
 
1299
                            "not deleted.")
1231
1300
 
1232
1301
    def _apply_insertions(self, adds):
1233
1302
        for key, minikind, executable, fingerprint, path_utf8 in sorted(adds):
1456
1525
                    # it is being resurrected here, so blank it out temporarily.
1457
1526
                    self._dirblocks[block_index][1][entry_index][1][1] = null
1458
1527
 
1459
 
    def update_entry(self, entry, abspath, stat_value,
1460
 
                     _stat_to_minikind=_stat_to_minikind,
1461
 
                     _pack_stat=pack_stat):
1462
 
        """Update the entry based on what is actually on disk.
 
1528
    def _observed_sha1(self, entry, sha1, stat_value,
 
1529
        _stat_to_minikind=_stat_to_minikind, _pack_stat=pack_stat):
 
1530
        """Note the sha1 of a file.
1463
1531
 
1464
 
        :param entry: This is the dirblock entry for the file in question.
1465
 
        :param abspath: The path on disk for this file.
1466
 
        :param stat_value: (optional) if we already have done a stat on the
1467
 
            file, re-use it.
1468
 
        :return: The sha1 hexdigest of the file (40 bytes) or link target of a
1469
 
                symlink.
 
1532
        :param entry: The entry the sha1 is for.
 
1533
        :param sha1: The observed sha1.
 
1534
        :param stat_value: The os.lstat for the file.
1470
1535
        """
1471
1536
        try:
1472
1537
            minikind = _stat_to_minikind[stat_value.st_mode & 0170000]
1474
1539
            # Unhandled kind
1475
1540
            return None
1476
1541
        packed_stat = _pack_stat(stat_value)
1477
 
        (saved_minikind, saved_link_or_sha1, saved_file_size,
1478
 
         saved_executable, saved_packed_stat) = entry[1][0]
1479
 
 
1480
 
        if (minikind == saved_minikind
1481
 
            and packed_stat == saved_packed_stat):
1482
 
            # The stat hasn't changed since we saved, so we can re-use the
1483
 
            # saved sha hash.
1484
 
            if minikind == 'd':
1485
 
                return None
1486
 
 
1487
 
            # size should also be in packed_stat
1488
 
            if saved_file_size == stat_value.st_size:
1489
 
                return saved_link_or_sha1
1490
 
 
1491
 
        # If we have gotten this far, that means that we need to actually
1492
 
        # process this entry.
1493
 
        link_or_sha1 = None
1494
1542
        if minikind == 'f':
1495
 
            link_or_sha1 = self._sha1_file(abspath)
1496
 
            executable = self._is_executable(stat_value.st_mode,
1497
 
                                             saved_executable)
1498
 
            if self._cutoff_time is None:
1499
 
                self._sha_cutoff_time()
1500
 
            if (stat_value.st_mtime < self._cutoff_time
1501
 
                and stat_value.st_ctime < self._cutoff_time):
1502
 
                entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
1503
 
                               executable, packed_stat)
1504
 
            else:
1505
 
                entry[1][0] = ('f', '', stat_value.st_size,
1506
 
                               executable, DirState.NULLSTAT)
1507
 
        elif minikind == 'd':
1508
 
            link_or_sha1 = None
1509
 
            entry[1][0] = ('d', '', 0, False, packed_stat)
1510
 
            if saved_minikind != 'd':
1511
 
                # This changed from something into a directory. Make sure we
1512
 
                # have a directory block for it. This doesn't happen very
1513
 
                # often, so this doesn't have to be super fast.
1514
 
                block_index, entry_index, dir_present, file_present = \
1515
 
                    self._get_block_entry_index(entry[0][0], entry[0][1], 0)
1516
 
                self._ensure_block(block_index, entry_index,
1517
 
                                   osutils.pathjoin(entry[0][0], entry[0][1]))
1518
 
        elif minikind == 'l':
1519
 
            link_or_sha1 = self._read_link(abspath, saved_link_or_sha1)
1520
 
            if self._cutoff_time is None:
1521
 
                self._sha_cutoff_time()
1522
 
            if (stat_value.st_mtime < self._cutoff_time
1523
 
                and stat_value.st_ctime < self._cutoff_time):
1524
 
                entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
1525
 
                               False, packed_stat)
1526
 
            else:
1527
 
                entry[1][0] = ('l', '', stat_value.st_size,
1528
 
                               False, DirState.NULLSTAT)
1529
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1530
 
        return link_or_sha1
 
1543
            if self._cutoff_time is None:
 
1544
                self._sha_cutoff_time()
 
1545
            if (stat_value.st_mtime < self._cutoff_time
 
1546
                and stat_value.st_ctime < self._cutoff_time):
 
1547
                entry[1][0] = ('f', sha1, entry[1][0][2], entry[1][0][3],
 
1548
                    packed_stat)
 
1549
                self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1531
1550
 
1532
1551
    def _sha_cutoff_time(self):
1533
1552
        """Return cutoff time.
1569
1588
        #       already in memory. However, this really needs to be done at a
1570
1589
        #       higher level, because there either won't be anything on disk,
1571
1590
        #       or the thing on disk will be a file.
1572
 
        return os.readlink(abspath)
 
1591
        return os.readlink(abspath.encode(osutils._fs_enc))
1573
1592
 
1574
1593
    def get_ghosts(self):
1575
1594
        """Return a list of the parent tree revision ids that are ghosts."""
1721
1740
            entry_index += 1
1722
1741
        return block_index, entry_index, True, False
1723
1742
 
1724
 
    def _get_entry(self, tree_index, fileid_utf8=None, path_utf8=None):
 
1743
    def _get_entry(self, tree_index, fileid_utf8=None, path_utf8=None, include_deleted=False):
1725
1744
        """Get the dirstate entry for path in tree tree_index.
1726
1745
 
1727
1746
        If either file_id or path is supplied, it is used as the key to lookup.
1735
1754
            trees.
1736
1755
        :param fileid_utf8: A utf8 file_id to look up.
1737
1756
        :param path_utf8: An utf8 path to be looked up.
 
1757
        :param include_deleted: If True, and performing a lookup via
 
1758
            fileid_utf8 rather than path_utf8, return an entry for deleted
 
1759
            (absent) paths.
1738
1760
        :return: The dirstate entry tuple for path, or (None, None)
1739
1761
        """
1740
1762
        self._read_dirblocks_if_needed()
1776
1798
                if present:
1777
1799
                    entry = self._dirblocks[block_index][1][entry_index]
1778
1800
                    if entry[1][tree_index][0] in 'fdlt':
1779
 
                        # this is the result we are looking for: the  
 
1801
                        # this is the result we are looking for: the
1780
1802
                        # real home of this file_id in this tree.
1781
1803
                        return entry
1782
1804
                    if entry[1][tree_index][0] == 'a':
1783
1805
                        # there is no home for this entry in this tree
 
1806
                        if include_deleted:
 
1807
                            return entry
1784
1808
                        return None, None
1785
1809
                    if entry[1][tree_index][0] != 'r':
1786
1810
                        raise AssertionError(
1825
1849
            raise
1826
1850
        return result
1827
1851
 
1828
 
    def _inv_entry_to_details(self, inv_entry):
 
1852
    @staticmethod
 
1853
    def _inv_entry_to_details(inv_entry):
1829
1854
        """Convert an inventory entry (from a revision tree) to state details.
1830
1855
 
1831
1856
        :param inv_entry: An inventory entry whose sha1 and link targets can be
1841
1866
            size = 0
1842
1867
            executable = False
1843
1868
        elif kind == 'symlink':
1844
 
            fingerprint = inv_entry.symlink_target or ''
 
1869
            # We don't support non-ascii targets for symlinks yet.
 
1870
            fingerprint = str(inv_entry.symlink_target or '')
1845
1871
            size = 0
1846
1872
            executable = False
1847
1873
        elif kind == 'file':
1859
1885
    def _iter_child_entries(self, tree_index, path_utf8):
1860
1886
        """Iterate over all the entries that are children of path_utf.
1861
1887
 
1862
 
        This only returns entries that are present (not in 'a', 'r') in 
 
1888
        This only returns entries that are present (not in 'a', 'r') in
1863
1889
        tree_index. tree_index data is not refreshed, so if tree 0 is used,
1864
1890
        results may differ from that obtained if paths were statted to
1865
1891
        determine what ones were directories.
1896
1922
                        else:
1897
1923
                            path = entry[0][1]
1898
1924
                        next_pending_dirs.append(path)
1899
 
    
 
1925
 
1900
1926
    def _iter_entries(self):
1901
1927
        """Iterate over all the entries in the dirstate.
1902
1928
 
1953
1979
 
1954
1980
    def _read_dirblocks_if_needed(self):
1955
1981
        """Read in all the dirblocks from the file if they are not in memory.
1956
 
        
 
1982
 
1957
1983
        This populates self._dirblocks, and sets self._dirblock_state to
1958
1984
        IN_MEMORY_UNMODIFIED. It is not currently ready for incremental block
1959
1985
        loading.
2084
2110
 
2085
2111
        :param parent_ids: A list of parent tree revision ids.
2086
2112
        :param dirblocks: A list containing one tuple for each directory in the
2087
 
            tree. Each tuple contains the directory path and a list of entries 
 
2113
            tree. Each tuple contains the directory path and a list of entries
2088
2114
            found in that directory.
2089
2115
        """
2090
2116
        # our memory copy is now authoritative.
2124
2150
        """Set the parent trees for the dirstate.
2125
2151
 
2126
2152
        :param trees: A list of revision_id, tree tuples. tree must be provided
2127
 
            even if the revision_id refers to a ghost: supply an empty tree in 
 
2153
            even if the revision_id refers to a ghost: supply an empty tree in
2128
2154
            this case.
2129
2155
        :param ghosts: A list of the revision_ids that are ghosts at the time
2130
2156
            of setting.
2131
 
        """ 
2132
 
        # TODO: generate a list of parent indexes to preserve to save 
 
2157
        """
 
2158
        # TODO: generate a list of parent indexes to preserve to save
2133
2159
        # processing specific parent trees. In the common case one tree will
2134
2160
        # be preserved - the left most parent.
2135
2161
        # TODO: if the parent tree is a dirstate, we might want to walk them
2140
2166
        # map and then walk the new parent trees only, mapping them into the
2141
2167
        # dirstate. Walk the dirstate at the same time to remove unreferenced
2142
2168
        # entries.
2143
 
        # for now: 
2144
 
        # sketch: loop over all entries in the dirstate, cherry picking 
 
2169
        # for now:
 
2170
        # sketch: loop over all entries in the dirstate, cherry picking
2145
2171
        # entries from the parent trees, if they are not ghost trees.
2146
2172
        # after we finish walking the dirstate, all entries not in the dirstate
2147
2173
        # are deletes, so we want to append them to the end as per the design
2152
2178
        #   links. We dont't trivially use the inventory from other trees
2153
2179
        #   because this leads to either double touching, or to accessing
2154
2180
        #   missing keys,
2155
 
        # - find other keys containing a path 
2156
 
        # We accumulate each entry via this dictionary, including the root 
 
2181
        # - find other keys containing a path
 
2182
        # We accumulate each entry via this dictionary, including the root
2157
2183
        by_path = {}
2158
2184
        id_index = {}
2159
2185
        # we could do parallel iterators, but because file id data may be
2163
2189
        # parent, but for now the common cases are adding a new parent (merge),
2164
2190
        # and replacing completely (commit), and commit is more common: so
2165
2191
        # optimise merge later.
2166
 
        
 
2192
 
2167
2193
        # ---- start generation of full tree mapping data
2168
2194
        # what trees should we use?
2169
2195
        parent_trees = [tree for rev_id, tree in trees if rev_id not in ghosts]
2170
 
        # how many trees do we end up with 
 
2196
        # how many trees do we end up with
2171
2197
        parent_count = len(parent_trees)
2172
2198
 
2173
2199
        # one: the current tree
2178
2204
            by_path[entry[0]] = [entry[1][0]] + \
2179
2205
                [DirState.NULL_PARENT_DETAILS] * parent_count
2180
2206
            id_index[entry[0][2]] = set([entry[0]])
2181
 
        
 
2207
 
2182
2208
        # now the parent trees:
2183
2209
        for tree_index, tree in enumerate(parent_trees):
2184
2210
            # the index is off by one, adjust it.
2198
2224
                # avoid checking all known paths for the id when generating a
2199
2225
                # new entry at this path: by adding the id->path mapping last,
2200
2226
                # all the mappings are valid and have correct relocation
2201
 
                # records where needed. 
 
2227
                # records where needed.
2202
2228
                file_id = entry.file_id
2203
2229
                path_utf8 = path.encode('utf8')
2204
2230
                dirname, basename = osutils.split(path_utf8)
2215
2241
                        # This is the vertical axis in the matrix, all pointing
2216
2242
                        # to the real path.
2217
2243
                        by_path[entry_key][tree_index] = ('r', path_utf8, 0, False, '')
2218
 
                # by path consistency: Insert into an existing path record (trivial), or 
 
2244
                # by path consistency: Insert into an existing path record (trivial), or
2219
2245
                # add a new one with relocation pointers for the other tree indexes.
2220
2246
                if new_entry_key in id_index[file_id]:
2221
2247
                    # there is already an entry where this data belongs, just insert it.
2234
2260
                            new_details.append(DirState.NULL_PARENT_DETAILS)
2235
2261
                        else:
2236
2262
                            # grab any one entry, use it to find the right path.
2237
 
                            # TODO: optimise this to reduce memory use in highly 
 
2263
                            # TODO: optimise this to reduce memory use in highly
2238
2264
                            # fragmented situations by reusing the relocation
2239
2265
                            # records.
2240
2266
                            a_key = iter(id_index[file_id]).next()
2273
2299
        return sorted(entry_list, key=_key)
2274
2300
 
2275
2301
    def set_state_from_inventory(self, new_inv):
2276
 
        """Set new_inv as the current state. 
 
2302
        """Set new_inv as the current state.
2277
2303
 
2278
2304
        This API is called by tree transform, and will usually occur with
2279
2305
        existing parent trees.
2285
2311
                "set_state_from_inventory called; please mutate the tree instead")
2286
2312
        self._read_dirblocks_if_needed()
2287
2313
        # sketch:
2288
 
        # Two iterators: current data and new data, both in dirblock order. 
 
2314
        # Two iterators: current data and new data, both in dirblock order.
2289
2315
        # We zip them together, which tells about entries that are new in the
2290
2316
        # inventory, or removed in the inventory, or present in both and
2291
 
        # possibly changed.  
 
2317
        # possibly changed.
2292
2318
        #
2293
2319
        # You might think we could just synthesize a new dirstate directly
2294
2320
        # since we're processing it in the right order.  However, we need to
2443
2469
        :param minikind: The type for the entry ('f' == 'file', 'd' ==
2444
2470
                'directory'), etc.
2445
2471
        :param executable: Should the executable bit be set?
2446
 
        :param fingerprint: Simple fingerprint for new entry: sha1 for files, 
 
2472
        :param fingerprint: Simple fingerprint for new entry: sha1 for files,
2447
2473
            referenced revision id for subtrees, etc.
2448
2474
        :param packed_stat: Packed stat value for new entry.
2449
2475
        :param size: Size information for new entry
2494
2520
                num_present_parents = self._num_present_parents()
2495
2521
                for lookup_index in xrange(1, num_present_parents + 1):
2496
2522
                    # grab any one entry, use it to find the right path.
2497
 
                    # TODO: optimise this to reduce memory use in highly 
 
2523
                    # TODO: optimise this to reduce memory use in highly
2498
2524
                    # fragmented situations by reusing the relocation
2499
2525
                    # records.
2500
2526
                    update_block_index, present = \
2517
2543
            block.insert(entry_index, new_entry)
2518
2544
            existing_keys.add(key)
2519
2545
        else:
2520
 
            # Does the new state matter? 
 
2546
            # Does the new state matter?
2521
2547
            block[entry_index][1][0] = new_details
2522
2548
            # parents cannot be affected by what we do.
2523
 
            # other occurences of this id can be found 
 
2549
            # other occurences of this id can be found
2524
2550
            # from the id index.
2525
2551
            # ---
2526
2552
            # tree index consistency: All other paths for this id in this tree
2559
2585
    def _validate(self):
2560
2586
        """Check that invariants on the dirblock are correct.
2561
2587
 
2562
 
        This can be useful in debugging; it shouldn't be necessary in 
 
2588
        This can be useful in debugging; it shouldn't be necessary in
2563
2589
        normal code.
2564
2590
 
2565
2591
        This must be called with a lock held.
2634
2660
        # For each file id, for each tree: either
2635
2661
        # the file id is not present at all; all rows with that id in the
2636
2662
        # key have it marked as 'absent'
2637
 
        # OR the file id is present under exactly one name; any other entries 
 
2663
        # OR the file id is present under exactly one name; any other entries
2638
2664
        # that mention that id point to the correct name.
2639
2665
        #
2640
2666
        # We check this with a dict per tree pointing either to the present
2687
2713
                        # absent; should not occur anywhere else
2688
2714
                        this_tree_map[file_id] = None, this_path
2689
2715
                    elif minikind == 'r':
2690
 
                        # relocation, must occur at expected location 
 
2716
                        # relocation, must occur at expected location
2691
2717
                        this_tree_map[file_id] = tree_state[1], this_path
2692
2718
                    else:
2693
2719
                        this_tree_map[file_id] = this_path, this_path
2756
2782
            raise errors.ObjectNotLocked(self)
2757
2783
 
2758
2784
 
 
2785
def py_update_entry(state, entry, abspath, stat_value,
 
2786
                 _stat_to_minikind=DirState._stat_to_minikind,
 
2787
                 _pack_stat=pack_stat):
 
2788
    """Update the entry based on what is actually on disk.
 
2789
 
 
2790
    This function only calculates the sha if it needs to - if the entry is
 
2791
    uncachable, or clearly different to the first parent's entry, no sha
 
2792
    is calculated, and None is returned.
 
2793
 
 
2794
    :param state: The dirstate this entry is in.
 
2795
    :param entry: This is the dirblock entry for the file in question.
 
2796
    :param abspath: The path on disk for this file.
 
2797
    :param stat_value: The stat value done on the path.
 
2798
    :return: None, or The sha1 hexdigest of the file (40 bytes) or link
 
2799
        target of a symlink.
 
2800
    """
 
2801
    try:
 
2802
        minikind = _stat_to_minikind[stat_value.st_mode & 0170000]
 
2803
    except KeyError:
 
2804
        # Unhandled kind
 
2805
        return None
 
2806
    packed_stat = _pack_stat(stat_value)
 
2807
    (saved_minikind, saved_link_or_sha1, saved_file_size,
 
2808
     saved_executable, saved_packed_stat) = entry[1][0]
 
2809
 
 
2810
    if (minikind == saved_minikind
 
2811
        and packed_stat == saved_packed_stat):
 
2812
        # The stat hasn't changed since we saved, so we can re-use the
 
2813
        # saved sha hash.
 
2814
        if minikind == 'd':
 
2815
            return None
 
2816
 
 
2817
        # size should also be in packed_stat
 
2818
        if saved_file_size == stat_value.st_size:
 
2819
            return saved_link_or_sha1
 
2820
 
 
2821
    # If we have gotten this far, that means that we need to actually
 
2822
    # process this entry.
 
2823
    link_or_sha1 = None
 
2824
    if minikind == 'f':
 
2825
        executable = state._is_executable(stat_value.st_mode,
 
2826
                                         saved_executable)
 
2827
        if state._cutoff_time is None:
 
2828
            state._sha_cutoff_time()
 
2829
        if (stat_value.st_mtime < state._cutoff_time
 
2830
            and stat_value.st_ctime < state._cutoff_time
 
2831
            and len(entry[1]) > 1
 
2832
            and entry[1][1][0] != 'a'):
 
2833
                # Could check for size changes for further optimised
 
2834
                # avoidance of sha1's. However the most prominent case of
 
2835
                # over-shaing is during initial add, which this catches.
 
2836
            link_or_sha1 = state._sha1_file(abspath)
 
2837
            entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
 
2838
                           executable, packed_stat)
 
2839
        else:
 
2840
            entry[1][0] = ('f', '', stat_value.st_size,
 
2841
                           executable, DirState.NULLSTAT)
 
2842
    elif minikind == 'd':
 
2843
        link_or_sha1 = None
 
2844
        entry[1][0] = ('d', '', 0, False, packed_stat)
 
2845
        if saved_minikind != 'd':
 
2846
            # This changed from something into a directory. Make sure we
 
2847
            # have a directory block for it. This doesn't happen very
 
2848
            # often, so this doesn't have to be super fast.
 
2849
            block_index, entry_index, dir_present, file_present = \
 
2850
                state._get_block_entry_index(entry[0][0], entry[0][1], 0)
 
2851
            state._ensure_block(block_index, entry_index,
 
2852
                               osutils.pathjoin(entry[0][0], entry[0][1]))
 
2853
    elif minikind == 'l':
 
2854
        link_or_sha1 = state._read_link(abspath, saved_link_or_sha1)
 
2855
        if state._cutoff_time is None:
 
2856
            state._sha_cutoff_time()
 
2857
        if (stat_value.st_mtime < state._cutoff_time
 
2858
            and stat_value.st_ctime < state._cutoff_time):
 
2859
            entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
 
2860
                           False, packed_stat)
 
2861
        else:
 
2862
            entry[1][0] = ('l', '', stat_value.st_size,
 
2863
                           False, DirState.NULLSTAT)
 
2864
    state._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
2865
    return link_or_sha1
 
2866
update_entry = py_update_entry
 
2867
 
 
2868
 
 
2869
class ProcessEntryPython(object):
 
2870
 
 
2871
    __slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id", "uninteresting",
 
2872
        "last_source_parent", "last_target_parent", "include_unchanged",
 
2873
        "use_filesystem_for_exec", "utf8_decode", "searched_specific_files",
 
2874
        "search_specific_files", "state", "source_index", "target_index",
 
2875
        "want_unversioned", "tree"]
 
2876
 
 
2877
    def __init__(self, include_unchanged, use_filesystem_for_exec,
 
2878
        search_specific_files, state, source_index, target_index,
 
2879
        want_unversioned, tree):
 
2880
        self.old_dirname_to_file_id = {}
 
2881
        self.new_dirname_to_file_id = {}
 
2882
        # Just a sentry, so that _process_entry can say that this
 
2883
        # record is handled, but isn't interesting to process (unchanged)
 
2884
        self.uninteresting = object()
 
2885
        # Using a list so that we can access the values and change them in
 
2886
        # nested scope. Each one is [path, file_id, entry]
 
2887
        self.last_source_parent = [None, None]
 
2888
        self.last_target_parent = [None, None]
 
2889
        self.include_unchanged = include_unchanged
 
2890
        self.use_filesystem_for_exec = use_filesystem_for_exec
 
2891
        self.utf8_decode = cache_utf8._utf8_decode
 
2892
        # for all search_indexs in each path at or under each element of
 
2893
        # search_specific_files, if the detail is relocated: add the id, and add the
 
2894
        # relocated path as one to search if its not searched already. If the
 
2895
        # detail is not relocated, add the id.
 
2896
        self.searched_specific_files = set()
 
2897
        self.search_specific_files = search_specific_files
 
2898
        self.state = state
 
2899
        self.source_index = source_index
 
2900
        self.target_index = target_index
 
2901
        self.want_unversioned = want_unversioned
 
2902
        self.tree = tree
 
2903
 
 
2904
    def _process_entry(self, entry, path_info, pathjoin=osutils.pathjoin):
 
2905
        """Compare an entry and real disk to generate delta information.
 
2906
 
 
2907
        :param path_info: top_relpath, basename, kind, lstat, abspath for
 
2908
            the path of entry. If None, then the path is considered absent.
 
2909
            (Perhaps we should pass in a concrete entry for this ?)
 
2910
            Basename is returned as a utf8 string because we expect this
 
2911
            tuple will be ignored, and don't want to take the time to
 
2912
            decode.
 
2913
        :return: None if these don't match
 
2914
                 A tuple of information about the change, or
 
2915
                 the object 'uninteresting' if these match, but are
 
2916
                 basically identical.
 
2917
        """
 
2918
        if self.source_index is None:
 
2919
            source_details = DirState.NULL_PARENT_DETAILS
 
2920
        else:
 
2921
            source_details = entry[1][self.source_index]
 
2922
        target_details = entry[1][self.target_index]
 
2923
        target_minikind = target_details[0]
 
2924
        if path_info is not None and target_minikind in 'fdlt':
 
2925
            if not (self.target_index == 0):
 
2926
                raise AssertionError()
 
2927
            link_or_sha1 = update_entry(self.state, entry,
 
2928
                abspath=path_info[4], stat_value=path_info[3])
 
2929
            # The entry may have been modified by update_entry
 
2930
            target_details = entry[1][self.target_index]
 
2931
            target_minikind = target_details[0]
 
2932
        else:
 
2933
            link_or_sha1 = None
 
2934
        file_id = entry[0][2]
 
2935
        source_minikind = source_details[0]
 
2936
        if source_minikind in 'fdltr' and target_minikind in 'fdlt':
 
2937
            # claimed content in both: diff
 
2938
            #   r    | fdlt   |      | add source to search, add id path move and perform
 
2939
            #        |        |      | diff check on source-target
 
2940
            #   r    | fdlt   |  a   | dangling file that was present in the basis.
 
2941
            #        |        |      | ???
 
2942
            if source_minikind in 'r':
 
2943
                # add the source to the search path to find any children it
 
2944
                # has.  TODO ? : only add if it is a container ?
 
2945
                if not osutils.is_inside_any(self.searched_specific_files,
 
2946
                                             source_details[1]):
 
2947
                    self.search_specific_files.add(source_details[1])
 
2948
                # generate the old path; this is needed for stating later
 
2949
                # as well.
 
2950
                old_path = source_details[1]
 
2951
                old_dirname, old_basename = os.path.split(old_path)
 
2952
                path = pathjoin(entry[0][0], entry[0][1])
 
2953
                old_entry = self.state._get_entry(self.source_index,
 
2954
                                             path_utf8=old_path)
 
2955
                # update the source details variable to be the real
 
2956
                # location.
 
2957
                if old_entry == (None, None):
 
2958
                    raise errors.CorruptDirstate(self.state._filename,
 
2959
                        "entry '%s/%s' is considered renamed from %r"
 
2960
                        " but source does not exist\n"
 
2961
                        "entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
 
2962
                source_details = old_entry[1][self.source_index]
 
2963
                source_minikind = source_details[0]
 
2964
            else:
 
2965
                old_dirname = entry[0][0]
 
2966
                old_basename = entry[0][1]
 
2967
                old_path = path = None
 
2968
            if path_info is None:
 
2969
                # the file is missing on disk, show as removed.
 
2970
                content_change = True
 
2971
                target_kind = None
 
2972
                target_exec = False
 
2973
            else:
 
2974
                # source and target are both versioned and disk file is present.
 
2975
                target_kind = path_info[2]
 
2976
                if target_kind == 'directory':
 
2977
                    if path is None:
 
2978
                        old_path = path = pathjoin(old_dirname, old_basename)
 
2979
                    self.new_dirname_to_file_id[path] = file_id
 
2980
                    if source_minikind != 'd':
 
2981
                        content_change = True
 
2982
                    else:
 
2983
                        # directories have no fingerprint
 
2984
                        content_change = False
 
2985
                    target_exec = False
 
2986
                elif target_kind == 'file':
 
2987
                    if source_minikind != 'f':
 
2988
                        content_change = True
 
2989
                    else:
 
2990
                        # If the size is the same, check the sha:
 
2991
                        if target_details[2] == source_details[2]:
 
2992
                            if link_or_sha1 is None:
 
2993
                                # Stat cache miss:
 
2994
                                file_obj = file(path_info[4], 'rb')
 
2995
                                try:
 
2996
                                    statvalue = os.fstat(file_obj.fileno())
 
2997
                                    link_or_sha1 = osutils.sha_file(file_obj)
 
2998
                                finally:
 
2999
                                    file_obj.close()
 
3000
                                self.state._observed_sha1(entry, link_or_sha1,
 
3001
                                    statvalue)
 
3002
                            content_change = (link_or_sha1 != source_details[1])
 
3003
                        else:
 
3004
                            # Size changed, so must be different
 
3005
                            content_change = True
 
3006
                    # Target details is updated at update_entry time
 
3007
                    if self.use_filesystem_for_exec:
 
3008
                        # We don't need S_ISREG here, because we are sure
 
3009
                        # we are dealing with a file.
 
3010
                        target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
 
3011
                    else:
 
3012
                        target_exec = target_details[3]
 
3013
                elif target_kind == 'symlink':
 
3014
                    if source_minikind != 'l':
 
3015
                        content_change = True
 
3016
                    else:
 
3017
                        content_change = (link_or_sha1 != source_details[1])
 
3018
                    target_exec = False
 
3019
                elif target_kind == 'tree-reference':
 
3020
                    if source_minikind != 't':
 
3021
                        content_change = True
 
3022
                    else:
 
3023
                        content_change = False
 
3024
                    target_exec = False
 
3025
                else:
 
3026
                    raise Exception, "unknown kind %s" % path_info[2]
 
3027
            if source_minikind == 'd':
 
3028
                if path is None:
 
3029
                    old_path = path = pathjoin(old_dirname, old_basename)
 
3030
                self.old_dirname_to_file_id[old_path] = file_id
 
3031
            # parent id is the entry for the path in the target tree
 
3032
            if old_dirname == self.last_source_parent[0]:
 
3033
                source_parent_id = self.last_source_parent[1]
 
3034
            else:
 
3035
                try:
 
3036
                    source_parent_id = self.old_dirname_to_file_id[old_dirname]
 
3037
                except KeyError:
 
3038
                    source_parent_entry = self.state._get_entry(self.source_index,
 
3039
                                                           path_utf8=old_dirname)
 
3040
                    source_parent_id = source_parent_entry[0][2]
 
3041
                if source_parent_id == entry[0][2]:
 
3042
                    # This is the root, so the parent is None
 
3043
                    source_parent_id = None
 
3044
                else:
 
3045
                    self.last_source_parent[0] = old_dirname
 
3046
                    self.last_source_parent[1] = source_parent_id
 
3047
            new_dirname = entry[0][0]
 
3048
            if new_dirname == self.last_target_parent[0]:
 
3049
                target_parent_id = self.last_target_parent[1]
 
3050
            else:
 
3051
                try:
 
3052
                    target_parent_id = self.new_dirname_to_file_id[new_dirname]
 
3053
                except KeyError:
 
3054
                    # TODO: We don't always need to do the lookup, because the
 
3055
                    #       parent entry will be the same as the source entry.
 
3056
                    target_parent_entry = self.state._get_entry(self.target_index,
 
3057
                                                           path_utf8=new_dirname)
 
3058
                    if target_parent_entry == (None, None):
 
3059
                        raise AssertionError(
 
3060
                            "Could not find target parent in wt: %s\nparent of: %s"
 
3061
                            % (new_dirname, entry))
 
3062
                    target_parent_id = target_parent_entry[0][2]
 
3063
                if target_parent_id == entry[0][2]:
 
3064
                    # This is the root, so the parent is None
 
3065
                    target_parent_id = None
 
3066
                else:
 
3067
                    self.last_target_parent[0] = new_dirname
 
3068
                    self.last_target_parent[1] = target_parent_id
 
3069
 
 
3070
            source_exec = source_details[3]
 
3071
            if (self.include_unchanged
 
3072
                or content_change
 
3073
                or source_parent_id != target_parent_id
 
3074
                or old_basename != entry[0][1]
 
3075
                or source_exec != target_exec
 
3076
                ):
 
3077
                if old_path is None:
 
3078
                    old_path = path = pathjoin(old_dirname, old_basename)
 
3079
                    old_path_u = self.utf8_decode(old_path)[0]
 
3080
                    path_u = old_path_u
 
3081
                else:
 
3082
                    old_path_u = self.utf8_decode(old_path)[0]
 
3083
                    if old_path == path:
 
3084
                        path_u = old_path_u
 
3085
                    else:
 
3086
                        path_u = self.utf8_decode(path)[0]
 
3087
                source_kind = DirState._minikind_to_kind[source_minikind]
 
3088
                return (entry[0][2],
 
3089
                       (old_path_u, path_u),
 
3090
                       content_change,
 
3091
                       (True, True),
 
3092
                       (source_parent_id, target_parent_id),
 
3093
                       (self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
 
3094
                       (source_kind, target_kind),
 
3095
                       (source_exec, target_exec))
 
3096
            else:
 
3097
                return self.uninteresting
 
3098
        elif source_minikind in 'a' and target_minikind in 'fdlt':
 
3099
            # looks like a new file
 
3100
            path = pathjoin(entry[0][0], entry[0][1])
 
3101
            # parent id is the entry for the path in the target tree
 
3102
            # TODO: these are the same for an entire directory: cache em.
 
3103
            parent_id = self.state._get_entry(self.target_index,
 
3104
                                         path_utf8=entry[0][0])[0][2]
 
3105
            if parent_id == entry[0][2]:
 
3106
                parent_id = None
 
3107
            if path_info is not None:
 
3108
                # Present on disk:
 
3109
                if self.use_filesystem_for_exec:
 
3110
                    # We need S_ISREG here, because we aren't sure if this
 
3111
                    # is a file or not.
 
3112
                    target_exec = bool(
 
3113
                        stat.S_ISREG(path_info[3].st_mode)
 
3114
                        and stat.S_IEXEC & path_info[3].st_mode)
 
3115
                else:
 
3116
                    target_exec = target_details[3]
 
3117
                return (entry[0][2],
 
3118
                       (None, self.utf8_decode(path)[0]),
 
3119
                       True,
 
3120
                       (False, True),
 
3121
                       (None, parent_id),
 
3122
                       (None, self.utf8_decode(entry[0][1])[0]),
 
3123
                       (None, path_info[2]),
 
3124
                       (None, target_exec))
 
3125
            else:
 
3126
                # Its a missing file, report it as such.
 
3127
                return (entry[0][2],
 
3128
                       (None, self.utf8_decode(path)[0]),
 
3129
                       False,
 
3130
                       (False, True),
 
3131
                       (None, parent_id),
 
3132
                       (None, self.utf8_decode(entry[0][1])[0]),
 
3133
                       (None, None),
 
3134
                       (None, False))
 
3135
        elif source_minikind in 'fdlt' and target_minikind in 'a':
 
3136
            # unversioned, possibly, or possibly not deleted: we dont care.
 
3137
            # if its still on disk, *and* theres no other entry at this
 
3138
            # path [we dont know this in this routine at the moment -
 
3139
            # perhaps we should change this - then it would be an unknown.
 
3140
            old_path = pathjoin(entry[0][0], entry[0][1])
 
3141
            # parent id is the entry for the path in the target tree
 
3142
            parent_id = self.state._get_entry(self.source_index, path_utf8=entry[0][0])[0][2]
 
3143
            if parent_id == entry[0][2]:
 
3144
                parent_id = None
 
3145
            return (entry[0][2],
 
3146
                   (self.utf8_decode(old_path)[0], None),
 
3147
                   True,
 
3148
                   (True, False),
 
3149
                   (parent_id, None),
 
3150
                   (self.utf8_decode(entry[0][1])[0], None),
 
3151
                   (DirState._minikind_to_kind[source_minikind], None),
 
3152
                   (source_details[3], None))
 
3153
        elif source_minikind in 'fdlt' and target_minikind in 'r':
 
3154
            # a rename; could be a true rename, or a rename inherited from
 
3155
            # a renamed parent. TODO: handle this efficiently. Its not
 
3156
            # common case to rename dirs though, so a correct but slow
 
3157
            # implementation will do.
 
3158
            if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
 
3159
                self.search_specific_files.add(target_details[1])
 
3160
        elif source_minikind in 'ra' and target_minikind in 'ra':
 
3161
            # neither of the selected trees contain this file,
 
3162
            # so skip over it. This is not currently directly tested, but
 
3163
            # is indirectly via test_too_much.TestCommands.test_conflicts.
 
3164
            pass
 
3165
        else:
 
3166
            raise AssertionError("don't know how to compare "
 
3167
                "source_minikind=%r, target_minikind=%r"
 
3168
                % (source_minikind, target_minikind))
 
3169
            ## import pdb;pdb.set_trace()
 
3170
        return None
 
3171
 
 
3172
    def __iter__(self):
 
3173
        return self
 
3174
 
 
3175
    def iter_changes(self):
 
3176
        """Iterate over the changes."""
 
3177
        utf8_decode = cache_utf8._utf8_decode
 
3178
        _cmp_by_dirs = cmp_by_dirs
 
3179
        _process_entry = self._process_entry
 
3180
        uninteresting = self.uninteresting
 
3181
        search_specific_files = self.search_specific_files
 
3182
        searched_specific_files = self.searched_specific_files
 
3183
        splitpath = osutils.splitpath
 
3184
        # sketch:
 
3185
        # compare source_index and target_index at or under each element of search_specific_files.
 
3186
        # follow the following comparison table. Note that we only want to do diff operations when
 
3187
        # the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
 
3188
        # for the target.
 
3189
        # cases:
 
3190
        #
 
3191
        # Source | Target | disk | action
 
3192
        #   r    | fdlt   |      | add source to search, add id path move and perform
 
3193
        #        |        |      | diff check on source-target
 
3194
        #   r    | fdlt   |  a   | dangling file that was present in the basis.
 
3195
        #        |        |      | ???
 
3196
        #   r    |  a     |      | add source to search
 
3197
        #   r    |  a     |  a   |
 
3198
        #   r    |  r     |      | this path is present in a non-examined tree, skip.
 
3199
        #   r    |  r     |  a   | this path is present in a non-examined tree, skip.
 
3200
        #   a    | fdlt   |      | add new id
 
3201
        #   a    | fdlt   |  a   | dangling locally added file, skip
 
3202
        #   a    |  a     |      | not present in either tree, skip
 
3203
        #   a    |  a     |  a   | not present in any tree, skip
 
3204
        #   a    |  r     |      | not present in either tree at this path, skip as it
 
3205
        #        |        |      | may not be selected by the users list of paths.
 
3206
        #   a    |  r     |  a   | not present in either tree at this path, skip as it
 
3207
        #        |        |      | may not be selected by the users list of paths.
 
3208
        #  fdlt  | fdlt   |      | content in both: diff them
 
3209
        #  fdlt  | fdlt   |  a   | deleted locally, but not unversioned - show as deleted ?
 
3210
        #  fdlt  |  a     |      | unversioned: output deleted id for now
 
3211
        #  fdlt  |  a     |  a   | unversioned and deleted: output deleted id
 
3212
        #  fdlt  |  r     |      | relocated in this tree, so add target to search.
 
3213
        #        |        |      | Dont diff, we will see an r,fd; pair when we reach
 
3214
        #        |        |      | this id at the other path.
 
3215
        #  fdlt  |  r     |  a   | relocated in this tree, so add target to search.
 
3216
        #        |        |      | Dont diff, we will see an r,fd; pair when we reach
 
3217
        #        |        |      | this id at the other path.
 
3218
 
 
3219
        # TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
 
3220
        #       keeping a cache of directories that we have seen.
 
3221
 
 
3222
        while search_specific_files:
 
3223
            # TODO: the pending list should be lexically sorted?  the
 
3224
            # interface doesn't require it.
 
3225
            current_root = search_specific_files.pop()
 
3226
            current_root_unicode = current_root.decode('utf8')
 
3227
            searched_specific_files.add(current_root)
 
3228
            # process the entries for this containing directory: the rest will be
 
3229
            # found by their parents recursively.
 
3230
            root_entries = self.state._entries_for_path(current_root)
 
3231
            root_abspath = self.tree.abspath(current_root_unicode)
 
3232
            try:
 
3233
                root_stat = os.lstat(root_abspath)
 
3234
            except OSError, e:
 
3235
                if e.errno == errno.ENOENT:
 
3236
                    # the path does not exist: let _process_entry know that.
 
3237
                    root_dir_info = None
 
3238
                else:
 
3239
                    # some other random error: hand it up.
 
3240
                    raise
 
3241
            else:
 
3242
                root_dir_info = ('', current_root,
 
3243
                    osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
 
3244
                    root_abspath)
 
3245
                if root_dir_info[2] == 'directory':
 
3246
                    if self.tree._directory_is_tree_reference(
 
3247
                        current_root.decode('utf8')):
 
3248
                        root_dir_info = root_dir_info[:2] + \
 
3249
                            ('tree-reference',) + root_dir_info[3:]
 
3250
 
 
3251
            if not root_entries and not root_dir_info:
 
3252
                # this specified path is not present at all, skip it.
 
3253
                continue
 
3254
            path_handled = False
 
3255
            for entry in root_entries:
 
3256
                result = _process_entry(entry, root_dir_info)
 
3257
                if result is not None:
 
3258
                    path_handled = True
 
3259
                    if result is not uninteresting:
 
3260
                        yield result
 
3261
            if self.want_unversioned and not path_handled and root_dir_info:
 
3262
                new_executable = bool(
 
3263
                    stat.S_ISREG(root_dir_info[3].st_mode)
 
3264
                    and stat.S_IEXEC & root_dir_info[3].st_mode)
 
3265
                yield (None,
 
3266
                       (None, current_root_unicode),
 
3267
                       True,
 
3268
                       (False, False),
 
3269
                       (None, None),
 
3270
                       (None, splitpath(current_root_unicode)[-1]),
 
3271
                       (None, root_dir_info[2]),
 
3272
                       (None, new_executable)
 
3273
                      )
 
3274
            initial_key = (current_root, '', '')
 
3275
            block_index, _ = self.state._find_block_index_from_key(initial_key)
 
3276
            if block_index == 0:
 
3277
                # we have processed the total root already, but because the
 
3278
                # initial key matched it we should skip it here.
 
3279
                block_index +=1
 
3280
            if root_dir_info and root_dir_info[2] == 'tree-reference':
 
3281
                current_dir_info = None
 
3282
            else:
 
3283
                dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
 
3284
                try:
 
3285
                    current_dir_info = dir_iterator.next()
 
3286
                except OSError, e:
 
3287
                    # on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
 
3288
                    # python 2.5 has e.errno == EINVAL,
 
3289
                    #            and e.winerror == ERROR_DIRECTORY
 
3290
                    e_winerror = getattr(e, 'winerror', None)
 
3291
                    win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
 
3292
                    # there may be directories in the inventory even though
 
3293
                    # this path is not a file on disk: so mark it as end of
 
3294
                    # iterator
 
3295
                    if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
 
3296
                        current_dir_info = None
 
3297
                    elif (sys.platform == 'win32'
 
3298
                          and (e.errno in win_errors
 
3299
                               or e_winerror in win_errors)):
 
3300
                        current_dir_info = None
 
3301
                    else:
 
3302
                        raise
 
3303
                else:
 
3304
                    if current_dir_info[0][0] == '':
 
3305
                        # remove .bzr from iteration
 
3306
                        bzr_index = bisect.bisect_left(current_dir_info[1], ('.bzr',))
 
3307
                        if current_dir_info[1][bzr_index][0] != '.bzr':
 
3308
                            raise AssertionError()
 
3309
                        del current_dir_info[1][bzr_index]
 
3310
            # walk until both the directory listing and the versioned metadata
 
3311
            # are exhausted.
 
3312
            if (block_index < len(self.state._dirblocks) and
 
3313
                osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
 
3314
                current_block = self.state._dirblocks[block_index]
 
3315
            else:
 
3316
                current_block = None
 
3317
            while (current_dir_info is not None or
 
3318
                   current_block is not None):
 
3319
                if (current_dir_info and current_block
 
3320
                    and current_dir_info[0][0] != current_block[0]):
 
3321
                    if _cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
 
3322
                        # filesystem data refers to paths not covered by the dirblock.
 
3323
                        # this has two possibilities:
 
3324
                        # A) it is versioned but empty, so there is no block for it
 
3325
                        # B) it is not versioned.
 
3326
 
 
3327
                        # if (A) then we need to recurse into it to check for
 
3328
                        # new unknown files or directories.
 
3329
                        # if (B) then we should ignore it, because we don't
 
3330
                        # recurse into unknown directories.
 
3331
                        path_index = 0
 
3332
                        while path_index < len(current_dir_info[1]):
 
3333
                                current_path_info = current_dir_info[1][path_index]
 
3334
                                if self.want_unversioned:
 
3335
                                    if current_path_info[2] == 'directory':
 
3336
                                        if self.tree._directory_is_tree_reference(
 
3337
                                            current_path_info[0].decode('utf8')):
 
3338
                                            current_path_info = current_path_info[:2] + \
 
3339
                                                ('tree-reference',) + current_path_info[3:]
 
3340
                                    new_executable = bool(
 
3341
                                        stat.S_ISREG(current_path_info[3].st_mode)
 
3342
                                        and stat.S_IEXEC & current_path_info[3].st_mode)
 
3343
                                    yield (None,
 
3344
                                        (None, utf8_decode(current_path_info[0])[0]),
 
3345
                                        True,
 
3346
                                        (False, False),
 
3347
                                        (None, None),
 
3348
                                        (None, utf8_decode(current_path_info[1])[0]),
 
3349
                                        (None, current_path_info[2]),
 
3350
                                        (None, new_executable))
 
3351
                                # dont descend into this unversioned path if it is
 
3352
                                # a dir
 
3353
                                if current_path_info[2] in ('directory',
 
3354
                                                            'tree-reference'):
 
3355
                                    del current_dir_info[1][path_index]
 
3356
                                    path_index -= 1
 
3357
                                path_index += 1
 
3358
 
 
3359
                        # This dir info has been handled, go to the next
 
3360
                        try:
 
3361
                            current_dir_info = dir_iterator.next()
 
3362
                        except StopIteration:
 
3363
                            current_dir_info = None
 
3364
                    else:
 
3365
                        # We have a dirblock entry for this location, but there
 
3366
                        # is no filesystem path for this. This is most likely
 
3367
                        # because a directory was removed from the disk.
 
3368
                        # We don't have to report the missing directory,
 
3369
                        # because that should have already been handled, but we
 
3370
                        # need to handle all of the files that are contained
 
3371
                        # within.
 
3372
                        for current_entry in current_block[1]:
 
3373
                            # entry referring to file not present on disk.
 
3374
                            # advance the entry only, after processing.
 
3375
                            result = _process_entry(current_entry, None)
 
3376
                            if result is not None:
 
3377
                                if result is not uninteresting:
 
3378
                                    yield result
 
3379
                        block_index +=1
 
3380
                        if (block_index < len(self.state._dirblocks) and
 
3381
                            osutils.is_inside(current_root,
 
3382
                                              self.state._dirblocks[block_index][0])):
 
3383
                            current_block = self.state._dirblocks[block_index]
 
3384
                        else:
 
3385
                            current_block = None
 
3386
                    continue
 
3387
                entry_index = 0
 
3388
                if current_block and entry_index < len(current_block[1]):
 
3389
                    current_entry = current_block[1][entry_index]
 
3390
                else:
 
3391
                    current_entry = None
 
3392
                advance_entry = True
 
3393
                path_index = 0
 
3394
                if current_dir_info and path_index < len(current_dir_info[1]):
 
3395
                    current_path_info = current_dir_info[1][path_index]
 
3396
                    if current_path_info[2] == 'directory':
 
3397
                        if self.tree._directory_is_tree_reference(
 
3398
                            current_path_info[0].decode('utf8')):
 
3399
                            current_path_info = current_path_info[:2] + \
 
3400
                                ('tree-reference',) + current_path_info[3:]
 
3401
                else:
 
3402
                    current_path_info = None
 
3403
                advance_path = True
 
3404
                path_handled = False
 
3405
                while (current_entry is not None or
 
3406
                    current_path_info is not None):
 
3407
                    if current_entry is None:
 
3408
                        # the check for path_handled when the path is adnvaced
 
3409
                        # will yield this path if needed.
 
3410
                        pass
 
3411
                    elif current_path_info is None:
 
3412
                        # no path is fine: the per entry code will handle it.
 
3413
                        result = _process_entry(current_entry, current_path_info)
 
3414
                        if result is not None:
 
3415
                            if result is not uninteresting:
 
3416
                                yield result
 
3417
                    elif (current_entry[0][1] != current_path_info[1]
 
3418
                          or current_entry[1][self.target_index][0] in 'ar'):
 
3419
                        # The current path on disk doesn't match the dirblock
 
3420
                        # record. Either the dirblock is marked as absent, or
 
3421
                        # the file on disk is not present at all in the
 
3422
                        # dirblock. Either way, report about the dirblock
 
3423
                        # entry, and let other code handle the filesystem one.
 
3424
 
 
3425
                        # Compare the basename for these files to determine
 
3426
                        # which comes first
 
3427
                        if current_path_info[1] < current_entry[0][1]:
 
3428
                            # extra file on disk: pass for now, but only
 
3429
                            # increment the path, not the entry
 
3430
                            advance_entry = False
 
3431
                        else:
 
3432
                            # entry referring to file not present on disk.
 
3433
                            # advance the entry only, after processing.
 
3434
                            result = _process_entry(current_entry, None)
 
3435
                            if result is not None:
 
3436
                                if result is not uninteresting:
 
3437
                                    yield result
 
3438
                            advance_path = False
 
3439
                    else:
 
3440
                        result = _process_entry(current_entry, current_path_info)
 
3441
                        if result is not None:
 
3442
                            path_handled = True
 
3443
                            if result is not uninteresting:
 
3444
                                yield result
 
3445
                    if advance_entry and current_entry is not None:
 
3446
                        entry_index += 1
 
3447
                        if entry_index < len(current_block[1]):
 
3448
                            current_entry = current_block[1][entry_index]
 
3449
                        else:
 
3450
                            current_entry = None
 
3451
                    else:
 
3452
                        advance_entry = True # reset the advance flaga
 
3453
                    if advance_path and current_path_info is not None:
 
3454
                        if not path_handled:
 
3455
                            # unversioned in all regards
 
3456
                            if self.want_unversioned:
 
3457
                                new_executable = bool(
 
3458
                                    stat.S_ISREG(current_path_info[3].st_mode)
 
3459
                                    and stat.S_IEXEC & current_path_info[3].st_mode)
 
3460
                                try:
 
3461
                                    relpath_unicode = utf8_decode(current_path_info[0])[0]
 
3462
                                except UnicodeDecodeError:
 
3463
                                    raise errors.BadFilenameEncoding(
 
3464
                                        current_path_info[0], osutils._fs_enc)
 
3465
                                yield (None,
 
3466
                                    (None, relpath_unicode),
 
3467
                                    True,
 
3468
                                    (False, False),
 
3469
                                    (None, None),
 
3470
                                    (None, utf8_decode(current_path_info[1])[0]),
 
3471
                                    (None, current_path_info[2]),
 
3472
                                    (None, new_executable))
 
3473
                            # dont descend into this unversioned path if it is
 
3474
                            # a dir
 
3475
                            if current_path_info[2] in ('directory'):
 
3476
                                del current_dir_info[1][path_index]
 
3477
                                path_index -= 1
 
3478
                        # dont descend the disk iterator into any tree
 
3479
                        # paths.
 
3480
                        if current_path_info[2] == 'tree-reference':
 
3481
                            del current_dir_info[1][path_index]
 
3482
                            path_index -= 1
 
3483
                        path_index += 1
 
3484
                        if path_index < len(current_dir_info[1]):
 
3485
                            current_path_info = current_dir_info[1][path_index]
 
3486
                            if current_path_info[2] == 'directory':
 
3487
                                if self.tree._directory_is_tree_reference(
 
3488
                                    current_path_info[0].decode('utf8')):
 
3489
                                    current_path_info = current_path_info[:2] + \
 
3490
                                        ('tree-reference',) + current_path_info[3:]
 
3491
                        else:
 
3492
                            current_path_info = None
 
3493
                        path_handled = False
 
3494
                    else:
 
3495
                        advance_path = True # reset the advance flagg.
 
3496
                if current_block is not None:
 
3497
                    block_index += 1
 
3498
                    if (block_index < len(self.state._dirblocks) and
 
3499
                        osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
 
3500
                        current_block = self.state._dirblocks[block_index]
 
3501
                    else:
 
3502
                        current_block = None
 
3503
                if current_dir_info is not None:
 
3504
                    try:
 
3505
                        current_dir_info = dir_iterator.next()
 
3506
                    except StopIteration:
 
3507
                        current_dir_info = None
 
3508
_process_entry = ProcessEntryPython
 
3509
 
 
3510
 
2759
3511
# Try to load the compiled form if possible
2760
3512
try:
2761
3513
    from bzrlib._dirstate_helpers_c import (
2764
3516
        _bisect_path_left_c as _bisect_path_left,
2765
3517
        _bisect_path_right_c as _bisect_path_right,
2766
3518
        cmp_by_dirs_c as cmp_by_dirs,
 
3519
        ProcessEntryC as _process_entry,
 
3520
        update_entry as update_entry,
2767
3521
        )
2768
3522
except ImportError:
2769
3523
    from bzrlib._dirstate_helpers_py import (