~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/dirstate.py

merge hpss changes

Show diffs side-by-side

added added

removed removed

Lines of Context:
200
200
"""
201
201
 
202
202
 
203
 
import binascii
 
203
import base64
204
204
import bisect
205
205
import errno
206
206
import os
207
207
from stat import S_IEXEC
208
 
import stat
209
208
import struct
210
209
import sys
211
210
import time
224
223
    """This just keeps track of information as we are bisecting."""
225
224
 
226
225
 
227
 
def pack_stat(st, _encode=binascii.b2a_base64, _pack=struct.pack):
228
 
    """Convert stat values into a packed representation."""
229
 
    # jam 20060614 it isn't really worth removing more entries if we
230
 
    # are going to leave it in packed form.
231
 
    # With only st_mtime and st_mode filesize is 5.5M and read time is 275ms
232
 
    # With all entries filesize is 5.9M and read time is mabye 280ms
233
 
    # well within the noise margin
234
 
 
235
 
    # base64 encoding always adds a final newline, so strip it off
236
 
    # The current version
237
 
    return _encode(_pack('>LLLLLL'
238
 
        , st.st_size, int(st.st_mtime), int(st.st_ctime)
239
 
        , st.st_dev, st.st_ino & 0xFFFFFFFF, st.st_mode))[:-1]
240
 
    # This is 0.060s / 1.520s faster by not encoding as much information
241
 
    # return _encode(_pack('>LL', int(st.st_mtime), st.st_mode))[:-1]
242
 
    # This is not strictly faster than _encode(_pack())[:-1]
243
 
    # return '%X.%X.%X.%X.%X.%X' % (
244
 
    #      st.st_size, int(st.st_mtime), int(st.st_ctime),
245
 
    #      st.st_dev, st.st_ino, st.st_mode)
246
 
    # Similar to the _encode(_pack('>LL'))
247
 
    # return '%X.%X' % (int(st.st_mtime), st.st_mode)
248
 
 
249
 
 
250
226
class DirState(object):
251
227
    """Record directory and metadata state for fast access.
252
228
 
279
255
            'r': 'relocated',
280
256
            't': 'tree-reference',
281
257
        }
282
 
    _stat_to_minikind = {
283
 
        stat.S_IFDIR:'d',
284
 
        stat.S_IFREG:'f',
285
 
        stat.S_IFLNK:'l',
286
 
    }
287
258
    _to_yesno = {True:'y', False: 'n'} # TODO profile the performance gain
288
259
     # of using int conversion rather than a dict here. AND BLAME ANDREW IF
289
260
     # it is faster.
1088
1059
            raise
1089
1060
        return result
1090
1061
 
1091
 
    def update_entry(self, entry, abspath, stat_value,
1092
 
                     _stat_to_minikind=_stat_to_minikind,
1093
 
                     _pack_stat=pack_stat):
 
1062
    def update_entry(self, entry, abspath, stat_value=None):
1094
1063
        """Update the entry based on what is actually on disk.
1095
1064
 
1096
1065
        :param entry: This is the dirblock entry for the file in question.
1100
1069
        :return: The sha1 hexdigest of the file (40 bytes) or link target of a
1101
1070
                symlink.
1102
1071
        """
 
1072
        # This code assumes that the entry passed in is directly held in one of
 
1073
        # the internal _dirblocks. So the dirblock state must have already been
 
1074
        # read.
 
1075
        assert self._dirblock_state != DirState.NOT_IN_MEMORY
 
1076
        if stat_value is None:
 
1077
            try:
 
1078
                # We could inline os.lstat but the common case is that
 
1079
                # stat_value will be passed in, not read here.
 
1080
                stat_value = self._lstat(abspath, entry)
 
1081
            except (OSError, IOError), e:
 
1082
                if e.errno in (errno.ENOENT, errno.EACCES,
 
1083
                               errno.EPERM):
 
1084
                    # The entry is missing, consider it gone
 
1085
                    return None
 
1086
                raise
 
1087
 
 
1088
        kind = osutils.file_kind_from_stat_mode(stat_value.st_mode)
1103
1089
        try:
1104
 
            minikind = _stat_to_minikind[stat_value.st_mode & 0170000]
