867
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
866
class Repository(_RelockDebugMixin):
868
867
"""Repository holding history for one or more branches.
870
869
The repository holds and retrieves historical information including
1029
1028
:seealso: add_inventory, for the contract.
1031
inv_lines = self._serializer.write_inventory_to_lines(inv)
1030
inv_lines = self._serialise_inventory_to_lines(inv)
1032
1031
return self._inventory_add_lines(revision_id, parents,
1033
1032
inv_lines, check_content=False)
1241
1240
"""Check a single text from this repository."""
1242
1241
if kind == 'inventories':
1243
1242
rev_id = record.key[0]
1244
inv = self._deserialise_inventory(rev_id,
1243
inv = self.deserialise_inventory(rev_id,
1245
1244
record.get_bytes_as('fulltext'))
1246
1245
if last_object is not None:
1247
1246
delta = inv._make_delta(last_object)
1292
1291
:param _format: The format of the repository on disk.
1293
1292
:param a_bzrdir: The BzrDir of the repository.
1294
In the future we will have a single api for all stores for
1295
getting file texts, inventories and revisions, then
1296
this construct will accept instances of those things.
1295
# In the future we will have a single api for all stores for
1296
# getting file texts, inventories and revisions, then
1297
# this construct will accept instances of those things.
1298
1298
super(Repository, self).__init__()
1299
1299
self._format = _format
1300
1300
# the following are part of the public API for Repository:
1315
1315
# rather copying them?
1316
1316
self._safe_to_return_from_cache = False
1319
def user_transport(self):
1320
return self.bzrdir.user_transport
1323
def control_transport(self):
1324
return self._transport
1326
1318
def __repr__(self):
1327
1319
if self._fallback_repositories:
1328
1320
return '%s(%r, fallback_repositories=%r)' % (
1477
1469
# now gather global repository information
1478
1470
# XXX: This is available for many repos regardless of listability.
1479
if self.user_transport.listable():
1471
if self.bzrdir.root_transport.listable():
1480
1472
# XXX: do we want to __define len__() ?
1481
1473
# Maybe the versionedfiles object should provide a different
1482
1474
# method to get the number of keys.
1492
1484
:param using: If True, list only branches using this repository.
1494
1486
if using and not self.is_shared():
1495
return self.bzrdir.list_branches()
1488
return [self.bzrdir.open_branch()]
1489
except errors.NotBranchError:
1496
1491
class Evaluator(object):
1498
1493
def __init__(self):
1507
1502
except errors.NoRepositoryPresent:
1510
return False, ([], repository)
1505
return False, (None, repository)
1511
1506
self.first_call = False
1512
value = (bzrdir.list_branches(), None)
1508
value = (bzrdir.open_branch(), None)
1509
except errors.NotBranchError:
1510
value = (None, None)
1513
1511
return True, value
1516
for branches, repository in bzrdir.BzrDir.find_bzrdirs(
1517
self.user_transport, evaluate=Evaluator()):
1518
if branches is not None:
1519
ret.extend(branches)
1514
for branch, repository in bzrdir.BzrDir.find_bzrdirs(
1515
self.bzrdir.root_transport, evaluate=Evaluator()):
1516
if branch is not None:
1517
branches.append(branch)
1520
1518
if not using and repository is not None:
1521
ret.extend(repository.find_branches())
1519
branches.extend(repository.find_branches())
1524
1522
@needs_read_lock
1525
1523
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1903
1901
rev = self._serializer.read_revision_from_string(text)
1904
1902
yield (revid, rev)
1905
def get_revision_xml(self, revision_id):
1906
# TODO: jam 20070210 This shouldn't be necessary since get_revision
1907
# would have already do it.
1908
# TODO: jam 20070210 Just use _serializer.write_revision_to_string()
1909
# TODO: this can't just be replaced by:
1910
# return self._serializer.write_revision_to_string(
1911
# self.get_revision(revision_id))
1912
# as cStringIO preservers the encoding unlike write_revision_to_string
1913
# or some other call down the path.
1914
rev = self.get_revision(revision_id)
1915
rev_tmp = cStringIO.StringIO()
1916
# the current serializer..
1917
self._serializer.write_revision(rev, rev_tmp)
1919
return rev_tmp.getvalue()
1906
1921
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1907
1922
"""Produce a generator of revision deltas.
2151
2166
selected_keys = set((revid,) for revid in revision_ids)
2152
2167
w = _inv_weave or self.inventories
2153
return self._find_file_ids_from_xml_inventory_lines(
2154
w.iter_lines_added_or_present_in_keys(
2155
selected_keys, pb=None),
2168
pb = ui.ui_factory.nested_progress_bar()
2170
return self._find_file_ids_from_xml_inventory_lines(
2171
w.iter_lines_added_or_present_in_keys(
2172
selected_keys, pb=pb),
2158
2177
def iter_files_bytes(self, desired_files):
2159
2178
"""Iterate through file versions.
2369
2388
"""single-document based inventory iteration."""
2370
2389
inv_xmls = self._iter_inventory_xmls(revision_ids, ordering)
2371
2390
for text, revision_id in inv_xmls:
2372
yield self._deserialise_inventory(revision_id, text)
2391
yield self.deserialise_inventory(revision_id, text)
2374
2393
def _iter_inventory_xmls(self, revision_ids, ordering):
2375
2394
if ordering is None:
2407
2426
next_key = None
2410
def _deserialise_inventory(self, revision_id, xml):
2429
def deserialise_inventory(self, revision_id, xml):
2411
2430
"""Transform the xml into an inventory object.
2413
2432
:param revision_id: The expected revision id of the inventory.
2421
2440
result.revision_id, revision_id))
2443
def serialise_inventory(self, inv):
2444
return self._serializer.write_inventory_to_string(inv)
2446
def _serialise_inventory_to_lines(self, inv):
2447
return self._serializer.write_inventory_to_lines(inv)
2424
2449
def get_serializer_format(self):
2425
2450
return self._serializer.format_num
2427
2452
@needs_read_lock
2428
def _get_inventory_xml(self, revision_id):
2429
"""Get serialized inventory as a string."""
2453
def get_inventory_xml(self, revision_id):
2454
"""Get inventory XML as a file object."""
2430
2455
texts = self._iter_inventory_xmls([revision_id], 'unordered')
2432
2457
text, revision_id = texts.next()
2434
2459
raise errors.HistoryMissing(self, 'inventory', revision_id)
2463
def get_inventory_sha1(self, revision_id):
2464
"""Return the sha1 hash of the inventory entry
2466
return self.get_revision(revision_id).inventory_sha1
2437
2468
def get_rev_id_for_revno(self, revno, known_pair):
2438
2469
"""Return the revision id of a revno, given a later (revno, revid)
2439
2470
pair in the same history.
2491
2522
next_id = parents[0]
2525
def get_revision_inventory(self, revision_id):
2526
"""Return inventory of a past revision."""
2527
# TODO: Unify this with get_inventory()
2528
# bzr 0.0.6 and later imposes the constraint that the inventory_id
2529
# must be the same as its revision, so this is trivial.
2530
if revision_id is None:
2531
# This does not make sense: if there is no revision,
2532
# then it is the current tree inventory surely ?!
2533
# and thus get_root_id() is something that looks at the last
2534
# commit on the branch, and the get_root_id is an inventory check.
2535
raise NotImplementedError
2536
# return Inventory(self.get_root_id())
2538
return self.get_inventory(revision_id)
2493
2540
def is_shared(self):
2494
2541
"""Return True if this repository is flagged as a shared repository."""
2495
2542
raise NotImplementedError(self.is_shared)
2529
2576
return RevisionTree(self, Inventory(root_id=None),
2530
2577
_mod_revision.NULL_REVISION)
2532
inv = self.get_inventory(revision_id)
2579
inv = self.get_revision_inventory(revision_id)
2533
2580
return RevisionTree(self, inv, revision_id)
2535
2582
def revision_trees(self, revision_ids):
2588
2635
keys = tsort.topo_sort(parent_map)
2589
2636
return [None] + list(keys)
2591
def pack(self, hint=None, clean_obsolete_packs=False):
2638
def pack(self, hint=None):
2592
2639
"""Compress the data within the repository.
2594
2641
This operation only makes sense for some repository types. For other
2604
2651
obtained from the result of commit_write_group(). Out of
2605
2652
date hints are simply ignored, because concurrent operations
2606
2653
can obsolete them rapidly.
2608
:param clean_obsolete_packs: Clean obsolete packs immediately after
2612
2656
def get_transaction(self):
2637
2681
def _make_parents_provider(self):
2641
def get_known_graph_ancestry(self, revision_ids):
2642
"""Return the known graph for a set of revision ids and their ancestors.
2644
st = static_tuple.StaticTuple
2645
revision_keys = [st(r_id).intern() for r_id in revision_ids]
2646
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
2647
return graph.GraphThunkIdsToKeys(known_graph)
2649
2684
def get_graph(self, other_repository=None):
2650
2685
"""Return the graph walker for this repository format"""
2651
2686
parents_provider = self._make_parents_provider()
3050
3085
pack_compresses = False
3051
3086
# Does the repository inventory storage understand references to trees?
3052
3087
supports_tree_reference = None
3053
# Is the format experimental ?
3054
experimental = False
3057
return "%s()" % self.__class__.__name__
3090
return "<%s>" % self.__class__.__name__
3059
3092
def __eq__(self, other):
3060
3093
# format objects are generally stateless
3179
3212
raise NotImplementedError(self.open)
3181
def _run_post_repo_init_hooks(self, repository, a_bzrdir, shared):
3182
from bzrlib.bzrdir import BzrDir, RepoInitHookParams
3183
hooks = BzrDir.hooks['post_repo_init']
3186
params = RepoInitHookParams(repository, self, a_bzrdir, shared)
3191
3215
class MetaDirRepositoryFormat(RepositoryFormat):
3192
3216
"""Common base class for the new repositories using the metadir layout."""
3398
3422
:param revision_id: if None all content is copied, if NULL_REVISION no
3399
3423
content is copied.
3424
:param pb: optional progress bar to use for progress reports. If not
3425
provided a default one will be created.
3403
ui.ui_factory.warn_experimental_format_fetch(self)
3404
from bzrlib.fetch import RepoFetcher
3405
# See <https://launchpad.net/bugs/456077> asking for a warning here
3406
if self.source._format.network_name() != self.target._format.network_name():
3407
ui.ui_factory.show_user_warning('cross_format_fetch',
3408
from_format=self.source._format,
3409
to_format=self.target._format)
3410
f = RepoFetcher(to_repository=self.target,
3428
f = _mod_fetch.RepoFetcher(to_repository=self.target,
3411
3429
from_repository=self.source,
3412
3430
last_revision=revision_id,
3413
3431
fetch_spec=fetch_spec,
3414
find_ghosts=find_ghosts)
3432
pb=pb, find_ghosts=find_ghosts)
3416
3434
def _walk_to_common_revisions(self, revision_ids):
3417
3435
"""Walk out from revision_ids in source to revisions target has.
3991
4009
"""See InterRepository.fetch()."""
3992
4010
if fetch_spec is not None:
3993
4011
raise AssertionError("Not implemented yet...")
3994
ui.ui_factory.warn_experimental_format_fetch(self)
4012
# See <https://launchpad.net/bugs/456077> asking for a warning here
4014
# nb this is only active for local-local fetches; other things using
4016
trace.warning("Fetching between repositories with different formats\n"
4018
"This may take some time. Upgrade the branches to the same format \n"
4019
"for better results.\n"
4020
% (self.source._format, self.target._format))
3995
4021
if (not self.source.supports_rich_root()
3996
4022
and self.target.supports_rich_root()):
3997
4023
self._converting_to_rich_root = True
3998
4024
self._revision_id_to_root_id = {}
4000
4026
self._converting_to_rich_root = False
4001
# See <https://launchpad.net/bugs/456077> asking for a warning here
4002
if self.source._format.network_name() != self.target._format.network_name():
4003
ui.ui_factory.show_user_warning('cross_format_fetch',
4004
from_format=self.source._format,
4005
to_format=self.target._format)
4006
4027
revision_ids = self.target.search_missing_revision_ids(self.source,
4007
4028
revision_id, find_ghosts=find_ghosts).get_keys()
4008
4029
if not revision_ids:
4077
4098
:param to_convert: The disk object to convert.
4078
4099
:param pb: a progress bar to use for progress information.
4080
pb = ui.ui_factory.nested_progress_bar()
4083
4104
# this is only useful with metadir layouts - separated repo content.
4084
4105
# trigger an assertion if not such
4085
4106
repo._format.get_format_string()
4086
4107
self.repo_dir = repo.bzrdir
4087
pb.update('Moving repository to repository.backup')
4108
self.step('Moving repository to repository.backup')
4088
4109
self.repo_dir.transport.move('repository', 'repository.backup')
4089
4110
backup_transport = self.repo_dir.transport.clone('repository.backup')
4090
4111
repo._format.check_conversion_target(self.target_format)
4091
4112
self.source_repo = repo._format.open(self.repo_dir,
4093
4114
_override_transport=backup_transport)
4094
pb.update('Creating new repository')
4115
self.step('Creating new repository')
4095
4116
converted = self.target_format.initialize(self.repo_dir,
4096
4117
self.source_repo.is_shared())
4097
4118
converted.lock_write()
4099
pb.update('Copying content')
4120
self.step('Copying content')
4100
4121
self.source_repo.copy_content_into(converted)
4102
4123
converted.unlock()
4103
pb.update('Deleting old repository content')
4124
self.step('Deleting old repository content')
4104
4125
self.repo_dir.transport.delete_tree('repository.backup')
4105
4126
ui.ui_factory.note('repository converted')
4128
def step(self, message):
4129
"""Update the pb by a step."""
4131
self.pb.update(message, self.count, self.total)
4109
4134
_unescape_map = {
4602
4627
def _get_convertable_inventory_stream(self, revision_ids,
4603
4628
delta_versus_null=False):
4604
# The two formats are sufficiently different that there is no fast
4605
# path, so we need to send just inventorydeltas, which any
4606
# sufficiently modern client can insert into any repository.
4607
# The StreamSink code expects to be able to
4629
# The source is using CHKs, but the target either doesn't or it has a
4630
# different serializer. The StreamSink code expects to be able to
4608
4631
# convert on the target, so we need to put bytes-on-the-wire that can
4609
4632
# be converted. That means inventory deltas (if the remote is <1.19,
4610
4633
# RemoteStreamSink will fallback to VFS to insert the deltas).