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
860
858
# 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
861
######################################################################
884
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
865
class Repository(_RelockDebugMixin):
885
866
"""Repository holding history for one or more branches.
887
868
The repository holds and retrieves historical information including
1046
1027
:seealso: add_inventory, for the contract.
1048
inv_lines = self._serializer.write_inventory_to_lines(inv)
1029
inv_lines = self._serialise_inventory_to_lines(inv)
1049
1030
return self._inventory_add_lines(revision_id, parents,
1050
1031
inv_lines, check_content=False)
1258
1239
"""Check a single text from this repository."""
1259
1240
if kind == 'inventories':
1260
1241
rev_id = record.key[0]
1261
inv = self._deserialise_inventory(rev_id,
1242
inv = self.deserialise_inventory(rev_id,
1262
1243
record.get_bytes_as('fulltext'))
1263
1244
if last_object is not None:
1264
1245
delta = inv._make_delta(last_object)
1309
1290
:param _format: The format of the repository on disk.
1310
1291
:param a_bzrdir: The BzrDir of the repository.
1293
In the future we will have a single api for all stores for
1294
getting file texts, inventories and revisions, then
1295
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
1297
super(Repository, self).__init__()
1316
1298
self._format = _format
1317
1299
# the following are part of the public API for Repository:
1332
1314
# rather copying them?
1333
1315
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
1317
def __repr__(self):
1344
1318
if self._fallback_repositories:
1345
1319
return '%s(%r, fallback_repositories=%r)' % (
1393
1367
data during reads, and allows a 'write_group' to be obtained. Write
1394
1368
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
1370
:param token: if this is already locked, then lock_write will fail
1403
1371
unless the token matches the existing lock.
1404
1372
:returns: a token if this instance supports tokens, otherwise None.
1407
1375
:raises MismatchedToken: if the specified token doesn't match the token
1408
1376
of the existing lock.
1409
1377
:seealso: start_write_group.
1410
:return: A RepositoryWriteLockResult.
1379
A token should be passed in if you know that you have locked the object
1380
some other way, and need to synchronise this object's state with that
1383
XXX: this docstring is duplicated in many places, e.g. lockable_files.py
1412
1385
locked = self.is_locked()
1413
token = self.control_files.lock_write(token=token)
1386
result = self.control_files.lock_write(token=token)
1415
1388
self._warn_if_deprecated()
1416
1389
self._note_lock('w')
1418
1391
# Writes don't affect fallback repos
1419
1392
repo.lock_read()
1420
1393
self._refresh_data()
1421
return RepositoryWriteLockResult(self.unlock, token)
1423
1396
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
1397
locked = self.is_locked()
1430
1398
self.control_files.lock_read()
1501
1468
# now gather global repository information
1502
1469
# XXX: This is available for many repos regardless of listability.
1503
if self.user_transport.listable():
1470
if self.bzrdir.root_transport.listable():
1504
1471
# XXX: do we want to __define len__() ?
1505
1472
# Maybe the versionedfiles object should provide a different
1506
1473
# method to get the number of keys.
1516
1483
:param using: If True, list only branches using this repository.
1518
1485
if using and not self.is_shared():
1519
return self.bzrdir.list_branches()
1487
return [self.bzrdir.open_branch()]
1488
except errors.NotBranchError:
1520
1490
class Evaluator(object):
1522
1492
def __init__(self):
1531
1501
except errors.NoRepositoryPresent:
1534
return False, ([], repository)
1504
return False, (None, repository)
1535
1505
self.first_call = False
1536
value = (bzrdir.list_branches(), None)
1507
value = (bzrdir.open_branch(), None)
1508
except errors.NotBranchError:
1509
value = (None, None)
1537
1510
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)
1513
for branch, repository in bzrdir.BzrDir.find_bzrdirs(
1514
self.bzrdir.root_transport, evaluate=Evaluator()):
1515
if branch is not None:
1516
branches.append(branch)
1544
1517
if not using and repository is not None:
1545
ret.extend(repository.find_branches())
1518
branches.extend(repository.find_branches())
1548
1521
@needs_read_lock
1549
1522
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1927
1900
rev = self._serializer.read_revision_from_string(text)
1928
1901
yield (revid, rev)
1904
def get_revision_xml(self, revision_id):
1905
# TODO: jam 20070210 This shouldn't be necessary since get_revision
1906
# would have already do it.
1907
# TODO: jam 20070210 Just use _serializer.write_revision_to_string()
1908
# TODO: this can't just be replaced by:
1909
# return self._serializer.write_revision_to_string(
1910
# self.get_revision(revision_id))
1911
# as cStringIO preservers the encoding unlike write_revision_to_string
1912
# or some other call down the path.
1913
rev = self.get_revision(revision_id)
1914
rev_tmp = cStringIO.StringIO()
1915
# the current serializer..
1916
self._serializer.write_revision(rev, rev_tmp)
1918
return rev_tmp.getvalue()
1930
1920
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1931
1921
"""Produce a generator of revision deltas.
2175
2165
selected_keys = set((revid,) for revid in revision_ids)
2176
2166
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),
2167
pb = ui.ui_factory.nested_progress_bar()
2169
return self._find_file_ids_from_xml_inventory_lines(
2170
w.iter_lines_added_or_present_in_keys(
2171
selected_keys, pb=pb),
2182
2176
def iter_files_bytes(self, desired_files):
2183
2177
"""Iterate through file versions.
2393
2387
"""single-document based inventory iteration."""
2394
2388
inv_xmls = self._iter_inventory_xmls(revision_ids, ordering)
2395
2389
for text, revision_id in inv_xmls:
2396
yield self._deserialise_inventory(revision_id, text)
2390
yield self.deserialise_inventory(revision_id, text)
2398
2392
def _iter_inventory_xmls(self, revision_ids, ordering):
2399
2393
if ordering is None:
2431
2425
next_key = None
2434
def _deserialise_inventory(self, revision_id, xml):
2428
def deserialise_inventory(self, revision_id, xml):
2435
2429
"""Transform the xml into an inventory object.
2437
2431
:param revision_id: The expected revision id of the inventory.
2445
2439
result.revision_id, revision_id))
2442
def serialise_inventory(self, inv):
2443
return self._serializer.write_inventory_to_string(inv)
2445
def _serialise_inventory_to_lines(self, inv):
2446
return self._serializer.write_inventory_to_lines(inv)
2448
2448
def get_serializer_format(self):
2449
2449
return self._serializer.format_num
2451
2451
@needs_read_lock
2452
def _get_inventory_xml(self, revision_id):
2453
"""Get serialized inventory as a string."""
2452
def get_inventory_xml(self, revision_id):
2453
"""Get inventory XML as a file object."""
2454
2454
texts = self._iter_inventory_xmls([revision_id], 'unordered')
2456
2456
text, revision_id = texts.next()
2458
2458
raise errors.HistoryMissing(self, 'inventory', revision_id)
2462
def get_inventory_sha1(self, revision_id):
2463
"""Return the sha1 hash of the inventory entry
2465
return self.get_revision(revision_id).inventory_sha1
2461
2467
def get_rev_id_for_revno(self, revno, known_pair):
2462
2468
"""Return the revision id of a revno, given a later (revno, revid)
2463
2469
pair in the same history.
2515
2521
next_id = parents[0]
2524
def get_revision_inventory(self, revision_id):
2525
"""Return inventory of a past revision."""
2526
# TODO: Unify this with get_inventory()
2527
# bzr 0.0.6 and later imposes the constraint that the inventory_id
2528
# must be the same as its revision, so this is trivial.
2529
if revision_id is None:
2530
# This does not make sense: if there is no revision,
2531
# then it is the current tree inventory surely ?!
2532
# and thus get_root_id() is something that looks at the last
2533
# commit on the branch, and the get_root_id is an inventory check.
2534
raise NotImplementedError
2535
# return Inventory(self.get_root_id())
2537
return self.get_inventory(revision_id)
2517
2539
def is_shared(self):
2518
2540
"""Return True if this repository is flagged as a shared repository."""
2519
2541
raise NotImplementedError(self.is_shared)
2553
2575
return RevisionTree(self, Inventory(root_id=None),
2554
2576
_mod_revision.NULL_REVISION)
2556
inv = self.get_inventory(revision_id)
2578
inv = self.get_revision_inventory(revision_id)
2557
2579
return RevisionTree(self, inv, revision_id)
2559
2581
def revision_trees(self, revision_ids):
2612
2634
keys = tsort.topo_sort(parent_map)
2613
2635
return [None] + list(keys)
2615
def pack(self, hint=None, clean_obsolete_packs=False):
2637
def pack(self, hint=None):
2616
2638
"""Compress the data within the repository.
2618
2640
This operation only makes sense for some repository types. For other
2628
2650
obtained from the result of commit_write_group(). Out of
2629
2651
date hints are simply ignored, because concurrent operations
2630
2652
can obsolete them rapidly.
2632
:param clean_obsolete_packs: Clean obsolete packs immediately after
2636
2655
def get_transaction(self):
2661
2680
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
2683
def get_graph(self, other_repository=None):
2674
2684
"""Return the graph walker for this repository format"""
2675
2685
parents_provider = self._make_parents_provider()
3074
3084
pack_compresses = False
3075
3085
# Does the repository inventory storage understand references to trees?
3076
3086
supports_tree_reference = None
3077
# Is the format experimental ?
3078
experimental = False
3081
return "%s()" % self.__class__.__name__
3089
return "<%s>" % self.__class__.__name__
3083
3091
def __eq__(self, other):
3084
3092
# format objects are generally stateless
3203
3211
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
3214
class MetaDirRepositoryFormat(RepositoryFormat):
3216
3215
"""Common base class for the new repositories using the metadir layout."""
3422
3421
:param revision_id: if None all content is copied, if NULL_REVISION no
3423
3422
content is copied.
3423
:param pb: optional progress bar to use for progress reports. If not
3424
provided a default one will be created.
3427
ui.ui_factory.warn_experimental_format_fetch(self)
3428
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
f = RepoFetcher(to_repository=self.target,
3427
f = _mod_fetch.RepoFetcher(to_repository=self.target,
3435
3428
from_repository=self.source,
3436
3429
last_revision=revision_id,
3437
3430
fetch_spec=fetch_spec,
3438
find_ghosts=find_ghosts)
3431
pb=pb, find_ghosts=find_ghosts)
3440
3433
def _walk_to_common_revisions(self, revision_ids):
3441
3434
"""Walk out from revision_ids in source to revisions target has.
4015
4008
"""See InterRepository.fetch()."""
4016
4009
if fetch_spec is not None:
4017
4010
raise AssertionError("Not implemented yet...")
4018
ui.ui_factory.warn_experimental_format_fetch(self)
4019
4011
if (not self.source.supports_rich_root()
4020
4012
and self.target.supports_rich_root()):
4021
4013
self._converting_to_rich_root = True
4022
4014
self._revision_id_to_root_id = {}
4024
4016
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
4017
revision_ids = self.target.search_missing_revision_ids(self.source,
4031
4018
revision_id, find_ghosts=find_ghosts).get_keys()
4032
4019
if not revision_ids:
4101
4088
:param to_convert: The disk object to convert.
4102
4089
:param pb: a progress bar to use for progress information.
4104
pb = ui.ui_factory.nested_progress_bar()
4107
4094
# this is only useful with metadir layouts - separated repo content.
4108
4095
# trigger an assertion if not such
4109
4096
repo._format.get_format_string()
4110
4097
self.repo_dir = repo.bzrdir
4111
pb.update('Moving repository to repository.backup')
4098
self.step('Moving repository to repository.backup')
4112
4099
self.repo_dir.transport.move('repository', 'repository.backup')
4113
4100
backup_transport = self.repo_dir.transport.clone('repository.backup')
4114
4101
repo._format.check_conversion_target(self.target_format)
4115
4102
self.source_repo = repo._format.open(self.repo_dir,
4117
4104
_override_transport=backup_transport)
4118
pb.update('Creating new repository')
4105
self.step('Creating new repository')
4119
4106
converted = self.target_format.initialize(self.repo_dir,
4120
4107
self.source_repo.is_shared())
4121
4108
converted.lock_write()
4123
pb.update('Copying content')
4110
self.step('Copying content')
4124
4111
self.source_repo.copy_content_into(converted)
4126
4113
converted.unlock()
4127
pb.update('Deleting old repository content')
4114
self.step('Deleting old repository content')
4128
4115
self.repo_dir.transport.delete_tree('repository.backup')
4129
4116
ui.ui_factory.note('repository converted')
4118
def step(self, message):
4119
"""Update the pb by a step."""
4121
self.pb.update(message, self.count, self.total)
4133
4124
_unescape_map = {
4626
4617
def _get_convertable_inventory_stream(self, revision_ids,
4627
4618
delta_versus_null=False):
4628
# The two formats are sufficiently different that there is no fast
4629
# path, so we need to send just inventorydeltas, which any
4630
# sufficiently modern client can insert into any repository.
4631
# The StreamSink code expects to be able to
4619
# The source is using CHKs, but the target either doesn't or it has a
4620
# different serializer. The StreamSink code expects to be able to
4632
4621
# convert on the target, so we need to put bytes-on-the-wire that can
4633
4622
# be converted. That means inventory deltas (if the remote is <1.19,
4634
4623
# RemoteStreamSink will fallback to VFS to insert the deltas).