1105
 
        except KeyError:
1106
 
            # Unhandled kind
 
1090
            minikind = DirState._kind_to_minikind[kind]
 
1091
        except KeyError: # Unknown kind
1107
1092
            return None
1108
 
        packed_stat = _pack_stat(stat_value)
 
1093
        packed_stat = pack_stat(stat_value)
1109
1094
        (saved_minikind, saved_link_or_sha1, saved_file_size,
1110
1095
         saved_executable, saved_packed_stat) = entry[1][0]
1111
1096
 
1112
1097
        if (minikind == saved_minikind
1113
 
            and packed_stat == saved_packed_stat):
1114
 
            # The stat hasn't changed since we saved, so we can re-use the
1115
 
            # saved sha hash.
 
1098
            and packed_stat == saved_packed_stat
 
1099
            # size should also be in packed_stat
 
1100
            and saved_file_size == stat_value.st_size):
 
1101
            # The stat hasn't changed since we saved, so we can potentially
 
1102
            # re-use the saved sha hash.
1116
1103
            if minikind == 'd':
1117
1104
                return None
1118
1105
 
1119
 
            # size should also be in packed_stat
1120
 
            if saved_file_size == stat_value.st_size:
 
1106
            if self._cutoff_time is None:
 
1107
                self._sha_cutoff_time()
 
1108
 
 
1109
            if (stat_value.st_mtime < self._cutoff_time
 
1110
                and stat_value.st_ctime < self._cutoff_time):
 
1111
                # Return the existing fingerprint
1121
1112
                return saved_link_or_sha1
1122
1113
 
1123
1114
        # If we have gotten this far, that means that we need to actually
1127
1118
            link_or_sha1 = self._sha1_file(abspath, entry)
1128
1119
            executable = self._is_executable(stat_value.st_mode,
1129
1120
                                             saved_executable)
1130
 
            if self._cutoff_time is None:
1131
 
                self._sha_cutoff_time()
1132
 
            if (stat_value.st_mtime < self._cutoff_time
1133
 
                and stat_value.st_ctime < self._cutoff_time):
1134
 
                entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
1135
 
                               executable, packed_stat)
1136
 
            else:
1137
 
                entry[1][0] = ('f', '', stat_value.st_size,
1138
 
                               executable, DirState.NULLSTAT)
 
1121
            entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
 
1122
                           executable, packed_stat)
1139
1123
        elif minikind == 'd':
1140
1124
            link_or_sha1 = None
1141
1125
            entry[1][0] = ('d', '', 0, False, packed_stat)
1149
1133
                                   osutils.pathjoin(entry[0][0], entry[0][1]))
1150
1134
        elif minikind == 'l':
1151
1135
            link_or_sha1 = self._read_link(abspath, saved_link_or_sha1)
1152
 
            if self._cutoff_time is None:
1153
 
                self._sha_cutoff_time()
1154
 
            if (stat_value.st_mtime < self._cutoff_time
1155
 
                and stat_value.st_ctime < self._cutoff_time):
1156
 
                entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
1157
 
                               False, packed_stat)
1158
 
            else:
1159
 
                entry[1][0] = ('l', '', stat_value.st_size,
1160
 
                               False, DirState.NULLSTAT)
 
1136
            entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
 
1137
                           False, packed_stat)
1161
1138
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1162
1139
        return link_or_sha1
1163
1140
 
1430
1407
        The new dirstate will be an empty tree - that is it has no parents,
1431
1408
        and only a root node - which has id ROOT_ID.
1432
1409
 
 
1410
        The object will be write locked when returned to the caller,
 
1411
        unless there was an exception in the writing, in which case it
 
1412
        will be unlocked.
 
1413
 
1433
1414
        :param path: The name of the file for the dirstate.
1434
 
        :return: A write-locked DirState object.
 
1415
        :return: A DirState object.
1435
1416
        """
1436
1417
        # This constructs a new DirState object on a path, sets the _state_file
1437
1418
        # to a new empty file for that path. It then calls _set_data() with our
1792
1773
        :param ghosts: A list of the revision_ids that are ghosts at the time
1793
1774
            of setting.
1794
1775
        """ 
 
