1
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
1
# Copyright (C) 2005-2010 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
206
210
# an inventory delta was accumulated without creating a new
208
212
basis_id = self.basis_delta_revision
209
self.inv_sha1 = self.repository.add_inventory_by_delta(
213
# We ignore the 'inventory' returned by add_inventory_by_delta
214
# because self.new_inventory is used to hint to the rest of the
215
# system what code path was taken
216
self.inv_sha1, _ = self.repository.add_inventory_by_delta(
210
217
basis_id, self._basis_delta, self._new_revision_id,
860
class Repository(_RelockDebugMixin):
867
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
861
868
"""Repository holding history for one or more branches.
863
870
The repository holds and retrieves historical information including
1022
1029
:seealso: add_inventory, for the contract.
1024
inv_lines = self._serialise_inventory_to_lines(inv)
1031
inv_lines = self._serializer.write_inventory_to_lines(inv)
1025
1032
return self._inventory_add_lines(revision_id, parents,
1026
1033
inv_lines, check_content=False)
1234
1241
"""Check a single text from this repository."""
1235
1242
if kind == 'inventories':
1236
1243
rev_id = record.key[0]
1237
inv = self.deserialise_inventory(rev_id,
1244
inv = self._deserialise_inventory(rev_id,
1238
1245
record.get_bytes_as('fulltext'))
1239
1246
if last_object is not None:
1240
1247
delta = inv._make_delta(last_object)
1285
1292
:param _format: The format of the repository on disk.
1286
1293
:param a_bzrdir: The BzrDir of the repository.
1288
In the future we will have a single api for all stores for
1289
getting file texts, inventories and revisions, then
1290
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.
1292
1298
super(Repository, self).__init__()
1293
1299
self._format = _format
1294
1300
# the following are part of the public API for Repository:
1300
1306
self._reconcile_does_inventory_gc = True
1301
1307
self._reconcile_fixes_text_parents = False
1302
1308
self._reconcile_backsup_inventory = True
1303
# not right yet - should be more semantically clear ?
1305
# TODO: make sure to construct the right store classes, etc, depending
1306
# on whether escaping is required.
1307
self._warn_if_deprecated()
1308
1309
self._write_group = None
1309
1310
# Additional places to query for data.
1310
1311
self._fallback_repositories = []
1311
1312
# An InventoryEntry cache, used during deserialization
1312
1313
self._inventory_entry_cache = fifo_cache.FIFOCache(10*1024)
1314
# Is it safe to return inventory entries directly from the entry cache,
1315
# rather copying them?
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
1314
1326
def __repr__(self):
1315
1327
if self._fallback_repositories:
1382
1394
locked = self.is_locked()
1383
1395
result = self.control_files.lock_write(token=token)
1397
self._warn_if_deprecated()
1385
1398
self._note_lock('w')
1386
1399
for repo in self._fallback_repositories:
1387
1400
# Writes don't affect fallback repos
1463
1477
# now gather global repository information
1464
1478
# XXX: This is available for many repos regardless of listability.
1465
if self.bzrdir.root_transport.listable():
1479
if self.user_transport.listable():
1466
1480
# XXX: do we want to __define len__() ?
1467
1481
# Maybe the versionedfiles object should provide a different
1468
1482
# method to get the number of keys.
1478
1492
:param using: If True, list only branches using this repository.
1480
1494
if using and not self.is_shared():
1482
return [self.bzrdir.open_branch()]
1483
except errors.NotBranchError:
1495
return self.bzrdir.list_branches()
1485
1496
class Evaluator(object):
1487
1498
def __init__(self):
1496
1507
except errors.NoRepositoryPresent:
1499
return False, (None, repository)
1510
return False, ([], repository)
1500
1511
self.first_call = False
1502
value = (bzrdir.open_branch(), None)
1503
except errors.NotBranchError:
1504
value = (None, None)
1512
value = (bzrdir.list_branches(), None)
1505
1513
return True, value
1508
for branch, repository in bzrdir.BzrDir.find_bzrdirs(
1509
self.bzrdir.root_transport, evaluate=Evaluator()):
1510
if branch is not None:
1511
branches.append(branch)
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)
1512
1520
if not using and repository is not None:
1513
branches.extend(repository.find_branches())
1521
ret.extend(repository.find_branches())
1516
1524
@needs_read_lock
1517
1525
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1895
1903
rev = self._serializer.read_revision_from_string(text)
1896
1904
yield (revid, rev)
1899
def get_revision_xml(self, revision_id):
1900
# TODO: jam 20070210 This shouldn't be necessary since get_revision
1901
# would have already do it.
1902
# TODO: jam 20070210 Just use _serializer.write_revision_to_string()
1903
# TODO: this can't just be replaced by:
1904
# return self._serializer.write_revision_to_string(
1905
# self.get_revision(revision_id))
1906
# as cStringIO preservers the encoding unlike write_revision_to_string
1907
# or some other call down the path.
1908
rev = self.get_revision(revision_id)
1909
rev_tmp = cStringIO.StringIO()
1910
# the current serializer..
1911
self._serializer.write_revision(rev, rev_tmp)
1913
return rev_tmp.getvalue()
1915
1906
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1916
1907
"""Produce a generator of revision deltas.
2160
2151
selected_keys = set((revid,) for revid in revision_ids)
2161
2152
w = _inv_weave or self.inventories
2162
pb = ui.ui_factory.nested_progress_bar()
2164
return self._find_file_ids_from_xml_inventory_lines(
2165
w.iter_lines_added_or_present_in_keys(
2166
selected_keys, pb=pb),
2153
return self._find_file_ids_from_xml_inventory_lines(
2154
w.iter_lines_added_or_present_in_keys(
2155
selected_keys, pb=None),
2171
2158
def iter_files_bytes(self, desired_files):
2172
2159
"""Iterate through file versions.
2382
2369
"""single-document based inventory iteration."""
2383
2370
inv_xmls = self._iter_inventory_xmls(revision_ids, ordering)
2384
2371
for text, revision_id in inv_xmls:
2385
yield self.deserialise_inventory(revision_id, text)
2372
yield self._deserialise_inventory(revision_id, text)
2387
2374
def _iter_inventory_xmls(self, revision_ids, ordering):
2388
2375
if ordering is None:
2420
2407
next_key = None
2423
def deserialise_inventory(self, revision_id, xml):
2410
def _deserialise_inventory(self, revision_id, xml):
2424
2411
"""Transform the xml into an inventory object.
2426
2413
:param revision_id: The expected revision id of the inventory.
2427
2414
:param xml: A serialised inventory.
2429
2416
result = self._serializer.read_inventory_from_string(xml, revision_id,
2430
entry_cache=self._inventory_entry_cache)
2417
entry_cache=self._inventory_entry_cache,
2418
return_from_cache=self._safe_to_return_from_cache)
2431
2419
if result.revision_id != revision_id:
2432
2420
raise AssertionError('revision id mismatch %s != %s' % (
2433
2421
result.revision_id, revision_id))
2436
def serialise_inventory(self, inv):
2437
return self._serializer.write_inventory_to_string(inv)
2439
def _serialise_inventory_to_lines(self, inv):
2440
return self._serializer.write_inventory_to_lines(inv)
2442
2424
def get_serializer_format(self):
2443
2425
return self._serializer.format_num
2445
2427
@needs_read_lock
2446
def get_inventory_xml(self, revision_id):
2447
"""Get inventory XML as a file object."""
2428
def _get_inventory_xml(self, revision_id):
2429
"""Get serialized inventory as a string."""
2448
2430
texts = self._iter_inventory_xmls([revision_id], 'unordered')
2450
2432
text, revision_id = texts.next()
2452
2434
raise errors.HistoryMissing(self, 'inventory', revision_id)
2456
def get_inventory_sha1(self, revision_id):
2457
"""Return the sha1 hash of the inventory entry
2459
return self.get_revision(revision_id).inventory_sha1
2461
2437
def get_rev_id_for_revno(self, revno, known_pair):
2462
2438
"""Return the revision id of a revno, given a later (revno, revid)
2463
2439
pair in the same history.
2515
2491
next_id = parents[0]
2518
def get_revision_inventory(self, revision_id):
2519
"""Return inventory of a past revision."""
2520
# TODO: Unify this with get_inventory()
2521
# bzr 0.0.6 and later imposes the constraint that the inventory_id
2522
# must be the same as its revision, so this is trivial.
2523
if revision_id is None:
2524
# This does not make sense: if there is no revision,
2525
# then it is the current tree inventory surely ?!
2526
# and thus get_root_id() is something that looks at the last
2527
# commit on the branch, and the get_root_id is an inventory check.
2528
raise NotImplementedError
2529
# return Inventory(self.get_root_id())
2531
return self.get_inventory(revision_id)
2533
2493
def is_shared(self):
2534
2494
"""Return True if this repository is flagged as a shared repository."""
2535
2495
raise NotImplementedError(self.is_shared)
2569
2529
return RevisionTree(self, Inventory(root_id=None),
2570
2530
_mod_revision.NULL_REVISION)
2572
inv = self.get_revision_inventory(revision_id)
2532
inv = self.get_inventory(revision_id)
2573
2533
return RevisionTree(self, inv, revision_id)
2575
2535
def revision_trees(self, revision_ids):
2628
2588
keys = tsort.topo_sort(parent_map)
2629
2589
return [None] + list(keys)
2631
def pack(self, hint=None):
2591
def pack(self, hint=None, clean_obsolete_packs=False):
2632
2592
"""Compress the data within the repository.
2634
2594
This operation only makes sense for some repository types. For other
2644
2604
obtained from the result of commit_write_group(). Out of
2645
2605
date hints are simply ignored, because concurrent operations
2646
2606
can obsolete them rapidly.
2608
:param clean_obsolete_packs: Clean obsolete packs immediately after
2649
2612
def get_transaction(self):
2665
2628
for ((revision_id,), parent_keys) in \
2666
2629
self.revisions.get_parent_map(query_keys).iteritems():
2667
2630
if parent_keys:
2668
result[revision_id] = tuple(parent_revid
2669
for (parent_revid,) in parent_keys)
2631
result[revision_id] = tuple([parent_revid
2632
for (parent_revid,) in parent_keys])
2671
2634
result[revision_id] = (_mod_revision.NULL_REVISION,)
2674
2637
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)
2677
2649
def get_graph(self, other_repository=None):
2678
2650
"""Return the graph walker for this repository format"""
2679
2651
parents_provider = self._make_parents_provider()
2774
2746
result.check(callback_refs)
2777
def _warn_if_deprecated(self):
2749
def _warn_if_deprecated(self, branch=None):
2778
2750
global _deprecation_warning_done
2779
2751
if _deprecation_warning_done:
2781
_deprecation_warning_done = True
2782
warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
2783
% (self._format, self.bzrdir.transport.base))
2755
conf = config.GlobalConfig()
2757
conf = branch.get_config()
2758
if conf.suppress_warning('format_deprecation'):
2760
warning("Format %s for %s is deprecated -"
2761
" please use 'bzr upgrade' to get better performance"
2762
% (self._format, self.bzrdir.transport.base))
2764
_deprecation_warning_done = True
2785
2766
def supports_rich_root(self):
2786
2767
return self._format.rich_root_data
3069
3050
pack_compresses = False
3070
3051
# Does the repository inventory storage understand references to trees?
3071
3052
supports_tree_reference = None
3053
# Is the format experimental ?
3054
experimental = False
3074
return "<%s>" % self.__class__.__name__
3057
return "%s()" % self.__class__.__name__
3076
3059
def __eq__(self, other):
3077
3060
# format objects are generally stateless
3092
3075
transport = a_bzrdir.get_repository_transport(None)
3093
format_string = transport.get("format").read()
3076
format_string = transport.get_bytes("format")
3094
3077
return format_registry.get(format_string)
3095
3078
except errors.NoSuchFile:
3096
3079
raise errors.NoRepositoryPresent(a_bzrdir)
3196
3179
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)
3199
3191
class MetaDirRepositoryFormat(RepositoryFormat):
3200
3192
"""Common base class for the new repositories using the metadir layout."""
3406
3398
:param revision_id: if None all content is copied, if NULL_REVISION no
3407
3399
content is copied.
3408
:param pb: optional progress bar to use for progress reports. If not
3409
provided a default one will be created.
3403
ui.ui_factory.warn_experimental_format_fetch(self)
3412
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)
3413
3410
f = RepoFetcher(to_repository=self.target,
3414
3411
from_repository=self.source,
3415
3412
last_revision=revision_id,
3416
3413
fetch_spec=fetch_spec,
3417
pb=pb, find_ghosts=find_ghosts)
3414
find_ghosts=find_ghosts)
3419
3416
def _walk_to_common_revisions(self, revision_ids):
3420
3417
"""Walk out from revision_ids in source to revisions target has.
3816
3813
basis_id, delta, current_revision_id, parents_parents)
3817
3814
cache[current_revision_id] = parent_tree
3819
def _fetch_batch(self, revision_ids, basis_id, cache):
3816
def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
3820
3817
"""Fetch across a few revisions.
3822
3819
:param revision_ids: The revisions to copy
3823
3820
:param basis_id: The revision_id of a tree that must be in cache, used
3824
3821
as a basis for delta when no other base is available
3825
3822
:param cache: A cache of RevisionTrees that we can use.
3823
:param a_graph: A Graph object to determine the heads() of the
3824
rich-root data stream.
3826
3825
:return: The revision_id of the last converted tree. The RevisionTree
3827
3826
for it will be in cache
3835
3834
pending_revisions = []
3836
3835
parent_map = self.source.get_parent_map(revision_ids)
3837
3836
self._fetch_parent_invs_for_stacking(parent_map, cache)
3837
self.source._safe_to_return_from_cache = True
3838
3838
for tree in self.source.revision_trees(revision_ids):
3839
3839
# Find a inventory delta for this revision.
3840
3840
# Find text entries that need to be copied, too.
3888
3888
pending_revisions.append(revision)
3889
3889
cache[current_revision_id] = tree
3890
3890
basis_id = current_revision_id
3891
self.source._safe_to_return_from_cache = False
3891
3892
# Copy file texts
3892
3893
from_texts = self.source.texts
3893
3894
to_texts = self.target.texts
3894
3895
if root_keys_to_create:
3895
from bzrlib.fetch import _new_root_data_stream
3896
root_stream = _new_root_data_stream(
3896
root_stream = _mod_fetch._new_root_data_stream(
3897
3897
root_keys_to_create, self._revision_id_to_root_id, parent_map,
3898
self.source, graph=a_graph)
3899
3899
to_texts.insert_record_stream(root_stream)
3900
3900
to_texts.insert_record_stream(from_texts.get_record_stream(
3901
3901
text_keys, self.target._format._fetch_order,
3958
3958
cache[basis_id] = basis_tree
3959
3959
del basis_tree # We don't want to hang on to it here
3961
if self._converting_to_rich_root and len(revision_ids) > 100:
3962
a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
3961
3967
for offset in range(0, len(revision_ids), batch_size):
3962
3968
self.target.start_write_group()
3964
3970
pb.update('Transferring revisions', offset,
3965
3971
len(revision_ids))
3966
3972
batch = revision_ids[offset:offset+batch_size]
3967
basis_id = self._fetch_batch(batch, basis_id, cache)
3973
basis_id = self._fetch_batch(batch, basis_id, cache,
3976
self.source._safe_to_return_from_cache = False
3969
3977
self.target.abort_write_group()
3983
3991
"""See InterRepository.fetch()."""
3984
3992
if fetch_spec is not None:
3985
3993
raise AssertionError("Not implemented yet...")
3994
ui.ui_factory.warn_experimental_format_fetch(self)
3986
3995
if (not self.source.supports_rich_root()
3987
3996
and self.target.supports_rich_root()):
3988
3997
self._converting_to_rich_root = True
3989
3998
self._revision_id_to_root_id = {}
3991
4000
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)
3992
4006
revision_ids = self.target.search_missing_revision_ids(self.source,
3993
4007
revision_id, find_ghosts=find_ghosts).get_keys()
3994
4008
if not revision_ids:
4063
4077
:param to_convert: The disk object to convert.
4064
4078
:param pb: a progress bar to use for progress information.
4080
pb = ui.ui_factory.nested_progress_bar()
4069
4083
# this is only useful with metadir layouts - separated repo content.
4070
4084
# trigger an assertion if not such
4071
4085
repo._format.get_format_string()
4072
4086
self.repo_dir = repo.bzrdir
4073
self.step('Moving repository to repository.backup')
4087
pb.update('Moving repository to repository.backup')
4074
4088
self.repo_dir.transport.move('repository', 'repository.backup')
4075
4089
backup_transport = self.repo_dir.transport.clone('repository.backup')
4076
4090
repo._format.check_conversion_target(self.target_format)
4077
4091
self.source_repo = repo._format.open(self.repo_dir,
4079
4093
_override_transport=backup_transport)
4080
self.step('Creating new repository')
4094
pb.update('Creating new repository')
4081
4095
converted = self.target_format.initialize(self.repo_dir,
4082
4096
self.source_repo.is_shared())
4083
4097
converted.lock_write()
4085
self.step('Copying content')
4099
pb.update('Copying content')
4086
4100
self.source_repo.copy_content_into(converted)
4088
4102
converted.unlock()
4089
self.step('Deleting old repository content')
4103
pb.update('Deleting old repository content')
4090
4104
self.repo_dir.transport.delete_tree('repository.backup')
4091
4105
ui.ui_factory.note('repository converted')
4093
def step(self, message):
4094
"""Update the pb by a step."""
4096
self.pb.update(message, self.count, self.total)
4099
4109
_unescape_map = {
4443
4453
fetching the inventory weave.
4445
4455
if self._rich_root_upgrade():
4447
return bzrlib.fetch.Inter1and2Helper(
4456
return _mod_fetch.Inter1and2Helper(
4448
4457
self.from_repository).generate_root_texts(revs)
4593
4602
def _get_convertable_inventory_stream(self, revision_ids,
4594
4603
delta_versus_null=False):
4595
# The source is using CHKs, but the target either doesn't or it has a
4596
# different serializer. The StreamSink code expects to be able to
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
4597
4608
# convert on the target, so we need to put bytes-on-the-wire that can
4598
4609
# be converted. That means inventory deltas (if the remote is <1.19,
4599
4610
# RemoteStreamSink will fallback to VFS to insert the deltas).