14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
17
19
"""DirState objects record the state of a directory and its bzr metadata.
19
21
Pseudo EBNF grammar for the state file. Fields are separated by NULLs, and
20
22
lines by NL. The field delimiters are ommitted in the grammar, line delimiters
21
23
are not - this is done for clarity of reading. All string data is in utf8.
23
MINIKIND = "f" | "d" | "l" | "a" | "r" | "t";
26
WHOLE_NUMBER = {digit}, digit;
28
REVISION_ID = a non-empty utf8 string;
30
dirstate format = header line, full checksum, row count, parent details,
31
ghost_details, entries;
32
header line = "#bazaar dirstate flat format 3", NL;
33
full checksum = "crc32: ", ["-"], WHOLE_NUMBER, NL;
34
row count = "num_entries: ", WHOLE_NUMBER, NL;
35
parent_details = WHOLE NUMBER, {REVISION_ID}* NL;
36
ghost_details = WHOLE NUMBER, {REVISION_ID}*, NL;
38
entry = entry_key, current_entry_details, {parent_entry_details};
39
entry_key = dirname, basename, fileid;
40
current_entry_details = common_entry_details, working_entry_details;
41
parent_entry_details = common_entry_details, history_entry_details;
42
common_entry_details = MINIKIND, fingerprint, size, executable
43
working_entry_details = packed_stat
44
history_entry_details = REVISION_ID;
47
fingerprint = a nonempty utf8 sequence with meaning defined by minikind.
49
Given this definition, the following is useful to know:
50
entry (aka row) - all the data for a given key.
51
entry[0]: The key (dirname, basename, fileid)
55
entry[1]: The tree(s) data for this path and id combination.
56
entry[1][0]: The current tree
57
entry[1][1]: The second tree
59
For an entry for a tree, we have (using tree 0 - current tree) to demonstrate:
60
entry[1][0][0]: minikind
61
entry[1][0][1]: fingerprint
63
entry[1][0][3]: executable
64
entry[1][0][4]: packed_stat
66
entry[1][1][4]: revision_id
27
MINIKIND = "f" | "d" | "l" | "a" | "r" | "t";
30
WHOLE_NUMBER = {digit}, digit;
32
REVISION_ID = a non-empty utf8 string;
34
dirstate format = header line, full checksum, row count, parent details,
35
ghost_details, entries;
36
header line = "#bazaar dirstate flat format 3", NL;
37
full checksum = "crc32: ", ["-"], WHOLE_NUMBER, NL;
38
row count = "num_entries: ", WHOLE_NUMBER, NL;
39
parent_details = WHOLE NUMBER, {REVISION_ID}* NL;
40
ghost_details = WHOLE NUMBER, {REVISION_ID}*, NL;
42
entry = entry_key, current_entry_details, {parent_entry_details};
43
entry_key = dirname, basename, fileid;
44
current_entry_details = common_entry_details, working_entry_details;
45
parent_entry_details = common_entry_details, history_entry_details;
46
common_entry_details = MINIKIND, fingerprint, size, executable
47
working_entry_details = packed_stat
48
history_entry_details = REVISION_ID;
51
fingerprint = a nonempty utf8 sequence with meaning defined by minikind.
53
Given this definition, the following is useful to know::
55
entry (aka row) - all the data for a given key.
56
entry[0]: The key (dirname, basename, fileid)
60
entry[1]: The tree(s) data for this path and id combination.
61
entry[1][0]: The current tree
62
entry[1][1]: The second tree
64
For an entry for a tree, we have (using tree 0 - current tree) to demonstrate::
66
entry[1][0][0]: minikind
67
entry[1][0][1]: fingerprint
69
entry[1][0][3]: executable
70
entry[1][0][4]: packed_stat
74
entry[1][1][4]: revision_id
68
76
There may be multiple rows at the root, one per id present in the root, so the
69
in memory root row is now:
70
self._dirblocks[0] -> ('', [entry ...]),
71
and the entries in there are
74
entries[0][2]: file_id
75
entries[1][0]: The tree data for the current tree for this fileid at /
79
'r' is a relocated entry: This path is not present in this tree with this id,
80
but the id can be found at another location. The fingerprint is used to
81
point to the target location.
82
'a' is an absent entry: In that tree the id is not present at this path.
83
'd' is a directory entry: This path in this tree is a directory with the
84
current file id. There is no fingerprint for directories.
85
'f' is a file entry: As for directory, but it's a file. The fingerprint is the
86
sha1 value of the file's canonical form, i.e. after any read filters have
87
been applied to the convenience form stored in the working tree.
88
'l' is a symlink entry: As for directory, but a symlink. The fingerprint is the
90
't' is a reference to a nested subtree; the fingerprint is the referenced
77
in memory root row is now::
79
self._dirblocks[0] -> ('', [entry ...]),
81
and the entries in there are::
85
entries[0][2]: file_id
86
entries[1][0]: The tree data for the current tree for this fileid at /
91
'r' is a relocated entry: This path is not present in this tree with this
92
id, but the id can be found at another location. The fingerprint is
93
used to point to the target location.
94
'a' is an absent entry: In that tree the id is not present at this path.
95
'd' is a directory entry: This path in this tree is a directory with the
96
current file id. There is no fingerprint for directories.
97
'f' is a file entry: As for directory, but it's a file. The fingerprint is
98
the sha1 value of the file's canonical form, i.e. after any read
99
filters have been applied to the convenience form stored in the working
101
'l' is a symlink entry: As for directory, but a symlink. The fingerprint is
103
't' is a reference to a nested subtree; the fingerprint is the referenced
95
The entries on disk and in memory are ordered according to the following keys:
108
The entries on disk and in memory are ordered according to the following keys::
97
110
directory, as a list of components
101
114
--- Format 1 had the following different definition: ---
102
rows = dirname, NULL, basename, NULL, MINIKIND, NULL, fileid_utf8, NULL,
103
WHOLE NUMBER (* size *), NULL, packed stat, NULL, sha1|symlink target,
105
PARENT ROW = NULL, revision_utf8, NULL, MINIKIND, NULL, dirname, NULL,
106
basename, NULL, WHOLE NUMBER (* size *), NULL, "y" | "n", NULL,
118
rows = dirname, NULL, basename, NULL, MINIKIND, NULL, fileid_utf8, NULL,
119
WHOLE NUMBER (* size *), NULL, packed stat, NULL, sha1|symlink target,
121
PARENT ROW = NULL, revision_utf8, NULL, MINIKIND, NULL, dirname, NULL,
122
basename, NULL, WHOLE NUMBER (* size *), NULL, "y" | "n", NULL,
109
125
PARENT ROW's are emitted for every parent that is not in the ghosts details
110
126
line. That is, if the parents are foo, bar, baz, and the ghosts are bar, then
231
253
ERROR_DIRECTORY = 267
234
if not getattr(struct, '_compile', None):
235
# Cannot pre-compile the dirstate pack_stat
236
def pack_stat(st, _encode=binascii.b2a_base64, _pack=struct.pack):
237
"""Convert stat values into a packed representation."""
238
return _encode(_pack('>LLLLLL', st.st_size, int(st.st_mtime),
239
int(st.st_ctime), st.st_dev, st.st_ino & 0xFFFFFFFF,
242
# compile the struct compiler we need, so as to only do it once
243
from _struct import Struct
244
_compiled_pack = Struct('>LLLLLL').pack
245
def pack_stat(st, _encode=binascii.b2a_base64, _pack=_compiled_pack):
246
"""Convert stat values into a packed representation."""
247
# jam 20060614 it isn't really worth removing more entries if we
248
# are going to leave it in packed form.
249
# With only st_mtime and st_mode filesize is 5.5M and read time is 275ms
250
# With all entries, filesize is 5.9M and read time is maybe 280ms
251
# well within the noise margin
253
# base64 encoding always adds a final newline, so strip it off
254
# The current version
255
return _encode(_pack(st.st_size, int(st.st_mtime), int(st.st_ctime),
256
st.st_dev, st.st_ino & 0xFFFFFFFF, st.st_mode))[:-1]
257
# This is 0.060s / 1.520s faster by not encoding as much information
258
# return _encode(_pack('>LL', int(st.st_mtime), st.st_mode))[:-1]
259
# This is not strictly faster than _encode(_pack())[:-1]
260
# return '%X.%X.%X.%X.%X.%X' % (
261
# st.st_size, int(st.st_mtime), int(st.st_ctime),
262
# st.st_dev, st.st_ino, st.st_mode)
263
# Similar to the _encode(_pack('>LL'))
264
# return '%X.%X' % (int(st.st_mtime), st.st_mode)
267
256
class SHA1Provider(object):
268
257
"""An interface for getting sha1s of a file."""
411
405
self._last_block_index = None
412
406
self._last_entry_index = None
407
# The set of known hash changes
408
self._known_hash_changes = set()
409
# How many hash changed entries can we have without saving
410
self._worth_saving_limit = worth_saving_limit
411
self._config_stack = config.LocationStack(urlutils.local_path_to_url(
414
414
def __repr__(self):
415
415
return "%s(%r)" % \
416
416
(self.__class__.__name__, self._filename)
418
def _mark_modified(self, hash_changed_entries=None, header_modified=False):
419
"""Mark this dirstate as modified.
421
:param hash_changed_entries: if non-None, mark just these entries as
422
having their hash modified.
423
:param header_modified: mark the header modified as well, not just the
426
#trace.mutter_callsite(3, "modified hash entries: %s", hash_changed_entries)
427
if hash_changed_entries:
428
self._known_hash_changes.update([e[0] for e in hash_changed_entries])
429
if self._dirblock_state in (DirState.NOT_IN_MEMORY,
430
DirState.IN_MEMORY_UNMODIFIED):
431
# If the dirstate is already marked a IN_MEMORY_MODIFIED, then
432
# that takes precedence.
433
self._dirblock_state = DirState.IN_MEMORY_HASH_MODIFIED
435
# TODO: Since we now have a IN_MEMORY_HASH_MODIFIED state, we
436
# should fail noisily if someone tries to set
437
# IN_MEMORY_MODIFIED but we don't have a write-lock!
438
# We don't know exactly what changed so disable smart saving
439
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
441
self._header_state = DirState.IN_MEMORY_MODIFIED
443
def _mark_unmodified(self):
444
"""Mark this dirstate as unmodified."""
445
self._header_state = DirState.IN_MEMORY_UNMODIFIED
446
self._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
447
self._known_hash_changes = set()
418
449
def add(self, path, file_id, kind, stat, fingerprint):
419
450
"""Add a path to be tracked.
1504
1548
self._update_basis_apply_deletes(deletes)
1506
1550
# Split into an add/delete pair recursively.
1507
adds.append((None, new_path_utf8, file_id,
1508
inv_to_entry(inv_entry), False))
1551
adds.append((old_path_utf8, new_path_utf8, file_id,
1552
inv_to_entry(inv_entry), False))
1509
1553
# Expunge deletes that we've seen so that deleted/renamed
1510
1554
# children of a rename directory are handled correctly.
1511
new_deletes = reversed(list(self._iter_child_entries(1,
1555
new_deletes = reversed(list(
1556
self._iter_child_entries(1, old_path_utf8)))
1513
1557
# Remove the current contents of the tree at orig_path, and
1514
1558
# reinsert at the correct new path.
1515
1559
for entry in new_deletes:
1517
source_path = entry[0][0] + '/' + entry[0][1]
1560
child_dirname, child_basename, child_file_id = entry[0]
1562
source_path = child_dirname + '/' + child_basename
1519
source_path = entry[0][1]
1564
source_path = child_basename
1520
1565
if new_path_utf8:
1521
1566
target_path = new_path_utf8 + source_path[len(old_path):]
1523
1568
if old_path == '':
1524
1569
raise AssertionError("cannot rename directory to"
1526
1571
target_path = source_path[len(old_path) + 1:]
1527
1572
adds.append((None, target_path, entry[0][2], entry[1][1], False))
1528
1573
deletes.append(
1529
1574
(source_path, target_path, entry[0][2], None, False))
1531
(encode(old_path), new_path, file_id, None, False))
1533
# changes to just the root should not require remove/insertion
1535
changes.append((encode(old_path), encode(new_path), file_id,
1536
inv_to_entry(inv_entry)))
1575
deletes.append((old_path_utf8, new_path, file_id, None, False))
1537
1576
self._check_delta_ids_absent(new_ids, delta, 1)
1539
1578
# Finish expunging deletes/first half of renames.
1597
1635
# Adds are accumulated partly from renames, so can be in any input
1598
1636
# order - sort it.
1637
# TODO: we may want to sort in dirblocks order. That way each entry
1638
# will end up in the same directory, allowing the _get_entry
1639
# fast-path for looking up 2 items in the same dir work.
1640
adds.sort(key=lambda x: x[1])
1600
1641
# adds is now in lexographic order, which places all parents before
1601
1642
# their children, so we can process it linearly.
1644
st = static_tuple.StaticTuple
1603
1645
for old_path, new_path, file_id, new_details, real_add in adds:
1604
# the entry for this file_id must be in tree 0.
1605
entry = self._get_entry(0, file_id, new_path)
1606
if entry[0] is None or entry[0][2] != file_id:
1607
self._changes_aborted = True
1608
raise errors.InconsistentDelta(new_path, file_id,
1609
'working tree does not contain new entry')
1610
if real_add and entry[1][1][0] not in absent:
1611
self._changes_aborted = True
1612
raise errors.InconsistentDelta(new_path, file_id,
1613
'The entry was considered to be a genuinely new record,'
1614
' but there was already an old record for it.')
1615
# We don't need to update the target of an 'r' because the handling
1616
# of renames turns all 'r' situations into a delete at the original
1618
entry[1][1] = new_details
1646
dirname, basename = osutils.split(new_path)
1647
entry_key = st(dirname, basename, file_id)
1648
block_index, present = self._find_block_index_from_key(entry_key)
1650
self._raise_invalid(new_path, file_id,
1651
"Unable to find block for this record."
1652
" Was the parent added?")
1653
block = self._dirblocks[block_index][1]
1654
entry_index, present = self._find_entry_index(entry_key, block)
1656
if old_path is not None:
1657
self._raise_invalid(new_path, file_id,
1658
'considered a real add but still had old_path at %s'
1661
entry = block[entry_index]
1662
basis_kind = entry[1][1][0]
1663
if basis_kind == 'a':
1664
entry[1][1] = new_details
1665
elif basis_kind == 'r':
1666
raise NotImplementedError()
1668
self._raise_invalid(new_path, file_id,
1669
"An entry was marked as a new add"
1670
" but the basis target already existed")
1672
# The exact key was not found in the block. However, we need to
1673
# check if there is a key next to us that would have matched.
1674
# We only need to check 2 locations, because there are only 2
1676
for maybe_index in range(entry_index-1, entry_index+1):
1677
if maybe_index < 0 or maybe_index >= len(block):
1679
maybe_entry = block[maybe_index]
1680
if maybe_entry[0][:2] != (dirname, basename):
1681
# Just a random neighbor
1683
if maybe_entry[0][2] == file_id:
1684
raise AssertionError(
1685
'_find_entry_index didnt find a key match'
1686
' but walking the data did, for %s'
1688
basis_kind = maybe_entry[1][1][0]
1689
if basis_kind not in 'ar':
1690
self._raise_invalid(new_path, file_id,
1691
"we have an add record for path, but the path"
1692
" is already present with another file_id %s"
1693
% (maybe_entry[0][2],))
1695
entry = (entry_key, [DirState.NULL_PARENT_DETAILS,
1697
block.insert(entry_index, entry)
1699
active_kind = entry[1][0][0]
1700
if active_kind == 'a':
1701
# The active record shows up as absent, this could be genuine,
1702
# or it could be present at some other location. We need to
1704
id_index = self._get_id_index()
1705
# The id_index may not be perfectly accurate for tree1, because
1706
# we haven't been keeping it updated. However, it should be
1707
# fine for tree0, and that gives us enough info for what we
1709
keys = id_index.get(file_id, ())
1711
block_i, entry_i, d_present, f_present = \
1712
self._get_block_entry_index(key[0], key[1], 0)
1715
active_entry = self._dirblocks[block_i][1][entry_i]
1716
if (active_entry[0][2] != file_id):
1717
# Some other file is at this path, we don't need to
1720
real_active_kind = active_entry[1][0][0]
1721
if real_active_kind in 'ar':
1722
# We found a record, which was not *this* record,
1723
# which matches the file_id, but is not actually
1724
# present. Something seems *really* wrong.
1725
self._raise_invalid(new_path, file_id,
1726
"We found a tree0 entry that doesnt make sense")
1727
# Now, we've found a tree0 entry which matches the file_id
1728
# but is at a different location. So update them to be
1730
active_dir, active_name = active_entry[0][:2]
1732
active_path = active_dir + '/' + active_name
1734
active_path = active_name
1735
active_entry[1][1] = st('r', new_path, 0, False, '')
1736
entry[1][0] = st('r', active_path, 0, False, '')
1737
elif active_kind == 'r':
1738
raise NotImplementedError()
1740
new_kind = new_details[0]
1742
self._ensure_block(block_index, entry_index, new_path)
1620
1744
def _update_basis_apply_changes(self, changes):
1621
1745
"""Apply a sequence of changes to tree 1 during update_basis_by_delta.
1653
1771
null = DirState.NULL_PARENT_DETAILS
1654
1772
for old_path, new_path, file_id, _, real_delete in deletes:
1655
1773
if real_delete != (new_path is None):
1656
self._changes_aborted = True
1657
raise AssertionError("bad delete delta")
1774
self._raise_invalid(old_path, file_id, "bad delete delta")
1658
1775
# the entry for this file_id must be in tree 1.
1659
1776
dirname, basename = osutils.split(old_path)
1660
1777
block_index, entry_index, dir_present, file_present = \
1661
1778
self._get_block_entry_index(dirname, basename, 1)
1662
1779
if not file_present:
1663
self._changes_aborted = True
1664
raise errors.InconsistentDelta(old_path, file_id,
1780
self._raise_invalid(old_path, file_id,
1665
1781
'basis tree does not contain removed entry')
1666
1782
entry = self._dirblocks[block_index][1][entry_index]
1783
# The state of the entry in the 'active' WT
1784
active_kind = entry[1][0][0]
1667
1785
if entry[0][2] != file_id:
1668
self._changes_aborted = True
1669
raise errors.InconsistentDelta(old_path, file_id,
1786
self._raise_invalid(old_path, file_id,
1670
1787
'mismatched file_id in tree 1')
1672
if entry[1][0][0] != 'a':
1673
self._changes_aborted = True
1674
raise errors.InconsistentDelta(old_path, file_id,
1675
'This was marked as a real delete, but the WT state'
1676
' claims that it still exists and is versioned.')
1789
old_kind = entry[1][1][0]
1790
if active_kind in 'ar':
1791
# The active tree doesn't have this file_id.
1792
# The basis tree is changing this record. If this is a
1793
# rename, then we don't want the record here at all
1794
# anymore. If it is just an in-place change, we want the
1795
# record here, but we'll add it if we need to. So we just
1797
if active_kind == 'r':
1798
active_path = entry[1][0][1]
1799
active_entry = self._get_entry(0, file_id, active_path)
1800
if active_entry[1][1][0] != 'r':
1801
self._raise_invalid(old_path, file_id,
1802
"Dirstate did not have matching rename entries")
1803
elif active_entry[1][0][0] in 'ar':
1804
self._raise_invalid(old_path, file_id,
1805
"Dirstate had a rename pointing at an inactive"
1807
active_entry[1][1] = null
1677
1808
del self._dirblocks[block_index][1][entry_index]
1810
# This was a directory, and the active tree says it
1811
# doesn't exist, and now the basis tree says it doesn't
1812
# exist. Remove its dirblock if present
1814
present) = self._find_block_index_from_key(
1817
dir_block = self._dirblocks[dir_block_index][1]
1819
# This entry is empty, go ahead and just remove it
1820
del self._dirblocks[dir_block_index]
1679
if entry[1][0][0] == 'a':
1680
self._changes_aborted = True
1681
raise errors.InconsistentDelta(old_path, file_id,
1682
'The entry was considered a rename, but the source path'
1683
' is marked as absent.')
1684
# For whatever reason, we were asked to rename an entry
1685
# that was originally marked as deleted. This could be
1686
# because we are renaming the parent directory, and the WT
1687
# current state has the file marked as deleted.
1688
elif entry[1][0][0] == 'r':
1689
# implement the rename
1690
del self._dirblocks[block_index][1][entry_index]
1692
# it is being resurrected here, so blank it out temporarily.
1693
self._dirblocks[block_index][1][entry_index][1][1] = null
1822
# There is still an active record, so just mark this
1825
block_i, entry_i, d_present, f_present = \
1826
self._get_block_entry_index(old_path, '', 1)
1828
dir_block = self._dirblocks[block_i][1]
1829
for child_entry in dir_block:
1830
child_basis_kind = child_entry[1][1][0]
1831
if child_basis_kind not in 'ar':
1832
self._raise_invalid(old_path, file_id,
1833
"The file id was deleted but its children were "
1695
1836
def _after_delta_check_parents(self, parents, index):
1696
1837
"""Check that parents required by the delta are all intact.
2145
2288
def _get_id_index(self):
2146
"""Get an id index of self._dirblocks."""
2289
"""Get an id index of self._dirblocks.
2291
This maps from file_id => [(directory, name, file_id)] entries where
2292
that file_id appears in one of the trees.
2147
2294
if self._id_index is None:
2149
2296
for key, tree_details in self._iter_entries():
2150
id_index.setdefault(key[2], set()).add(key)
2297
self._add_to_id_index(id_index, key)
2151
2298
self._id_index = id_index
2152
2299
return self._id_index
2301
def _add_to_id_index(self, id_index, entry_key):
2302
"""Add this entry to the _id_index mapping."""
2303
# This code used to use a set for every entry in the id_index. However,
2304
# it is *rare* to have more than one entry. So a set is a large
2305
# overkill. And even when we do, we won't ever have more than the
2306
# number of parent trees. Which is still a small number (rarely >2). As
2307
# such, we use a simple tuple, and do our own uniqueness checks. While
2308
# the 'in' check is O(N) since N is nicely bounded it shouldn't ever
2309
# cause quadratic failure.
2310
file_id = entry_key[2]
2311
entry_key = static_tuple.StaticTuple.from_sequence(entry_key)
2312
if file_id not in id_index:
2313
id_index[file_id] = static_tuple.StaticTuple(entry_key,)
2315
entry_keys = id_index[file_id]
2316
if entry_key not in entry_keys:
2317
id_index[file_id] = entry_keys + (entry_key,)
2319
def _remove_from_id_index(self, id_index, entry_key):
2320
"""Remove this entry from the _id_index mapping.
2322
It is an programming error to call this when the entry_key is not
2325
file_id = entry_key[2]
2326
entry_keys = list(id_index[file_id])
2327
entry_keys.remove(entry_key)
2328
id_index[file_id] = static_tuple.StaticTuple.from_sequence(entry_keys)
2154
2330
def _get_output_lines(self, lines):
2155
2331
"""Format lines for final output.
2285
2465
trace.mutter('Not saving DirState because '
2286
2466
'_changes_aborted is set.')
2288
if (self._header_state == DirState.IN_MEMORY_MODIFIED or
2289
self._dirblock_state == DirState.IN_MEMORY_MODIFIED):
2468
# TODO: Since we now distinguish IN_MEMORY_MODIFIED from
2469
# IN_MEMORY_HASH_MODIFIED, we should only fail quietly if we fail
2470
# to save an IN_MEMORY_HASH_MODIFIED, and fail *noisily* if we
2471
# fail to save IN_MEMORY_MODIFIED
2472
if not self._worth_saving():
2291
grabbed_write_lock = False
2292
if self._lock_state != 'w':
2293
grabbed_write_lock, new_lock = self._lock_token.temporary_write_lock()
2294
# Switch over to the new lock, as the old one may be closed.
2475
grabbed_write_lock = False
2476
if self._lock_state != 'w':
2477
grabbed_write_lock, new_lock = self._lock_token.temporary_write_lock()
2478
# Switch over to the new lock, as the old one may be closed.
2479
# TODO: jam 20070315 We should validate the disk file has
2480
# not changed contents, since temporary_write_lock may
2481
# not be an atomic operation.
2482
self._lock_token = new_lock
2483
self._state_file = new_lock.f
2484
if not grabbed_write_lock:
2485
# We couldn't grab a write lock, so we switch back to a read one
2488
lines = self.get_lines()
2489
self._state_file.seek(0)
2490
self._state_file.writelines(lines)
2491
self._state_file.truncate()
2492
self._state_file.flush()
2493
self._maybe_fdatasync()
2494
self._mark_unmodified()
2496
if grabbed_write_lock:
2497
self._lock_token = self._lock_token.restore_read_lock()
2498
self._state_file = self._lock_token.f
2295
2499
# TODO: jam 20070315 We should validate the disk file has
2296
# not changed contents. Since temporary_write_lock may
2297
# not be an atomic operation.
2298
self._lock_token = new_lock
2299
self._state_file = new_lock.f
2300
if not grabbed_write_lock:
2301
# We couldn't grab a write lock, so we switch back to a read one
2304
self._state_file.seek(0)
2305
self._state_file.writelines(self.get_lines())
2306
self._state_file.truncate()
2307
self._state_file.flush()
2308
self._header_state = DirState.IN_MEMORY_UNMODIFIED
2309
self._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
2311
if grabbed_write_lock:
2312
self._lock_token = self._lock_token.restore_read_lock()
2313
self._state_file = self._lock_token.f
2314
# TODO: jam 20070315 We should validate the disk file has
2315
# not changed contents. Since restore_read_lock may
2316
# not be an atomic operation.
2500
# not changed contents. Since restore_read_lock may
2501
# not be an atomic operation.
2503
def _maybe_fdatasync(self):
2504
"""Flush to disk if possible and if not configured off."""
2505
if self._config_stack.get('dirstate.fdatasync'):
2506
osutils.fdatasync(self._state_file.fileno())
2508
def _worth_saving(self):
2509
"""Is it worth saving the dirstate or not?"""
2510
if (self._header_state == DirState.IN_MEMORY_MODIFIED
2511
or self._dirblock_state == DirState.IN_MEMORY_MODIFIED):
2513
if self._dirblock_state == DirState.IN_MEMORY_HASH_MODIFIED:
2514
if self._worth_saving_limit == -1:
2515
# We never save hash changes when the limit is -1
2517
# If we're using smart saving and only a small number of
2518
# entries have changed their hash, don't bother saving. John has
2519
# suggested using a heuristic here based on the size of the
2520
# changed files and/or tree. For now, we go with a configurable
2521
# number of changes, keeping the calculation time
2522
# as low overhead as possible. (This also keeps all existing
2523
# tests passing as the default is 0, i.e. always save.)
2524
if len(self._known_hash_changes) >= self._worth_saving_limit:
2318
2528
def _set_data(self, parent_ids, dirblocks):
2319
2529
"""Set the full dirstate data in memory.
2465
2694
new_details = []
2466
2695
for lookup_index in xrange(tree_index):
2467
2696
# boundary case: this is the first occurence of file_id
2468
# so there are no id_indexs, possibly take this out of
2697
# so there are no id_indexes, possibly take this out of
2470
if not len(id_index[file_id]):
2699
if not len(entry_keys):
2471
2700
new_details.append(DirState.NULL_PARENT_DETAILS)
2473
2702
# grab any one entry, use it to find the right path.
2474
# TODO: optimise this to reduce memory use in highly
2475
# fragmented situations by reusing the relocation
2477
a_key = iter(id_index[file_id]).next()
2703
a_key = iter(entry_keys).next()
2478
2704
if by_path[a_key][lookup_index][0] in ('r', 'a'):
2479
# its a pointer or missing statement, use it as is.
2705
# its a pointer or missing statement, use it as
2480
2707
new_details.append(by_path[a_key][lookup_index])
2482
2709
# we have the right key, make a pointer to it.
2483
2710
real_path = ('/'.join(a_key[0:2])).strip('/')
2484
new_details.append(('r', real_path, 0, False, ''))
2711
new_details.append(st('r', real_path, 0, False,
2485
2713
new_details.append(self._inv_entry_to_details(entry))
2486
2714
new_details.extend(new_location_suffix)
2487
2715
by_path[new_entry_key] = new_details
2488
id_index[file_id].add(new_entry_key)
2716
self._add_to_id_index(id_index, new_entry_key)
2489
2717
# --- end generation of full tree mappings
2491
2719
# sort and output all the entries