52
51
from bzrlib.testament import Testament
55
59
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
56
60
from bzrlib.inter import InterObject
57
61
from bzrlib.inventory import (
63
from bzrlib.lock import _RelockDebugMixin
64
from bzrlib import registry
67
from bzrlib.recordcounter import RecordCounter
68
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
65
69
from bzrlib.trace import (
66
70
log_exception_quietly, note, mutter, mutter_callsite, warning)
70
74
_deprecation_warning_done = False
77
class IsInWriteGroupError(errors.InternalBzrError):
79
_fmt = "May not refresh_data of repo %(repo)s while in a write group."
81
def __init__(self, repo):
82
errors.InternalBzrError.__init__(self, repo=repo)
73
85
class CommitBuilder(object):
74
86
"""Provides an interface to build up a commit.
278
290
:param tree: The tree which is being committed.
280
# NB: if there are no parents then this method is not called, so no
281
# need to guard on parents having length.
292
if len(self.parents) == 0:
293
raise errors.RootMissing()
282
294
entry = entry_factory['directory'](tree.path2id(''), '',
284
296
entry.revision = self._new_revision_id
859
871
# versioned roots do not change unless the tree found a change.
874
class RepositoryWriteLockResult(LogicalLockResult):
875
"""The result of write locking a repository.
877
:ivar repository_token: The token obtained from the underlying lock, or
879
:ivar unlock: A callable which will unlock the lock.
882
def __init__(self, unlock, repository_token):
883
LogicalLockResult.__init__(self, unlock)
884
self.repository_token = repository_token
887
return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
862
891
######################################################################
866
class Repository(_RelockDebugMixin):
895
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
867
896
"""Repository holding history for one or more branches.
869
898
The repository holds and retrieves historical information including
1017
1046
" id and insertion revid (%r, %r)"
1018
1047
% (inv.revision_id, revision_id))
1019
1048
if inv.root is None:
1020
raise AssertionError()
1049
raise errors.RootMissing()
1021
1050
return self._add_inventory_checked(revision_id, inv, parents)
1023
1052
def _add_inventory_checked(self, revision_id, inv, parents):
1028
1057
:seealso: add_inventory, for the contract.
1030
inv_lines = self._serialise_inventory_to_lines(inv)
1059
inv_lines = self._serializer.write_inventory_to_lines(inv)
1031
1060
return self._inventory_add_lines(revision_id, parents,
1032
1061
inv_lines, check_content=False)
1240
1269
"""Check a single text from this repository."""
1241
1270
if kind == 'inventories':
1242
1271
rev_id = record.key[0]
1243
inv = self.deserialise_inventory(rev_id,
1272
inv = self._deserialise_inventory(rev_id,
1244
1273
record.get_bytes_as('fulltext'))
1245
1274
if last_object is not None:
1246
1275
delta = inv._make_delta(last_object)
1291
1320
:param _format: The format of the repository on disk.
1292
1321
: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.
1323
# In the future we will have a single api for all stores for
1324
# getting file texts, inventories and revisions, then
1325
# this construct will accept instances of those things.
1298
1326
super(Repository, self).__init__()
1299
1327
self._format = _format
1300
1328
# the following are part of the public API for Repository:
1315
1343
# rather copying them?
1316
1344
self._safe_to_return_from_cache = False
1347
def user_transport(self):
1348
return self.bzrdir.user_transport
1351
def control_transport(self):
1352
return self._transport
1318
1354
def __repr__(self):
1319
1355
if self._fallback_repositories:
1320
1356
return '%s(%r, fallback_repositories=%r)' % (
1368
1404
data during reads, and allows a 'write_group' to be obtained. Write
1369
1405
groups must be used for actual data insertion.
1407
A token should be passed in if you know that you have locked the object
1408
some other way, and need to synchronise this object's state with that
1411
XXX: this docstring is duplicated in many places, e.g. lockable_files.py
1371
1413
:param token: if this is already locked, then lock_write will fail
1372
1414
unless the token matches the existing lock.
1373
1415
:returns: a token if this instance supports tokens, otherwise None.
1376
1418
:raises MismatchedToken: if the specified token doesn't match the token
1377
1419
of the existing lock.
1378
1420
:seealso: start_write_group.
1380
A token should be passed in if you know that you have locked the object
1381
some other way, and need to synchronise this object's state with that
1384
XXX: this docstring is duplicated in many places, e.g. lockable_files.py
1421
:return: A RepositoryWriteLockResult.
1386
1423
locked = self.is_locked()
1387
result = self.control_files.lock_write(token=token)
1424
token = self.control_files.lock_write(token=token)
1389
1426
self._warn_if_deprecated()
1390
1427
self._note_lock('w')
1392
1429
# Writes don't affect fallback repos
1393
1430
repo.lock_read()
1394
1431
self._refresh_data()
1432
return RepositoryWriteLockResult(self.unlock, token)
1397
1434
def lock_read(self):
1435
"""Lock the repository for read operations.
1437
:return: An object with an unlock method which will release the lock
1398
1440
locked = self.is_locked()
1399
1441
self.control_files.lock_read()
1469
1512
# now gather global repository information
1470
1513
# XXX: This is available for many repos regardless of listability.
1471
if self.bzrdir.root_transport.listable():
1514
if self.user_transport.listable():
1472
1515
# XXX: do we want to __define len__() ?
1473
1516
# Maybe the versionedfiles object should provide a different
1474
1517
# method to get the number of keys.
1484
1527
:param using: If True, list only branches using this repository.
1486
1529
if using and not self.is_shared():
1488
return [self.bzrdir.open_branch()]
1489
except errors.NotBranchError:
1530
return self.bzrdir.list_branches()
1491
1531
class Evaluator(object):
1493
1533
def __init__(self):
1502
1542
except errors.NoRepositoryPresent:
1505
return False, (None, repository)
1545
return False, ([], repository)
1506
1546
self.first_call = False
1508
value = (bzrdir.open_branch(), None)
1509
except errors.NotBranchError:
1510
value = (None, None)
1547
value = (bzrdir.list_branches(), None)
1511
1548
return True, value
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)
1551
for branches, repository in bzrdir.BzrDir.find_bzrdirs(
1552
self.user_transport, evaluate=Evaluator()):
1553
if branches is not None:
1554
ret.extend(branches)
1518
1555
if not using and repository is not None:
1519
branches.extend(repository.find_branches())
1556
ret.extend(repository.find_branches())
1522
1559
@needs_read_lock
1523
1560
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1632
1669
return missing_keys
1634
1671
def refresh_data(self):
1635
"""Re-read any data needed to to synchronise with disk.
1672
"""Re-read any data needed to synchronise with disk.
1637
1674
This method is intended to be called after another repository instance
1638
1675
(such as one used by a smart server) has inserted data into the
1639
repository. It may not be called during a write group, but may be
1640
called at any other time.
1676
repository. On all repositories this will work outside of write groups.
1677
Some repository formats (pack and newer for bzrlib native formats)
1678
support refresh_data inside write groups. If called inside a write
1679
group on a repository that does not support refreshing in a write group
1680
IsInWriteGroupError will be raised.
1642
if self.is_in_write_group():
1643
raise errors.InternalBzrError(
1644
"May not refresh_data while in a write group.")
1645
1682
self._refresh_data()
1647
1684
def resume_write_group(self, tokens):
1686
1723
"May not fetch while in a write group.")
1687
1724
# fast path same-url fetch operations
1688
1725
# TODO: lift out to somewhere common with RemoteRepository
1689
# <https://bugs.edge.launchpad.net/bzr/+bug/401646>
1726
# <https://bugs.launchpad.net/bzr/+bug/401646>
1690
1727
if (self.has_same_location(source)
1691
1728
and fetch_spec is None
1692
1729
and self._has_same_fallbacks(source)):
1901
1938
rev = self._serializer.read_revision_from_string(text)
1902
1939
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()
1921
1941
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1922
1942
"""Produce a generator of revision deltas.
2166
2186
selected_keys = set((revid,) for revid in revision_ids)
2167
2187
w = _inv_weave or self.inventories
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),
2188
return self._find_file_ids_from_xml_inventory_lines(
2189
w.iter_lines_added_or_present_in_keys(
2190
selected_keys, pb=None),
2177
2193
def iter_files_bytes(self, desired_files):
2178
2194
"""Iterate through file versions.
2388
2404
"""single-document based inventory iteration."""
2389
2405
inv_xmls = self._iter_inventory_xmls(revision_ids, ordering)
2390
2406
for text, revision_id in inv_xmls:
2391
yield self.deserialise_inventory(revision_id, text)
2407
yield self._deserialise_inventory(revision_id, text)
2393
2409
def _iter_inventory_xmls(self, revision_ids, ordering):
2394
2410
if ordering is None:
2426
2442
next_key = None
2429
def deserialise_inventory(self, revision_id, xml):
2445
def _deserialise_inventory(self, revision_id, xml):
2430
2446
"""Transform the xml into an inventory object.
2432
2448
:param revision_id: The expected revision id of the inventory.
2440
2456
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)
2449
2459
def get_serializer_format(self):
2450
2460
return self._serializer.format_num
2452
2462
@needs_read_lock
2453
def get_inventory_xml(self, revision_id):
2454
"""Get inventory XML as a file object."""
2463
def _get_inventory_xml(self, revision_id):
2464
"""Get serialized inventory as a string."""
2455
2465
texts = self._iter_inventory_xmls([revision_id], 'unordered')
2457
2467
text, revision_id = texts.next()
2459
2469
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
2468
2472
def get_rev_id_for_revno(self, revno, known_pair):
2469
2473
"""Return the revision id of a revno, given a later (revno, revid)
2470
2474
pair in the same history.
2522
2526
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)
2540
2528
def is_shared(self):
2541
2529
"""Return True if this repository is flagged as a shared repository."""
2542
2530
raise NotImplementedError(self.is_shared)
2576
2564
return RevisionTree(self, Inventory(root_id=None),
2577
2565
_mod_revision.NULL_REVISION)
2579
inv = self.get_revision_inventory(revision_id)
2567
inv = self.get_inventory(revision_id)
2580
2568
return RevisionTree(self, inv, revision_id)
2582
2570
def revision_trees(self, revision_ids):
2635
2623
keys = tsort.topo_sort(parent_map)
2636
2624
return [None] + list(keys)
2638
def pack(self, hint=None):
2626
def pack(self, hint=None, clean_obsolete_packs=False):
2639
2627
"""Compress the data within the repository.
2641
2629
This operation only makes sense for some repository types. For other
2651
2639
obtained from the result of commit_write_group(). Out of
2652
2640
date hints are simply ignored, because concurrent operations
2653
2641
can obsolete them rapidly.
2643
:param clean_obsolete_packs: Clean obsolete packs immediately after
2656
2647
def get_transaction(self):
2681
2672
def _make_parents_provider(self):
2676
def get_known_graph_ancestry(self, revision_ids):
2677
"""Return the known graph for a set of revision ids and their ancestors.
2679
st = static_tuple.StaticTuple
2680
revision_keys = [st(r_id).intern() for r_id in revision_ids]
2681
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
2682
return graph.GraphThunkIdsToKeys(known_graph)
2684
2684
def get_graph(self, other_repository=None):
2685
2685
"""Return the graph walker for this repository format"""
2686
2686
parents_provider = self._make_parents_provider()
3212
3214
raise NotImplementedError(self.open)
3216
def _run_post_repo_init_hooks(self, repository, a_bzrdir, shared):
3217
from bzrlib.bzrdir import BzrDir, RepoInitHookParams
3218
hooks = BzrDir.hooks['post_repo_init']
3221
params = RepoInitHookParams(repository, self, a_bzrdir, shared)
3215
3226
class MetaDirRepositoryFormat(RepositoryFormat):
3216
3227
"""Common base class for the new repositories using the metadir layout."""
3422
3433
:param revision_id: if None all content is copied, if NULL_REVISION no
3423
3434
content is copied.
3424
:param pb: optional progress bar to use for progress reports. If not
3425
provided a default one will be created.
3438
ui.ui_factory.warn_experimental_format_fetch(self)
3428
3439
from bzrlib.fetch import RepoFetcher
3429
3440
# See <https://launchpad.net/bugs/456077> asking for a warning here
3430
3441
if self.source._format.network_name() != self.target._format.network_name():
3435
3446
from_repository=self.source,
3436
3447
last_revision=revision_id,
3437
3448
fetch_spec=fetch_spec,
3438
pb=pb, find_ghosts=find_ghosts)
3449
find_ghosts=find_ghosts)
3440
3451
def _walk_to_common_revisions(self, revision_ids):
3441
3452
"""Walk out from revision_ids in source to revisions target has.
4015
4026
"""See InterRepository.fetch()."""
4016
4027
if fetch_spec is not None:
4017
4028
raise AssertionError("Not implemented yet...")
4029
ui.ui_factory.warn_experimental_format_fetch(self)
4018
4030
if (not self.source.supports_rich_root()
4019
4031
and self.target.supports_rich_root()):
4020
4032
self._converting_to_rich_root = True
4100
4112
:param to_convert: The disk object to convert.
4101
4113
:param pb: a progress bar to use for progress information.
4115
pb = ui.ui_factory.nested_progress_bar()
4106
4118
# this is only useful with metadir layouts - separated repo content.
4107
4119
# trigger an assertion if not such
4108
4120
repo._format.get_format_string()
4109
4121
self.repo_dir = repo.bzrdir
4110
self.step('Moving repository to repository.backup')
4122
pb.update('Moving repository to repository.backup')
4111
4123
self.repo_dir.transport.move('repository', 'repository.backup')
4112
4124
backup_transport = self.repo_dir.transport.clone('repository.backup')
4113
4125
repo._format.check_conversion_target(self.target_format)
4114
4126
self.source_repo = repo._format.open(self.repo_dir,
4116
4128
_override_transport=backup_transport)
4117
self.step('Creating new repository')
4129
pb.update('Creating new repository')
4118
4130
converted = self.target_format.initialize(self.repo_dir,
4119
4131
self.source_repo.is_shared())
4120
4132
converted.lock_write()
4122
self.step('Copying content')
4134
pb.update('Copying content')
4123
4135
self.source_repo.copy_content_into(converted)
4125
4137
converted.unlock()
4126
self.step('Deleting old repository content')
4138
pb.update('Deleting old repository content')
4127
4139
self.repo_dir.transport.delete_tree('repository.backup')
4128
4140
ui.ui_factory.note('repository converted')
4130
def step(self, message):
4131
"""Update the pb by a step."""
4133
self.pb.update(message, self.count, self.total)
4136
4144
_unescape_map = {
4276
4284
is_resume = False
4278
4286
# locked_insert_stream performs a commit|suspend.
4279
return self._locked_insert_stream(stream, src_format, is_resume)
4287
return self._locked_insert_stream(stream, src_format,
4281
4290
self.target_repo.abort_write_group(suppress_errors=True)
4329
4338
# required if the serializers are different only in terms of
4330
4339
# the inventory.
4331
4340
if src_serializer == to_serializer:
4332
self.target_repo.revisions.insert_record_stream(
4341
self.target_repo.revisions.insert_record_stream(substream)
4335
4343
self._extract_and_insert_revisions(substream,
4336
4344
src_serializer)
4444
4452
"""Create a StreamSource streaming from from_repository."""
4445
4453
self.from_repository = from_repository
4446
4454
self.to_format = to_format
4455
self._record_counter = RecordCounter()
4448
4457
def delta_on_metadata(self):
4449
4458
"""Return True if delta's are permitted on metadata streams.