1176
def update_by_delta(self, delta):
1177
"""Apply an inventory delta to the dirstate for tree 0
1179
:param delta: An inventory delta. See Inventory.apply_delta for
1182
self._read_dirblocks_if_needed()
1185
for old_path, new_path, file_id, inv_entry in sorted(delta, reverse=True):
1186
if (file_id in insertions) or (file_id in removals):
1187
raise AssertionError("repeated file id in delta %r" % (file_id,))
1188
if old_path is not None:
1189
old_path = old_path.encode('utf-8')
1190
removals[file_id] = old_path
1191
if new_path is not None:
1192
new_path = new_path.encode('utf-8')
1193
dirname, basename = osutils.split(new_path)
1194
key = (dirname, basename, file_id)
1195
minikind = DirState._kind_to_minikind[inv_entry.kind]
1197
fingerprint = inv_entry.reference_revision
1200
insertions[file_id] = (key, minikind, inv_entry.executable,
1201
fingerprint, new_path)
1202
# Transform moves into delete+add pairs
1203
if None not in (old_path, new_path):
1204
for child in self._iter_child_entries(0, old_path):
1205
if child[0][2] in insertions or child[0][2] in removals:
1207
child_dirname = child[0][0]
1208
child_basename = child[0][1]
1209
minikind = child[1][0][0]
1210
fingerprint = child[1][0][4]
1211
executable = child[1][0][3]
1212
old_child_path = osutils.pathjoin(child[0][0],
1214
removals[child[0][2]] = old_child_path
1215
child_suffix = child_dirname[len(old_path):]
1216
new_child_dirname = (new_path + child_suffix)
1217
key = (new_child_dirname, child_basename, child[0][2])
1218
new_child_path = os.path.join(new_child_dirname,
1220
insertions[child[0][2]] = (key, minikind, executable,
1221
fingerprint, new_child_path)
1222
self._apply_removals(removals.values())
1223
self._apply_insertions(insertions.values())
1225
def _apply_removals(self, removals):
1226
for path in sorted(removals, reverse=True):
1227
dirname, basename = osutils.split(path)
1228
block_i, entry_i, d_present, f_present = \
1229
self._get_block_entry_index(dirname, basename, 0)
1230
entry = self._dirblocks[block_i][1][entry_i]
1231
self._make_absent(entry)
1232
# See if we have a malformed delta: deleting a directory must not
1233
# leave crud behind. This increases the number of bisects needed
1234
# substantially, but deletion or renames of large numbers of paths
1235
# is rare enough it shouldn't be an issue (famous last words?) RBC
1237
block_i, entry_i, d_present, f_present = \
1238
self._get_block_entry_index(path, '', 0)
1240
# The dir block is still present in the dirstate; this could
1241
# be due to it being in a parent tree, or a corrupt delta.
1242
for child_entry in self._dirblocks[block_i][1]:
1243
if child_entry[1][0][0] not in ('r', 'a'):
1244
raise errors.InconsistentDelta(path, entry[0][2],
1245
"The file id was deleted but its children were "
1248
def _apply_insertions(self, adds):
1249
for key, minikind, executable, fingerprint, path_utf8 in sorted(adds):
1250
self.update_minimal(key, minikind, executable, fingerprint,
1251
path_utf8=path_utf8)
1253
def update_basis_by_delta(self, delta, new_revid):
1254
"""Update the parents of this tree after a commit.
1256
This gives the tree one parent, with revision id new_revid. The
1257
inventory delta is applied to the current basis tree to generate the
1258
inventory for the parent new_revid, and all other parent trees are
1261
Note that an exception during the operation of this method will leave
1262
the dirstate in a corrupt state where it should not be saved.
1264
Finally, we expect all changes to be synchronising the basis tree with
1267
:param new_revid: The new revision id for the trees parent.
1268
:param delta: An inventory delta (see apply_inventory_delta) describing
1269
the changes from the current left most parent revision to new_revid.
1271
self._read_dirblocks_if_needed()
1272
self._discard_merge_parents()
1273
if self._ghosts != []:
1274
raise NotImplementedError(self.update_basis_by_delta)
1275
if len(self._parents) == 0:
1276
# setup a blank tree, the most simple way.
1277
empty_parent = DirState.NULL_PARENT_DETAILS
1278
for entry in self._iter_entries():
1279
entry[1].append(empty_parent)
1280
self._parents.append(new_revid)
1282
self._parents[0] = new_revid
1284
delta = sorted(delta, reverse=True)
1288
# The paths this function accepts are unicode and must be encoded as we
1290
encode = cache_utf8.encode
1291
inv_to_entry = self._inv_entry_to_details
1292
# delta is now (deletes, changes), (adds) in reverse lexographical
1294
# deletes in reverse lexographic order are safe to process in situ.
1295
# renames are not, as a rename from any path could go to a path
1296
# lexographically lower, so we transform renames into delete, add pairs,
1297
# expanding them recursively as needed.
1298
# At the same time, to reduce interface friction we convert the input
1299
# inventory entries to dirstate.
1300
root_only = ('', '')
1301
for old_path, new_path, file_id, inv_entry in delta:
1302
if old_path is None:
1303
adds.append((None, encode(new_path), file_id,
1304
inv_to_entry(inv_entry), True))
1305
elif new_path is None:
1306
deletes.append((encode(old_path), None, file_id, None, True))
1307
elif (old_path, new_path) != root_only:
1309
# Because renames must preserve their children we must have
1310
# processed all relocations and removes before hand. The sort
1311
# order ensures we've examined the child paths, but we also
1312
# have to execute the removals, or the split to an add/delete
1313
# pair will result in the deleted item being reinserted, or
1314
# renamed items being reinserted twice - and possibly at the
1315
# wrong place. Splitting into a delete/add pair also simplifies
1316
# the handling of entries with ('f', ...), ('r' ...) because
1317
# the target of the 'r' is old_path here, and we add that to
1318
# deletes, meaning that the add handler does not need to check
1319
# for 'r' items on every pass.
1320
self._update_basis_apply_deletes(deletes)
1322
new_path_utf8 = encode(new_path)
1323
# Split into an add/delete pair recursively.
1324
adds.append((None, new_path_utf8, file_id,
1325
inv_to_entry(inv_entry), False))
1326
# Expunge deletes that we've seen so that deleted/renamed
1327
# children of a rename directory are handled correctly.
1328
new_deletes = reversed(list(self._iter_child_entries(1,
1330
# Remove the current contents of the tree at orig_path, and
1331
# reinsert at the correct new path.
1332
for entry in new_deletes:
1334
source_path = entry[0][0] + '/' + entry[0][1]
1336
source_path = entry[0][1]
1338
target_path = new_path_utf8 + source_path[len(old_path):]
1341
raise AssertionError("cannot rename directory to"
1343
target_path = source_path[len(old_path) + 1:]
1344
adds.append((None, target_path, entry[0][2], entry[1][1], False))
1346
(source_path, target_path, entry[0][2], None, False))
1348
(encode(old_path), new_path, file_id, None, False))
1350
# changes to just the root should not require remove/insertion
1352
changes.append((encode(old_path), encode(new_path), file_id,
1353
inv_to_entry(inv_entry)))
1355
# Finish expunging deletes/first half of renames.
1356
self._update_basis_apply_deletes(deletes)
1357
# Reinstate second half of renames and new paths.
1358
self._update_basis_apply_adds(adds)
1359
# Apply in-situ changes.
1360
self._update_basis_apply_changes(changes)
1362
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1363
self._header_state = DirState.IN_MEMORY_MODIFIED
1364
self._id_index = None
1367
def _update_basis_apply_adds(self, adds):
1368
"""Apply a sequence of adds to tree 1 during update_basis_by_delta.
1370
They may be adds, or renames that have been split into add/delete
1373
:param adds: A sequence of adds. Each add is a tuple:
1374
(None, new_path_utf8, file_id, (entry_details), real_add). real_add
1375
is False when the add is the second half of a remove-and-reinsert
1376
pair created to handle renames and deletes.
1378
# Adds are accumulated partly from renames, so can be in any input
1381
# adds is now in lexographic order, which places all parents before
1382
# their children, so we can process it linearly.
1384
for old_path, new_path, file_id, new_details, real_add in adds:
1385
# the entry for this file_id must be in tree 0.
1386
entry = self._get_entry(0, file_id, new_path)
1387
if entry[0] is None or entry[0][2] != file_id:
1388
self._changes_aborted = True
1389
raise errors.InconsistentDelta(new_path, file_id,
1390
'working tree does not contain new entry')
1391
if real_add and entry[1][1][0] not in absent:
1392
self._changes_aborted = True
1393
raise errors.InconsistentDelta(new_path, file_id,
1394
'The entry was considered to be a genuinely new record,'
1395
' but there was already an old record for it.')
1396
# We don't need to update the target of an 'r' because the handling
1397
# of renames turns all 'r' situations into a delete at the original
1399
entry[1][1] = new_details
1401
def _update_basis_apply_changes(self, changes):
1402
"""Apply a sequence of changes to tree 1 during update_basis_by_delta.
1404
:param adds: A sequence of changes. Each change is a tuple:
1405
(path_utf8, path_utf8, file_id, (entry_details))
1408
for old_path, new_path, file_id, new_details in changes:
1409
# the entry for this file_id must be in tree 0.
1410
entry = self._get_entry(0, file_id, new_path)
1411
if entry[0] is None or entry[0][2] != file_id:
1412
self._changes_aborted = True
1413
raise errors.InconsistentDelta(new_path, file_id,
1414
'working tree does not contain new entry')
1415
if (entry[1][0][0] in absent or
1416
entry[1][1][0] in absent):
1417
self._changes_aborted = True
1418
raise errors.InconsistentDelta(new_path, file_id,
1419
'changed considered absent')
1420
entry[1][1] = new_details
1422
def _update_basis_apply_deletes(self, deletes):
1423
"""Apply a sequence of deletes to tree 1 during update_basis_by_delta.
1425
They may be deletes, or renames that have been split into add/delete
1428
:param deletes: A sequence of deletes. Each delete is a tuple:
1429
(old_path_utf8, new_path_utf8, file_id, None, real_delete).
1430
real_delete is True when the desired outcome is an actual deletion
1431
rather than the rename handling logic temporarily deleting a path
1432
during the replacement of a parent.
1434
null = DirState.NULL_PARENT_DETAILS
1435
for old_path, new_path, file_id, _, real_delete in deletes:
1436
if real_delete != (new_path is None):
1437
raise AssertionError("bad delete delta")
1438
# the entry for this file_id must be in tree 1.
1439
dirname, basename = osutils.split(old_path)
1440
block_index, entry_index, dir_present, file_present = \
1441
self._get_block_entry_index(dirname, basename, 1)
1442
if not file_present:
1443
self._changes_aborted = True
1444
raise errors.InconsistentDelta(old_path, file_id,
1445
'basis tree does not contain removed entry')
1446
entry = self._dirblocks[block_index][1][entry_index]
1447
if entry[0][2] != file_id:
1448
self._changes_aborted = True
1449
raise errors.InconsistentDelta(old_path, file_id,
1450
'mismatched file_id in tree 1')
1452
if entry[1][0][0] != 'a':
1453
self._changes_aborted = True
1454
raise errors.InconsistentDelta(old_path, file_id,
1455
'This was marked as a real delete, but the WT state'
1456
' claims that it still exists and is versioned.')
1457
del self._dirblocks[block_index][1][entry_index]
1459
if entry[1][0][0] == 'a':
1460
self._changes_aborted = True
1461
raise errors.InconsistentDelta(old_path, file_id,
1462
'The entry was considered a rename, but the source path'
1463
' is marked as absent.')
1464
# For whatever reason, we were asked to rename an entry
1465
# that was originally marked as deleted. This could be
1466
# because we are renaming the parent directory, and the WT
1467
# current state has the file marked as deleted.
1468
elif entry[1][0][0] == 'r':
1469
# implement the rename
1470
del self._dirblocks[block_index][1][entry_index]
1472
# it is being resurrected here, so blank it out temporarily.
1473
self._dirblocks[block_index][1][entry_index][1][1] = null
1475
def update_entry(self, entry, abspath, stat_value,
1476
_stat_to_minikind=_stat_to_minikind,
1477
_pack_stat=pack_stat):
1062
def update_entry(self, entry, abspath, stat_value=None):
1478
1063
"""Update the entry based on what is actually on disk.
1480
1065
:param entry: This is the dirblock entry for the file in question.
1484
1069
:return: The sha1 hexdigest of the file (40 bytes) or link target of a
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
1075
assert self._dirblock_state != DirState.NOT_IN_MEMORY
1076
if stat_value is None:
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,
1084
# The entry is missing, consider it gone
1088
kind = osutils.file_kind_from_stat_mode(stat_value.st_mode)
1488
minikind = _stat_to_minikind[stat_value.st_mode & 0170000]
1090
minikind = DirState._kind_to_minikind[kind]
1091
except KeyError: # Unknown kind
1492
packed_stat = _pack_stat(stat_value)
1093
packed_stat = pack_stat(stat_value)
1493
1094
(saved_minikind, saved_link_or_sha1, saved_file_size,
1494
1095
saved_executable, saved_packed_stat) = entry[1][0]
1496
1097
if (minikind == saved_minikind
1497
and packed_stat == saved_packed_stat):
1498
# The stat hasn't changed since we saved, so we can re-use the
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.
1500
1103
if minikind == 'd':
1503
# size should also be in packed_stat
1504
if saved_file_size == stat_value.st_size:
1106
if self._cutoff_time is None:
1107
self._sha_cutoff_time()
1109
if (stat_value.st_mtime < self._cutoff_time
1110
and stat_value.st_ctime < self._cutoff_time):
1111
# Return the existing fingerprint
1505
1112
return saved_link_or_sha1
1507
1114
# If we have gotten this far, that means that we need to actually
1508
1115
# process this entry.
1509
1116
link_or_sha1 = None
1510
1117
if minikind == 'f':
1511
link_or_sha1 = self._sha1_file(abspath)
1118
link_or_sha1 = self._sha1_file(abspath, entry)
1512
1119
executable = self._is_executable(stat_value.st_mode,
1513
1120
saved_executable)
1514
if self._cutoff_time is None:
1515
self._sha_cutoff_time()
1516
if (stat_value.st_mtime < self._cutoff_time
1517
and stat_value.st_ctime < self._cutoff_time):
1518
entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
1519
executable, packed_stat)
1521
entry[1][0] = ('f', '', stat_value.st_size,
1522
executable, DirState.NULLSTAT)
1121
entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
1122
executable, packed_stat)
1523
1123
elif minikind == 'd':
1524
1124
link_or_sha1 = None
1525
1125
entry[1][0] = ('d', '', 0, False, packed_stat)
1979
1532
self._read_header_if_needed()
1980
1533
if self._dirblock_state == DirState.NOT_IN_MEMORY:
1981
_read_dirblocks(self)
1534
# move the _state_file pointer to after the header (in case bisect
1535
# has been called in the mean time)
1536
self._state_file.seek(self._end_of_header)
1537
text = self._state_file.read()
1538
# TODO: check the crc checksums. crc_measured = zlib.crc32(text)
1540
fields = text.split('\0')
1541
# Remove the last blank entry
1542
trailing = fields.pop()
1543
assert trailing == ''
1544
# consider turning fields into a tuple.
1546
# skip the first field which is the trailing null from the header.
1548
# Each line now has an extra '\n' field which is not used
1549
# so we just skip over it
1551
# 3 fields for the key
1552
# + number of fields per tree_data (5) * tree count
1554
num_present_parents = self._num_present_parents()
1555
tree_count = 1 + num_present_parents
1556
entry_size = self._fields_per_entry()
1557
expected_field_count = entry_size * self._num_entries
1558
field_count = len(fields)
1559
# this checks our adjustment, and also catches file too short.
1560
assert field_count - cur == expected_field_count, \
1561
'field count incorrect %s != %s, entry_size=%s, '\
1562
'num_entries=%s fields=%r' % (
1563
field_count - cur, expected_field_count, entry_size,
1564
self._num_entries, fields)
1566
if num_present_parents == 1:
1567
# Bind external functions to local names
1569
# We access all fields in order, so we can just iterate over
1570
# them. Grab an straight iterator over the fields. (We use an
1571
# iterator because we don't want to do a lot of additions, nor
1572
# do we want to do a lot of slicing)
1573
next = iter(fields).next
1574
# Move the iterator to the current position
1575
for x in xrange(cur):
1577
# The two blocks here are deliberate: the root block and the
1578
# contents-of-root block.
1579
self._dirblocks = [('', []), ('', [])]
1580
current_block = self._dirblocks[0][1]
1581
current_dirname = ''
1582
append_entry = current_block.append
1583
for count in xrange(self._num_entries):
1587
if dirname != current_dirname:
1588
# new block - different dirname
1590
current_dirname = dirname
1591
self._dirblocks.append((current_dirname, current_block))
1592
append_entry = current_block.append
1593
# we know current_dirname == dirname, so re-use it to avoid
1594
# creating new strings
1595
entry = ((current_dirname, name, file_id),
1598
next(), # fingerprint
1599
_int(next()), # size
1600
next() == 'y', # executable
1601
next(), # packed_stat or revision_id
1605
next(), # fingerprint
1606
_int(next()), # size
1607
next() == 'y', # executable
1608
next(), # packed_stat or revision_id
1612
assert trailing == '\n'
1613
# append the entry to the current block
1615
self._split_root_dirblock_into_contents()
1617
fields_to_entry = self._get_fields_to_entry()
1618
entries = [fields_to_entry(fields[pos:pos+entry_size])
1619
for pos in xrange(cur, field_count, entry_size)]
1620
self._entries_to_current_state(entries)
1621
# To convert from format 2 => format 3
1622
# self._dirblocks = sorted(self._dirblocks,
1623
# key=lambda blk:blk[0].split('/'))
1624
# To convert from format 3 => format 2
1625
# self._dirblocks = sorted(self._dirblocks)
1626
self._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
1983
1628
def _read_header(self):
1984
1629
"""This reads in the metadata header, and the parent ids.
2617
2189
for dirblock in self._dirblocks:
2618
2190
# within each dirblock, the entries are sorted by filename and
2620
for entry in dirblock[1]:
2621
if dirblock[0] != entry[0][0]:
2622
raise AssertionError(
2624
"doesn't match directory name in\n%r" %
2625
(entry, pformat(dirblock)))
2626
if dirblock[1] != sorted(dirblock[1]):
2627
raise AssertionError(
2628
"dirblock for %r is not sorted:\n%s" % \
2629
(dirblock[0], pformat(dirblock)))
2631
def check_valid_parent():
2632
"""Check that the current entry has a valid parent.
2634
This makes sure that the parent has a record,
2635
and that the parent isn't marked as "absent" in the
2636
current tree. (It is invalid to have a non-absent file in an absent
2639
if entry[0][0:2] == ('', ''):
2640
# There should be no parent for the root row
2642
parent_entry = self._get_entry(tree_index, path_utf8=entry[0][0])
2643
if parent_entry == (None, None):
2644
raise AssertionError(
2645
"no parent entry for: %s in tree %s"
2646
% (this_path, tree_index))
2647
if parent_entry[1][tree_index][0] != 'd':
2648
raise AssertionError(
2649
"Parent entry for %s is not marked as a valid"
2650
" directory. %s" % (this_path, parent_entry,))
2652
# For each file id, for each tree: either
2653
# the file id is not present at all; all rows with that id in the
2654
# key have it marked as 'absent'
2655
# OR the file id is present under exactly one name; any other entries
2656
# that mention that id point to the correct name.
2658
# We check this with a dict per tree pointing either to the present
2659
# name, or None if absent.
2660
tree_count = self._num_present_parents() + 1
2661
id_path_maps = [dict() for i in range(tree_count)]
2662
# Make sure that all renamed entries point to the correct location.
2663
for entry in self._iter_entries():
2664
file_id = entry[0][2]
2665
this_path = osutils.pathjoin(entry[0][0], entry[0][1])
2666
if len(entry[1]) != tree_count:
2667
raise AssertionError(
2668
"wrong number of entry details for row\n%s" \
2669
",\nexpected %d" % \
2670
(pformat(entry), tree_count))
2671
absent_positions = 0
2672
for tree_index, tree_state in enumerate(entry[1]):
2673
this_tree_map = id_path_maps[tree_index]
2674
minikind = tree_state[0]
2675
if minikind in 'ar':
2676
absent_positions += 1
2677
# have we seen this id before in this column?
2678
if file_id in this_tree_map:
2679
previous_path, previous_loc = this_tree_map[file_id]
2680
# any later mention of this file must be consistent with
2681
# what was said before
2683
if previous_path is not None:
2684
raise AssertionError(
2685
"file %s is absent in row %r but also present " \
2687
(file_id, entry, previous_path))
2688
elif minikind == 'r':
2689
target_location = tree_state[1]
2690
if previous_path != target_location:
2691
raise AssertionError(
2692
"file %s relocation in row %r but also at %r" \
2693
% (file_id, entry, previous_path))
2695
# a file, directory, etc - may have been previously
2696
# pointed to by a relocation, which must point here
2697
if previous_path != this_path:
2698
raise AssertionError(
2699
"entry %r inconsistent with previous path %r "
2701
(entry, previous_path, previous_loc))
2702
check_valid_parent()
2705
# absent; should not occur anywhere else
2706
this_tree_map[file_id] = None, this_path
2707
elif minikind == 'r':
2708
# relocation, must occur at expected location
2709
this_tree_map[file_id] = tree_state[1], this_path
2711
this_tree_map[file_id] = this_path, this_path
2712
check_valid_parent()
2713
if absent_positions == tree_count:
2714
raise AssertionError(
2715
"entry %r has no data for any tree." % (entry,))
2192
assert dirblock[1] == sorted(dirblock[1]), \
2193
"dirblock for %r is not sorted:\n%s" % \
2194
(dirblock[0], pformat(dirblock))
2717
2196
def _wipe_state(self):
2718
2197
"""Forget all state information about the dirstate."""
2719
2198
self._header_state = DirState.NOT_IN_MEMORY
2720
2199
self._dirblock_state = DirState.NOT_IN_MEMORY
2721
self._changes_aborted = False
2722
2200
self._parents = []
2723
2201
self._ghosts = []
2724
2202
self._dirblocks = []
2725
2203
self._id_index = None
2726
self._packed_stat_index = None
2727
2204
self._end_of_header = None
2728
2205
self._cutoff_time = None
2729
2206
self._split_path_cache = {}
2731
2208
def lock_read(self):
2732
"""Acquire a read lock on the dirstate."""
2209
"""Acquire a read lock on the dirstate"""
2733
2210
if self._lock_token is not None:
2734
2211
raise errors.LockContention(self._lock_token)
2735
2212
# TODO: jam 20070301 Rather than wiping completely, if the blocks are