240
241
# A pack_stat (the x's) that is just noise and will never match the output
241
242
# of base64 encode.
242
243
NULLSTAT = 'x' * 32
243
NULL_PARENT_DETAILS = ('absent', '', 0, False, '')
244
NULL_PARENT_DETAILS = ('a', '', 0, False, '')
246
def __init__(self, path):
246
247
"""Create a DirState object.
248
249
Attributes of note:
306
310
# faster than three separate encodes.
307
311
utf8path = (dirname + '/' + basename).strip('/').encode('utf8')
308
312
dirname, basename = os.path.split(utf8path)
309
entry_key = (dirname, basename, file_id.encode('utf8'))
313
assert file_id.__class__ == str, \
314
"must be a utf8 file_id not %s" % (type(file_id))
315
entry_key = (dirname, basename, file_id)
310
316
self._read_dirblocks_if_needed()
311
317
block_index, present = self._find_block_index_from_key(entry_key)
323
329
size = stat.st_size
324
330
packed_stat = pack_stat(stat)
325
331
parent_info = self._empty_parent_info()
332
minikind = DirState._kind_to_minikind[kind]
326
333
if kind == 'file':
327
334
entry_data = entry_key, [
328
(kind, link_or_sha1, size, False, packed_stat),
335
(minikind, link_or_sha1, size, False, packed_stat),
330
337
elif kind == 'directory':
331
338
entry_data = entry_key, [
332
(kind, '', 0, False, packed_stat),
339
(minikind, '', 0, False, packed_stat),
334
341
elif kind == 'symlink':
335
342
entry_data = entry_key, [
336
(kind, link_or_sha1, size, False, packed_stat),
343
(minikind, link_or_sha1, size, False, packed_stat),
339
346
raise errors.BzrError('unknown kind %r' % kind)
401
408
current_block = self._dirblocks[0][1]
402
409
current_dirname = ''
403
410
root_key = ('', '')
411
append_entry = current_block.append
404
412
for entry in new_entries:
405
413
if entry[0][0] != current_dirname:
406
414
# new block - different dirname
407
415
current_block = []
408
416
current_dirname = entry[0][0]
409
417
self._dirblocks.append((current_dirname, current_block))
411
# this is not a root entry for a tree (it has a basename)
412
current_block = self._dirblocks[-1][1]
418
append_entry = current_block.append
413
419
# append the entry to the current block
414
current_block.append(entry)
421
self._split_root_dirblock_into_contents()
423
def _split_root_dirblock_into_contents(self):
424
"""Split the root dirblocks into root and contents-of-root.
426
After parsing by path, we end up with root entries and contents-of-root
427
entries in the same block. This loop splits them out again.
429
# The above loop leaves the "root block" entries mixed with the
430
# "contents-of-root block". But we don't want an if check on
431
# all entries, so instead we just fix it up here.
432
assert self._dirblocks[1] == ('', [])
434
contents_of_root_block = []
435
for entry in self._dirblocks[0][1]:
436
if not entry[0][1]: # This is a root entry
437
root_block.append(entry)
439
contents_of_root_block.append(entry)
440
self._dirblocks[0] = ('', root_block)
441
self._dirblocks[1] = ('', contents_of_root_block)
416
443
def _entry_to_line(self, entry):
417
444
"""Serialize entry to a NULL delimited line ready for _get_output_lines.
421
448
entire_entry = list(entry[0])
422
449
for tree_number, tree_data in enumerate(entry[1]):
423
# (kind, fingerprint, size, executable, tree_specific_string)
450
# (minikind, fingerprint, size, executable, tree_specific_string)
424
451
entire_entry.extend(tree_data)
425
452
# 3 for the key, 5 for the fields per tree.
426
453
tree_offset = 3 + tree_number * 5
428
entire_entry[tree_offset + 0] = DirState._kind_to_minikind[tree_data[0]]
455
entire_entry[tree_offset + 0] = tree_data[0]
430
457
entire_entry[tree_offset + 2] = str(tree_data[2])
502
529
:param tree: The tree which should provide parent information and
531
:return: a DirState object which is currently locked for writing.
532
(it was locked by DirState.initialize)
505
534
result = DirState.initialize(dir_state_filename)
507
parent_ids = tree.get_parent_ids()
508
num_parents = len(parent_ids)
510
for parent_id in parent_ids:
511
parent_trees.append((parent_id, tree.branch.repository.revision_tree(parent_id)))
512
parent_trees[-1][1].lock_read()
513
result.set_parent_trees(parent_trees, [])
514
result.set_state_from_inventory(tree.inventory)
516
for revid, parent in parent_trees:
538
parent_ids = tree.get_parent_ids()
539
num_parents = len(parent_ids)
541
for parent_id in parent_ids:
542
parent_tree = tree.branch.repository.revision_tree(parent_id)
543
parent_trees.append((parent_id, parent_tree))
544
parent_tree.lock_read()
545
result.set_parent_trees(parent_trees, [])
546
result.set_state_from_inventory(tree.inventory)
548
for revid, parent_tree in parent_trees:
552
# The caller won't have a chance to unlock this, so make sure we
521
558
def get_ghosts(self):
548
585
def _get_fields_to_entry(self):
549
586
"""Get a function which converts entry fields into a entry record.
551
This handles kind, size, and executable, as well as parent records.
588
This handles size and executable, as well as parent records.
553
590
:return: A function which takes a list of fields, and returns an
554
591
appropriate record for storing in memory.
556
593
# This is intentionally unrolled for performance
557
594
num_present_parents = self._num_present_parents()
558
595
if num_present_parents == 0:
559
def fields_to_entry_0_parents(fields, _int=int, _tuple=tuple,
560
_mini_to_kind=self._minikind_to_kind):
561
path_name_file_id_key = _tuple(fields[:3])
596
def fields_to_entry_0_parents(fields, _int=int):
597
path_name_file_id_key = (fields[0], fields[1], fields[2])
562
598
return (path_name_file_id_key, [
564
_mini_to_kind[fields[3]], # kind
600
fields[3], # minikind
565
601
fields[4], # fingerprint
566
602
_int(fields[5]), # size
567
603
fields[6] == 'y', # executable
570
606
return fields_to_entry_0_parents
571
607
elif num_present_parents == 1:
572
def fields_to_entry_1_parent(fields, _int=int, _tuple=tuple,
573
_mini_to_kind=self._minikind_to_kind):
574
path_name_file_id_key = _tuple(fields[:3])
608
def fields_to_entry_1_parent(fields, _int=int):
609
path_name_file_id_key = (fields[0], fields[1], fields[2])
575
610
return (path_name_file_id_key, [
577
_mini_to_kind[fields[3]], # kind
612
fields[3], # minikind
578
613
fields[4], # fingerprint
579
614
_int(fields[5]), # size
580
615
fields[6] == 'y', # executable
581
616
fields[7], # packed_stat or revision_id
584
_mini_to_kind[fields[8]], # kind
619
fields[8], # minikind
585
620
fields[9], # fingerprint
586
621
_int(fields[10]), # size
587
622
fields[11] == 'y', # executable
591
626
return fields_to_entry_1_parent
592
627
elif num_present_parents == 2:
593
def fields_to_entry_2_parents(fields, _int=int, _tuple=tuple,
594
_mini_to_kind=self._minikind_to_kind):
595
path_name_file_id_key = _tuple(fields[:3])
628
def fields_to_entry_2_parents(fields, _int=int):
629
path_name_file_id_key = (fields[0], fields[1], fields[2])
596
630
return (path_name_file_id_key, [
598
_mini_to_kind[fields[3]], # kind
632
fields[3], # minikind
599
633
fields[4], # fingerprint
600
634
_int(fields[5]), # size
601
635
fields[6] == 'y', # executable
602
636
fields[7], # packed_stat or revision_id
605
_mini_to_kind[fields[8]], # kind
639
fields[8], # minikind
606
640
fields[9], # fingerprint
607
641
_int(fields[10]), # size
608
642
fields[11] == 'y', # executable
609
643
fields[12], # packed_stat or revision_id
612
_mini_to_kind[fields[13]],# kind
646
fields[13], # minikind
613
647
fields[14], # fingerprint
614
648
_int(fields[15]), # size
615
649
fields[16] == 'y', # executable
619
653
return fields_to_entry_2_parents
621
def fields_to_entry_n_parents(fields, _int=int, _tuple=tuple,
622
_mini_to_kind=self._minikind_to_kind):
623
path_name_file_id_key = _tuple(fields[:3])
624
trees = [(_mini_to_kind[fields[cur]], # kind
655
def fields_to_entry_n_parents(fields, _int=int):
656
path_name_file_id_key = (fields[0], fields[1], fields[2])
657
trees = [(fields[cur], # minikind
625
658
fields[cur+1], # fingerprint
626
659
_int(fields[cur+2]), # size
627
660
fields[cur+3] == 'y', # executable
668
701
while entry_index < len(block) and block[entry_index][0][1] == basename:
669
702
if block[entry_index][1][tree_index][0] not in \
670
('absent', 'relocated'):
703
('a', 'r'): # absent, relocated
671
704
return block_index, entry_index, True, True
673
706
return block_index, entry_index, True, False
696
729
if not file_present:
697
730
return None, None
698
731
entry = self._dirblocks[block_index][1][entry_index]
699
assert entry[0][2] and entry[1][tree_index][0] not in ('absent', 'relocated'), 'unversioned entry?!?!'
732
assert entry[0][2] and entry[1][tree_index][0] not in ('a', 'r'), 'unversioned entry?!?!'
701
734
if entry[0][2] != fileid_utf8:
702
735
raise BzrError('integrity error ? : mismatching tree_index, file_id and path')
705
738
for entry in self._iter_entries():
706
739
if entry[0][2] == fileid_utf8:
707
if entry[1][tree_index][0] == 'relocated':
740
if entry[1][tree_index][0] == 'r': # relocated
708
741
# look up the real location directly by path
709
742
return self._get_entry(tree_index,
710
743
fileid_utf8=fileid_utf8,
711
744
path_utf8=entry[1][tree_index][1])
712
if entry[1][tree_index][0] == 'absent':
745
if entry[1][tree_index][0] == 'a': # absent
713
746
# not in the tree at all.
714
747
return None, None
722
755
The new dirstate will be an empty tree - that is it has no parents,
723
756
and only a root node - which has id ROOT_ID.
758
The object will be write locked when returned to the caller,
759
unless there was an exception in the writing, in which case it
725
762
:param path: The name of the file for the dirstate.
726
763
:return: A DirState object.
730
767
# stock empty dirstate information - a root with ROOT_ID, no children,
731
768
# and no parents. Finally it calls save() to ensure that this data will
734
result._state_file = open(path, 'wb+')
770
result = DirState(path)
735
771
# root dir and root dir contents with no children.
736
772
empty_tree_dirblocks = [('', []), ('', [])]
737
773
# a new root directory, with a NULLSTAT.
738
774
empty_tree_dirblocks[0][1].append(
739
775
(('', '', bzrlib.inventory.ROOT_ID), [
740
('directory', '', 0, False, DirState.NULLSTAT),
776
('d', '', 0, False, DirState.NULLSTAT),
742
result._set_data([], empty_tree_dirblocks)
780
result._set_data([], empty_tree_dirblocks)
746
result._state_file.close()
772
810
executable = inv_entry.executable
775
return (kind, fingerprint, size, executable, tree_data)
813
return (minikind, fingerprint, size, executable, tree_data)
777
815
def _iter_entries(self):
778
816
"""Iterate over all the entries in the dirstate.
821
859
def on_file(path):
822
"""Construct a DirState on the file at path path."""
824
result._state_file = open(path, 'rb+')
860
"""Construct a DirState on the file at path path.
862
:return: An unlocked DirState object, associated with the given path.
864
result = DirState(path)
827
867
def _read_dirblocks_if_needed(self):
868
908
field_count - cur, expected_field_count, entry_size,
869
909
self._num_entries, fields)
871
fields_to_entry = self._get_fields_to_entry()
872
entries = [fields_to_entry(fields[pos:pos+entry_size])
873
for pos in xrange(cur, field_count, entry_size)]
874
self._entries_to_current_state(entries)
911
if num_present_parents == 1:
912
# Bind external functions to local names
914
# We access all fields in order, so we can just iterate over
915
# them. Grab an straight iterator over the fields. (We use an
916
# iterator because we don't want to do a lot of additions, nor
917
# do we want to do a lot of slicing)
918
next = iter(fields).next
919
# Move the iterator to the current position
920
for x in xrange(cur):
922
# The two blocks here are deliberate: the root block and the
923
# contents-of-root block.
924
self._dirblocks = [('', []), ('', [])]
925
current_block = self._dirblocks[0][1]
927
append_entry = current_block.append
928
for count in xrange(self._num_entries):
932
if dirname != current_dirname:
933
# new block - different dirname
935
current_dirname = dirname
936
self._dirblocks.append((current_dirname, current_block))
937
append_entry = current_block.append
938
# we know current_dirname == dirname, so re-use it to avoid
939
# creating new strings
940
entry = ((current_dirname, name, file_id),
943
next(), # fingerprint
945
next() == 'y', # executable
946
next(), # packed_stat or revision_id
950
next(), # fingerprint
952
next() == 'y', # executable
953
next(), # packed_stat or revision_id
957
assert trailing == '\n'
958
# append the entry to the current block
960
self._split_root_dirblock_into_contents()
962
fields_to_entry = self._get_fields_to_entry()
963
entries = [fields_to_entry(fields[pos:pos+entry_size])
964
for pos in xrange(cur, field_count, entry_size)]
965
self._entries_to_current_state(entries)
875
966
self._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
877
968
def _read_header(self):
967
1061
:param path: The path inside the tree to set - '' is the root, 'foo'
968
1062
is the path foo in the root.
969
:param new_id: The new id to assign to the path. If unicode, it will
970
be encoded to utf8. In future this will be deprecated: avoid using
971
unicode ids if possible.
1063
:param new_id: The new id to assign to the path. This must be a utf8
1064
file id (not unicode, and not None).
973
1066
# TODO: start warning here.
974
if new_id.__class__ == unicode:
975
new_id = new_id.encode('utf8')
1067
assert new_id.__class__ == str
976
1068
self._read_dirblocks_if_needed()
978
1070
import pdb;pdb.set_trace()
1041
1133
# one: the current tree
1042
1134
for entry in self._iter_entries():
1043
1135
# skip entries not in the current tree
1044
if entry[1][0][0] in ('absent', 'relocated'):
1136
if entry[1][0][0] in ('a', 'r'): # absent, relocated
1046
1138
by_path[entry[0]] = [entry[1][0]] + \
1047
1139
[DirState.NULL_PARENT_DETAILS] * parent_count
1067
1159
# new entry at this path: by adding the id->path mapping last,
1068
1160
# all the mappings are valid and have correct relocation
1069
1161
# records where needed.
1070
file_id = entry.file_id.encode('utf8')
1162
file_id = entry.file_id
1071
1163
path_utf8 = path.encode('utf8')
1072
1164
dirname, basename = os.path.split(path_utf8)
1073
1165
new_entry_key = (dirname, basename, file_id)
1082
1174
# other trees, so put absent pointers there
1083
1175
# This is the vertical axis in the matrix, all pointing
1084
1176
# tot he real path.
1085
by_path[entry_key][tree_index] = ('relocated', path_utf8, 0, False, '')
1177
by_path[entry_key][tree_index] = ('r', path_utf8, 0, False, '')
1086
1178
# by path consistency: Insert into an existing path record (trivial), or
1087
1179
# add a new one with relocation pointers for the other tree indexes.
1088
1180
if new_entry_key in id_index[file_id]:
1106
1198
# fragmented situations by reusing the relocation
1108
1200
a_key = iter(id_index[file_id]).next()
1109
if by_path[a_key][lookup_index][0] in ('relocated', 'absent'):
1201
if by_path[a_key][lookup_index][0] in ('r', 'a'):
1110
1202
# its a pointer or missing statement, use it as is.
1111
1203
new_details.append(by_path[a_key][lookup_index])
1113
1205
# we have the right key, make a pointer to it.
1114
1206
real_path = ('/'.join(a_key[0:2])).strip('/')
1115
new_details.append(('relocated', real_path, 0, False, ''))
1207
new_details.append(('r', real_path, 0, False, ''))
1116
1208
new_details.append(self._inv_entry_to_details(entry))
1117
1209
new_details.extend(new_location_suffix)
1118
1210
by_path[new_entry_key] = new_details
1159
1251
while current_new or current_old:
1160
1252
# skip entries in old that are not really there
1161
if current_old and current_old[1][0][0] in ('relocated', 'absent'):
1253
if current_old and current_old[1][0][0] in ('r', 'a'):
1254
# relocated or absent
1162
1255
current_old = advance(old_iterator)
1164
1257
if current_new:
1165
1258
# convert new into dirblock style
1166
1259
new_path_utf8 = current_new[0].encode('utf8')
1167
1260
new_dirname, new_basename = os.path.split(new_path_utf8)
1168
new_id = current_new[1].file_id.encode('utf8')
1261
new_id = current_new[1].file_id
1169
1262
new_entry_key = (new_dirname, new_basename, new_id)
1171
1264
# for safety disable variables
1187
1280
# TODO: update the record if anything significant has changed.
1188
1281
# the minimal required trigger is if the execute bit or cached
1189
1282
# kind has changed.
1283
kind = DirState._minikind_to_kind[current_old[1][0][0]]
1190
1284
if (current_old[1][0][3] != current_new[1].executable or
1191
current_old[1][0][0] != current_new[1].kind):
1285
kind != current_new[1].kind):
1192
1286
self.update_minimal(current_old[0], current_new[1].kind,
1193
1287
num_present_parents,
1194
1288
executable=current_new[1].executable,
1223
1317
all_remaining_keys = set()
1224
1318
# Dont check the working tree, because its going.
1225
1319
for details in current_old[1][1:]:
1226
if details[0] not in ('absent', 'relocated'):
1320
if details[0] not in ('a', 'r'): # absent, relocated
1227
1321
all_remaining_keys.add(current_old[0])
1228
elif details[0] == 'relocated':
1322
elif details[0] == 'r': # relocated
1229
1323
# record the key for the real path.
1230
1324
all_remaining_keys.add(tuple(os.path.split(details[1])) + (current_old[0][2],))
1231
1325
# absent rows are not present at any path.
1255
1349
update_tree_details = self._dirblocks[update_block_index][1][update_entry_index][1]
1256
1350
# it must not be absent at the moment
1257
assert update_tree_details[0][0] != 'absent'
1351
assert update_tree_details[0][0] != 'a' # absent
1258
1352
update_tree_details[0] = DirState.NULL_PARENT_DETAILS
1259
1353
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1260
1354
return last_reference
1267
1361
if packed_stat is None:
1268
1362
packed_stat = DirState.NULLSTAT
1269
1363
entry_index, present = self._find_entry_index(key, block)
1270
new_details = (kind, fingerprint, size, executable, packed_stat)
1364
minikind = DirState._kind_to_minikind[kind]
1365
new_details = (minikind, fingerprint, size, executable, packed_stat)
1271
1366
assert id_index is not None, 'need an id index to do updates for now !'
1272
1367
if not present:
1273
1368
# new entry, synthesis cross reference here,
1293
1388
assert path_utf8 is not None
1294
1389
self._dirblocks[other_block_index][1][other_entry_index][1][0] = \
1295
('relocated', path_utf8, 0, False, '')
1390
('r', path_utf8, 0, False, '')
1297
1392
for lookup_index in xrange(1, num_present_parents + 1):
1298
1393
# grab any one entry, use it to find the right path.
1306
1401
self._find_entry_index(other_key, self._dirblocks[update_block_index][1])
1308
1403
update_details = self._dirblocks[update_block_index][1][update_entry_index][1][lookup_index]
1309
if update_details[0] in ('relocated', 'absent'):
1404
if update_details[0] in ('r', 'a'): # relocated, absent
1310
1405
# its a pointer or absent in lookup_index's tree, use
1312
1407
new_entry[1].append(update_details)
1314
1409
# we have the right key, make a pointer to it.
1315
1410
pointer_path = os.path.join(*other_key[0:2])
1316
new_entry[1].append(('relocated', pointer_path, 0, False, ''))
1411
new_entry[1].append(('r', pointer_path, 0, False, ''))
1317
1412
block.insert(entry_index, new_entry)
1318
1413
existing_keys.add(key)
1343
1438
entry_index, present = self._find_entry_index(entry_key, self._dirblocks[block_index][1])
1345
1440
self._dirblocks[block_index][1][entry_index][1][0] = \
1346
('relocated', path_utf8, 0, False, '')
1441
('r', path_utf8, 0, False, '')
1347
1442
# add a containing dirblock if needed.
1348
if new_details[0] == 'directory':
1443
if new_details[0] == 'd':
1349
1444
subdir_key = (os.path.join(*key[0:2]), '', '')
1350
1445
block_index, present = self._find_block_index_from_key(subdir_key)
1351
1446
if not present:
1354
1449
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1452
def _wipe_state(self):
1453
"""Forget all state information about the dirstate."""
1454
self._header_state = DirState.NOT_IN_MEMORY
1455
self._dirblock_state = DirState.NOT_IN_MEMORY
1458
self._dirblocks = []
1460
def lock_read(self):
1461
"""Acquire a read lock on the dirstate"""
1462
if self._lock_token is not None:
1463
raise errors.LockContention(self._lock_token)
1464
self._lock_token = lock.ReadLock(self._filename)
1465
self._state_file = self._lock_token.f
1468
def lock_write(self):
1469
"""Acquire a write lock on the dirstate"""
1470
if self._lock_token is not None:
1471
raise errors.LockContention(self._lock_token)
1472
self._lock_token = lock.WriteLock(self._filename)
1473
self._state_file = self._lock_token.f
1477
"""Drop any locks held on the dirstate"""
1478
if self._lock_token is None:
1479
raise errors.LockNotHeld(self)
1480
self._state_file = None
1481
self._lock_token.unlock()
1482
self._lock_token = None
1484
def _requires_lock(self):
1485
"""Checks that a lock is currently held by someone on the dirstate"""
1486
if not self._lock_token:
1487
raise errors.ObjectNotLocked(self)
1358
1489
def pack_stat(st, _encode=base64.encodestring, _pack=struct.pack):
1359
1490
"""Convert stat values into a packed representation."""