292
281
# we don't need to commit this, because the caller already
293
282
# determined that an existing revision of this file is
294
# appropriate. If its not being considered for committing then
295
# it and all its parents to the root must be unaltered so
296
# no-change against the basis.
297
if ie.revision == self._new_revision_id:
298
raise AssertionError("Impossible situation, a skipped "
299
"inventory entry (%r) claims to be modified in this "
300
"commit (%r).", (ie, self._new_revision_id))
284
return None, (ie.revision == self._new_revision_id)
302
285
# XXX: Friction: parent_candidates should return a list not a dict
303
286
# so that we don't have to walk the inventories again.
304
287
parent_candiate_entries = ie.parent_candidates(parent_invs)
305
head_set = self._heads(ie.file_id, parent_candiate_entries.keys())
288
head_set = self._heads(parent_candiate_entries.keys())
307
290
for inv in parent_invs:
308
291
if ie.file_id in inv:
417
400
return self._get_delta(ie, basis_inv, path), True
419
402
def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
403
versionedfile = self.repository.weave_store.get_weave_or_empty(
404
file_id, self.repository.get_transaction())
405
# Don't change this to add_lines - add_lines_with_ghosts is cheaper
406
# than add_lines, and allows committing when a parent is ghosted for
420
408
# Note: as we read the content directly from the tree, we know its not
421
409
# been turned into unicode or badly split - but a broken tree
422
410
# implementation could give us bad output from readlines() so this is
423
411
# not a guarantee of safety. What would be better is always checking
424
412
# the content during test suite execution. RBC 20070912
425
parent_keys = tuple((file_id, parent) for parent in parents)
426
return self.repository.texts.add_lines(
427
(file_id, self._new_revision_id), parent_keys, new_lines,
428
nostore_sha=nostore_sha, random_id=self.random_revid,
429
check_content=False)[0:2]
414
return versionedfile.add_lines_with_ghosts(
415
self._new_revision_id, parents, new_lines,
416
nostore_sha=nostore_sha, random_id=self.random_revid,
417
check_content=False)[0:2]
419
versionedfile.clear_cache()
432
422
class RootCommitBuilder(CommitBuilder):
455
445
revisions and file history. It's normally accessed only by the Branch,
456
446
which views a particular line of development through that history.
458
The Repository builds on top of some byte storage facilies (the revisions,
459
signatures, inventories and texts attributes) and a Transport, which
460
respectively provide byte storage and a means to access the (possibly
448
The Repository builds on top of Stores and a Transport, which respectively
449
describe the disk data format and the way of accessing the (possibly
463
The byte storage facilities are addressed via tuples, which we refer to
464
as 'keys' throughout the code base. Revision_keys, inventory_keys and
465
signature_keys are all 1-tuples: (revision_id,). text_keys are two-tuples:
466
(file_id, revision_id). We use this interface because it allows low
467
friction with the underlying code that implements disk indices, network
468
encoding and other parts of bzrlib.
470
:ivar revisions: A bzrlib.versionedfile.VersionedFiles instance containing
471
the serialised revisions for the repository. This can be used to obtain
472
revision graph information or to access raw serialised revisions.
473
The result of trying to insert data into the repository via this store
474
is undefined: it should be considered read-only except for implementors
476
:ivar signatures: A bzrlib.versionedfile.VersionedFiles instance containing
477
the serialised signatures for the repository. This can be used to
478
obtain access to raw serialised signatures. The result of trying to
479
insert data into the repository via this store is undefined: it should
480
be considered read-only except for implementors of repositories.
481
:ivar inventories: A bzrlib.versionedfile.VersionedFiles instance containing
482
the serialised inventories for the repository. This can be used to
483
obtain unserialised inventories. The result of trying to insert data
484
into the repository via this store is undefined: it should be
485
considered read-only except for implementors of repositories.
486
:ivar texts: A bzrlib.versionedfile.VersionedFiles instance containing the
487
texts of files and directories for the repository. This can be used to
488
obtain file texts or file graphs. Note that Repository.iter_file_bytes
489
is usually a better interface for accessing file texts.
490
The result of trying to insert data into the repository via this store
491
is undefined: it should be considered read-only except for implementors
493
:ivar _transport: Transport for file access to repository, typically
494
pointing to .bzr/repository.
497
453
# What class to use for a CommitBuilder. Often its simpler to change this
532
def add_fallback_repository(self, repository):
533
"""Add a repository to use for looking up data not held locally.
535
:param repository: A repository.
537
if not self._format.supports_external_lookups:
538
raise errors.UnstackableRepositoryFormat(self._format, self.base)
539
self._check_fallback_repository(repository)
540
self._fallback_repositories.append(repository)
541
self.texts.add_fallback_versioned_files(repository.texts)
542
self.inventories.add_fallback_versioned_files(repository.inventories)
543
self.revisions.add_fallback_versioned_files(repository.revisions)
544
self.signatures.add_fallback_versioned_files(repository.signatures)
546
def _check_fallback_repository(self, repository):
547
"""Check that this repository can fallback to repository safely.
549
Raise an error if not.
551
:param repository: A repository to fallback to.
553
return InterRepository._assert_same_model(self, repository)
555
489
def add_inventory(self, revision_id, inv, parents):
556
490
"""Add the inventory inv to the repository as revision_id.
558
492
:param parents: The revision ids of the parents that revision_id
559
493
is known to have and are in the repository already.
561
:returns: The validator(which is a sha1 digest, though what is sha'd is
562
repository format specific) of the serialized inventory.
495
returns the sha1 of the serialized inventory.
564
if not self.is_in_write_group():
565
raise AssertionError("%r not in write group" % (self,))
497
assert self.is_in_write_group()
566
498
_mod_revision.check_not_reserved_id(revision_id)
567
if not (inv.revision_id is None or inv.revision_id == revision_id):
568
raise AssertionError(
569
"Mismatch between inventory revision"
570
" id and insertion revid (%r, %r)"
571
% (inv.revision_id, revision_id))
573
raise AssertionError()
499
assert inv.revision_id is None or inv.revision_id == revision_id, \
500
"Mismatch between inventory revision" \
501
" id and insertion revid (%r, %r)" % (inv.revision_id, revision_id)
502
assert inv.root is not None
574
503
inv_lines = self._serialise_inventory_to_lines(inv)
575
return self._inventory_add_lines(revision_id, parents,
504
inv_vf = self.get_inventory_weave()
505
return self._inventory_add_lines(inv_vf, revision_id, parents,
576
506
inv_lines, check_content=False)
578
def _inventory_add_lines(self, revision_id, parents, lines,
508
def _inventory_add_lines(self, inv_vf, revision_id, parents, lines,
579
509
check_content=True):
580
510
"""Store lines in inv_vf and return the sha1 of the inventory."""
581
parents = [(parent,) for parent in parents]
582
return self.inventories.add_lines((revision_id,), parents, lines,
512
for parent in parents:
514
final_parents.append(parent)
515
return inv_vf.add_lines(revision_id, final_parents, lines,
583
516
check_content=check_content)[0]
585
519
def add_revision(self, revision_id, rev, inv=None, config=None):
586
520
"""Add rev to the revision store as revision_id.
602
536
plaintext = Testament(rev, inv).as_short_text()
603
537
self.store_revision_signature(
604
538
gpg.GPGStrategy(config), plaintext, revision_id)
605
# check inventory present
606
if not self.inventories.get_parent_map([(revision_id,)]):
539
if not revision_id in self.get_inventory_weave():
608
541
raise errors.WeaveRevisionNotPresent(revision_id,
542
self.get_inventory_weave())
611
544
# yes, this is not suitable for adding with ghosts.
612
rev.inventory_sha1 = self.add_inventory(revision_id, inv,
616
rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
617
self._add_revision(rev)
545
self.add_inventory(revision_id, inv, rev.parent_ids)
546
self._revision_store.add_revision(rev, self.get_transaction())
619
def _add_revision(self, revision):
620
text = self._serializer.write_revision_to_string(revision)
621
key = (revision.revision_id,)
622
parents = tuple((parent,) for parent in revision.parent_ids)
623
self.revisions.add_lines(key, parents, osutils.split_lines(text))
548
def _add_revision_text(self, revision_id, text):
549
revision = self._revision_store._serializer.read_revision_from_string(
551
self._revision_store._add_revision(revision, StringIO(text),
552
self.get_transaction())
625
554
def all_revision_ids(self):
626
555
"""Returns a list of all the revision ids in the repository.
628
This is conceptually deprecated because code should generally work on
629
the graph reachable from a particular revision, and ignore any other
630
revisions that might be present. There is no direct replacement
557
This is deprecated because code should generally work on the graph
558
reachable from a particular revision, and ignore any other revisions
559
that might be present. There is no direct replacement method.
633
561
if 'evil' in debug.debug_flags:
634
562
mutter_callsite(2, "all_revision_ids is linear with history.")
682
610
# the following are part of the public API for Repository:
683
611
self.bzrdir = a_bzrdir
684
612
self.control_files = control_files
685
self._transport = control_files._transport
686
self.base = self._transport.base
613
self._revision_store = _revision_store
614
# backwards compatibility
615
self.weave_store = text_store
688
617
self._reconcile_does_inventory_gc = True
689
618
self._reconcile_fixes_text_parents = False
690
self._reconcile_backsup_inventory = True
691
619
# not right yet - should be more semantically clear ?
621
self.control_store = control_store
622
self.control_weaves = control_store
693
623
# TODO: make sure to construct the right store classes, etc, depending
694
624
# on whether escaping is required.
695
625
self._warn_if_deprecated()
696
626
self._write_group = None
697
# Additional places to query for data.
698
self._fallback_repositories = []
699
# What order should fetch operations request streams in?
700
# The default is unordered as that is the cheapest for an origin to
702
self._fetch_order = 'unordered'
703
# Does this repository use deltas that can be fetched as-deltas ?
704
# (E.g. knits, where the knit deltas can be transplanted intact.
705
# We default to False, which will ensure that enough data to get
706
# a full text out of any fetch stream will be grabbed.
707
self._fetch_uses_deltas = False
708
# Should fetch trigger a reconcile after the fetch? Only needed for
709
# some repository formats that can suffer internal inconsistencies.
710
self._fetch_reconcile = False
627
self.base = control_files._transport.base
712
629
def __repr__(self):
713
630
return '%s(%r)' % (self.__class__.__name__,
836
749
last_revision.timezone)
838
751
# now gather global repository information
839
# XXX: This is available for many repos regardless of listability.
840
752
if self.bzrdir.root_transport.listable():
841
# XXX: do we want to __define len__() ?
842
# Maybe the versionedfiles object should provide a different
843
# method to get the number of keys.
844
result['revisions'] = len(self.revisions.keys())
753
c, t = self._revision_store.total_size(self.get_transaction())
754
result['revisions'] = c
848
def find_branches(self, using=False):
849
"""Find branches underneath this repository.
851
This will include branches inside other branches.
853
:param using: If True, list only branches using this repository.
855
if using and not self.is_shared():
857
return [self.bzrdir.open_branch()]
858
except errors.NotBranchError:
860
class Evaluator(object):
863
self.first_call = True
865
def __call__(self, bzrdir):
866
# On the first call, the parameter is always the bzrdir
867
# containing the current repo.
868
if not self.first_call:
870
repository = bzrdir.open_repository()
871
except errors.NoRepositoryPresent:
874
return False, (None, repository)
875
self.first_call = False
877
value = (bzrdir.open_branch(), None)
878
except errors.NotBranchError:
883
for branch, repository in bzrdir.BzrDir.find_bzrdirs(
884
self.bzrdir.root_transport, evaluate=Evaluator()):
885
if branch is not None:
886
branches.append(branch)
887
if not using and repository is not None:
888
branches.extend(repository.find_branches())
892
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
893
"""Return the revision ids that other has that this does not.
895
These are returned in topological order.
897
revision_id: only return revision ids included by revision_id.
899
return InterRepository.get(other, self).search_missing_revision_ids(
900
revision_id, find_ghosts)
902
@deprecated_method(one_two)
904
def missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
905
"""Return the revision ids that other has that this does not.
907
These are returned in topological order.
909
revision_id: only return revision ids included by revision_id.
911
keys = self.search_missing_revision_ids(
912
other, revision_id, find_ghosts).get_keys()
915
parents = other.get_graph().get_parent_map(keys)
918
return tsort.topo_sort(parents)
758
def get_data_stream(self, revision_ids):
759
raise NotImplementedError(self.get_data_stream)
761
def insert_data_stream(self, stream):
762
"""XXX What does this really do?
764
Is it a substitute for fetch?
765
Should it manage its own write group ?
767
for item_key, bytes in stream:
768
if item_key[0] == 'file':
769
(file_id,) = item_key[1:]
770
knit = self.weave_store.get_weave_or_empty(
771
file_id, self.get_transaction())
772
elif item_key == ('inventory',):
773
knit = self.get_inventory_weave()
774
elif item_key == ('revisions',):
775
knit = self._revision_store.get_revision_file(
776
self.get_transaction())
777
elif item_key == ('signatures',):
778
knit = self._revision_store.get_signature_file(
779
self.get_transaction())
781
raise RepositoryDataStreamError(
782
"Unrecognised data stream key '%s'" % (item_key,))
783
decoded_list = bencode.bdecode(bytes)
784
format = decoded_list.pop(0)
787
for version, options, parents, some_bytes in decoded_list:
788
data_list.append((version, options, len(some_bytes), parents))
789
knit_bytes += some_bytes
790
knit.insert_data_stream(
791
(format, data_list, StringIO(knit_bytes).read))
794
def missing_revision_ids(self, other, revision_id=None):
795
"""Return the revision ids that other has that this does not.
797
These are returned in topological order.
799
revision_id: only return revision ids included by revision_id.
801
return InterRepository.get(other, self).missing_revision_ids(revision_id)
973
856
not _mod_revision.is_null(revision_id)):
974
857
self.get_revision(revision_id)
976
# if there is no specific appropriate InterRepository, this will get
977
# the InterRepository base class, which raises an
978
# IncompatibleRepositories when asked to fetch.
979
859
inter = InterRepository.get(source, self)
980
return inter.fetch(revision_id=revision_id, pb=pb,
981
find_ghosts=find_ghosts)
861
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
862
except NotImplementedError:
863
raise errors.IncompatibleRepositories(source, self)
983
865
def create_bundle(self, target, base, fileobj, format=None):
984
866
return serializer.write_bundle(self, target, base, fileobj, format)
1084
964
@needs_read_lock
1085
965
def has_revision(self, revision_id):
1086
966
"""True if this repository has a copy of the revision."""
1087
return revision_id in self.has_revisions((revision_id,))
1090
def has_revisions(self, revision_ids):
1091
"""Probe to find out the presence of multiple revisions.
1093
:param revision_ids: An iterable of revision_ids.
1094
:return: A set of the revision_ids that were present.
1096
parent_map = self.revisions.get_parent_map(
1097
[(rev_id,) for rev_id in revision_ids])
1099
if _mod_revision.NULL_REVISION in revision_ids:
1100
result.add(_mod_revision.NULL_REVISION)
1101
result.update([key[0] for key in parent_map])
967
if 'evil' in debug.debug_flags:
968
mutter_callsite(3, "has_revision is a LBYL symptom.")
969
return self._revision_store.has_revision_id(revision_id,
970
self.get_transaction())
1104
972
@needs_read_lock
1105
973
def get_revision(self, revision_id):
1128
996
for rev_id in revision_ids:
1129
997
if not rev_id or not isinstance(rev_id, basestring):
1130
998
raise errors.InvalidRevisionId(revision_id=rev_id, branch=self)
1131
keys = [(key,) for key in revision_ids]
1132
stream = self.revisions.get_record_stream(keys, 'unordered', True)
1134
for record in stream:
1135
if record.storage_kind == 'absent':
1136
raise errors.NoSuchRevision(self, record.key[0])
1137
text = record.get_bytes_as('fulltext')
1138
rev = self._serializer.read_revision_from_string(text)
1139
revs[record.key[0]] = rev
1140
return [revs[revid] for revid in revision_ids]
999
revs = self._revision_store.get_revisions(revision_ids,
1000
self.get_transaction())
1002
assert not isinstance(rev.revision_id, unicode)
1003
for parent_id in rev.parent_ids:
1004
assert not isinstance(parent_id, unicode)
1142
1007
@needs_read_lock
1143
1008
def get_revision_xml(self, revision_id):
1184
1050
@needs_write_lock
1185
1051
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1186
1052
signature = gpg_strategy.sign(plaintext)
1187
self.add_signature_text(revision_id, signature)
1190
def add_signature_text(self, revision_id, signature):
1191
self.signatures.add_lines((revision_id,), (),
1192
osutils.split_lines(signature))
1194
def find_text_key_references(self):
1195
"""Find the text key references within the repository.
1197
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
1053
self._revision_store.add_revision_signature_text(revision_id,
1055
self.get_transaction())
1057
def _find_file_ids_from_xml_inventory_lines(self, line_iterator,
1059
"""Helper routine for fileids_altered_by_revision_ids.
1061
This performs the translation of xml lines to revision ids.
1063
:param line_iterator: An iterator of lines
1064
:param revision_ids: The revision ids to filter for. This should be a
1065
set or other type which supports efficient __contains__ lookups, as
1066
the revision id from each parsed line will be looked up in the
1067
revision_ids filter.
1068
:return: a dictionary mapping altered file-ids to an iterable of
1198
1069
revision_ids. Each altered file-ids has the exact revision_ids that
1199
1070
altered it listed explicitly.
1200
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1201
to whether they were referred to by the inventory of the
1202
revision_id that they contain. The inventory texts from all present
1203
revision ids are assessed to generate this report.
1205
revision_keys = self.revisions.keys()
1206
w = self.inventories
1207
pb = ui.ui_factory.nested_progress_bar()
1209
return self._find_text_key_references_from_xml_inventory_lines(
1210
w.iter_lines_added_or_present_in_keys(revision_keys, pb=pb))
1214
def _find_text_key_references_from_xml_inventory_lines(self,
1216
"""Core routine for extracting references to texts from inventories.
1218
This performs the translation of xml lines to revision ids.
1220
:param line_iterator: An iterator of lines, origin_version_id
1221
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1222
to whether they were referred to by the inventory of the
1223
revision_id that they contain. Note that if that revision_id was
1224
not part of the line_iterator's output then False will be given -
1225
even though it may actually refer to that key.
1227
if not self._serializer.support_altered_by_hack:
1228
raise AssertionError(
1229
"_find_text_key_references_from_xml_inventory_lines only "
1230
"supported for branches which store inventory as unnested xml"
1231
", not on %r" % self)
1234
1074
# this code needs to read every new line in every inventory for the
1274
1114
unescape_revid_cache[revision_id] = unescaped
1275
1115
revision_id = unescaped
1277
# Note that unconditionally unescaping means that we deserialise
1278
# every fileid, which for general 'pull' is not great, but we don't
1279
# really want to have some many fulltexts that this matters anyway.
1282
file_id = unescape_fileid_cache[file_id]
1284
unescaped = unescape(file_id)
1285
unescape_fileid_cache[file_id] = unescaped
1288
key = (file_id, revision_id)
1289
setdefault(key, False)
1290
if revision_id == line_key[-1]:
1294
def _find_file_ids_from_xml_inventory_lines(self, line_iterator,
1296
"""Helper routine for fileids_altered_by_revision_ids.
1298
This performs the translation of xml lines to revision ids.
1300
:param line_iterator: An iterator of lines, origin_version_id
1301
:param revision_ids: The revision ids to filter for. This should be a
1302
set or other type which supports efficient __contains__ lookups, as
1303
the revision id from each parsed line will be looked up in the
1304
revision_ids filter.
1305
:return: a dictionary mapping altered file-ids to an iterable of
1306
revision_ids. Each altered file-ids has the exact revision_ids that
1307
altered it listed explicitly.
1310
setdefault = result.setdefault
1312
self._find_text_key_references_from_xml_inventory_lines(
1313
line_iterator).iterkeys():
1314
# once data is all ensured-consistent; then this is
1315
# if revision_id == version_id
1316
if key[-1:] in revision_ids:
1317
setdefault(key[0], set()).add(key[-1])
1320
def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
1117
if revision_id in revision_ids:
1119
file_id = unescape_fileid_cache[file_id]
1121
unescaped = unescape(file_id)
1122
unescape_fileid_cache[file_id] = unescaped
1124
setdefault(file_id, set()).add(revision_id)
1127
def fileids_altered_by_revision_ids(self, revision_ids):
1321
1128
"""Find the file ids and versions affected by revisions.
1323
1130
:param revisions: an iterable containing revision ids.
1324
:param _inv_weave: The inventory weave from this repository or None.
1325
If None, the inventory weave will be opened automatically.
1326
1131
:return: a dictionary mapping altered file-ids to an iterable of
1327
1132
revision_ids. Each altered file-ids has the exact revision_ids that
1328
1133
altered it listed explicitly.
1330
selected_keys = set((revid,) for revid in revision_ids)
1331
w = _inv_weave or self.inventories
1135
assert self._serializer.support_altered_by_hack, \
1136
("fileids_altered_by_revision_ids only supported for branches "
1137
"which store inventory as unnested xml, not on %r" % self)
1138
selected_revision_ids = set(revision_ids)
1139
w = self.get_inventory_weave()
1332
1140
pb = ui.ui_factory.nested_progress_bar()
1334
1142
return self._find_file_ids_from_xml_inventory_lines(
1335
w.iter_lines_added_or_present_in_keys(
1336
selected_keys, pb=pb),
1143
w.iter_lines_added_or_present_in_versions(
1144
selected_revision_ids, pb=pb),
1145
selected_revision_ids)
1352
1160
bytes_iterator is an iterable of bytestrings for the file. The
1353
1161
kind of iterable and length of the bytestrings are unspecified, but for
1354
this implementation, it is a list of bytes produced by
1355
VersionedFile.get_record_stream().
1162
this implementation, it is a list of lines produced by
1163
VersionedFile.get_lines().
1357
1165
:param desired_files: a list of (file_id, revision_id, identifier)
1360
1168
transaction = self.get_transaction()
1362
1169
for file_id, revision_id, callable_data in desired_files:
1363
text_keys[(file_id, revision_id)] = callable_data
1364
for record in self.texts.get_record_stream(text_keys, 'unordered', True):
1365
if record.storage_kind == 'absent':
1366
raise errors.RevisionNotPresent(record.key, self)
1367
yield text_keys[record.key], record.get_bytes_as('fulltext')
1369
def _generate_text_key_index(self, text_key_references=None,
1371
"""Generate a new text key index for the repository.
1373
This is an expensive function that will take considerable time to run.
1375
:return: A dict mapping text keys ((file_id, revision_id) tuples) to a
1376
list of parents, also text keys. When a given key has no parents,
1377
the parents list will be [NULL_REVISION].
1379
# All revisions, to find inventory parents.
1380
if ancestors is None:
1381
graph = self.get_graph()
1382
ancestors = graph.get_parent_map(self.all_revision_ids())
1383
if text_key_references is None:
1384
text_key_references = self.find_text_key_references()
1385
pb = ui.ui_factory.nested_progress_bar()
1387
return self._do_generate_text_key_index(ancestors,
1388
text_key_references, pb)
1392
def _do_generate_text_key_index(self, ancestors, text_key_references, pb):
1393
"""Helper for _generate_text_key_index to avoid deep nesting."""
1394
revision_order = tsort.topo_sort(ancestors)
1395
invalid_keys = set()
1397
for revision_id in revision_order:
1398
revision_keys[revision_id] = set()
1399
text_count = len(text_key_references)
1400
# a cache of the text keys to allow reuse; costs a dict of all the
1401
# keys, but saves a 2-tuple for every child of a given key.
1403
for text_key, valid in text_key_references.iteritems():
1405
invalid_keys.add(text_key)
1407
revision_keys[text_key[1]].add(text_key)
1408
text_key_cache[text_key] = text_key
1409
del text_key_references
1411
text_graph = graph.Graph(graph.DictParentsProvider(text_index))
1412
NULL_REVISION = _mod_revision.NULL_REVISION
1413
# Set a cache with a size of 10 - this suffices for bzr.dev but may be
1414
# too small for large or very branchy trees. However, for 55K path
1415
# trees, it would be easy to use too much memory trivially. Ideally we
1416
# could gauge this by looking at available real memory etc, but this is
1417
# always a tricky proposition.
1418
inventory_cache = lru_cache.LRUCache(10)
1419
batch_size = 10 # should be ~150MB on a 55K path tree
1420
batch_count = len(revision_order) / batch_size + 1
1422
pb.update("Calculating text parents.", processed_texts, text_count)
1423
for offset in xrange(batch_count):
1424
to_query = revision_order[offset * batch_size:(offset + 1) *
1428
for rev_tree in self.revision_trees(to_query):
1429
revision_id = rev_tree.get_revision_id()
1430
parent_ids = ancestors[revision_id]
1431
for text_key in revision_keys[revision_id]:
1432
pb.update("Calculating text parents.", processed_texts)
1433
processed_texts += 1
1434
candidate_parents = []
1435
for parent_id in parent_ids:
1436
parent_text_key = (text_key[0], parent_id)
1438
check_parent = parent_text_key not in \
1439
revision_keys[parent_id]
1441
# the parent parent_id is a ghost:
1442
check_parent = False
1443
# truncate the derived graph against this ghost.
1444
parent_text_key = None
1446
# look at the parent commit details inventories to
1447
# determine possible candidates in the per file graph.
1450
inv = inventory_cache[parent_id]
1452
inv = self.revision_tree(parent_id).inventory
1453
inventory_cache[parent_id] = inv
1454
parent_entry = inv._byid.get(text_key[0], None)
1455
if parent_entry is not None:
1457
text_key[0], parent_entry.revision)
1459
parent_text_key = None
1460
if parent_text_key is not None:
1461
candidate_parents.append(
1462
text_key_cache[parent_text_key])
1463
parent_heads = text_graph.heads(candidate_parents)
1464
new_parents = list(parent_heads)
1465
new_parents.sort(key=lambda x:candidate_parents.index(x))
1466
if new_parents == []:
1467
new_parents = [NULL_REVISION]
1468
text_index[text_key] = new_parents
1470
for text_key in invalid_keys:
1471
text_index[text_key] = [NULL_REVISION]
1171
weave = self.weave_store.get_weave(file_id, transaction)
1172
except errors.NoSuchFile:
1173
raise errors.NoSuchIdInRepository(self, file_id)
1174
yield callable_data, weave.get_lines(revision_id)
1474
1176
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1475
1177
"""Get an iterable listing the keys of all the data introduced by a set
1517
1222
revisions_with_signatures.add(rev_id)
1518
1224
yield ("signatures", None, revisions_with_signatures)
1521
1227
yield ("revisions", None, revision_ids)
1523
1229
@needs_read_lock
1230
def get_inventory_weave(self):
1231
return self.control_weaves.get_weave('inventory',
1232
self.get_transaction())
1524
1235
def get_inventory(self, revision_id):
1525
"""Get Inventory object by revision id."""
1526
return self.iter_inventories([revision_id]).next()
1528
def iter_inventories(self, revision_ids):
1529
"""Get many inventories by revision_ids.
1531
This will buffer some or all of the texts used in constructing the
1532
inventories in memory, but will only parse a single inventory at a
1535
:return: An iterator of inventories.
1537
if ((None in revision_ids)
1538
or (_mod_revision.NULL_REVISION in revision_ids)):
1539
raise ValueError('cannot get null revision inventory')
1540
return self._iter_inventories(revision_ids)
1542
def _iter_inventories(self, revision_ids):
1543
"""single-document based inventory iteration."""
1544
for text, revision_id in self._iter_inventory_xmls(revision_ids):
1545
yield self.deserialise_inventory(revision_id, text)
1547
def _iter_inventory_xmls(self, revision_ids):
1548
keys = [(revision_id,) for revision_id in revision_ids]
1549
stream = self.inventories.get_record_stream(keys, 'unordered', True)
1551
for record in stream:
1552
if record.storage_kind != 'absent':
1553
texts[record.key] = record.get_bytes_as('fulltext')
1555
raise errors.NoSuchRevision(self, record.key)
1557
yield texts[key], key[-1]
1236
"""Get Inventory object by hash."""
1237
return self.deserialise_inventory(
1238
revision_id, self.get_inventory_xml(revision_id))
1559
1240
def deserialise_inventory(self, revision_id, xml):
1560
1241
"""Transform the xml into an inventory object.
1594
1271
return self.get_revision(revision_id).inventory_sha1
1274
def get_revision_graph(self, revision_id=None):
1275
"""Return a dictionary containing the revision graph.
1277
NB: This method should not be used as it accesses the entire graph all
1278
at once, which is much more data than most operations should require.
1280
:param revision_id: The revision_id to get a graph from. If None, then
1281
the entire revision graph is returned. This is a deprecated mode of
1282
operation and will be removed in the future.
1283
:return: a dictionary of revision_id->revision_parents_list.
1285
raise NotImplementedError(self.get_revision_graph)
1288
def get_revision_graph_with_ghosts(self, revision_ids=None):
1289
"""Return a graph of the revisions with ghosts marked as applicable.
1291
:param revision_ids: an iterable of revisions to graph or None for all.
1292
:return: a Graph object with the graph reachable from revision_ids.
1294
if 'evil' in debug.debug_flags:
1296
"get_revision_graph_with_ghosts scales with size of history.")
1297
result = deprecated_graph.Graph()
1298
if not revision_ids:
1299
pending = set(self.all_revision_ids())
1302
pending = set(revision_ids)
1303
# special case NULL_REVISION
1304
if _mod_revision.NULL_REVISION in pending:
1305
pending.remove(_mod_revision.NULL_REVISION)
1306
required = set(pending)
1309
revision_id = pending.pop()
1311
rev = self.get_revision(revision_id)
1312
except errors.NoSuchRevision:
1313
if revision_id in required:
1316
result.add_ghost(revision_id)
1318
for parent_id in rev.parent_ids:
1319
# is this queued or done ?
1320
if (parent_id not in pending and
1321
parent_id not in done):
1323
pending.add(parent_id)
1324
result.add_node(revision_id, rev.parent_ids)
1325
done.add(revision_id)
1328
def _get_history_vf(self):
1329
"""Get a versionedfile whose history graph reflects all revisions.
1331
For weave repositories, this is the inventory weave.
1333
return self.get_inventory_weave()
1596
1335
def iter_reverse_revision_history(self, revision_id):
1597
1336
"""Iterate backwards through revision ids in the lefthand history
1599
1338
:param revision_id: The revision id to start with. All its lefthand
1600
1339
ancestors will be traversed.
1602
graph = self.get_graph()
1341
if revision_id in (None, _mod_revision.NULL_REVISION):
1603
1343
next_id = revision_id
1344
versionedfile = self._get_history_vf()
1605
if next_id in (None, _mod_revision.NULL_REVISION):
1608
# Note: The following line may raise KeyError in the event of
1609
# truncated history. We decided not to have a try:except:raise
1610
# RevisionNotPresent here until we see a use for it, because of the
1611
# cost in an inner loop that is by its very nature O(history).
1612
# Robert Collins 20080326
1613
parents = graph.get_parent_map([next_id])[next_id]
1347
parents = versionedfile.get_parents(next_id)
1614
1348
if len(parents) == 0:
1669
1404
inv = self.get_revision_inventory(revision_id)
1670
1405
return RevisionTree(self, inv, revision_id)
1672
1408
def revision_trees(self, revision_ids):
1673
1409
"""Return Tree for a revision on this branch.
1675
1411
`revision_id` may not be None or 'null:'"""
1676
inventories = self.iter_inventories(revision_ids)
1677
for inv in inventories:
1678
yield RevisionTree(self, inv, inv.revision_id)
1412
assert None not in revision_ids
1413
assert _mod_revision.NULL_REVISION not in revision_ids
1414
texts = self.get_inventory_weave().get_texts(revision_ids)
1415
for text, revision_id in zip(texts, revision_ids):
1416
inv = self.deserialise_inventory(revision_id, text)
1417
yield RevisionTree(self, inv, revision_id)
1680
1419
@needs_read_lock
1681
1420
def get_ancestry(self, revision_id, topo_sorted=True):
1740
1466
def get_transaction(self):
1741
1467
return self.control_files.get_transaction()
1743
@deprecated_method(one_one)
1469
def revision_parents(self, revision_id):
1470
return self.get_inventory_weave().parent_names(revision_id)
1744
1472
def get_parents(self, revision_ids):
1745
1473
"""See StackedParentsProvider.get_parents"""
1746
parent_map = self.get_parent_map(revision_ids)
1747
return [parent_map.get(r, None) for r in revision_ids]
1749
def get_parent_map(self, revision_ids):
1750
"""See graph._StackedParentsProvider.get_parent_map"""
1751
# revisions index works in keys; this just works in revisions
1752
# therefore wrap and unwrap
1755
1475
for revision_id in revision_ids:
1756
1476
if revision_id == _mod_revision.NULL_REVISION:
1757
result[revision_id] = ()
1758
elif revision_id is None:
1759
raise ValueError('get_parent_map(None) is not valid')
1761
query_keys.append((revision_id ,))
1762
for ((revision_id,), parent_keys) in \
1763
self.revisions.get_parent_map(query_keys).iteritems():
1765
result[revision_id] = tuple(parent_revid
1766
for (parent_revid,) in parent_keys)
1768
result[revision_id] = (_mod_revision.NULL_REVISION,)
1480
parents = self.get_revision(revision_id).parent_ids
1481
except errors.NoSuchRevision:
1484
if len(parents) == 0:
1485
parents = [_mod_revision.NULL_REVISION]
1486
parents_list.append(parents)
1771
1489
def _make_parents_provider(self):
1775
1493
"""Return the graph walker for this repository format"""
1776
1494
parents_provider = self._make_parents_provider()
1777
1495
if (other_repository is not None and
1778
not self.has_same_location(other_repository)):
1496
other_repository.bzrdir.transport.base !=
1497
self.bzrdir.transport.base):
1779
1498
parents_provider = graph._StackedParentsProvider(
1780
1499
[parents_provider, other_repository._make_parents_provider()])
1781
1500
return graph.Graph(parents_provider)
1783
def _get_versioned_file_checker(self):
1784
"""Return an object suitable for checking versioned files."""
1785
return _VersionedFileChecker(self)
1787
def revision_ids_to_search_result(self, result_set):
1788
"""Convert a set of revision ids to a graph SearchResult."""
1789
result_parents = set()
1790
for parents in self.get_graph().get_parent_map(
1791
result_set).itervalues():
1792
result_parents.update(parents)
1793
included_keys = result_set.intersection(result_parents)
1794
start_keys = result_set.difference(included_keys)
1795
exclude_keys = result_parents.difference(result_set)
1796
result = graph.SearchResult(start_keys, exclude_keys,
1797
len(result_set), result_set)
1502
def get_versioned_file_checker(self, revisions, revision_versions_cache):
1503
return VersionedFileChecker(revisions, revision_versions_cache, self)
1800
1505
@needs_write_lock
1801
1506
def set_make_working_trees(self, new_value):
1821
1526
@needs_read_lock
1822
1527
def has_signature_for_revision_id(self, revision_id):
1823
1528
"""Query for a revision signature for revision_id in the repository."""
1824
if not self.has_revision(revision_id):
1825
raise errors.NoSuchRevision(self, revision_id)
1826
sig_present = (1 == len(
1827
self.signatures.get_parent_map([(revision_id,)])))
1529
return self._revision_store.has_signature(revision_id,
1530
self.get_transaction())
1830
1532
@needs_read_lock
1831
1533
def get_signature_text(self, revision_id):
1832
1534
"""Return the text for a signature."""
1833
stream = self.signatures.get_record_stream([(revision_id,)],
1835
record = stream.next()
1836
if record.storage_kind == 'absent':
1837
raise errors.NoSuchRevision(self, revision_id)
1838
return record.get_bytes_as('fulltext')
1535
return self._revision_store.get_signature_text(revision_id,
1536
self.get_transaction())
1840
1538
@needs_read_lock
1841
1539
def check(self, revision_ids=None):
1929
1626
def install_revision(repository, rev, revision_tree):
1930
1627
"""Install all revision data into a repository."""
1931
install_revisions(repository, [(rev, revision_tree, None)])
1934
def install_revisions(repository, iterable, num_revisions=None, pb=None):
1935
"""Install all revision data into a repository.
1937
Accepts an iterable of revision, tree, signature tuples. The signature
1940
1628
repository.start_write_group()
1942
for n, (revision, revision_tree, signature) in enumerate(iterable):
1943
_install_revision(repository, revision, revision_tree, signature)
1945
pb.update('Transferring revisions', n + 1, num_revisions)
1630
_install_revision(repository, rev, revision_tree)
1947
1632
repository.abort_write_group()
1968
1653
path, root = entries.next()
1969
1654
if root.revision != rev.revision_id:
1970
1655
raise errors.IncompatibleRevision(repr(repository))
1656
# Add the texts that are not already present
1972
1657
for path, ie in entries:
1973
text_keys[(ie.file_id, ie.revision)] = ie
1974
text_parent_map = repository.texts.get_parent_map(text_keys)
1975
missing_texts = set(text_keys) - set(text_parent_map)
1976
# Add the texts that are not already present
1977
for text_key in missing_texts:
1978
ie = text_keys[text_key]
1980
# FIXME: TODO: The following loop overlaps/duplicates that done by
1981
# commit to determine parents. There is a latent/real bug here where
1982
# the parents inserted are not those commit would do - in particular
1983
# they are not filtered by heads(). RBC, AB
1984
for revision, tree in parent_trees.iteritems():
1985
if ie.file_id not in tree:
1987
parent_id = tree.inventory[ie.file_id].revision
1988
if parent_id in text_parents:
1990
text_parents.append((ie.file_id, parent_id))
1991
lines = revision_tree.get_file(ie.file_id).readlines()
1992
repository.texts.add_lines(text_key, text_parents, lines)
1658
w = repository.weave_store.get_weave_or_empty(ie.file_id,
1659
repository.get_transaction())
1660
if ie.revision not in w:
1662
# FIXME: TODO: The following loop *may* be overlapping/duplicate
1663
# with InventoryEntry.find_previous_heads(). if it is, then there
1664
# is a latent bug here where the parents may have ancestors of each
1666
for revision, tree in parent_trees.iteritems():
1667
if ie.file_id not in tree:
1669
parent_id = tree.inventory[ie.file_id].revision
1670
if parent_id in text_parents:
1672
text_parents.append(parent_id)
1674
vfile = repository.weave_store.get_weave_or_empty(ie.file_id,
1675
repository.get_transaction())
1676
lines = revision_tree.get_file(ie.file_id).readlines()
1677
vfile.add_lines(rev.revision_id, text_parents, lines)
1994
1679
# install the inventory
1995
1680
repository.add_inventory(rev.revision_id, inv, present_parents)
1996
1681
except errors.RevisionAlreadyPresent:
1998
if signature is not None:
1999
repository.add_signature_text(rev.revision_id, signature)
2000
1683
repository.add_revision(rev.revision_id, rev, inv)
2003
1686
class MetaDirRepository(Repository):
2004
"""Repositories in the new meta-dir layout.
2006
:ivar _transport: Transport for access to repository control files,
2007
typically pointing to .bzr/repository.
2010
def __init__(self, _format, a_bzrdir, control_files):
2011
super(MetaDirRepository, self).__init__(_format, a_bzrdir, control_files)
2012
self._transport = control_files._transport
1687
"""Repositories in the new meta-dir layout."""
1689
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
1690
super(MetaDirRepository, self).__init__(_format,
1696
dir_mode = self.control_files._dir_mode
1697
file_mode = self.control_files._file_mode
2014
1700
def is_shared(self):
2015
1701
"""Return True if this repository is flagged as a shared repository."""
2016
return self._transport.has('shared-storage')
1702
return self.control_files._transport.has('shared-storage')
2018
1704
@needs_write_lock
2019
1705
def set_make_working_trees(self, new_value):
2030
self._transport.delete('no-working-trees')
1716
self.control_files._transport.delete('no-working-trees')
2031
1717
except errors.NoSuchFile:
2034
self._transport.put_bytes('no-working-trees', '',
2035
mode=self.bzrdir._get_file_mode())
1720
self.control_files.put_utf8('no-working-trees', '')
2037
1722
def make_working_trees(self):
2038
1723
"""Returns the policy for making working trees on new branches."""
2039
return not self._transport.has('no-working-trees')
2042
class MetaDirVersionedFileRepository(MetaDirRepository):
2043
"""Repositories in a meta-dir, that work via versioned file objects."""
2045
def __init__(self, _format, a_bzrdir, control_files):
2046
super(MetaDirVersionedFileRepository, self).__init__(_format, a_bzrdir,
1724
return not self.control_files._transport.has('no-working-trees')
2050
1727
class RepositoryFormatRegistry(registry.Registry):
2158
1835
"""Return the short description for this format."""
2159
1836
raise NotImplementedError(self.get_format_description)
1838
def _get_revision_store(self, repo_transport, control_files):
1839
"""Return the revision store object for this a_bzrdir."""
1840
raise NotImplementedError(self._get_revision_store)
1842
def _get_text_rev_store(self,
1849
"""Common logic for getting a revision store for a repository.
1851
see self._get_revision_store for the subclass-overridable method to
1852
get the store for a repository.
1854
from bzrlib.store.revision.text import TextRevisionStore
1855
dir_mode = control_files._dir_mode
1856
file_mode = control_files._file_mode
1857
text_store = TextStore(transport.clone(name),
1859
compressed=compressed,
1861
file_mode=file_mode)
1862
_revision_store = TextRevisionStore(text_store, serializer)
1863
return _revision_store
2161
1865
# TODO: this shouldn't be in the base class, it's specific to things that
2162
1866
# use weaves or knits -- mbp 20070207
2163
1867
def _get_versioned_file_store(self,
2237
1940
"""Upload the initial blank content."""
2238
1941
control_files = self._create_control_files(a_bzrdir)
2239
1942
control_files.lock_write()
2240
transport = control_files._transport
2242
utf8_files += [('shared-storage', '')]
2244
transport.mkdir_multi(dirs, mode=a_bzrdir._get_dir_mode())
2245
for (filename, content_stream) in files:
2246
transport.put_file(filename, content_stream,
2247
mode=a_bzrdir._get_file_mode())
2248
for (filename, content_bytes) in utf8_files:
2249
transport.put_bytes_non_atomic(filename, content_bytes,
2250
mode=a_bzrdir._get_file_mode())
1944
control_files._transport.mkdir_multi(dirs,
1945
mode=control_files._dir_mode)
1946
for file, content in files:
1947
control_files.put(file, content)
1948
for file, content in utf8_files:
1949
control_files.put_utf8(file, content)
1951
control_files.put_utf8('shared-storage', '')
2252
1953
control_files.unlock()
2295
1993
'bzrlib.repofmt.pack_repo',
2296
1994
'RepositoryFormatKnitPack3',
2298
format_registry.register_lazy(
2299
'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
2300
'bzrlib.repofmt.pack_repo',
2301
'RepositoryFormatKnitPack4',
2303
format_registry.register_lazy(
2304
'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
2305
'bzrlib.repofmt.pack_repo',
2306
'RepositoryFormatKnitPack5',
2308
format_registry.register_lazy(
2309
'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
2310
'bzrlib.repofmt.pack_repo',
2311
'RepositoryFormatKnitPack5RichRoot',
2313
format_registry.register_lazy(
2314
'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
2315
'bzrlib.repofmt.pack_repo',
2316
'RepositoryFormatKnitPack5RichRootBroken',
2319
# Development formats.
2321
format_registry.register_lazy(
2322
"Bazaar development format 1 (needs bzr.dev from before 1.6)\n",
2323
'bzrlib.repofmt.pack_repo',
2324
'RepositoryFormatPackDevelopment1',
2326
format_registry.register_lazy(
2327
("Bazaar development format 1 with subtree support "
2328
"(needs bzr.dev from before 1.6)\n"),
2329
'bzrlib.repofmt.pack_repo',
2330
'RepositoryFormatPackDevelopment1Subtree',
2332
# 1.6->1.7 go below here
2335
1998
class InterRepository(InterObject):
2360
2023
:param pb: optional progress bar to use for progress reports. If not
2361
2024
provided a default one will be created.
2363
:returns: (copied_revision_count, failures).
2365
# Normally we should find a specific InterRepository subclass to do
2366
# the fetch; if nothing else then at least InterSameDataRepository.
2367
# If none of them is suitable it looks like fetching is not possible;
2368
# we try to give a good message why. _assert_same_model will probably
2369
# give a helpful message; otherwise a generic one.
2370
self._assert_same_model(self.source, self.target)
2371
raise errors.IncompatibleRepositories(self.source, self.target,
2372
"no suitableInterRepository found")
2374
def _walk_to_common_revisions(self, revision_ids):
2375
"""Walk out from revision_ids in source to revisions target has.
2377
:param revision_ids: The start point for the search.
2378
:return: A set of revision ids.
2380
target_graph = self.target.get_graph()
2381
revision_ids = frozenset(revision_ids)
2382
if set(target_graph.get_parent_map(revision_ids)) == revision_ids:
2383
return graph.SearchResult(revision_ids, set(), 0, set())
2384
missing_revs = set()
2385
source_graph = self.source.get_graph()
2386
# ensure we don't pay silly lookup costs.
2387
searcher = source_graph._make_breadth_first_searcher(revision_ids)
2388
null_set = frozenset([_mod_revision.NULL_REVISION])
2391
next_revs, ghosts = searcher.next_with_ghosts()
2392
except StopIteration:
2394
if revision_ids.intersection(ghosts):
2395
absent_ids = set(revision_ids.intersection(ghosts))
2396
# If all absent_ids are present in target, no error is needed.
2397
absent_ids.difference_update(
2398
set(target_graph.get_parent_map(absent_ids)))
2400
raise errors.NoSuchRevision(self.source, absent_ids.pop())
2401
# we don't care about other ghosts as we can't fetch them and
2402
# haven't been asked to.
2403
next_revs = set(next_revs)
2404
# we always have NULL_REVISION present.
2405
have_revs = set(target_graph.get_parent_map(next_revs)).union(null_set)
2406
missing_revs.update(next_revs - have_revs)
2407
searcher.stop_searching_any(have_revs)
2408
return searcher.get_result()
2026
Returns the copied revision count and the failed revisions in a tuple:
2029
raise NotImplementedError(self.fetch)
2410
@deprecated_method(one_two)
2411
2031
@needs_read_lock
2412
def missing_revision_ids(self, revision_id=None, find_ghosts=True):
2032
def missing_revision_ids(self, revision_id=None):
2413
2033
"""Return the revision ids that source has that target does not.
2415
2035
These are returned in topological order.
2417
2037
:param revision_id: only return revision ids included by this
2419
:param find_ghosts: If True find missing revisions in deep history
2420
rather than just finding the surface difference.
2422
return list(self.search_missing_revision_ids(
2423
revision_id, find_ghosts).get_keys())
2426
def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2427
"""Return the revision ids that source has that target does not.
2429
:param revision_id: only return revision ids included by this
2431
:param find_ghosts: If True find missing revisions in deep history
2432
rather than just finding the surface difference.
2433
:return: A bzrlib.graph.SearchResult.
2435
# stop searching at found target revisions.
2436
if not find_ghosts and revision_id is not None:
2437
return self._walk_to_common_revisions([revision_id])
2438
2040
# generic, possibly worst case, slow code path.
2439
2041
target_ids = set(self.target.all_revision_ids())
2440
2042
if revision_id is not None:
2441
2043
source_ids = self.source.get_ancestry(revision_id)
2442
if source_ids[0] is not None:
2443
raise AssertionError()
2044
assert source_ids[0] is None
2444
2045
source_ids.pop(0)
2446
2047
source_ids = self.source.all_revision_ids()
2447
2048
result_set = set(source_ids).difference(target_ids)
2448
return self.source.revision_ids_to_search_result(result_set)
2049
# this may look like a no-op: its not. It preserves the ordering
2050
# other_ids had while only returning the members from other_ids
2051
# that we've decided we need.
2052
return [rev_id for rev_id in source_ids if rev_id in result_set]
2451
2055
def _same_model(source, target):
2452
"""True if source and target have the same data representation.
2454
Note: this is always called on the base class; overriding it in a
2455
subclass will have no effect.
2458
InterRepository._assert_same_model(source, target)
2460
except errors.IncompatibleRepositories, e:
2056
"""True if source and target have the same data representation."""
2057
if source.supports_rich_root() != target.supports_rich_root():
2464
def _assert_same_model(source, target):
2465
"""Raise an exception if two repositories do not use the same model.
2467
if source.supports_rich_root() != target.supports_rich_root():
2468
raise errors.IncompatibleRepositories(source, target,
2469
"different rich-root support")
2470
2059
if source._serializer != target._serializer:
2471
raise errors.IncompatibleRepositories(source, target,
2472
"different serializers")
2475
2064
class InterSameDataRepository(InterRepository):
2518
2107
@needs_write_lock
2519
2108
def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2520
2109
"""See InterRepository.fetch()."""
2521
from bzrlib.fetch import RepoFetcher
2110
from bzrlib.fetch import GenericRepoFetcher
2522
2111
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2523
2112
self.source, self.source._format, self.target,
2524
2113
self.target._format)
2525
f = RepoFetcher(to_repository=self.target,
2114
f = GenericRepoFetcher(to_repository=self.target,
2526
2115
from_repository=self.source,
2527
2116
last_revision=revision_id,
2528
pb=pb, find_ghosts=find_ghosts)
2529
2118
return f.count_copied, f.failed_revisions
2570
2159
# weave specific optimised path:
2572
2161
self.target.set_make_working_trees(self.source.make_working_trees())
2573
except (errors.RepositoryUpgradeRequired, NotImplemented):
2162
except NotImplementedError:
2575
2164
# FIXME do not peek!
2576
if self.source._transport.listable():
2165
if self.source.control_files._transport.listable():
2577
2166
pb = ui.ui_factory.nested_progress_bar()
2579
self.target.texts.insert_record_stream(
2580
self.source.texts.get_record_stream(
2581
self.source.texts.keys(), 'topological', False))
2168
self.target.weave_store.copy_all_ids(
2169
self.source.weave_store,
2171
from_transaction=self.source.get_transaction(),
2172
to_transaction=self.target.get_transaction())
2582
2173
pb.update('copying inventory', 0, 1)
2583
self.target.inventories.insert_record_stream(
2584
self.source.inventories.get_record_stream(
2585
self.source.inventories.keys(), 'topological', False))
2586
self.target.signatures.insert_record_stream(
2587
self.source.signatures.get_record_stream(
2588
self.source.signatures.keys(),
2590
self.target.revisions.insert_record_stream(
2591
self.source.revisions.get_record_stream(
2592
self.source.revisions.keys(),
2593
'topological', True))
2174
self.target.control_weaves.copy_multi(
2175
self.source.control_weaves, ['inventory'],
2176
from_transaction=self.source.get_transaction(),
2177
to_transaction=self.target.get_transaction())
2178
self.target._revision_store.text_store.copy_all_ids(
2179
self.source._revision_store.text_store,
2599
2186
@needs_write_lock
2600
2187
def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2601
2188
"""See InterRepository.fetch()."""
2602
from bzrlib.fetch import RepoFetcher
2189
from bzrlib.fetch import GenericRepoFetcher
2603
2190
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2604
2191
self.source, self.source._format, self.target, self.target._format)
2605
f = RepoFetcher(to_repository=self.target,
2192
f = GenericRepoFetcher(to_repository=self.target,
2606
2193
from_repository=self.source,
2607
2194
last_revision=revision_id,
2608
pb=pb, find_ghosts=find_ghosts)
2609
2196
return f.count_copied, f.failed_revisions
2611
2198
@needs_read_lock
2612
def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2199
def missing_revision_ids(self, revision_id=None):
2613
2200
"""See InterRepository.missing_revision_ids()."""
2614
2201
# we want all revisions to satisfy revision_id in source.
2615
2202
# but we don't want to stat every file here and there.
2636
2222
# we do not have a revision as that would be pointless.
2637
2223
target_ids = set(self.target._all_possible_ids())
2638
2224
possibly_present_revisions = target_ids.intersection(source_ids_set)
2639
actually_present_revisions = set(
2640
self.target._eliminate_revisions_not_present(possibly_present_revisions))
2225
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
2641
2226
required_revisions = source_ids_set.difference(actually_present_revisions)
2227
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
2642
2228
if revision_id is not None:
2643
2229
# we used get_ancestry to determine source_ids then we are assured all
2644
2230
# revisions referenced are present as they are installed in topological order.
2645
2231
# and the tip revision was validated by get_ancestry.
2646
result_set = required_revisions
2232
return required_topo_revisions
2648
2234
# if we just grabbed the possibly available ids, then
2649
2235
# we only have an estimate of whats available and need to validate
2650
2236
# that against the revision records.
2652
self.source._eliminate_revisions_not_present(required_revisions))
2653
return self.source.revision_ids_to_search_result(result_set)
2237
return self.source._eliminate_revisions_not_present(required_topo_revisions)
2656
2240
class InterKnitRepo(InterSameDataRepository):
2680
2264
@needs_write_lock
2681
2265
def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2682
2266
"""See InterRepository.fetch()."""
2683
from bzrlib.fetch import RepoFetcher
2267
from bzrlib.fetch import KnitRepoFetcher
2684
2268
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2685
2269
self.source, self.source._format, self.target, self.target._format)
2686
f = RepoFetcher(to_repository=self.target,
2270
f = KnitRepoFetcher(to_repository=self.target,
2687
2271
from_repository=self.source,
2688
2272
last_revision=revision_id,
2689
pb=pb, find_ghosts=find_ghosts)
2690
2274
return f.count_copied, f.failed_revisions
2692
2276
@needs_read_lock
2693
def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2277
def missing_revision_ids(self, revision_id=None):
2694
2278
"""See InterRepository.missing_revision_ids()."""
2695
2279
if revision_id is not None:
2696
2280
source_ids = self.source.get_ancestry(revision_id)
2697
if source_ids[0] is not None:
2698
raise AssertionError()
2281
assert source_ids[0] is None
2699
2282
source_ids.pop(0)
2701
2284
source_ids = self.source.all_revision_ids()
2706
2289
# we do not have a revision as that would be pointless.
2707
2290
target_ids = set(self.target.all_revision_ids())
2708
2291
possibly_present_revisions = target_ids.intersection(source_ids_set)
2709
actually_present_revisions = set(
2710
self.target._eliminate_revisions_not_present(possibly_present_revisions))
2292
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
2711
2293
required_revisions = source_ids_set.difference(actually_present_revisions)
2294
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
2712
2295
if revision_id is not None:
2713
2296
# we used get_ancestry to determine source_ids then we are assured all
2714
2297
# revisions referenced are present as they are installed in topological order.
2715
2298
# and the tip revision was validated by get_ancestry.
2716
result_set = required_revisions
2299
return required_topo_revisions
2718
2301
# if we just grabbed the possibly available ids, then
2719
2302
# we only have an estimate of whats available and need to validate
2720
2303
# that against the revision records.
2722
self.source._eliminate_revisions_not_present(required_revisions))
2723
return self.source.revision_ids_to_search_result(result_set)
2304
return self.source._eliminate_revisions_not_present(required_topo_revisions)
2726
2307
class InterPackRepo(InterSameDataRepository):
2750
2331
@needs_write_lock
2751
2332
def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2752
2333
"""See InterRepository.fetch()."""
2753
if (len(self.source._fallback_repositories) > 0 or
2754
len(self.target._fallback_repositories) > 0):
2755
# The pack layer is not aware of fallback repositories, so when
2756
# fetching from a stacked repository or into a stacked repository
2757
# we use the generic fetch logic which uses the VersionedFiles
2758
# attributes on repository.
2759
from bzrlib.fetch import RepoFetcher
2760
fetcher = RepoFetcher(self.target, self.source, revision_id,
2762
return fetcher.count_copied, fetcher.failed_revisions
2763
from bzrlib.repofmt.pack_repo import Packer
2764
2334
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2765
2335
self.source, self.source._format, self.target, self.target._format)
2766
2336
self.count_copied = 0
2787
2350
# sensibly detect 'new revisions' without doing a full index scan.
2788
2351
elif _mod_revision.is_null(revision_id):
2789
2352
# nothing to do:
2793
revision_ids = self.search_missing_revision_ids(revision_id,
2794
find_ghosts=find_ghosts).get_keys()
2356
revision_ids = self.missing_revision_ids(revision_id,
2357
find_ghosts=find_ghosts)
2795
2358
except errors.NoSuchRevision:
2796
2359
raise errors.InstallFailed([revision_id])
2797
if len(revision_ids) == 0:
2799
2360
packs = self.source._pack_collection.all_packs()
2800
pack = Packer(self.target._pack_collection, packs, '.fetch',
2801
revision_ids).pack()
2361
pack = self.target._pack_collection.create_pack_from_packs(
2362
packs, '.fetch', revision_ids,
2802
2364
if pack is not None:
2803
2365
self.target._pack_collection._save_pack_names()
2804
2366
# Trigger an autopack. This may duplicate effort as we've just done
2805
2367
# a pack creation, but for now it is simpler to think about as
2806
2368
# 'upload data, then repack if needed'.
2807
2369
self.target._pack_collection.autopack()
2808
return (pack.get_revision_count(), [])
2370
return pack.get_revision_count()
2812
2374
@needs_read_lock
2813
def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2375
def missing_revision_ids(self, revision_id=None, find_ghosts=True):
2814
2376
"""See InterRepository.missing_revision_ids().
2816
:param find_ghosts: Find ghosts throughout the ancestry of
2378
:param find_ghosts: Find ghosts throughough the ancestry of
2819
2381
if not find_ghosts and revision_id is not None:
2820
return self._walk_to_common_revisions([revision_id])
2821
elif revision_id is not None:
2822
# Find ghosts: search for revisions pointing from one repository to
2823
# the other, and vice versa, anywhere in the history of revision_id.
2824
graph = self.target.get_graph(other_repository=self.source)
2382
graph = self.source.get_graph()
2383
missing_revs = set()
2825
2384
searcher = graph._make_breadth_first_searcher([revision_id])
2386
self.target._pack_collection.revision_index.combined_index
2387
null_set = frozenset([_mod_revision.NULL_REVISION])
2829
next_revs, ghosts = searcher.next_with_ghosts()
2390
next_revs = set(searcher.next())
2830
2391
except StopIteration:
2832
if revision_id in ghosts:
2833
raise errors.NoSuchRevision(self.source, revision_id)
2834
found_ids.update(next_revs)
2835
found_ids.update(ghosts)
2836
found_ids = frozenset(found_ids)
2837
# Double query here: should be able to avoid this by changing the
2838
# graph api further.
2839
result_set = found_ids - frozenset(
2840
self.target.get_parent_map(found_ids))
2393
next_revs.difference_update(null_set)
2394
target_keys = [(key,) for key in next_revs]
2395
have_revs = frozenset(node[1][0] for node in
2396
target_index.iter_entries(target_keys))
2397
missing_revs.update(next_revs - have_revs)
2398
searcher.stop_searching_any(have_revs)
2400
elif revision_id is not None:
2401
source_ids = self.source.get_ancestry(revision_id)
2402
assert source_ids[0] is None
2842
2405
source_ids = self.source.all_revision_ids()
2843
# source_ids is the worst possible case we may need to pull.
2844
# now we want to filter source_ids against what we actually
2845
# have in target, but don't try to check for existence where we know
2846
# we do not have a revision as that would be pointless.
2847
target_ids = set(self.target.all_revision_ids())
2848
result_set = set(source_ids).difference(target_ids)
2849
return self.source.revision_ids_to_search_result(result_set)
2406
# source_ids is the worst possible case we may need to pull.
2407
# now we want to filter source_ids against what we actually
2408
# have in target, but don't try to check for existence where we know
2409
# we do not have a revision as that would be pointless.
2410
target_ids = set(self.target.all_revision_ids())
2411
return [r for r in source_ids if (r not in target_ids)]
2852
2414
class InterModel1and2(InterRepository):
2903
2465
def is_compatible(source, target):
2904
2466
"""Be compatible with Knit1 source and Knit3 target"""
2467
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit3
2906
from bzrlib.repofmt.knitrepo import (
2907
RepositoryFormatKnit1,
2908
RepositoryFormatKnit3,
2910
from bzrlib.repofmt.pack_repo import (
2911
RepositoryFormatKnitPack1,
2912
RepositoryFormatKnitPack3,
2913
RepositoryFormatKnitPack4,
2914
RepositoryFormatKnitPack5,
2915
RepositoryFormatKnitPack5RichRoot,
2916
RepositoryFormatPackDevelopment1,
2917
RepositoryFormatPackDevelopment1Subtree,
2920
RepositoryFormatKnit1, # no rr, no subtree
2921
RepositoryFormatKnitPack1, # no rr, no subtree
2922
RepositoryFormatPackDevelopment1, # no rr, no subtree
2923
RepositoryFormatKnitPack5, # no rr, no subtree
2926
RepositoryFormatKnit3, # rr, subtree
2927
RepositoryFormatKnitPack3, # rr, subtree
2928
RepositoryFormatKnitPack4, # rr, no subtree
2929
RepositoryFormatKnitPack5RichRoot,# rr, no subtree
2930
RepositoryFormatPackDevelopment1Subtree, # rr, subtree
2932
for format in norichroot:
2933
if format.rich_root_data:
2934
raise AssertionError('Format %s is a rich-root format'
2935
' but is included in the non-rich-root list'
2937
for format in richroot:
2938
if not format.rich_root_data:
2939
raise AssertionError('Format %s is not a rich-root format'
2940
' but is included in the rich-root list'
2942
# TODO: One alternative is to just check format.rich_root_data,
2943
# instead of keeping membership lists. However, the formats
2944
# *also* have to use the same 'Knit' style of storage
2945
# (line-deltas, fulltexts, etc.)
2946
return (isinstance(source._format, norichroot) and
2947
isinstance(target._format, richroot))
2469
from bzrlib.repofmt.knitrepo import (RepositoryFormatKnit1,
2470
RepositoryFormatKnit3)
2471
from bzrlib.repofmt.pack_repo import (RepositoryFormatKnitPack1,
2472
RepositoryFormatKnitPack3)
2473
return (isinstance(source._format,
2474
(RepositoryFormatKnit1, RepositoryFormatKnitPack1)) and
2475
isinstance(target._format,
2476
(RepositoryFormatKnit3, RepositoryFormatKnitPack3))
2948
2478
except AttributeError:
2958
2488
f = Knit1to2Fetcher(to_repository=self.target,
2959
2489
from_repository=self.source,
2960
2490
last_revision=revision_id,
2961
pb=pb, find_ghosts=find_ghosts)
2962
2492
return f.count_copied, f.failed_revisions
2965
class InterDifferingSerializer(InterKnitRepo):
2495
class InterRemoteToOther(InterRepository):
2968
def _get_repo_format_to_test(self):
2497
def __init__(self, source, target):
2498
InterRepository.__init__(self, source, target)
2499
self._real_inter = None
2972
2502
def is_compatible(source, target):
2973
"""Be compatible with Knit2 source and Knit3 target"""
2974
if source.supports_rich_root() != target.supports_rich_root():
2976
# Ideally, we'd support fetching if the source had no tree references
2977
# even if it supported them...
2978
if (getattr(source, '_format.supports_tree_reference', False) and
2979
not getattr(target, '_format.supports_tree_reference', False)):
2503
if not isinstance(source, remote.RemoteRepository):
2505
source._ensure_real()
2506
real_source = source._real_repository
2507
# Is source's model compatible with target's model, and are they the
2508
# same format? Currently we can only optimise fetching from an
2509
# identical model & format repo.
2510
assert not isinstance(real_source, remote.RemoteRepository), (
2511
"We don't support remote repos backed by remote repos yet.")
2512
return real_source._format == target._format
2983
2514
@needs_write_lock
2984
2515
def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2985
2516
"""See InterRepository.fetch()."""
2986
revision_ids = self.target.search_missing_revision_ids(self.source,
2987
revision_id, find_ghosts=find_ghosts).get_keys()
2988
revision_ids = tsort.topo_sort(
2989
self.source.get_graph().get_parent_map(revision_ids))
2990
def revisions_iterator():
2991
for current_revision_id in revision_ids:
2992
revision = self.source.get_revision(current_revision_id)
2993
tree = self.source.revision_tree(current_revision_id)
2995
signature = self.source.get_signature_text(
2996
current_revision_id)
2997
except errors.NoSuchRevision:
2999
yield revision, tree, signature
3001
my_pb = ui.ui_factory.nested_progress_bar()
3006
install_revisions(self.target, revisions_iterator(),
3007
len(revision_ids), pb)
3009
if my_pb is not None:
3011
return len(revision_ids), 0
2517
from bzrlib.fetch import RemoteToOtherFetcher
2518
mutter("Using fetch logic to copy between %s(remote) and %s(%s)",
2519
self.source, self.target, self.target._format)
2520
# TODO: jam 20070210 This should be an assert, not a translate
2521
revision_id = osutils.safe_revision_id(revision_id)
2522
f = RemoteToOtherFetcher(to_repository=self.target,
2523
from_repository=self.source,
2524
last_revision=revision_id,
2526
return f.count_copied, f.failed_revisions
2529
def _get_repo_format_to_test(self):
3014
2533
class InterOtherToRemote(InterRepository):
3036
2555
def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3037
2556
self._ensure_real_inter()
3038
return self._real_inter.fetch(revision_id=revision_id, pb=pb,
3039
find_ghosts=find_ghosts)
3042
def _get_repo_format_to_test(self):
3046
class InterRemoteToOther(InterRepository):
3048
def __init__(self, source, target):
3049
InterRepository.__init__(self, source, target)
3050
self._real_inter = None
3053
def is_compatible(source, target):
3054
if not isinstance(source, remote.RemoteRepository):
3056
# Is source's model compatible with target's model?
3057
source._ensure_real()
3058
real_source = source._real_repository
3059
if isinstance(real_source, remote.RemoteRepository):
3060
raise NotImplementedError(
3061
"We don't support remote repos backed by remote repos yet.")
3062
return InterRepository._same_model(real_source, target)
3064
def _ensure_real_inter(self):
3065
if self._real_inter is None:
3066
self.source._ensure_real()
3067
real_source = self.source._real_repository
3068
self._real_inter = InterRepository.get(real_source, self.target)
3070
def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3071
self._ensure_real_inter()
3072
return self._real_inter.fetch(revision_id=revision_id, pb=pb,
3073
find_ghosts=find_ghosts)
3075
def copy_content(self, revision_id=None):
3076
self._ensure_real_inter()
3077
self._real_inter.copy_content(revision_id=revision_id)
3080
def _get_repo_format_to_test(self):
3085
InterRepository.register_optimiser(InterDifferingSerializer)
2557
self._real_inter.fetch(revision_id=revision_id, pb=pb)
2560
def _get_repo_format_to_test(self):
3086
2564
InterRepository.register_optimiser(InterSameDataRepository)
3087
2565
InterRepository.register_optimiser(InterWeaveRepo)
3088
2566
InterRepository.register_optimiser(InterKnitRepo)
3089
2567
InterRepository.register_optimiser(InterModel1and2)
3090
2568
InterRepository.register_optimiser(InterKnit1and2)
3091
2569
InterRepository.register_optimiser(InterPackRepo)
2570
InterRepository.register_optimiser(InterRemoteToOther)
3092
2571
InterRepository.register_optimiser(InterOtherToRemote)
3093
InterRepository.register_optimiser(InterRemoteToOther)
3096
2574
class CopyConverter(object):
3175
2653
return _unescape_re.sub(_unescaper, data)
3178
class _VersionedFileChecker(object):
2656
class _RevisionTextVersionCache(object):
2657
"""A cache of the versionedfile versions for revision and file-id."""
3180
2659
def __init__(self, repository):
3181
2660
self.repository = repository
3182
self.text_index = self.repository._generate_text_key_index()
2661
self.revision_versions = {}
2662
self.revision_parents = {}
2663
self.repo_graph = self.repository.get_graph()
2664
# XXX: RBC: I haven't tracked down what uses this, but it would be
2665
# better to use the headscache directly I think.
2666
self.heads = graph.HeadsCache(self.repo_graph).heads
2668
def add_revision_text_versions(self, tree):
2669
"""Cache text version data from the supplied revision tree"""
2671
for path, entry in tree.iter_entries_by_dir():
2672
inv_revisions[entry.file_id] = entry.revision
2673
self.revision_versions[tree.get_revision_id()] = inv_revisions
2674
return inv_revisions
2676
def get_text_version(self, file_id, revision_id):
2677
"""Determine the text version for a given file-id and revision-id"""
2679
inv_revisions = self.revision_versions[revision_id]
2682
tree = self.repository.revision_tree(revision_id)
2683
except errors.RevisionNotPresent:
2684
self.revision_versions[revision_id] = inv_revisions = {}
2686
inv_revisions = self.add_revision_text_versions(tree)
2687
return inv_revisions.get(file_id)
2689
def prepopulate_revs(self, revision_ids):
2690
# Filter out versions that we don't have an inventory for, so that the
2691
# revision_trees() call won't fail.
2692
inv_weave = self.repository.get_inventory_weave()
2693
revs = [r for r in revision_ids if inv_weave.has_version(r)]
2694
# XXX: this loop is very similar to
2695
# bzrlib.fetch.Inter1and2Helper.iter_rev_trees.
2697
mutter('%d revisions left to prepopulate', len(revs))
2698
for tree in self.repository.revision_trees(revs[:100]):
2699
if tree.inventory.revision_id is None:
2700
tree.inventory.revision_id = tree.get_revision_id()
2701
self.add_revision_text_versions(tree)
2704
def get_parents(self, revision_id):
2706
return self.revision_parents[revision_id]
2708
parents = self.repository.get_parents([revision_id])[0]
2709
self.revision_parents[revision_id] = parents
2712
def used_file_versions(self):
2713
"""Return a set of (revision_id, file_id) pairs for each file version
2714
referenced by any inventory cached by this _RevisionTextVersionCache.
2716
If the entire repository has been cached, this can be used to find all
2717
file versions that are actually referenced by inventories. Thus any
2718
other file version is completely unused and can be removed safely.
2721
for inventory_summary in self.revision_versions.itervalues():
2722
result.update(inventory_summary.items())
2726
class VersionedFileChecker(object):
2728
def __init__(self, planned_revisions, revision_versions, repository):
2729
self.planned_revisions = planned_revisions
2730
self.revision_versions = revision_versions
2731
self.repository = repository
3184
def calculate_file_version_parents(self, text_key):
2733
def calculate_file_version_parents(self, revision_id, file_id):
3185
2734
"""Calculate the correct parents for a file version according to
3186
2735
the inventories.
3188
parent_keys = self.text_index[text_key]
3189
if parent_keys == [_mod_revision.NULL_REVISION]:
3191
return tuple(parent_keys)
2737
text_revision = self.revision_versions.get_text_version(
2738
file_id, revision_id)
2739
if text_revision is None:
2741
parents_of_text_revision = self.revision_versions.get_parents(
2743
parents_from_inventories = []
2744
for parent in parents_of_text_revision:
2745
if parent == _mod_revision.NULL_REVISION:
2747
introduced_in = self.revision_versions.get_text_version(file_id,
2749
if introduced_in is not None:
2750
parents_from_inventories.append(introduced_in)
2751
heads = set(self.revision_versions.heads(parents_from_inventories))
2753
for parent in parents_from_inventories:
2754
if parent in heads and parent not in new_parents:
2755
new_parents.append(parent)
2756
return tuple(new_parents)
3193
def check_file_version_parents(self, texts, progress_bar=None):
2758
def check_file_version_parents(self, weave, file_id):
3194
2759
"""Check the parents stored in a versioned file are correct.
3196
2761
It also detects file versions that are not referenced by their
3204
2769
file, but not used by the corresponding inventory.
3206
2771
wrong_parents = {}
3207
self.file_ids = set([file_id for file_id, _ in
3208
self.text_index.iterkeys()])
3209
# text keys is now grouped by file_id
3210
n_weaves = len(self.file_ids)
3211
files_in_revisions = {}
3212
revisions_of_files = {}
3213
n_versions = len(self.text_index)
3214
progress_bar.update('loading text store', 0, n_versions)
3215
parent_map = self.repository.texts.get_parent_map(self.text_index)
3216
# On unlistable transports this could well be empty/error...
3217
text_keys = self.repository.texts.keys()
3218
unused_keys = frozenset(text_keys) - set(self.text_index)
3219
for num, key in enumerate(self.text_index.iterkeys()):
3220
if progress_bar is not None:
3221
progress_bar.update('checking text graph', num, n_versions)
3222
correct_parents = self.calculate_file_version_parents(key)
2772
dangling_file_versions = set()
2773
for num, revision_id in enumerate(self.planned_revisions):
2774
correct_parents = self.calculate_file_version_parents(
2775
revision_id, file_id)
2776
if correct_parents is None:
2778
text_revision = self.revision_versions.get_text_version(
2779
file_id, revision_id)
3224
knit_parents = parent_map[key]
2781
knit_parents = tuple(weave.get_parents(revision_id))
3225
2782
except errors.RevisionNotPresent:
3227
2783
knit_parents = None
2784
if text_revision != revision_id:
2785
# This file version is not referenced by its corresponding
2787
dangling_file_versions.add((file_id, revision_id))
3228
2788
if correct_parents != knit_parents:
3229
wrong_parents[key] = (knit_parents, correct_parents)
3230
return wrong_parents, unused_keys
3233
def _old_get_graph(repository, revision_id):
3234
"""DO NOT USE. That is all. I'm serious."""
3235
graph = repository.get_graph()
3236
revision_graph = dict(((key, value) for key, value in
3237
graph.iter_ancestry([revision_id]) if value is not None))
3238
return _strip_NULL_ghosts(revision_graph)
3241
def _strip_NULL_ghosts(revision_graph):
3242
"""Also don't use this. more compatibility code for unmigrated clients."""
3243
# Filter ghosts, and null:
3244
if _mod_revision.NULL_REVISION in revision_graph:
3245
del revision_graph[_mod_revision.NULL_REVISION]
3246
for key, parents in revision_graph.items():
3247
revision_graph[key] = tuple(parent for parent in parents if parent
3249
return revision_graph
2789
wrong_parents[revision_id] = (knit_parents, correct_parents)
2790
return wrong_parents, dangling_file_versions