1776
        self._validate()
1795
1777
        # TODO: generate a list of parent indexes to preserve to save 
1796
1778
        # processing specific parent trees. In the common case one tree will
1797
1779
        # be preserved - the left most parent.
1922
1904
        self._header_state = DirState.IN_MEMORY_MODIFIED
1923
1905
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1924
1906
        self._id_index = id_index
 
1907
        self._validate()
1925
1908
 
1926
1909
    def _sort_entries(self, entry_list):
1927
1910
        """Given a list of entries, sort them into the right order.
2007
1990
                # both sides are dealt with, move on
2008
1991
                current_old = advance(old_iterator)
2009
1992
                current_new = advance(new_iterator)
2010
 
            elif (new_entry_key[0].split('/') < current_old[0][0].split('/')
2011
 
                  and new_entry_key[1:] < current_old[0][1:]):
 
1993
            elif new_entry_key < current_old[0]:
2012
1994
                # new comes before:
2013
1995
                # add a entry for this and advance new
2014
1996
                self.update_minimal(new_entry_key, current_new_minikind,
2234
2216
                    "dirblock for %r is not sorted:\n%s" % \
2235
2217
                    (dirblock[0], pformat(dirblock)))
2236
2218
 
2237
 
 
2238
 
        def check_valid_parent():
2239
 
            """Check that the current entry has a valid parent.
2240
 
 
2241
 
            This makes sure that the parent has a record,
2242
 
            and that the parent isn't marked as "absent" in the
2243
 
            current tree. (It is invalid to have a non-absent file in an absent
2244
 
            directory.)
2245
 
            """
2246
 
            if entry[0][0:2] == ('', ''):
2247
 
                # There should be no parent for the root row
2248
 
                return
2249
 
            parent_entry = self._get_entry(tree_index, path_utf8=entry[0][0])
2250
 
            if parent_entry == (None, None):
2251
 
                raise AssertionError(
2252
 
                    "no parent entry for: %s in tree %s"
2253
 
                    % (this_path, tree_index))
2254
 
            if parent_entry[1][tree_index][0] != 'd':
2255
 
                raise AssertionError(
2256
 
                    "Parent entry for %s is not marked as a valid"
2257
 
                    " directory. %s" % (this_path, parent_entry,))
2258
 
 
2259
2219
        # For each file id, for each tree: either
2260
2220
        # the file id is not present at all; all rows with that id in the
2261
2221
        # key have it marked as 'absent'
2302
2262
                            raise AssertionError(
2303
2263
                            "entry %r inconsistent with previous path %r" % \
2304
2264
                            (entry, previous_path))
2305
 
                        check_valid_parent()
2306
2265
                else:
2307
2266
                    if minikind == 'a':
2308
2267
                        # absent; should not occur anywhere else
2312
2271
                        this_tree_map[file_id] = tree_state[1]
2313
2272
                    else:
2314
2273
                        this_tree_map[file_id] = this_path
2315
 
                        check_valid_parent()
2316
2274
 
2317
2275
    def _wipe_state(self):
2318
2276
        """Forget all state information about the dirstate."""
2401
2359
        if cur_split < dirname_split: lo = mid+1
2402
2360
        else: hi = mid
2403
2361
    return lo
 
2362
 
 
2363
 
 
2364
 
 
2365
def pack_stat(st, _encode=base64.encodestring, _pack=struct.pack):
 
2366
    """Convert stat values into a packed representation."""
 
2367
    # jam 20060614 it isn't really worth removing more entries if we
 
2368
    # are going to leave it in packed form.
 
2369
    # With only st_mtime and st_mode filesize is 5.5M and read time is 275ms
 
2370
    # With all entries filesize is 5.9M and read time is mabye 280ms
 
2371
    # well within the noise margin
 
2372
 
 
2373
    # base64.encode always adds a final newline, so strip it off
 
2374
    return _encode(_pack('>LLLLLL'
 
2375
        , st.st_size, int(st.st_mtime), int(st.st_ctime)
 
2376
        , st.st_dev, st.st_ino & 0xFFFFFFFF, st.st_mode))[:-1]