14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
17
19
from .lazy_import import lazy_import
18
20
lazy_import(globals(), """
21
24
from breezy import (
27
31
revision as _mod_revision,
32
testament as _mod_testament,
35
from breezy.bundle import serializer
30
36
from breezy.i18n import gettext
59
70
_fmt = "Repository format does not support setting revision ids."
62
class FetchResult(object):
63
"""Result of a fetch operation.
65
:ivar revidmap: For lossy fetches, map from source revid to target revid.
66
:ivar total_fetched: Number of revisions fetched
69
def __init__(self, total_fetched=None, revidmap=None):
70
self.total_fetched = total_fetched
71
self.revidmap = revidmap
74
73
class CommitBuilder(object):
75
74
"""Provides an interface to build up a commit.
142
141
def _validate_unicode_text(self, text, context):
143
142
"""Verify things like commit messages don't have bogus characters."""
144
# TODO(jelmer): Make this repository-format specific
146
144
raise ValueError('Invalid value for %s: %r' % (context, text))
148
146
def _validate_revprops(self, revprops):
149
for key, value in revprops.items():
147
for key, value in viewitems(revprops):
150
148
# We know that the XML serializers do not round trip '\r'
151
149
# correctly, so refuse to accept them
152
if not isinstance(value, str):
150
if not isinstance(value, (text_type, str)):
153
151
raise ValueError('revision property (%s) is not a valid'
154
152
' (unicode) string: %r' % (key, value))
155
# TODO(jelmer): Make this repository-format specific
156
153
self._validate_unicode_text(value,
157
154
'revision property (%s)' % (key,))
239
240
def __repr__(self):
240
241
return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
244
class WriteGroup(object):
245
"""Context manager that manages a write group.
247
Raising an exception will result in the write group being aborted.
250
def __init__(self, repository, suppress_errors=False):
251
self.repository = repository
252
self._suppress_errors = suppress_errors
255
self.repository.start_write_group()
258
def __exit__(self, exc_type, exc_val, exc_tb):
260
self.repository.abort_write_group(self._suppress_errors)
263
self.repository.commit_write_group()
266
245
######################################################################
295
270
# has an unlock or relock occured ?
296
271
if suppress_errors:
298
'(suppressed) mismatched lock context and write group. %r, %r',
299
self._write_group, self.get_transaction())
273
'(suppressed) mismatched lock context and write group. %r, %r',
274
self._write_group, self.get_transaction())
301
276
raise errors.BzrError(
302
277
'mismatched lock context and write group. %r, %r' %
592
564
value = (controldir.list_branches(), None)
593
565
return True, value
595
568
for branches, repository in controldir.ControlDir.find_controldirs(
596
569
self.user_transport, evaluate=Evaluator()):
597
570
if branches is not None:
598
for branch in branches:
600
572
if not using and repository is not None:
601
for branch in repository.find_branches():
573
ret.extend(repository.find_branches())
604
576
def search_missing_revision_ids(self, other,
605
find_ghosts=True, revision_ids=None, if_present_ids=None,
577
find_ghosts=True, revision_ids=None, if_present_ids=None,
607
579
"""Return the revision ids that other has that this does not.
609
581
These are returned in topological order.
637
609
"""Commit the contents accrued within the current write group.
639
611
:seealso: start_write_group.
641
613
:return: it may return an opaque hint that can be passed to 'pack'.
643
615
if self._write_group is not self.get_transaction():
644
616
# has an unlock or relock occured ?
645
617
raise errors.BzrError('mismatched lock context %r and '
647
(self.get_transaction(), self._write_group))
619
(self.get_transaction(), self._write_group))
648
620
result = self._commit_write_group()
649
621
self._write_group = None
692
664
def _resume_write_group(self, tokens):
693
665
raise errors.UnsuspendableWriteGroup(self)
695
def fetch(self, source, revision_id=None, find_ghosts=False, lossy=False):
667
def fetch(self, source, revision_id=None, find_ghosts=False):
696
668
"""Fetch the content required to construct revision_id from source.
698
670
If revision_id is None, then all content is copied.
716
687
# TODO: lift out to somewhere common with RemoteRepository
717
688
# <https://bugs.launchpad.net/bzr/+bug/401646>
718
689
if (self.has_same_location(source)
719
and self._has_same_fallbacks(source)):
690
and self._has_same_fallbacks(source)):
720
691
# check that last_revision is in 'from' and then return a
722
693
if (revision_id is not None and
723
not _mod_revision.is_null(revision_id)):
694
not _mod_revision.is_null(revision_id)):
724
695
self.get_revision(revision_id)
726
697
inter = InterRepository.get(source, self)
728
revision_id=revision_id, find_ghosts=find_ghosts, lossy=lossy)
698
return inter.fetch(revision_id=revision_id, find_ghosts=find_ghosts)
700
def create_bundle(self, target, base, fileobj, format=None):
701
return serializer.write_bundle(self, target, base, fileobj, format)
730
703
def get_commit_builder(self, branch, parents, config_stack, timestamp=None,
731
704
timezone=None, committer=None, revprops=None,
885
858
raise NotImplementedError(self.iter_revisions)
887
def get_revision_delta(self, revision_id):
860
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
861
"""Produce a generator of revision deltas.
863
Note that the input is a sequence of REVISIONS, not revision_ids.
864
Trees will be held in memory until the generator exits.
865
Each delta is relative to the revision's lefthand predecessor.
867
:param specific_fileids: if not None, the result is filtered
868
so that only those file-ids, their parents and their
869
children are included.
871
raise NotImplementedError(self.get_deltas_for_revisions)
873
def get_revision_delta(self, revision_id, specific_fileids=None):
888
874
"""Return the delta for one revision.
890
876
The delta is relative to the left-hand predecessor of the
879
:param specific_fileids: if not None, the result is filtered
880
so that only those file-ids, their parents and their
881
children are included.
893
883
with self.lock_read():
894
884
r = self.get_revision(revision_id)
895
return list(self.get_revision_deltas([r]))[0]
897
def get_revision_deltas(self, revisions, specific_files=None):
898
"""Produce a generator of revision deltas.
900
Note that the input is a sequence of REVISIONS, not revision ids.
901
Trees will be held in memory until the generator exits.
902
Each delta is relative to the revision's lefthand predecessor.
904
specific_files should exist in the first revision.
906
:param specific_files: if not None, the result is filtered
907
so that only those files, their parents and their
908
children are included.
910
from .tree import InterTree
911
# Get the revision-ids of interest
912
required_trees = set()
913
for revision in revisions:
914
required_trees.add(revision.revision_id)
915
required_trees.update(revision.parent_ids[:1])
918
t.get_revision_id(): t
919
for t in self.revision_trees(required_trees)}
921
# Calculate the deltas
922
for revision in revisions:
923
if not revision.parent_ids:
924
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
926
old_tree = trees[revision.parent_ids[0]]
927
intertree = InterTree.get(old_tree, trees[revision.revision_id])
928
yield intertree.compare(specific_files=specific_files)
929
if specific_files is not None:
931
p for p in intertree.find_source_paths(
932
specific_files).values()
885
return list(self.get_deltas_for_revisions(
886
[r], specific_fileids=specific_fileids))[0]
935
888
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
936
raise NotImplementedError(self.store_revision_signature)
889
with self.lock_write():
890
signature = gpg_strategy.sign(plaintext)
891
self.add_signature_text(revision_id, signature)
938
893
def add_signature_text(self, revision_id, signature):
939
894
"""Store a signature text for a revision.
971
926
partial_history = [known_revid]
972
927
distance_from_known = known_revno - revno
973
928
if distance_from_known < 0:
974
raise errors.RevnoOutOfBounds(revno, (0, known_revno))
930
'requested revno (%d) is later than given known revno (%d)'
931
% (revno, known_revno))
977
934
self, partial_history, stop_index=distance_from_known)
978
935
except errors.RevisionNotPresent as err:
979
936
if err.revision_id == known_revid:
980
937
# The start revision (known_revid) wasn't found.
981
raise errors.NoSuchRevision(self, known_revid)
982
939
# This is a stacked repository with no fallbacks, or a there's a
983
940
# left-hand ghost. Either way, even though the revision named in
984
941
# the error isn't in this repo, we know it's the next step in this
1072
1033
query_keys.append((revision_id,))
1073
1034
vf = self.revisions.without_fallbacks()
1074
for (revision_id,), parent_keys in (
1075
vf.get_parent_map(query_keys).items()):
1035
for (revision_id,), parent_keys in viewitems(
1036
vf.get_parent_map(query_keys)):
1076
1037
if parent_keys:
1077
1038
result[revision_id] = tuple([parent_revid
1078
for (parent_revid,) in parent_keys])
1039
for (parent_revid,) in parent_keys])
1080
1041
result[revision_id] = (_mod_revision.NULL_REVISION,)
1125
1086
raise NotImplementedError(self.make_working_trees)
1127
1088
def sign_revision(self, revision_id, gpg_strategy):
1128
raise NotImplementedError(self.sign_revision)
1089
with self.lock_write():
1090
testament = _mod_testament.Testament.from_revision(self, revision_id)
1091
plaintext = testament.as_short_text()
1092
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1130
1094
def verify_revision_signature(self, revision_id, gpg_strategy):
1131
1095
"""Verify the signature on a revision.
1136
1100
:return: gpg.SIGNATURE_VALID or a failed SIGNATURE_ value
1138
raise NotImplementedError(self.verify_revision_signature)
1102
with self.lock_read():
1103
if not self.has_signature_for_revision_id(revision_id):
1104
return gpg.SIGNATURE_NOT_SIGNED, None
1105
signature = self.get_signature_text(revision_id)
1107
testament = _mod_testament.Testament.from_revision(
1109
plaintext = testament.as_short_text()
1111
return gpg_strategy.verify(signature, plaintext)
1140
1113
def verify_revision_signatures(self, revision_ids, gpg_strategy):
1141
1114
"""Verify revision signatures for a number of revisions.
1167
1140
:param callback_refs: A dict of check-refs to resolve and callback
1168
1141
the check/_check method on the items listed as wanting the ref.
1169
1142
see breezy.check.
1170
:param check_repo: If False do not check the repository contents, just
1143
:param check_repo: If False do not check the repository contents, just
1171
1144
calculate the data callback_refs requires and call them back.
1173
1146
return self._check(revision_ids=revision_ids, callback_refs=callback_refs,
1174
check_repo=check_repo)
1147
check_repo=check_repo)
1176
1149
def _check(self, revision_ids=None, callback_refs=None, check_repo=True):
1177
1150
raise NotImplementedError(self.check)
1390
1361
raise errors.BadConversionTarget(
1391
1362
'Does not support rich root data.', target_format,
1392
1363
from_format=self)
1393
if (self.supports_tree_reference
1394
and not getattr(target_format, 'supports_tree_reference', False)):
1364
if (self.supports_tree_reference and
1365
not getattr(target_format, 'supports_tree_reference', False)):
1395
1366
raise errors.BadConversionTarget(
1396
1367
'Does not support nested trees', target_format,
1397
1368
from_format=self)
1420
1391
# the repository is not separately opened are similar.
1422
1393
format_registry.register_lazy(
1423
b'Bazaar-NG Knit Repository Format 1',
1394
'Bazaar-NG Knit Repository Format 1',
1424
1395
'breezy.bzr.knitrepo',
1425
1396
'RepositoryFormatKnit1',
1428
1399
format_registry.register_lazy(
1429
b'Bazaar Knit Repository Format 3 (bzr 0.15)\n',
1400
'Bazaar Knit Repository Format 3 (bzr 0.15)\n',
1430
1401
'breezy.bzr.knitrepo',
1431
1402
'RepositoryFormatKnit3',
1434
1405
format_registry.register_lazy(
1435
b'Bazaar Knit Repository Format 4 (bzr 1.0)\n',
1406
'Bazaar Knit Repository Format 4 (bzr 1.0)\n',
1436
1407
'breezy.bzr.knitrepo',
1437
1408
'RepositoryFormatKnit4',
1441
1412
# post-subtrees to allow ease of testing.
1442
1413
# NOTE: These are experimental in 0.92. Stable in 1.0 and above
1443
1414
format_registry.register_lazy(
1444
b'Bazaar pack repository format 1 (needs bzr 0.92)\n',
1415
'Bazaar pack repository format 1 (needs bzr 0.92)\n',
1445
1416
'breezy.bzr.knitpack_repo',
1446
1417
'RepositoryFormatKnitPack1',
1448
1419
format_registry.register_lazy(
1449
b'Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n',
1420
'Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n',
1450
1421
'breezy.bzr.knitpack_repo',
1451
1422
'RepositoryFormatKnitPack3',
1453
1424
format_registry.register_lazy(
1454
b'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
1425
'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
1455
1426
'breezy.bzr.knitpack_repo',
1456
1427
'RepositoryFormatKnitPack4',
1458
1429
format_registry.register_lazy(
1459
b'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
1430
'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
1460
1431
'breezy.bzr.knitpack_repo',
1461
1432
'RepositoryFormatKnitPack5',
1463
1434
format_registry.register_lazy(
1464
b'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
1435
'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
1465
1436
'breezy.bzr.knitpack_repo',
1466
1437
'RepositoryFormatKnitPack5RichRoot',
1468
1439
format_registry.register_lazy(
1469
b'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
1440
'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
1470
1441
'breezy.bzr.knitpack_repo',
1471
1442
'RepositoryFormatKnitPack5RichRootBroken',
1473
1444
format_registry.register_lazy(
1474
b'Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n',
1445
'Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n',
1475
1446
'breezy.bzr.knitpack_repo',
1476
1447
'RepositoryFormatKnitPack6',
1478
1449
format_registry.register_lazy(
1479
b'Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n',
1450
'Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n',
1480
1451
'breezy.bzr.knitpack_repo',
1481
1452
'RepositoryFormatKnitPack6RichRoot',
1483
1454
format_registry.register_lazy(
1484
b'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
1455
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
1485
1456
'breezy.bzr.groupcompress_repo',
1486
1457
'RepositoryFormat2a',
1489
1460
# Development formats.
1490
1461
# Check their docstrings to see if/when they are obsolete.
1491
1462
format_registry.register_lazy(
1492
(b"Bazaar development format 2 with subtree support "
1493
b"(needs bzr.dev from before 1.8)\n"),
1463
("Bazaar development format 2 with subtree support "
1464
"(needs bzr.dev from before 1.8)\n"),
1494
1465
'breezy.bzr.knitpack_repo',
1495
1466
'RepositoryFormatPackDevelopment2Subtree',
1497
1468
format_registry.register_lazy(
1498
b'Bazaar development format 8\n',
1469
'Bazaar development format 8\n',
1499
1470
'breezy.bzr.groupcompress_repo',
1500
1471
'RepositoryFormat2aSubtree',
1530
1501
self.target.set_make_working_trees(
1531
1502
self.source.make_working_trees())
1532
except (NotImplementedError, errors.RepositoryUpgradeRequired):
1503
except NotImplementedError:
1534
1505
self.target.fetch(self.source, revision_id=revision_id)
1536
def fetch(self, revision_id=None, find_ghosts=False, lossy=False):
1507
def fetch(self, revision_id=None, find_ghosts=False):
1537
1508
"""Fetch the content required to construct revision_id.
1539
1510
The content is copied from self.source to self.target.
1541
1512
:param revision_id: if None all content is copied, if NULL_REVISION no
1542
1513
content is copied.
1543
:return: FetchResult
1545
1516
raise NotImplementedError(self.fetch)
1608
1579
:param to_convert: The disk object to convert.
1609
1580
:param pb: a progress bar to use for progress information.
1611
with ui.ui_factory.nested_progress_bar() as pb:
1614
# this is only useful with metadir layouts - separated repo content.
1615
# trigger an assertion if not such
1616
repo._format.get_format_string()
1617
self.repo_dir = repo.controldir
1618
pb.update(gettext('Moving repository to repository.backup'))
1619
self.repo_dir.transport.move('repository', 'repository.backup')
1620
backup_transport = self.repo_dir.transport.clone(
1621
'repository.backup')
1622
repo._format.check_conversion_target(self.target_format)
1623
self.source_repo = repo._format.open(self.repo_dir,
1625
_override_transport=backup_transport)
1626
pb.update(gettext('Creating new repository'))
1627
converted = self.target_format.initialize(self.repo_dir,
1628
self.source_repo.is_shared())
1629
with converted.lock_write():
1630
pb.update(gettext('Copying content'))
1631
self.source_repo.copy_content_into(converted)
1632
pb.update(gettext('Deleting old repository content'))
1633
self.repo_dir.transport.delete_tree('repository.backup')
1634
ui.ui_factory.note(gettext('repository converted'))
1582
pb = ui.ui_factory.nested_progress_bar()
1585
# this is only useful with metadir layouts - separated repo content.
1586
# trigger an assertion if not such
1587
repo._format.get_format_string()
1588
self.repo_dir = repo.controldir
1589
pb.update(gettext('Moving repository to repository.backup'))
1590
self.repo_dir.transport.move('repository', 'repository.backup')
1591
backup_transport = self.repo_dir.transport.clone('repository.backup')
1592
repo._format.check_conversion_target(self.target_format)
1593
self.source_repo = repo._format.open(self.repo_dir,
1595
_override_transport=backup_transport)
1596
pb.update(gettext('Creating new repository'))
1597
converted = self.target_format.initialize(self.repo_dir,
1598
self.source_repo.is_shared())
1599
converted.lock_write()
1601
pb.update(gettext('Copying content'))
1602
self.source_repo.copy_content_into(converted)
1605
pb.update(gettext('Deleting old repository content'))
1606
self.repo_dir.transport.delete_tree('repository.backup')
1607
ui.ui_factory.note(gettext('repository converted'))
1637
1611
def _strip_NULL_ghosts(revision_graph):
1662
1636
start_revision = partial_history_cache[-1]
1663
1637
graph = repo.get_graph()
1664
1638
iterator = graph.iter_lefthand_ancestry(start_revision,
1665
(_mod_revision.NULL_REVISION,))
1639
(_mod_revision.NULL_REVISION,))
1667
1641
# skip the last revision in the list
1670
1644
if (stop_index is not None and
1671
len(partial_history_cache) > stop_index):
1645
len(partial_history_cache) > stop_index):
1673
1647
if partial_history_cache[-1] == stop_revision: