97
92
:param revprops: Optional dictionary of revision properties.
98
93
:param revision_id: Optional revision id.
99
94
:param lossy: Whether to discard data that can not be natively
100
represented, when pushing to a foreign VCS
95
represented, when pushing to a foreign VCS
102
97
self._config_stack = config_stack
103
98
self._lossy = lossy
105
100
if committer is None:
106
101
self._committer = self._config_stack.get('email')
107
elif not isinstance(committer, str):
108
self._committer = committer.decode() # throw if non-ascii
102
elif not isinstance(committer, unicode):
103
self._committer = committer.decode() # throw if non-ascii
110
105
self._committer = committer
107
self._new_revision_id = revision_id
112
108
self.parents = parents
113
109
self.repository = repository
142
138
def _validate_unicode_text(self, text, context):
143
139
"""Verify things like commit messages don't have bogus characters."""
144
# TODO(jelmer): Make this repository-format specific
146
141
raise ValueError('Invalid value for %s: %r' % (context, text))
148
143
def _validate_revprops(self, revprops):
149
for key, value in revprops.items():
144
for key, value in viewitems(revprops):
150
145
# We know that the XML serializers do not round trip '\r'
151
146
# correctly, so refuse to accept them
152
if not isinstance(value, str):
147
if not isinstance(value, basestring):
153
148
raise ValueError('revision property (%s) is not a valid'
154
149
' (unicode) string: %r' % (key, value))
155
# TODO(jelmer): Make this repository-format specific
156
150
self._validate_unicode_text(value,
157
151
'revision property (%s)' % (key,))
197
195
:raises: CannotSetRevisionId
199
if not self.repository._format.supports_setting_revision_ids:
200
if revision_id is not None:
201
raise CannotSetRevisionId()
203
if revision_id is None:
197
if self._new_revision_id is None:
204
198
self._new_revision_id = self._gen_revision_id()
205
199
self.random_revid = True
207
self._new_revision_id = revision_id
208
201
self.random_revid = False
203
def will_record_deletes(self):
204
"""Tell the commit builder that deletes are being notified.
206
This enables the accumulation of an inventory delta; for the resulting
207
commit to be valid, deletes against the basis MUST be recorded via
208
builder.record_delete().
210
raise NotImplementedError(self.will_record_deletes)
210
212
def record_iter_changes(self, tree, basis_revision_id, iter_changes):
211
213
"""Record a new tree via iter_changes.
239
241
def __repr__(self):
240
242
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
246
######################################################################
533
510
revisions: The total revision count in the repository.
534
511
size: An estimate disk size of the repository in bytes.
536
with self.lock_read():
538
if revid and committers:
539
result['committers'] = 0
540
if revid and revid != _mod_revision.NULL_REVISION:
541
graph = self.get_graph()
543
all_committers = set()
544
revisions = [r for (r, p) in graph.iter_ancestry([revid])
545
if r != _mod_revision.NULL_REVISION]
548
# ignore the revisions in the middle - just grab first and last
549
revisions = revisions[0], revisions[-1]
550
for revision in self.get_revisions(revisions):
551
if not last_revision:
552
last_revision = revision
554
all_committers.add(revision.committer)
555
first_revision = revision
557
result['committers'] = len(all_committers)
558
result['firstrev'] = (first_revision.timestamp,
559
first_revision.timezone)
560
result['latestrev'] = (last_revision.timestamp,
561
last_revision.timezone)
514
if revid and committers:
515
result['committers'] = 0
516
if revid and revid != _mod_revision.NULL_REVISION:
517
graph = self.get_graph()
519
all_committers = set()
520
revisions = [r for (r, p) in graph.iter_ancestry([revid])
521
if r != _mod_revision.NULL_REVISION]
524
# ignore the revisions in the middle - just grab first and last
525
revisions = revisions[0], revisions[-1]
526
for revision in self.get_revisions(revisions):
527
if not last_revision:
528
last_revision = revision
530
all_committers.add(revision.committer)
531
first_revision = revision
533
result['committers'] = len(all_committers)
534
result['firstrev'] = (first_revision.timestamp,
535
first_revision.timezone)
536
result['latestrev'] = (last_revision.timestamp,
537
last_revision.timezone)
564
540
def find_branches(self, using=False):
565
541
"""Find branches underneath this repository.
592
565
value = (controldir.list_branches(), None)
593
566
return True, value
595
for branches, repository in controldir.ControlDir.find_controldirs(
569
for branches, repository in controldir.ControlDir.find_bzrdirs(
596
570
self.user_transport, evaluate=Evaluator()):
597
571
if branches is not None:
598
for branch in branches:
600
573
if not using and repository is not None:
601
for branch in repository.find_branches():
574
ret.extend(repository.find_branches())
604
578
def search_missing_revision_ids(self, other,
605
find_ghosts=True, revision_ids=None, if_present_ids=None,
579
find_ghosts=True, revision_ids=None, if_present_ids=None,
607
581
"""Return the revision ids that other has that this does not.
609
583
These are returned in topological order.
611
585
revision_ids: only return revision ids included by revision_id.
613
with self.lock_read():
614
return InterRepository.get(other, self).search_missing_revision_ids(
615
find_ghosts=find_ghosts, revision_ids=revision_ids,
616
if_present_ids=if_present_ids, limit=limit)
587
return InterRepository.get(other, self).search_missing_revision_ids(
588
find_ghosts=find_ghosts, revision_ids=revision_ids,
589
if_present_ids=if_present_ids, limit=limit)
716
688
# TODO: lift out to somewhere common with RemoteRepository
717
689
# <https://bugs.launchpad.net/bzr/+bug/401646>
718
690
if (self.has_same_location(source)
719
and self._has_same_fallbacks(source)):
691
and self._has_same_fallbacks(source)):
720
692
# check that last_revision is in 'from' and then return a
722
694
if (revision_id is not None and
723
not _mod_revision.is_null(revision_id)):
695
not _mod_revision.is_null(revision_id)):
724
696
self.get_revision(revision_id)
726
698
inter = InterRepository.get(source, self)
728
revision_id=revision_id, find_ghosts=find_ghosts, lossy=lossy)
699
return inter.fetch(revision_id=revision_id, find_ghosts=find_ghosts)
701
def create_bundle(self, target, base, fileobj, format=None):
702
return serializer.write_bundle(self, target, base, fileobj, format)
730
704
def get_commit_builder(self, branch, parents, config_stack, timestamp=None,
731
705
timezone=None, committer=None, revprops=None,
808
782
def sprout(self, to_bzrdir, revision_id=None):
809
783
"""Create a descendent repository for new development.
811
785
Unlike clone, this does not copy the settings of the repository.
813
with self.lock_read():
814
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
815
dest_repo.fetch(self, revision_id=revision_id)
787
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
788
dest_repo.fetch(self, revision_id=revision_id)
818
def _create_sprouting_repo(self, a_controldir, shared):
820
a_controldir._format, self.controldir._format.__class__):
791
def _create_sprouting_repo(self, a_bzrdir, shared):
792
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
821
793
# use target default format.
822
dest_repo = a_controldir.create_repository()
794
dest_repo = a_bzrdir.create_repository()
824
796
# Most control formats need the repository to be specifically
825
797
# created, but on some old all-in-one formats it's not needed
827
dest_repo = self._format.initialize(
828
a_controldir, shared=shared)
799
dest_repo = self._format.initialize(a_bzrdir, shared=shared)
829
800
except errors.UninitializableFormat:
830
dest_repo = a_controldir.open_repository()
801
dest_repo = a_bzrdir.open_repository()
833
805
def has_revision(self, revision_id):
834
806
"""True if this repository has a copy of the revision."""
835
with self.lock_read():
836
return revision_id in self.has_revisions((revision_id,))
807
return revision_id in self.has_revisions((revision_id,))
838
810
def has_revisions(self, revision_ids):
839
811
"""Probe to find out the presence of multiple revisions.
861
833
def get_revisions(self, revision_ids):
862
834
"""Get many revisions at once.
864
Repositories that need to check data on every revision read should
836
Repositories that need to check data on every revision read should
865
837
subclass this method.
868
for revid, rev in self.iter_revisions(revision_ids):
870
raise errors.NoSuchRevision(self, revid)
872
return [revs[revid] for revid in revision_ids]
874
def iter_revisions(self, revision_ids):
875
"""Iterate over revision objects.
877
:param revision_ids: An iterable of revisions to examine. None may be
878
passed to request all revisions known to the repository. Note that
879
not all repositories can find unreferenced revisions; for those
880
repositories only referenced ones will be returned.
881
:return: An iterator of (revid, revision) tuples. Absent revisions (
882
those asked for but not available) are returned as (revid, None).
883
N.B.: Revisions are not necessarily yielded in order.
885
raise NotImplementedError(self.iter_revisions)
887
def get_revision_delta(self, revision_id):
888
"""Return the delta for one revision.
890
The delta is relative to the left-hand predecessor of the
893
with self.lock_read():
894
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):
839
raise NotImplementedError(self.get_revisions)
841
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
898
842
"""Produce a generator of revision deltas.
900
Note that the input is a sequence of REVISIONS, not revision ids.
844
Note that the input is a sequence of REVISIONS, not revision_ids.
901
845
Trees will be held in memory until the generator exits.
902
846
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
848
:param specific_fileids: if not None, the result is filtered
849
so that only those file-ids, their parents and their
908
850
children are included.
910
from .tree import InterTree
911
852
# Get the revision-ids of interest
912
853
required_trees = set()
913
854
for revision in revisions:
914
855
required_trees.add(revision.revision_id)
915
856
required_trees.update(revision.parent_ids[:1])
918
t.get_revision_id(): t
919
for t in self.revision_trees(required_trees)}
858
# Get the matching filtered trees. Note that it's more
859
# efficient to pass filtered trees to changes_from() rather
860
# than doing the filtering afterwards. changes_from() could
861
# arguably do the filtering itself but it's path-based, not
862
# file-id based, so filtering before or afterwards is
864
if specific_fileids is None:
865
trees = dict((t.get_revision_id(), t) for
866
t in self.revision_trees(required_trees))
868
trees = dict((t.get_revision_id(), t) for
869
t in self._filtered_revision_trees(required_trees,
921
872
# Calculate the deltas
922
873
for revision in revisions:
924
875
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
926
877
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()
878
yield trees[revision.revision_id].changes_from(old_tree)
881
def get_revision_delta(self, revision_id, specific_fileids=None):
882
"""Return the delta for one revision.
884
The delta is relative to the left-hand predecessor of the
887
:param specific_fileids: if not None, the result is filtered
888
so that only those file-ids, their parents and their
889
children are included.
891
r = self.get_revision(revision_id)
892
return list(self.get_deltas_for_revisions([r],
893
specific_fileids=specific_fileids))[0]
935
896
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
936
raise NotImplementedError(self.store_revision_signature)
897
signature = gpg_strategy.sign(plaintext)
898
self.add_signature_text(revision_id, signature)
938
900
def add_signature_text(self, revision_id, signature):
939
901
"""Store a signature text for a revision.
971
933
partial_history = [known_revid]
972
934
distance_from_known = known_revno - revno
973
935
if distance_from_known < 0:
974
raise errors.RevnoOutOfBounds(revno, (0, known_revno))
937
'requested revno (%d) is later than given known revno (%d)'
938
% (revno, known_revno))
977
941
self, partial_history, stop_index=distance_from_known)
978
942
except errors.RevisionNotPresent as err:
979
943
if err.revision_id == known_revid:
980
944
# The start revision (known_revid) wasn't found.
981
raise errors.NoSuchRevision(self, known_revid)
982
946
# This is a stacked repository with no fallbacks, or a there's a
983
947
# left-hand ghost. Either way, even though the revision named in
984
948
# the error isn't in this repo, we know it's the next step in this
1069
1038
elif revision_id is None:
1070
1039
raise ValueError('get_parent_map(None) is not valid')
1072
query_keys.append((revision_id,))
1041
query_keys.append((revision_id ,))
1073
1042
vf = self.revisions.without_fallbacks()
1074
for (revision_id,), parent_keys in (
1075
vf.get_parent_map(query_keys).items()):
1043
for (revision_id,), parent_keys in viewitems(
1044
vf.get_parent_map(query_keys)):
1076
1045
if parent_keys:
1077
1046
result[revision_id] = tuple([parent_revid
1078
for (parent_revid,) in parent_keys])
1047
for (parent_revid,) in parent_keys])
1080
1049
result[revision_id] = (_mod_revision.NULL_REVISION,)
1416
1387
# formats which have no format string are not discoverable or independently
1417
1388
# creatable on disk, so are not registered in format_registry. They're
1418
# all in breezy.bzr.knitreponow. When an instance of one of these is
1389
# all in breezy.repofmt.knitreponow. When an instance of one of these is
1419
1390
# needed, it's constructed directly by the ControlDir. Non-native formats where
1420
1391
# the repository is not separately opened are similar.
1422
1393
format_registry.register_lazy(
1423
b'Bazaar-NG Knit Repository Format 1',
1424
'breezy.bzr.knitrepo',
1394
'Bazaar-NG Knit Repository Format 1',
1395
'breezy.repofmt.knitrepo',
1425
1396
'RepositoryFormatKnit1',
1428
1399
format_registry.register_lazy(
1429
b'Bazaar Knit Repository Format 3 (bzr 0.15)\n',
1430
'breezy.bzr.knitrepo',
1400
'Bazaar Knit Repository Format 3 (bzr 0.15)\n',
1401
'breezy.repofmt.knitrepo',
1431
1402
'RepositoryFormatKnit3',
1434
1405
format_registry.register_lazy(
1435
b'Bazaar Knit Repository Format 4 (bzr 1.0)\n',
1436
'breezy.bzr.knitrepo',
1406
'Bazaar Knit Repository Format 4 (bzr 1.0)\n',
1407
'breezy.repofmt.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',
1445
'breezy.bzr.knitpack_repo',
1415
'Bazaar pack repository format 1 (needs bzr 0.92)\n',
1416
'breezy.repofmt.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',
1450
'breezy.bzr.knitpack_repo',
1420
'Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n',
1421
'breezy.repofmt.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',
1455
'breezy.bzr.knitpack_repo',
1425
'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
1426
'breezy.repofmt.knitpack_repo',
1456
1427
'RepositoryFormatKnitPack4',
1458
1429
format_registry.register_lazy(
1459
b'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
1460
'breezy.bzr.knitpack_repo',
1430
'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
1431
'breezy.repofmt.knitpack_repo',
1461
1432
'RepositoryFormatKnitPack5',
1463
1434
format_registry.register_lazy(
1464
b'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
1465
'breezy.bzr.knitpack_repo',
1435
'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
1436
'breezy.repofmt.knitpack_repo',
1466
1437
'RepositoryFormatKnitPack5RichRoot',
1468
1439
format_registry.register_lazy(
1469
b'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
1470
'breezy.bzr.knitpack_repo',
1440
'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
1441
'breezy.repofmt.knitpack_repo',
1471
1442
'RepositoryFormatKnitPack5RichRootBroken',
1473
1444
format_registry.register_lazy(
1474
b'Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n',
1475
'breezy.bzr.knitpack_repo',
1445
'Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n',
1446
'breezy.repofmt.knitpack_repo',
1476
1447
'RepositoryFormatKnitPack6',
1478
1449
format_registry.register_lazy(
1479
b'Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n',
1480
'breezy.bzr.knitpack_repo',
1450
'Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n',
1451
'breezy.repofmt.knitpack_repo',
1481
1452
'RepositoryFormatKnitPack6RichRoot',
1483
1454
format_registry.register_lazy(
1484
b'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
1485
'breezy.bzr.groupcompress_repo',
1455
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
1456
'breezy.repofmt.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"),
1494
'breezy.bzr.knitpack_repo',
1463
("Bazaar development format 2 with subtree support "
1464
"(needs bzr.dev from before 1.8)\n"),
1465
'breezy.repofmt.knitpack_repo',
1495
1466
'RepositoryFormatPackDevelopment2Subtree',
1497
1468
format_registry.register_lazy(
1498
b'Bazaar development format 8\n',
1499
'breezy.bzr.groupcompress_repo',
1469
'Bazaar development format 8\n',
1470
'breezy.repofmt.groupcompress_repo',
1500
1471
'RepositoryFormat2aSubtree',
1525
1497
:param revision_id: Only copy the content needed to construct
1526
1498
revision_id and its parents.
1528
with self.lock_write():
1530
self.target.set_make_working_trees(
1531
self.source.make_working_trees())
1532
except (NotImplementedError, errors.RepositoryUpgradeRequired):
1534
self.target.fetch(self.source, revision_id=revision_id)
1501
self.target.set_make_working_trees(
1502
self.source.make_working_trees())
1503
except NotImplementedError:
1505
self.target.fetch(self.source, revision_id=revision_id)
1536
def fetch(self, revision_id=None, find_ghosts=False, lossy=False):
1508
def fetch(self, revision_id=None, find_ghosts=False):
1537
1509
"""Fetch the content required to construct revision_id.
1539
1511
The content is copied from self.source to self.target.
1541
1513
:param revision_id: if None all content is copied, if NULL_REVISION no
1542
1514
content is copied.
1543
:return: FetchResult
1545
1517
raise NotImplementedError(self.fetch)
1547
1520
def search_missing_revision_ids(
1548
1521
self, find_ghosts=True, revision_ids=None, if_present_ids=None,
1608
1581
:param to_convert: The disk object to convert.
1609
1582
: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'))
1584
pb = ui.ui_factory.nested_progress_bar()
1587
# this is only useful with metadir layouts - separated repo content.
1588
# trigger an assertion if not such
1589
repo._format.get_format_string()
1590
self.repo_dir = repo.bzrdir
1591
pb.update(gettext('Moving repository to repository.backup'))
1592
self.repo_dir.transport.move('repository', 'repository.backup')
1593
backup_transport = self.repo_dir.transport.clone('repository.backup')
1594
repo._format.check_conversion_target(self.target_format)
1595
self.source_repo = repo._format.open(self.repo_dir,
1597
_override_transport=backup_transport)
1598
pb.update(gettext('Creating new repository'))
1599
converted = self.target_format.initialize(self.repo_dir,
1600
self.source_repo.is_shared())
1601
converted.lock_write()
1603
pb.update(gettext('Copying content'))
1604
self.source_repo.copy_content_into(converted)
1607
pb.update(gettext('Deleting old repository content'))
1608
self.repo_dir.transport.delete_tree('repository.backup')
1609
ui.ui_factory.note(gettext('repository converted'))
1637
1613
def _strip_NULL_ghosts(revision_graph):