1
# Copyright (C) 2005-2010 Canonical Ltd
1
# Copyright (C) 2005, 2006, 2007, 2008, 2009 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
52
49
from bzrlib.testament import Testament
55
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
52
from bzrlib.decorators import needs_read_lock, needs_write_lock
56
53
from bzrlib.inter import InterObject
57
54
from bzrlib.inventory import (
63
from bzrlib.lock import _RelockDebugMixin
64
60
from bzrlib import registry
65
61
from bzrlib.trace import (
66
62
log_exception_quietly, note, mutter, mutter_callsite, warning)
209
205
# an inventory delta was accumulated without creating a new
211
207
basis_id = self.basis_delta_revision
212
# We ignore the 'inventory' returned by add_inventory_by_delta
213
# because self.new_inventory is used to hint to the rest of the
214
# system what code path was taken
215
self.inv_sha1, _ = self.repository.add_inventory_by_delta(
208
self.inv_sha1 = self.repository.add_inventory_by_delta(
216
209
basis_id, self._basis_delta, self._new_revision_id,
1028
1021
:seealso: add_inventory, for the contract.
1030
inv_lines = self._serializer.write_inventory_to_lines(inv)
1023
inv_lines = self._serialise_inventory_to_lines(inv)
1031
1024
return self._inventory_add_lines(revision_id, parents,
1032
1025
inv_lines, check_content=False)
1240
1233
"""Check a single text from this repository."""
1241
1234
if kind == 'inventories':
1242
1235
rev_id = record.key[0]
1243
inv = self._deserialise_inventory(rev_id,
1236
inv = self.deserialise_inventory(rev_id,
1244
1237
record.get_bytes_as('fulltext'))
1245
1238
if last_object is not None:
1246
1239
delta = inv._make_delta(last_object)
1306
1299
self._reconcile_does_inventory_gc = True
1307
1300
self._reconcile_fixes_text_parents = False
1308
1301
self._reconcile_backsup_inventory = True
1302
# not right yet - should be more semantically clear ?
1304
# TODO: make sure to construct the right store classes, etc, depending
1305
# on whether escaping is required.
1306
self._warn_if_deprecated()
1309
1307
self._write_group = None
1310
1308
# Additional places to query for data.
1311
1309
self._fallback_repositories = []
1312
1310
# An InventoryEntry cache, used during deserialization
1313
1311
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
1318
1313
def __repr__(self):
1319
1314
if self._fallback_repositories:
1386
1381
locked = self.is_locked()
1387
1382
result = self.control_files.lock_write(token=token)
1389
self._warn_if_deprecated()
1390
self._note_lock('w')
1391
1384
for repo in self._fallback_repositories:
1392
1385
# Writes don't affect fallback repos
1393
1386
repo.lock_read()
1484
1475
:param using: If True, list only branches using this repository.
1486
1477
if using and not self.is_shared():
1487
return self.bzrdir.list_branches()
1479
return [self.bzrdir.open_branch()]
1480
except errors.NotBranchError:
1488
1482
class Evaluator(object):
1490
1484
def __init__(self):
1499
1493
except errors.NoRepositoryPresent:
1502
return False, ([], repository)
1496
return False, (None, repository)
1503
1497
self.first_call = False
1504
value = (bzrdir.list_branches(), None)
1499
value = (bzrdir.open_branch(), None)
1500
except errors.NotBranchError:
1501
value = (None, None)
1505
1502
return True, value
1508
for branches, repository in bzrdir.BzrDir.find_bzrdirs(
1505
for branch, repository in bzrdir.BzrDir.find_bzrdirs(
1509
1506
self.bzrdir.root_transport, evaluate=Evaluator()):
1510
if branches is not None:
1511
ret.extend(branches)
1507
if branch is not None:
1508
branches.append(branch)
1512
1509
if not using and repository is not None:
1513
ret.extend(repository.find_branches())
1510
branches.extend(repository.find_branches())
1516
1513
@needs_read_lock
1517
1514
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1895
1891
rev = self._serializer.read_revision_from_string(text)
1896
1892
yield (revid, rev)
1895
def get_revision_xml(self, revision_id):
1896
# TODO: jam 20070210 This shouldn't be necessary since get_revision
1897
# would have already do it.
1898
# TODO: jam 20070210 Just use _serializer.write_revision_to_string()
1899
# TODO: this can't just be replaced by:
1900
# return self._serializer.write_revision_to_string(
1901
# self.get_revision(revision_id))
1902
# as cStringIO preservers the encoding unlike write_revision_to_string
1903
# or some other call down the path.
1904
rev = self.get_revision(revision_id)
1905
rev_tmp = cStringIO.StringIO()
1906
# the current serializer..
1907
self._serializer.write_revision(rev, rev_tmp)
1909
return rev_tmp.getvalue()
1898
1911
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1899
1912
"""Produce a generator of revision deltas.
2143
2156
selected_keys = set((revid,) for revid in revision_ids)
2144
2157
w = _inv_weave or self.inventories
2145
return self._find_file_ids_from_xml_inventory_lines(
2146
w.iter_lines_added_or_present_in_keys(
2147
selected_keys, pb=None),
2158
pb = ui.ui_factory.nested_progress_bar()
2160
return self._find_file_ids_from_xml_inventory_lines(
2161
w.iter_lines_added_or_present_in_keys(
2162
selected_keys, pb=pb),
2150
2167
def iter_files_bytes(self, desired_files):
2151
2168
"""Iterate through file versions.
2312
2329
num_file_ids = len(file_ids)
2313
2330
for file_id, altered_versions in file_ids.iteritems():
2314
2331
if pb is not None:
2315
pb.update("Fetch texts", count, num_file_ids)
2332
pb.update("fetch texts", count, num_file_ids)
2317
2334
yield ("file", file_id, altered_versions)
2361
2378
"""single-document based inventory iteration."""
2362
2379
inv_xmls = self._iter_inventory_xmls(revision_ids, ordering)
2363
2380
for text, revision_id in inv_xmls:
2364
yield self._deserialise_inventory(revision_id, text)
2381
yield self.deserialise_inventory(revision_id, text)
2366
2383
def _iter_inventory_xmls(self, revision_ids, ordering):
2367
2384
if ordering is None:
2399
2416
next_key = None
2402
def _deserialise_inventory(self, revision_id, xml):
2419
def deserialise_inventory(self, revision_id, xml):
2403
2420
"""Transform the xml into an inventory object.
2405
2422
:param revision_id: The expected revision id of the inventory.
2406
2423
:param xml: A serialised inventory.
2408
2425
result = self._serializer.read_inventory_from_string(xml, revision_id,
2409
entry_cache=self._inventory_entry_cache,
2410
return_from_cache=self._safe_to_return_from_cache)
2426
entry_cache=self._inventory_entry_cache)
2411
2427
if result.revision_id != revision_id:
2412
2428
raise AssertionError('revision id mismatch %s != %s' % (
2413
2429
result.revision_id, revision_id))
2432
def serialise_inventory(self, inv):
2433
return self._serializer.write_inventory_to_string(inv)
2435
def _serialise_inventory_to_lines(self, inv):
2436
return self._serializer.write_inventory_to_lines(inv)
2416
2438
def get_serializer_format(self):
2417
2439
return self._serializer.format_num
2419
2441
@needs_read_lock
2420
def _get_inventory_xml(self, revision_id):
2421
"""Get serialized inventory as a string."""
2442
def get_inventory_xml(self, revision_id):
2443
"""Get inventory XML as a file object."""
2422
2444
texts = self._iter_inventory_xmls([revision_id], 'unordered')
2424
2446
text, revision_id = texts.next()
2426
2448
raise errors.HistoryMissing(self, 'inventory', revision_id)
2452
def get_inventory_sha1(self, revision_id):
2453
"""Return the sha1 hash of the inventory entry
2455
return self.get_revision(revision_id).inventory_sha1
2429
2457
def get_rev_id_for_revno(self, revno, known_pair):
2430
2458
"""Return the revision id of a revno, given a later (revno, revid)
2431
2459
pair in the same history.
2483
2511
next_id = parents[0]
2514
def get_revision_inventory(self, revision_id):
2515
"""Return inventory of a past revision."""
2516
# TODO: Unify this with get_inventory()
2517
# bzr 0.0.6 and later imposes the constraint that the inventory_id
2518
# must be the same as its revision, so this is trivial.
2519
if revision_id is None:
2520
# This does not make sense: if there is no revision,
2521
# then it is the current tree inventory surely ?!
2522
# and thus get_root_id() is something that looks at the last
2523
# commit on the branch, and the get_root_id is an inventory check.
2524
raise NotImplementedError
2525
# return Inventory(self.get_root_id())
2527
return self.get_inventory(revision_id)
2485
2529
def is_shared(self):
2486
2530
"""Return True if this repository is flagged as a shared repository."""
2487
2531
raise NotImplementedError(self.is_shared)
2521
2565
return RevisionTree(self, Inventory(root_id=None),
2522
2566
_mod_revision.NULL_REVISION)
2524
inv = self.get_inventory(revision_id)
2568
inv = self.get_revision_inventory(revision_id)
2525
2569
return RevisionTree(self, inv, revision_id)
2527
2571
def revision_trees(self, revision_ids):
2617
2661
for ((revision_id,), parent_keys) in \
2618
2662
self.revisions.get_parent_map(query_keys).iteritems():
2619
2663
if parent_keys:
2620
result[revision_id] = tuple([parent_revid
2621
for (parent_revid,) in parent_keys])
2664
result[revision_id] = tuple(parent_revid
2665
for (parent_revid,) in parent_keys)
2623
2667
result[revision_id] = (_mod_revision.NULL_REVISION,)
2726
2770
result.check(callback_refs)
2729
def _warn_if_deprecated(self, branch=None):
2773
def _warn_if_deprecated(self):
2730
2774
global _deprecation_warning_done
2731
2775
if _deprecation_warning_done:
2735
conf = config.GlobalConfig()
2737
conf = branch.get_config()
2738
if conf.suppress_warning('format_deprecation'):
2740
warning("Format %s for %s is deprecated -"
2741
" please use 'bzr upgrade' to get better performance"
2742
% (self._format, self.bzrdir.transport.base))
2744
_deprecation_warning_done = True
2777
_deprecation_warning_done = True
2778
warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
2779
% (self._format, self.bzrdir.transport.base))
2746
2781
def supports_rich_root(self):
2747
2782
return self._format.rich_root_data
3055
3088
transport = a_bzrdir.get_repository_transport(None)
3056
format_string = transport.get_bytes("format")
3089
format_string = transport.get("format").read()
3057
3090
return format_registry.get(format_string)
3058
3091
except errors.NoSuchFile:
3059
3092
raise errors.NoRepositoryPresent(a_bzrdir)
3369
3402
:param revision_id: if None all content is copied, if NULL_REVISION no
3370
3403
content is copied.
3404
:param pb: optional progress bar to use for progress reports. If not
3405
provided a default one will be created.
3374
ui.ui_factory.warn_experimental_format_fetch(self)
3375
f = _mod_fetch.RepoFetcher(to_repository=self.target,
3408
from bzrlib.fetch import RepoFetcher
3409
f = RepoFetcher(to_repository=self.target,
3376
3410
from_repository=self.source,
3377
3411
last_revision=revision_id,
3378
3412
fetch_spec=fetch_spec,
3379
find_ghosts=find_ghosts)
3413
pb=pb, find_ghosts=find_ghosts)
3381
3415
def _walk_to_common_revisions(self, revision_ids):
3382
3416
"""Walk out from revision_ids in source to revisions target has.
3551
3585
self.target.texts.insert_record_stream(
3552
3586
self.source.texts.get_record_stream(
3553
3587
self.source.texts.keys(), 'topological', False))
3554
pb.update('Copying inventory', 0, 1)
3588
pb.update('copying inventory', 0, 1)
3555
3589
self.target.inventories.insert_record_stream(
3556
3590
self.source.inventories.get_record_stream(
3557
3591
self.source.inventories.keys(), 'topological', False))
3778
3812
basis_id, delta, current_revision_id, parents_parents)
3779
3813
cache[current_revision_id] = parent_tree
3781
def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
3815
def _fetch_batch(self, revision_ids, basis_id, cache):
3782
3816
"""Fetch across a few revisions.
3784
3818
:param revision_ids: The revisions to copy
3785
3819
:param basis_id: The revision_id of a tree that must be in cache, used
3786
3820
as a basis for delta when no other base is available
3787
3821
:param cache: A cache of RevisionTrees that we can use.
3788
:param a_graph: A Graph object to determine the heads() of the
3789
rich-root data stream.
3790
3822
:return: The revision_id of the last converted tree. The RevisionTree
3791
3823
for it will be in cache
3799
3831
pending_revisions = []
3800
3832
parent_map = self.source.get_parent_map(revision_ids)
3801
3833
self._fetch_parent_invs_for_stacking(parent_map, cache)
3802
self.source._safe_to_return_from_cache = True
3803
3834
for tree in self.source.revision_trees(revision_ids):
3804
3835
# Find a inventory delta for this revision.
3805
3836
# Find text entries that need to be copied, too.
3853
3884
pending_revisions.append(revision)
3854
3885
cache[current_revision_id] = tree
3855
3886
basis_id = current_revision_id
3856
self.source._safe_to_return_from_cache = False
3857
3887
# Copy file texts
3858
3888
from_texts = self.source.texts
3859
3889
to_texts = self.target.texts
3860
3890
if root_keys_to_create:
3861
root_stream = _mod_fetch._new_root_data_stream(
3891
from bzrlib.fetch import _new_root_data_stream
3892
root_stream = _new_root_data_stream(
3862
3893
root_keys_to_create, self._revision_id_to_root_id, parent_map,
3863
self.source, graph=a_graph)
3864
3895
to_texts.insert_record_stream(root_stream)
3865
3896
to_texts.insert_record_stream(from_texts.get_record_stream(
3866
3897
text_keys, self.target._format._fetch_order,
3923
3954
cache[basis_id] = basis_tree
3924
3955
del basis_tree # We don't want to hang on to it here
3926
if self._converting_to_rich_root and len(revision_ids) > 100:
3927
a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
3932
3957
for offset in range(0, len(revision_ids), batch_size):
3933
3958
self.target.start_write_group()
3935
3960
pb.update('Transferring revisions', offset,
3936
3961
len(revision_ids))
3937
3962
batch = revision_ids[offset:offset+batch_size]
3938
basis_id = self._fetch_batch(batch, basis_id, cache,
3963
basis_id = self._fetch_batch(batch, basis_id, cache)
3941
self.source._safe_to_return_from_cache = False
3942
3965
self.target.abort_write_group()
3956
3979
"""See InterRepository.fetch()."""
3957
3980
if fetch_spec is not None:
3958
3981
raise AssertionError("Not implemented yet...")
3959
# See <https://launchpad.net/bugs/456077> asking for a warning here
3961
# nb this is only active for local-local fetches; other things using
3963
ui.ui_factory.warn_cross_format_fetch(self.source._format,
3964
self.target._format)
3965
ui.ui_factory.warn_experimental_format_fetch(self)
3966
3982
if (not self.source.supports_rich_root()
3967
3983
and self.target.supports_rich_root()):
3968
3984
self._converting_to_rich_root = True
4043
4059
:param to_convert: The disk object to convert.
4044
4060
:param pb: a progress bar to use for progress information.
4046
pb = ui.ui_factory.nested_progress_bar()
4049
4065
# this is only useful with metadir layouts - separated repo content.
4050
4066
# trigger an assertion if not such
4051
4067
repo._format.get_format_string()
4052
4068
self.repo_dir = repo.bzrdir
4053
pb.update('Moving repository to repository.backup')
4069
self.step('Moving repository to repository.backup')
4054
4070
self.repo_dir.transport.move('repository', 'repository.backup')
4055
4071
backup_transport = self.repo_dir.transport.clone('repository.backup')
4056
4072
repo._format.check_conversion_target(self.target_format)
4057
4073
self.source_repo = repo._format.open(self.repo_dir,
4059
4075
_override_transport=backup_transport)
4060
pb.update('Creating new repository')
4076
self.step('Creating new repository')
4061
4077
converted = self.target_format.initialize(self.repo_dir,
4062
4078
self.source_repo.is_shared())
4063
4079
converted.lock_write()
4065
pb.update('Copying content')
4081
self.step('Copying content into repository.')
4066
4082
self.source_repo.copy_content_into(converted)
4068
4084
converted.unlock()
4069
pb.update('Deleting old repository content')
4085
self.step('Deleting old repository content.')
4070
4086
self.repo_dir.transport.delete_tree('repository.backup')
4071
ui.ui_factory.note('repository converted')
4087
self.pb.note('repository converted')
4089
def step(self, message):
4090
"""Update the pb by a step."""
4092
self.pb.update(message, self.count, self.total)
4075
4095
_unescape_map = {
4257
4277
self._extract_and_insert_inventories(
4258
4278
substream, src_serializer)
4259
4279
elif substream_type == 'inventory-deltas':
4260
ui.ui_factory.warn_cross_format_fetch(src_format,
4261
self.target_repo._format)
4262
4280
self._extract_and_insert_inventory_deltas(
4263
4281
substream, src_serializer)
4264
4282
elif substream_type == 'chk_bytes':
4298
4316
if versioned_file is None:
4300
# TODO: key is often going to be a StaticTuple object
4301
# I don't believe we can define a method by which
4302
# (prefix,) + StaticTuple will work, though we could
4303
# define a StaticTuple.sq_concat that would allow you to
4304
# pass in either a tuple or a StaticTuple as the second
4305
# object, so instead we could have:
4306
# StaticTuple(prefix) + key here...
4307
4318
missing_keys.update((prefix,) + key for key in
4308
4319
versioned_file.get_missing_compression_parent_keys())
4309
4320
except NotImplementedError:
4421
4432
fetching the inventory weave.
4423
4434
if self._rich_root_upgrade():
4424
return _mod_fetch.Inter1and2Helper(
4436
return bzrlib.fetch.Inter1and2Helper(
4425
4437
self.from_repository).generate_root_texts(revs)
4570
4582
def _get_convertable_inventory_stream(self, revision_ids,
4571
4583
delta_versus_null=False):
4572
# The two formats are sufficiently different that there is no fast
4573
# path, so we need to send just inventorydeltas, which any
4574
# sufficiently modern client can insert into any repository.
4575
# The StreamSink code expects to be able to
4584
# The source is using CHKs, but the target either doesn't or it has a
4585
# different serializer. The StreamSink code expects to be able to
4576
4586
# convert on the target, so we need to put bytes-on-the-wire that can
4577
4587
# be converted. That means inventory deltas (if the remote is <1.19,
4578
4588
# RemoteStreamSink will fallback to VFS to insert the deltas).