14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
from cStringIO import StringIO
19
from bzrlib.lazy_import import lazy_import
20
lazy_import(globals(), """
21
17
from binascii import hexlify
22
18
from copy import deepcopy
19
from cStringIO import StringIO
22
from unittest import TestSuite
38
revision as _mod_revision,
47
from bzrlib.osutils import (
24
from bzrlib import bzrdir, check, delta, gpg, errors, xml5, ui, transactions, osutils
25
from bzrlib.decorators import needs_read_lock, needs_write_lock
26
from bzrlib.errors import InvalidRevisionId
27
from bzrlib.graph import Graph
28
from bzrlib.inter import InterObject
29
from bzrlib.inventory import Inventory
30
from bzrlib.knit import KnitVersionedFile, KnitPlainFactory
31
from bzrlib.lockable_files import LockableFiles, TransportLock
32
from bzrlib.lockdir import LockDir
33
from bzrlib.osutils import (safe_unicode, rand_bytes, compact_date,
35
from bzrlib.revision import NULL_REVISION, Revision
52
36
from bzrlib.revisiontree import RevisionTree
53
from bzrlib.store.versioned import VersionedFileStore
37
from bzrlib.store.versioned import VersionedFileStore, WeaveStore
54
38
from bzrlib.store.text import TextStore
39
from bzrlib.symbol_versioning import (deprecated_method,
55
42
from bzrlib.testament import Testament
58
from bzrlib.decorators import needs_read_lock, needs_write_lock
59
from bzrlib.inter import InterObject
60
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
61
from bzrlib.symbol_versioning import (
65
43
from bzrlib.trace import mutter, note, warning
44
from bzrlib.tsort import topo_sort
45
from bzrlib.weave import WeaveFile
68
48
# Old formats display a warning, but only once
282
260
:param revprops: Optional dictionary of revision properties.
283
261
:param revision_id: Optional revision id.
285
return _CommitBuilder(self, parents, config, timestamp, timezone,
286
committer, revprops, revision_id)
263
return CommitBuilder(self, parents, config, timestamp, timezone,
264
committer, revprops, revision_id)
288
266
def unlock(self):
289
267
self.control_files.unlock()
435
415
# revisions. We don't need to see all lines in the inventory because
436
416
# only those added in an inventory in rev X can contain a revision=X
438
pb = ui.ui_factory.nested_progress_bar()
440
for line in w.iter_lines_added_or_present_in_versions(
441
selected_revision_ids, pb=pb):
442
start = line.find('file_id="')+9
443
if start < 9: continue
444
end = line.find('"', start)
446
file_id = _unescape_xml(line[start:end])
418
for line in w.iter_lines_added_or_present_in_versions(selected_revision_ids):
419
start = line.find('file_id="')+9
420
if start < 9: continue
421
end = line.find('"', start)
423
file_id = _unescape_xml(line[start:end])
448
start = line.find('revision="')+10
449
if start < 10: continue
450
end = line.find('"', start)
452
revision_id = _unescape_xml(line[start:end])
453
if revision_id in selected_revision_ids:
454
result.setdefault(file_id, set()).add(revision_id)
425
start = line.find('revision="')+10
426
if start < 10: continue
427
end = line.find('"', start)
429
revision_id = _unescape_xml(line[start:end])
430
if revision_id in selected_revision_ids:
431
result.setdefault(file_id, set()).add(revision_id)
605
574
# TODO: refactor this to use an existing revision object
606
575
# so we don't need to read it in twice.
607
if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
608
return RevisionTree(self, Inventory(root_id=None),
609
_mod_revision.NULL_REVISION)
576
if revision_id is None or revision_id == NULL_REVISION:
577
return RevisionTree(self, Inventory(), NULL_REVISION)
611
579
inv = self.get_revision_inventory(revision_id)
612
580
return RevisionTree(self, inv, revision_id)
796
761
parent_trees[p_id] = repository.revision_tree(None)
798
763
inv = revision_tree.inventory
765
# backwards compatability hack: skip the root id.
799
766
entries = inv.iter_entries()
800
# backwards compatability hack: skip the root id.
801
if not repository.supports_rich_root():
802
path, root = entries.next()
803
if root.revision != rev.revision_id:
804
raise errors.IncompatibleRevision(repr(repository))
805
768
# Add the texts that are not already present
806
769
for path, ie in entries:
807
770
w = repository.weave_store.get_weave_or_empty(ie.file_id,
944
907
:return: a dictionary of revision_id->revision_parents_list.
946
909
# special case NULL_REVISION
947
if revision_id == _mod_revision.NULL_REVISION:
910
if revision_id == NULL_REVISION:
949
a_weave = self._get_revision_vf()
950
entire_graph = a_weave.get_graph()
912
weave = self._get_revision_vf()
913
entire_graph = weave.get_graph()
951
914
if revision_id is None:
952
return a_weave.get_graph()
953
elif revision_id not in a_weave:
915
return weave.get_graph()
916
elif revision_id not in weave:
954
917
raise errors.NoSuchRevision(self, revision_id)
956
919
# add what can be reached from revision_id
1022
985
return self._get_revision_vf().get_parents(revision_id)
1025
class KnitRepository2(KnitRepository):
1027
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
1028
control_store, text_store):
1029
KnitRepository.__init__(self, _format, a_bzrdir, control_files,
1030
_revision_store, control_store, text_store)
1031
self._serializer = xml6.serializer_v6
1033
def deserialise_inventory(self, revision_id, xml):
1034
"""Transform the xml into an inventory object.
1036
:param revision_id: The expected revision id of the inventory.
1037
:param xml: A serialised inventory.
1039
result = self._serializer.read_inventory_from_string(xml)
1040
assert result.root.revision is not None
1043
def serialise_inventory(self, inv):
1044
"""Transform the inventory object into XML text.
1046
:param revision_id: The expected revision id of the inventory.
1047
:param xml: A serialised inventory.
1049
assert inv.revision_id is not None
1050
assert inv.root.revision is not None
1051
return KnitRepository.serialise_inventory(self, inv)
1053
def get_commit_builder(self, branch, parents, config, timestamp=None,
1054
timezone=None, committer=None, revprops=None,
1056
"""Obtain a CommitBuilder for this repository.
1058
:param branch: Branch to commit to.
1059
:param parents: Revision ids of the parents of the new revision.
1060
:param config: Configuration to use.
1061
:param timestamp: Optional timestamp recorded for commit.
1062
:param timezone: Optional timezone for timestamp.
1063
:param committer: Optional committer to set for commit.
1064
:param revprops: Optional dictionary of revision properties.
1065
:param revision_id: Optional revision id.
1067
return RootCommitBuilder(self, parents, config, timestamp, timezone,
1068
committer, revprops, revision_id)
1071
988
class RepositoryFormat(object):
1072
989
"""A repository format.
1225
1137
class PreSplitOutRepositoryFormat(RepositoryFormat):
1226
1138
"""Base class for the pre split out repository formats."""
1228
rich_root_data = False
1230
1140
def initialize(self, a_bzrdir, shared=False, _internal=False):
1231
1141
"""Create a weave repository.
1233
1143
TODO: when creating split out bzr branch formats, move this to a common
1234
1144
base for Format5, Format6. or something like that.
1146
from bzrlib.weavefile import write_weave_v5
1147
from bzrlib.weave import Weave
1237
1150
raise errors.IncompatibleFormat(self, a_bzrdir._format)
1557
1465
repo_transport,
1558
1466
prefixed=False,
1559
1467
file_mode=control_files._file_mode,
1560
versionedfile_class=knit.KnitVersionedFile,
1561
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
1468
versionedfile_class=KnitVersionedFile,
1469
versionedfile_kwargs={'factory':KnitPlainFactory()},
1472
def get_format_string(self):
1473
"""See RepositoryFormat.get_format_string()."""
1474
return "Bazaar-NG Knit Repository Format 1"
1476
def get_format_description(self):
1477
"""See RepositoryFormat.get_format_description()."""
1478
return "Knit repository format 1"
1564
1480
def _get_revision_store(self, repo_transport, control_files):
1565
1481
"""See RepositoryFormat._get_revision_store()."""
1566
1482
from bzrlib.store.revision.knit import KnitRevisionStore
1580
1494
def _get_text_store(self, transport, control_files):
1581
1495
"""See RepositoryFormat._get_text_store()."""
1582
1496
return self._get_versioned_file_store('knits',
1585
versionedfile_class=knit.KnitVersionedFile,
1586
versionedfile_kwargs={
1587
'create_parent_dir':True,
1588
'delay_create':True,
1589
'dir_mode':control_files._dir_mode,
1499
versionedfile_class=KnitVersionedFile,
1593
1502
def initialize(self, a_bzrdir, shared=False):
1594
1503
"""Create a knit format 1 repository.
1643
1550
text_store=text_store)
1646
class RepositoryFormatKnit1(RepositoryFormatKnit):
1647
"""Bzr repository knit format 1.
1649
This repository format has:
1650
- knits for file texts and inventory
1651
- hash subdirectory based stores.
1652
- knits for revisions and signatures
1653
- TextStores for revisions and signatures.
1654
- a format marker of its own
1655
- an optional 'shared-storage' flag
1656
- an optional 'no-working-trees' flag
1659
This format was introduced in bzr 0.8.
1661
def get_format_string(self):
1662
"""See RepositoryFormat.get_format_string()."""
1663
return "Bazaar-NG Knit Repository Format 1"
1665
def get_format_description(self):
1666
"""See RepositoryFormat.get_format_description()."""
1667
return "Knit repository format 1"
1669
def check_conversion_target(self, target_format):
1673
class RepositoryFormatKnit2(RepositoryFormatKnit):
1674
"""Bzr repository knit format 2.
1676
THIS FORMAT IS EXPERIMENTAL
1677
This repository format has:
1678
- knits for file texts and inventory
1679
- hash subdirectory based stores.
1680
- knits for revisions and signatures
1681
- TextStores for revisions and signatures.
1682
- a format marker of its own
1683
- an optional 'shared-storage' flag
1684
- an optional 'no-working-trees' flag
1686
- Support for recording full info about the tree root
1690
rich_root_data = True
1692
def get_format_string(self):
1693
"""See RepositoryFormat.get_format_string()."""
1694
return "Bazaar Knit Repository Format 2\n"
1696
def get_format_description(self):
1697
"""See RepositoryFormat.get_format_description()."""
1698
return "Knit repository format 2"
1700
def check_conversion_target(self, target_format):
1701
if not target_format.rich_root_data:
1702
raise errors.BadConversionTarget(
1703
'Does not support rich root data.', target_format)
1705
def open(self, a_bzrdir, _found=False, _override_transport=None):
1706
"""See RepositoryFormat.open().
1708
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1709
repository at a slightly different url
1710
than normal. I.e. during 'upgrade'.
1713
format = RepositoryFormat.find_format(a_bzrdir)
1714
assert format.__class__ == self.__class__
1715
if _override_transport is not None:
1716
repo_transport = _override_transport
1718
repo_transport = a_bzrdir.get_repository_transport(None)
1719
control_files = lockable_files.LockableFiles(repo_transport, 'lock',
1721
text_store = self._get_text_store(repo_transport, control_files)
1722
control_store = self._get_control_store(repo_transport, control_files)
1723
_revision_store = self._get_revision_store(repo_transport, control_files)
1724
return KnitRepository2(_format=self,
1726
control_files=control_files,
1727
_revision_store=_revision_store,
1728
control_store=control_store,
1729
text_store=text_store)
1733
1553
# formats which have no format string are not discoverable
1734
1554
# and not independently creatable, so are not registered.
1735
1555
RepositoryFormat.register_format(RepositoryFormat7())
1736
1556
_default_format = RepositoryFormatKnit1()
1737
1557
RepositoryFormat.register_format(_default_format)
1738
RepositoryFormat.register_format(RepositoryFormatKnit2())
1739
1558
RepositoryFormat.set_default_format(_default_format)
1740
1559
_legacy_formats = [RepositoryFormat4(),
1741
1560
RepositoryFormat5(),
1754
1573
InterRepository.get(other).method_name(parameters).
1758
1577
"""The available optimised InterRepository types."""
1760
1580
def copy_content(self, revision_id=None, basis=None):
1761
raise NotImplementedError(self.copy_content)
1581
"""Make a complete copy of the content in self into destination.
1583
This is a destructive operation! Do not use it on existing
1586
:param revision_id: Only copy the content needed to construct
1587
revision_id and its parents.
1588
:param basis: Copy the needed data preferentially from basis.
1591
self.target.set_make_working_trees(self.source.make_working_trees())
1592
except NotImplementedError:
1594
# grab the basis available data
1595
if basis is not None:
1596
self.target.fetch(basis, revision_id=revision_id)
1597
# but don't bother fetching if we have the needed data now.
1598
if (revision_id not in (None, NULL_REVISION) and
1599
self.target.has_revision(revision_id)):
1601
self.target.fetch(self.source, revision_id=revision_id)
1763
1604
def fetch(self, revision_id=None, pb=None):
1764
1605
"""Fetch the content required to construct revision_id.
1766
The content is copied from self.source to self.target.
1607
The content is copied from source to target.
1768
1609
:param revision_id: if None all content is copied, if NULL_REVISION no
1769
1610
content is copied.
1773
1614
Returns the copied revision count and the failed revisions in a tuple:
1774
1615
(copied, failures).
1776
raise NotImplementedError(self.fetch)
1617
from bzrlib.fetch import GenericRepoFetcher
1618
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1619
self.source, self.source._format, self.target, self.target._format)
1620
f = GenericRepoFetcher(to_repository=self.target,
1621
from_repository=self.source,
1622
last_revision=revision_id,
1624
return f.count_copied, f.failed_revisions
1778
1626
@needs_read_lock
1779
1627
def missing_revision_ids(self, revision_id=None):
1780
1628
"""Return the revision ids that source has that target does not.
1799
1647
return [rev_id for rev_id in source_ids if rev_id in result_set]
1802
class InterSameDataRepository(InterRepository):
1803
"""Code for converting between repositories that represent the same data.
1805
Data format and model must match for this to work.
1808
_matching_repo_format = RepositoryFormat4()
1809
"""Repository format for testing with."""
1812
def is_compatible(source, target):
1813
if not isinstance(source, Repository):
1815
if not isinstance(target, Repository):
1817
if source._format.rich_root_data == target._format.rich_root_data:
1823
def copy_content(self, revision_id=None, basis=None):
1824
"""Make a complete copy of the content in self into destination.
1826
This is a destructive operation! Do not use it on existing
1829
:param revision_id: Only copy the content needed to construct
1830
revision_id and its parents.
1831
:param basis: Copy the needed data preferentially from basis.
1834
self.target.set_make_working_trees(self.source.make_working_trees())
1835
except NotImplementedError:
1837
# grab the basis available data
1838
if basis is not None:
1839
self.target.fetch(basis, revision_id=revision_id)
1840
# but don't bother fetching if we have the needed data now.
1841
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1842
self.target.has_revision(revision_id)):
1844
self.target.fetch(self.source, revision_id=revision_id)
1847
def fetch(self, revision_id=None, pb=None):
1848
"""See InterRepository.fetch()."""
1849
from bzrlib.fetch import GenericRepoFetcher
1850
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1851
self.source, self.source._format, self.target,
1852
self.target._format)
1853
f = GenericRepoFetcher(to_repository=self.target,
1854
from_repository=self.source,
1855
last_revision=revision_id,
1857
return f.count_copied, f.failed_revisions
1860
class InterWeaveRepo(InterSameDataRepository):
1650
class InterWeaveRepo(InterRepository):
1861
1651
"""Optimised code paths between Weave based repositories."""
1863
1653
_matching_repo_format = RepositoryFormat7()
2037
1827
# that against the revision records.
2038
1828
return self.source._eliminate_revisions_not_present(required_topo_revisions)
2041
class InterModel1and2(InterRepository):
2043
_matching_repo_format = None
2046
def is_compatible(source, target):
2047
if not isinstance(source, Repository):
2049
if not isinstance(target, Repository):
2051
if not source._format.rich_root_data and target._format.rich_root_data:
2057
def fetch(self, revision_id=None, pb=None):
2058
"""See InterRepository.fetch()."""
2059
from bzrlib.fetch import Model1toKnit2Fetcher
2060
f = Model1toKnit2Fetcher(to_repository=self.target,
2061
from_repository=self.source,
2062
last_revision=revision_id,
2064
return f.count_copied, f.failed_revisions
2067
def copy_content(self, revision_id=None, basis=None):
2068
"""Make a complete copy of the content in self into destination.
2070
This is a destructive operation! Do not use it on existing
2073
:param revision_id: Only copy the content needed to construct
2074
revision_id and its parents.
2075
:param basis: Copy the needed data preferentially from basis.
2078
self.target.set_make_working_trees(self.source.make_working_trees())
2079
except NotImplementedError:
2081
# grab the basis available data
2082
if basis is not None:
2083
self.target.fetch(basis, revision_id=revision_id)
2084
# but don't bother fetching if we have the needed data now.
2085
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
2086
self.target.has_revision(revision_id)):
2088
self.target.fetch(self.source, revision_id=revision_id)
2091
class InterKnit1and2(InterKnitRepo):
2093
_matching_repo_format = None
2096
def is_compatible(source, target):
2097
"""Be compatible with Knit1 source and Knit2 target"""
2099
return (isinstance(source._format, (RepositoryFormatKnit1)) and
2100
isinstance(target._format, (RepositoryFormatKnit2)))
2101
except AttributeError:
2105
def fetch(self, revision_id=None, pb=None):
2106
"""See InterRepository.fetch()."""
2107
from bzrlib.fetch import Knit1to2Fetcher
2108
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2109
self.source, self.source._format, self.target,
2110
self.target._format)
2111
f = Knit1to2Fetcher(to_repository=self.target,
2112
from_repository=self.source,
2113
last_revision=revision_id,
2115
return f.count_copied, f.failed_revisions
2118
InterRepository.register_optimiser(InterSameDataRepository)
2119
1830
InterRepository.register_optimiser(InterWeaveRepo)
2120
1831
InterRepository.register_optimiser(InterKnitRepo)
2121
InterRepository.register_optimiser(InterModel1and2)
2122
InterRepository.register_optimiser(InterKnit1and2)
2125
1834
class RepositoryTestProviderAdapter(object):
2190
1899
# default format.
2191
1900
# XXX: robertc 20060220 reinstate this when there are two supported
2192
1901
# formats which do not have an optimal code path between them.
2193
#result.append((InterRepository,
2194
# RepositoryFormat6(),
2195
# RepositoryFormatKnit1()))
1902
result.append((InterRepository,
1903
RepositoryFormat6(),
1904
RepositoryFormatKnit1()))
2196
1905
for optimiser in InterRepository._optimisers:
2197
if optimiser._matching_repo_format is not None:
2198
result.append((optimiser,
2199
optimiser._matching_repo_format,
2200
optimiser._matching_repo_format
1906
result.append((optimiser,
1907
optimiser._matching_repo_format,
1908
optimiser._matching_repo_format
2202
1910
# if there are specific combinations we want to use, we can add them
2204
result.append((InterModel1and2, RepositoryFormat5(),
2205
RepositoryFormatKnit2()))
2206
result.append((InterKnit1and2, RepositoryFormatKnit1(),
2207
RepositoryFormatKnit2()))
2329
2029
self.new_inventory, self._config)
2330
2030
return self._new_revision_id
2332
def revision_tree(self):
2333
"""Return the tree that was just committed.
2335
After calling commit() this can be called to get a RevisionTree
2336
representing the newly committed tree. This is preferred to
2337
calling Repository.revision_tree() because that may require
2338
deserializing the inventory, while we already have a copy in
2341
return RevisionTree(self.repository, self.new_inventory,
2342
self._new_revision_id)
2344
2032
def finish_inventory(self):
2345
2033
"""Tell the builder that the inventory is finished."""
2346
if self.new_inventory.root is None:
2347
symbol_versioning.warn('Root entry should be supplied to'
2348
' record_entry_contents, as of bzr 0.10.',
2349
DeprecationWarning, stacklevel=2)
2350
self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
2351
2034
self.new_inventory.revision_id = self._new_revision_id
2352
2035
self.inv_sha1 = self.repository.add_inventory(
2353
2036
self._new_revision_id,
2471
2139
versionedfile.clear_cache()
2474
class _CommitBuilder(CommitBuilder):
2475
"""Temporary class so old CommitBuilders are detected properly
2477
Note: CommitBuilder works whether or not root entry is recorded.
2480
record_root_entry = True
2483
class RootCommitBuilder(CommitBuilder):
2484
"""This commitbuilder actually records the root id"""
2486
record_root_entry = True
2488
def record_entry_contents(self, ie, parent_invs, path, tree):
2489
"""Record the content of ie from tree into the commit if needed.
2491
Side effect: sets ie.revision when unchanged
2493
:param ie: An inventory entry present in the commit.
2494
:param parent_invs: The inventories of the parent revisions of the
2496
:param path: The path the entry is at in the tree.
2497
:param tree: The tree which contains this entry and should be used to
2500
assert self.new_inventory.root is not None or ie.parent_id is None
2501
self.new_inventory.add(ie)
2503
# ie.revision is always None if the InventoryEntry is considered
2504
# for committing. ie.snapshot will record the correct revision
2505
# which may be the sole parent if it is untouched.
2506
if ie.revision is not None:
2509
previous_entries = ie.find_previous_heads(
2511
self.repository.weave_store,
2512
self.repository.get_transaction())
2513
# we are creating a new revision for ie in the history store
2515
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2518
2142
_unescape_map = {