53
50
from bzrlib.testament import Testament
56
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
53
from bzrlib.decorators import needs_read_lock, needs_write_lock
57
54
from bzrlib.inter import InterObject
58
55
from bzrlib.inventory import (
64
from bzrlib.lock import _RelockDebugMixin
65
61
from bzrlib import registry
66
62
from bzrlib.trace import (
67
63
log_exception_quietly, note, mutter, mutter_callsite, warning)
210
206
# an inventory delta was accumulated without creating a new
212
208
basis_id = self.basis_delta_revision
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(
209
self.inv_sha1 = self.repository.add_inventory_by_delta(
217
210
basis_id, self._basis_delta, self._new_revision_id,
860
853
# versioned roots do not change unless the tree found a change.
863
class RepositoryWriteLockResult(object):
864
"""The result of write locking a repository.
866
:ivar repository_token: The token obtained from the underlying lock, or
868
:ivar unlock: A callable which will unlock the lock.
871
def __init__(self, unlock, repository_token):
872
self.repository_token = repository_token
876
return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
880
856
######################################################################
884
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
860
class Repository(object):
885
861
"""Repository holding history for one or more branches.
887
863
The repository holds and retrieves historical information including
1046
1022
:seealso: add_inventory, for the contract.
1048
inv_lines = self._serializer.write_inventory_to_lines(inv)
1024
inv_lines = self._serialise_inventory_to_lines(inv)
1049
1025
return self._inventory_add_lines(revision_id, parents,
1050
1026
inv_lines, check_content=False)
1258
1234
"""Check a single text from this repository."""
1259
1235
if kind == 'inventories':
1260
1236
rev_id = record.key[0]
1261
inv = self._deserialise_inventory(rev_id,
1237
inv = self.deserialise_inventory(rev_id,
1262
1238
record.get_bytes_as('fulltext'))
1263
1239
if last_object is not None:
1264
1240
delta = inv._make_delta(last_object)
1309
1285
:param _format: The format of the repository on disk.
1310
1286
: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.
1312
# In the future we will have a single api for all stores for
1313
# getting file texts, inventories and revisions, then
1314
# this construct will accept instances of those things.
1315
1292
super(Repository, self).__init__()
1316
1293
self._format = _format
1317
1294
# the following are part of the public API for Repository:
1323
1300
self._reconcile_does_inventory_gc = True
1324
1301
self._reconcile_fixes_text_parents = False
1325
1302
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()
1326
1308
self._write_group = None
1327
1309
# Additional places to query for data.
1328
1310
self._fallback_repositories = []
1329
1311
# An InventoryEntry cache, used during deserialization
1330
1312
self._inventory_entry_cache = fifo_cache.FIFOCache(10*1024)
1331
# Is it safe to return inventory entries directly from the entry cache,
1332
# rather copying them?
1333
self._safe_to_return_from_cache = False
1336
def user_transport(self):
1337
return self.bzrdir.user_transport
1340
def control_transport(self):
1341
return self._transport
1343
1314
def __repr__(self):
1344
1315
if self._fallback_repositories:
1393
1364
data during reads, and allows a 'write_group' to be obtained. Write
1394
1365
groups must be used for actual data insertion.
1396
A token should be passed in if you know that you have locked the object
1397
some other way, and need to synchronise this object's state with that
1400
XXX: this docstring is duplicated in many places, e.g. lockable_files.py
1402
1367
:param token: if this is already locked, then lock_write will fail
1403
1368
unless the token matches the existing lock.
1404
1369
:returns: a token if this instance supports tokens, otherwise None.
1407
1372
:raises MismatchedToken: if the specified token doesn't match the token
1408
1373
of the existing lock.
1409
1374
:seealso: start_write_group.
1410
:return: A RepositoryWriteLockResult.
1376
A token should be passed in if you know that you have locked the object
1377
some other way, and need to synchronise this object's state with that
1380
XXX: this docstring is duplicated in many places, e.g. lockable_files.py
1412
1382
locked = self.is_locked()
1413
token = self.control_files.lock_write(token=token)
1383
result = self.control_files.lock_write(token=token)
1415
self._warn_if_deprecated()
1416
self._note_lock('w')
1417
1385
for repo in self._fallback_repositories:
1418
1386
# Writes don't affect fallback repos
1419
1387
repo.lock_read()
1420
1388
self._refresh_data()
1421
return RepositoryWriteLockResult(self.unlock, token)
1423
1391
def lock_read(self):
1424
"""Lock the repository for read operations.
1426
:return: An object with an unlock method which will release the lock
1429
1392
locked = self.is_locked()
1430
1393
self.control_files.lock_read()
1432
self._warn_if_deprecated()
1433
self._note_lock('r')
1434
1395
for repo in self._fallback_repositories:
1435
1396
repo.lock_read()
1436
1397
self._refresh_data()
1439
1399
def get_physical_lock_status(self):
1440
1400
return self.control_files.get_physical_lock_status()
1501
1461
# now gather global repository information
1502
1462
# XXX: This is available for many repos regardless of listability.
1503
if self.user_transport.listable():
1463
if self.bzrdir.root_transport.listable():
1504
1464
# XXX: do we want to __define len__() ?
1505
1465
# Maybe the versionedfiles object should provide a different
1506
1466
# method to get the number of keys.
1516
1476
:param using: If True, list only branches using this repository.
1518
1478
if using and not self.is_shared():
1519
return self.bzrdir.list_branches()
1480
return [self.bzrdir.open_branch()]
1481
except errors.NotBranchError:
1520
1483
class Evaluator(object):
1522
1485
def __init__(self):
1531
1494
except errors.NoRepositoryPresent:
1534
return False, ([], repository)
1497
return False, (None, repository)
1535
1498
self.first_call = False
1536
value = (bzrdir.list_branches(), None)
1500
value = (bzrdir.open_branch(), None)
1501
except errors.NotBranchError:
1502
value = (None, None)
1537
1503
return True, value
1540
for branches, repository in bzrdir.BzrDir.find_bzrdirs(
1541
self.user_transport, evaluate=Evaluator()):
1542
if branches is not None:
1543
ret.extend(branches)
1506
for branch, repository in bzrdir.BzrDir.find_bzrdirs(
1507
self.bzrdir.root_transport, evaluate=Evaluator()):
1508
if branch is not None:
1509
branches.append(branch)
1544
1510
if not using and repository is not None:
1545
ret.extend(repository.find_branches())
1511
branches.extend(repository.find_branches())
1548
1514
@needs_read_lock
1549
1515
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1927
1892
rev = self._serializer.read_revision_from_string(text)
1928
1893
yield (revid, rev)
1896
def get_revision_xml(self, revision_id):
1897
# TODO: jam 20070210 This shouldn't be necessary since get_revision
1898
# would have already do it.
1899
# TODO: jam 20070210 Just use _serializer.write_revision_to_string()
1900
# TODO: this can't just be replaced by:
1901
# return self._serializer.write_revision_to_string(
1902
# self.get_revision(revision_id))
1903
# as cStringIO preservers the encoding unlike write_revision_to_string
1904
# or some other call down the path.
1905
rev = self.get_revision(revision_id)
1906
rev_tmp = cStringIO.StringIO()
1907
# the current serializer..
1908
self._serializer.write_revision(rev, rev_tmp)
1910
return rev_tmp.getvalue()
1930
1912
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1931
1913
"""Produce a generator of revision deltas.
2175
2157
selected_keys = set((revid,) for revid in revision_ids)
2176
2158
w = _inv_weave or self.inventories
2177
return self._find_file_ids_from_xml_inventory_lines(
2178
w.iter_lines_added_or_present_in_keys(
2179
selected_keys, pb=None),
2159
pb = ui.ui_factory.nested_progress_bar()
2161
return self._find_file_ids_from_xml_inventory_lines(
2162
w.iter_lines_added_or_present_in_keys(
2163
selected_keys, pb=pb),
2182
2168
def iter_files_bytes(self, desired_files):
2183
2169
"""Iterate through file versions.
2344
2330
num_file_ids = len(file_ids)
2345
2331
for file_id, altered_versions in file_ids.iteritems():
2346
2332
if pb is not None:
2347
pb.update("Fetch texts", count, num_file_ids)
2333
pb.update("fetch texts", count, num_file_ids)
2349
2335
yield ("file", file_id, altered_versions)
2393
2379
"""single-document based inventory iteration."""
2394
2380
inv_xmls = self._iter_inventory_xmls(revision_ids, ordering)
2395
2381
for text, revision_id in inv_xmls:
2396
yield self._deserialise_inventory(revision_id, text)
2382
yield self.deserialise_inventory(revision_id, text)
2398
2384
def _iter_inventory_xmls(self, revision_ids, ordering):
2399
2385
if ordering is None:
2431
2417
next_key = None
2434
def _deserialise_inventory(self, revision_id, xml):
2420
def deserialise_inventory(self, revision_id, xml):
2435
2421
"""Transform the xml into an inventory object.
2437
2423
:param revision_id: The expected revision id of the inventory.
2438
2424
:param xml: A serialised inventory.
2440
2426
result = self._serializer.read_inventory_from_string(xml, revision_id,
2441
entry_cache=self._inventory_entry_cache,
2442
return_from_cache=self._safe_to_return_from_cache)
2427
entry_cache=self._inventory_entry_cache)
2443
2428
if result.revision_id != revision_id:
2444
2429
raise AssertionError('revision id mismatch %s != %s' % (
2445
2430
result.revision_id, revision_id))
2433
def serialise_inventory(self, inv):
2434
return self._serializer.write_inventory_to_string(inv)
2436
def _serialise_inventory_to_lines(self, inv):
2437
return self._serializer.write_inventory_to_lines(inv)
2448
2439
def get_serializer_format(self):
2449
2440
return self._serializer.format_num
2451
2442
@needs_read_lock
2452
def _get_inventory_xml(self, revision_id):
2453
"""Get serialized inventory as a string."""
2443
def get_inventory_xml(self, revision_id):
2444
"""Get inventory XML as a file object."""
2454
2445
texts = self._iter_inventory_xmls([revision_id], 'unordered')
2456
2447
text, revision_id = texts.next()
2458
2449
raise errors.HistoryMissing(self, 'inventory', revision_id)
2453
def get_inventory_sha1(self, revision_id):
2454
"""Return the sha1 hash of the inventory entry
2456
return self.get_revision(revision_id).inventory_sha1
2461
2458
def get_rev_id_for_revno(self, revno, known_pair):
2462
2459
"""Return the revision id of a revno, given a later (revno, revid)
2463
2460
pair in the same history.
2515
2512
next_id = parents[0]
2515
def get_revision_inventory(self, revision_id):
2516
"""Return inventory of a past revision."""
2517
# TODO: Unify this with get_inventory()
2518
# bzr 0.0.6 and later imposes the constraint that the inventory_id
2519
# must be the same as its revision, so this is trivial.
2520
if revision_id is None:
2521
# This does not make sense: if there is no revision,
2522
# then it is the current tree inventory surely ?!
2523
# and thus get_root_id() is something that looks at the last
2524
# commit on the branch, and the get_root_id is an inventory check.
2525
raise NotImplementedError
2526
# return Inventory(self.get_root_id())
2528
return self.get_inventory(revision_id)
2517
2530
def is_shared(self):
2518
2531
"""Return True if this repository is flagged as a shared repository."""
2519
2532
raise NotImplementedError(self.is_shared)
2553
2566
return RevisionTree(self, Inventory(root_id=None),
2554
2567
_mod_revision.NULL_REVISION)
2556
inv = self.get_inventory(revision_id)
2569
inv = self.get_revision_inventory(revision_id)
2557
2570
return RevisionTree(self, inv, revision_id)
2559
2572
def revision_trees(self, revision_ids):
2612
2625
keys = tsort.topo_sort(parent_map)
2613
2626
return [None] + list(keys)
2615
def pack(self, hint=None, clean_obsolete_packs=False):
2628
def pack(self, hint=None):
2616
2629
"""Compress the data within the repository.
2618
2631
This operation only makes sense for some repository types. For other
2628
2641
obtained from the result of commit_write_group(). Out of
2629
2642
date hints are simply ignored, because concurrent operations
2630
2643
can obsolete them rapidly.
2632
:param clean_obsolete_packs: Clean obsolete packs immediately after
2636
2646
def get_transaction(self):
2652
2662
for ((revision_id,), parent_keys) in \
2653
2663
self.revisions.get_parent_map(query_keys).iteritems():
2654
2664
if parent_keys:
2655
result[revision_id] = tuple([parent_revid
2656
for (parent_revid,) in parent_keys])
2665
result[revision_id] = tuple(parent_revid
2666
for (parent_revid,) in parent_keys)
2658
2668
result[revision_id] = (_mod_revision.NULL_REVISION,)
2661
2671
def _make_parents_provider(self):
2665
def get_known_graph_ancestry(self, revision_ids):
2666
"""Return the known graph for a set of revision ids and their ancestors.
2668
st = static_tuple.StaticTuple
2669
revision_keys = [st(r_id).intern() for r_id in revision_ids]
2670
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
2671
return graph.GraphThunkIdsToKeys(known_graph)
2673
2674
def get_graph(self, other_repository=None):
2674
2675
"""Return the graph walker for this repository format"""
2675
2676
parents_provider = self._make_parents_provider()
2770
2771
result.check(callback_refs)
2773
def _warn_if_deprecated(self, branch=None):
2774
def _warn_if_deprecated(self):
2774
2775
global _deprecation_warning_done
2775
2776
if _deprecation_warning_done:
2779
conf = config.GlobalConfig()
2781
conf = branch.get_config()
2782
if conf.suppress_warning('format_deprecation'):
2784
warning("Format %s for %s is deprecated -"
2785
" please use 'bzr upgrade' to get better performance"
2786
% (self._format, self.bzrdir.transport.base))
2788
_deprecation_warning_done = True
2778
_deprecation_warning_done = True
2779
warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
2780
% (self._format, self.bzrdir.transport.base))
2790
2782
def supports_rich_root(self):
2791
2783
return self._format.rich_root_data
3074
3066
pack_compresses = False
3075
3067
# Does the repository inventory storage understand references to trees?
3076
3068
supports_tree_reference = None
3077
# Is the format experimental ?
3078
experimental = False
3081
return "%s()" % self.__class__.__name__
3071
return "<%s>" % self.__class__.__name__
3083
3073
def __eq__(self, other):
3084
3074
# format objects are generally stateless
3099
3089
transport = a_bzrdir.get_repository_transport(None)
3100
format_string = transport.get_bytes("format")
3090
format_string = transport.get("format").read()
3101
3091
return format_registry.get(format_string)
3102
3092
except errors.NoSuchFile:
3103
3093
raise errors.NoRepositoryPresent(a_bzrdir)
3203
3193
raise NotImplementedError(self.open)
3205
def _run_post_repo_init_hooks(self, repository, a_bzrdir, shared):
3206
from bzrlib.bzrdir import BzrDir, RepoInitHookParams
3207
hooks = BzrDir.hooks['post_repo_init']
3210
params = RepoInitHookParams(repository, self, a_bzrdir, shared)
3215
3196
class MetaDirRepositoryFormat(RepositoryFormat):
3216
3197
"""Common base class for the new repositories using the metadir layout."""
3422
3403
:param revision_id: if None all content is copied, if NULL_REVISION no
3423
3404
content is copied.
3405
:param pb: optional progress bar to use for progress reports. If not
3406
provided a default one will be created.
3427
ui.ui_factory.warn_experimental_format_fetch(self)
3428
3409
from bzrlib.fetch import RepoFetcher
3429
# See <https://launchpad.net/bugs/456077> asking for a warning here
3430
if self.source._format.network_name() != self.target._format.network_name():
3431
ui.ui_factory.show_user_warning('cross_format_fetch',
3432
from_format=self.source._format,
3433
to_format=self.target._format)
3434
3410
f = RepoFetcher(to_repository=self.target,
3435
3411
from_repository=self.source,
3436
3412
last_revision=revision_id,
3437
3413
fetch_spec=fetch_spec,
3438
find_ghosts=find_ghosts)
3414
pb=pb, find_ghosts=find_ghosts)
3440
3416
def _walk_to_common_revisions(self, revision_ids):
3441
3417
"""Walk out from revision_ids in source to revisions target has.
3610
3586
self.target.texts.insert_record_stream(
3611
3587
self.source.texts.get_record_stream(
3612
3588
self.source.texts.keys(), 'topological', False))
3613
pb.update('Copying inventory', 0, 1)
3589
pb.update('copying inventory', 0, 1)
3614
3590
self.target.inventories.insert_record_stream(
3615
3591
self.source.inventories.get_record_stream(
3616
3592
self.source.inventories.keys(), 'topological', False))
3837
3813
basis_id, delta, current_revision_id, parents_parents)
3838
3814
cache[current_revision_id] = parent_tree
3840
def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
3816
def _fetch_batch(self, revision_ids, basis_id, cache):
3841
3817
"""Fetch across a few revisions.
3843
3819
:param revision_ids: The revisions to copy
3844
3820
:param basis_id: The revision_id of a tree that must be in cache, used
3845
3821
as a basis for delta when no other base is available
3846
3822
:param cache: A cache of RevisionTrees that we can use.
3847
:param a_graph: A Graph object to determine the heads() of the
3848
rich-root data stream.
3849
3823
:return: The revision_id of the last converted tree. The RevisionTree
3850
3824
for it will be in cache
3858
3832
pending_revisions = []
3859
3833
parent_map = self.source.get_parent_map(revision_ids)
3860
3834
self._fetch_parent_invs_for_stacking(parent_map, cache)
3861
self.source._safe_to_return_from_cache = True
3862
3835
for tree in self.source.revision_trees(revision_ids):
3863
3836
# Find a inventory delta for this revision.
3864
3837
# Find text entries that need to be copied, too.
3912
3885
pending_revisions.append(revision)
3913
3886
cache[current_revision_id] = tree
3914
3887
basis_id = current_revision_id
3915
self.source._safe_to_return_from_cache = False
3916
3888
# Copy file texts
3917
3889
from_texts = self.source.texts
3918
3890
to_texts = self.target.texts
3919
3891
if root_keys_to_create:
3920
root_stream = _mod_fetch._new_root_data_stream(
3892
from bzrlib.fetch import _new_root_data_stream
3893
root_stream = _new_root_data_stream(
3921
3894
root_keys_to_create, self._revision_id_to_root_id, parent_map,
3922
self.source, graph=a_graph)
3923
3896
to_texts.insert_record_stream(root_stream)
3924
3897
to_texts.insert_record_stream(from_texts.get_record_stream(
3925
3898
text_keys, self.target._format._fetch_order,
3982
3955
cache[basis_id] = basis_tree
3983
3956
del basis_tree # We don't want to hang on to it here
3985
if self._converting_to_rich_root and len(revision_ids) > 100:
3986
a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
3991
3958
for offset in range(0, len(revision_ids), batch_size):
3992
3959
self.target.start_write_group()
3994
3961
pb.update('Transferring revisions', offset,
3995
3962
len(revision_ids))
3996
3963
batch = revision_ids[offset:offset+batch_size]
3997
basis_id = self._fetch_batch(batch, basis_id, cache,
3964
basis_id = self._fetch_batch(batch, basis_id, cache)
4000
self.source._safe_to_return_from_cache = False
4001
3966
self.target.abort_write_group()
4015
3980
"""See InterRepository.fetch()."""
4016
3981
if fetch_spec is not None:
4017
3982
raise AssertionError("Not implemented yet...")
4018
ui.ui_factory.warn_experimental_format_fetch(self)
3983
# See <https://launchpad.net/bugs/456077> asking for a warning here
3985
# nb this is only active for local-local fetches; other things using
3987
ui.ui_factory.warn_cross_format_fetch(self.source._format,
3988
self.target._format)
4019
3989
if (not self.source.supports_rich_root()
4020
3990
and self.target.supports_rich_root()):
4021
3991
self._converting_to_rich_root = True
4022
3992
self._revision_id_to_root_id = {}
4024
3994
self._converting_to_rich_root = False
4025
# See <https://launchpad.net/bugs/456077> asking for a warning here
4026
if self.source._format.network_name() != self.target._format.network_name():
4027
ui.ui_factory.show_user_warning('cross_format_fetch',
4028
from_format=self.source._format,
4029
to_format=self.target._format)
4030
3995
revision_ids = self.target.search_missing_revision_ids(self.source,
4031
3996
revision_id, find_ghosts=find_ghosts).get_keys()
4032
3997
if not revision_ids:
4101
4066
:param to_convert: The disk object to convert.
4102
4067
:param pb: a progress bar to use for progress information.
4104
pb = ui.ui_factory.nested_progress_bar()
4107
4072
# this is only useful with metadir layouts - separated repo content.
4108
4073
# trigger an assertion if not such
4109
4074
repo._format.get_format_string()
4110
4075
self.repo_dir = repo.bzrdir
4111
pb.update('Moving repository to repository.backup')
4076
self.step('Moving repository to repository.backup')
4112
4077
self.repo_dir.transport.move('repository', 'repository.backup')
4113
4078
backup_transport = self.repo_dir.transport.clone('repository.backup')
4114
4079
repo._format.check_conversion_target(self.target_format)
4115
4080
self.source_repo = repo._format.open(self.repo_dir,
4117
4082
_override_transport=backup_transport)
4118
pb.update('Creating new repository')
4083
self.step('Creating new repository')
4119
4084
converted = self.target_format.initialize(self.repo_dir,
4120
4085
self.source_repo.is_shared())
4121
4086
converted.lock_write()
4123
pb.update('Copying content')
4088
self.step('Copying content into repository.')
4124
4089
self.source_repo.copy_content_into(converted)
4126
4091
converted.unlock()
4127
pb.update('Deleting old repository content')
4092
self.step('Deleting old repository content.')
4128
4093
self.repo_dir.transport.delete_tree('repository.backup')
4129
ui.ui_factory.note('repository converted')
4094
self.pb.note('repository converted')
4096
def step(self, message):
4097
"""Update the pb by a step."""
4099
self.pb.update(message, self.count, self.total)
4133
4102
_unescape_map = {
4315
4284
self._extract_and_insert_inventories(
4316
4285
substream, src_serializer)
4317
4286
elif substream_type == 'inventory-deltas':
4287
ui.ui_factory.warn_cross_format_fetch(src_format,
4288
self.target_repo._format)
4318
4289
self._extract_and_insert_inventory_deltas(
4319
4290
substream, src_serializer)
4320
4291
elif substream_type == 'chk_bytes':
4354
4325
if versioned_file is None:
4356
# TODO: key is often going to be a StaticTuple object
4357
# I don't believe we can define a method by which
4358
# (prefix,) + StaticTuple will work, though we could
4359
# define a StaticTuple.sq_concat that would allow you to
4360
# pass in either a tuple or a StaticTuple as the second
4361
# object, so instead we could have:
4362
# StaticTuple(prefix) + key here...
4363
4327
missing_keys.update((prefix,) + key for key in
4364
4328
versioned_file.get_missing_compression_parent_keys())
4365
4329
except NotImplementedError:
4477
4441
fetching the inventory weave.
4479
4443
if self._rich_root_upgrade():
4480
return _mod_fetch.Inter1and2Helper(
4445
return bzrlib.fetch.Inter1and2Helper(
4481
4446
self.from_repository).generate_root_texts(revs)