22
22
from unittest import TestSuite
24
import bzrlib.bzrdir as bzrdir
24
from bzrlib import bzrdir, check, delta, gpg, errors, xml5, ui, transactions, osutils
25
25
from bzrlib.decorators import needs_read_lock, needs_write_lock
26
import bzrlib.errors as errors
27
26
from bzrlib.errors import InvalidRevisionId
28
import bzrlib.gpg as gpg
29
27
from bzrlib.graph import Graph
30
28
from bzrlib.inter import InterObject
31
29
from bzrlib.inventory import Inventory
37
35
from bzrlib.revision import NULL_REVISION, Revision
38
36
from bzrlib.store.versioned import VersionedFileStore, WeaveStore
39
37
from bzrlib.store.text import TextStore
40
from bzrlib.symbol_versioning import *
38
from bzrlib.symbol_versioning import (deprecated_method,
41
from bzrlib.testament import Testament
41
42
from bzrlib.trace import mutter, note
42
43
from bzrlib.tree import RevisionTree
43
44
from bzrlib.tsort import topo_sort
44
from bzrlib.testament import Testament
45
from bzrlib.tree import EmptyTree
47
45
from bzrlib.weave import WeaveFile
51
48
class Repository(object):
72
69
assert inv.revision_id is None or inv.revision_id == revid, \
73
70
"Mismatch between inventory revision" \
74
71
" id and insertion revid (%r, %r)" % (inv.revision_id, revid)
75
inv_text = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
76
inv_sha1 = bzrlib.osutils.sha_string(inv_text)
72
inv_text = xml5.serializer_v5.write_inventory_to_string(inv)
73
inv_sha1 = osutils.sha_string(inv_text)
77
74
inv_vf = self.control_weaves.get_weave('inventory',
78
75
self.get_transaction())
79
self._inventory_add_lines(inv_vf, revid, parents, bzrlib.osutils.split_lines(inv_text))
76
self._inventory_add_lines(inv_vf, revid, parents, osutils.split_lines(inv_text))
82
79
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
119
116
"""Return all the possible revisions that we could find."""
120
117
return self.get_inventory_weave().versions()
119
def all_revision_ids(self):
120
"""Returns a list of all the revision ids in the repository.
122
This is deprecated because code should generally work on the graph
123
reachable from a particular revision, and ignore any other revisions
124
that might be present. There is no direct replacement method.
126
return self._all_revision_ids()
123
def all_revision_ids(self):
129
def _all_revision_ids(self):
124
130
"""Returns a list of all the revision ids in the repository.
126
132
These are in as much topological order as the underlying store can
174
180
self.control_files = control_files
175
181
self._revision_store = _revision_store
176
182
self.text_store = text_store
177
# backwards compatability
183
# backwards compatibility
178
184
self.weave_store = text_store
179
185
# not right yet - should be more semantically clear ?
216
222
For instance, if the repository is at URL/.bzr/repository,
217
223
Repository.open(URL) -> a Repository instance.
219
control = bzrlib.bzrdir.BzrDir.open(base)
225
control = bzrdir.BzrDir.open(base)
220
226
return control.open_repository()
222
228
def copy_content_into(self, destination, revision_id=None, basis=None):
267
273
result = a_bzrdir.create_repository()
268
274
# FIXME RBC 20060209 split out the repository type to avoid this check ?
269
275
elif isinstance(a_bzrdir._format,
270
(bzrlib.bzrdir.BzrDirFormat4,
271
bzrlib.bzrdir.BzrDirFormat5,
272
bzrlib.bzrdir.BzrDirFormat6)):
276
(bzrdir.BzrDirFormat4,
277
bzrdir.BzrDirFormat5,
278
bzrdir.BzrDirFormat6)):
273
279
result = a_bzrdir.open_repository()
275
281
result = self._format.initialize(a_bzrdir, shared=self.is_shared())
294
300
if not revision_id or not isinstance(revision_id, basestring):
295
301
raise InvalidRevisionId(revision_id=revision_id, branch=self)
296
return self._revision_store.get_revision(revision_id,
297
self.get_transaction())
302
return self._revision_store.get_revisions([revision_id],
303
self.get_transaction())[0]
305
def get_revisions(self, revision_ids):
306
return self._revision_store.get_revisions(revision_ids,
307
self.get_transaction())
300
310
def get_revision_xml(self, revision_id):
320
330
self._check_revision_parents(r, inv)
334
def get_deltas_for_revisions(self, revisions):
335
"""Produce a generator of revision deltas.
337
Note that the input is a sequence of REVISIONS, not revision_ids.
338
Trees will be held in memory until the generator exits.
339
Each delta is relative to the revision's lefthand predecessor.
341
required_trees = set()
342
for revision in revisions:
343
required_trees.add(revision.revision_id)
344
required_trees.update(revision.parent_ids[:1])
345
trees = dict((t.get_revision_id(), t) for
346
t in self.revision_trees(required_trees))
347
for revision in revisions:
348
if not revision.parent_ids:
349
old_tree = self.revision_tree(None)
351
old_tree = trees[revision.parent_ids[0]]
352
yield delta.compare_trees(old_tree, trees[revision.revision_id])
355
def get_revision_delta(self, revision_id):
356
"""Return the delta for one revision.
358
The delta is relative to the left-hand predecessor of the
361
r = self.get_revision(revision_id)
362
return list(self.get_deltas_for_revisions([r]))[0]
323
364
def _check_revision_parents(self, revision, inventory):
324
365
"""Private to Repository and Fetch.
355
396
RepositoryFormat6,
356
397
RepositoryFormat7,
357
398
RepositoryFormatKnit1)), \
358
"fileid_involved only supported for branches which store inventory as unnested xml"
399
("fileids_altered_by_revision_ids only supported for branches "
400
"which store inventory as unnested xml, not on %r" % self)
359
401
selected_revision_ids = set(revision_ids)
360
402
w = self.get_inventory_weave()
363
405
# this code needs to read every new line in every inventory for the
364
406
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
365
# not pesent in one of those inventories is unnecessary but not
407
# not present in one of those inventories is unnecessary but not
366
408
# harmful because we are filtering by the revision id marker in the
367
409
# inventory lines : we only select file ids altered in one of those
368
# revisions. We dont need to see all lines in the inventory because
410
# revisions. We don't need to see all lines in the inventory because
369
411
# only those added in an inventory in rev X can contain a revision=X
371
413
for line in w.iter_lines_added_or_present_in_versions(selected_revision_ids):
401
443
:param revision_id: The expected revision id of the inventory.
402
444
:param xml: A serialised inventory.
404
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
446
return xml5.serializer_v5.read_inventory_from_string(xml)
407
449
def get_inventory_xml(self, revision_id):
411
453
iw = self.get_inventory_weave()
412
454
return iw.get_text(revision_id)
413
455
except IndexError:
414
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
456
raise errors.HistoryMissing(self, 'inventory', revision_id)
417
459
def get_inventory_sha1(self, revision_id):
423
465
def get_revision_graph(self, revision_id=None):
424
466
"""Return a dictionary containing the revision graph.
468
:param revision_id: The revision_id to get a graph from. If None, then
469
the entire revision graph is returned. This is a deprecated mode of
470
operation and will be removed in the future.
426
471
:return: a dictionary of revision_id->revision_parents_list.
473
# special case NULL_REVISION
474
if revision_id == NULL_REVISION:
428
476
weave = self.get_inventory_weave()
429
477
all_revisions = self._eliminate_revisions_not_present(weave.versions())
430
478
entire_graph = dict([(node, weave.get_parents(node)) for
458
506
required = set([])
460
508
pending = set(revision_ids)
461
required = set(revision_ids)
509
# special case NULL_REVISION
510
if NULL_REVISION in pending:
511
pending.remove(NULL_REVISION)
512
required = set(pending)
463
514
while len(pending):
464
515
revision_id = pending.pop()
513
564
def revision_tree(self, revision_id):
514
565
"""Return Tree for a revision on this branch.
516
`revision_id` may be None for the null revision, in which case
517
an `EmptyTree` is returned."""
567
`revision_id` may be None for the empty tree revision.
518
569
# TODO: refactor this to use an existing revision object
519
570
# so we don't need to read it in twice.
520
571
if revision_id is None or revision_id == NULL_REVISION:
572
return RevisionTree(self, Inventory(), NULL_REVISION)
523
574
inv = self.get_revision_inventory(revision_id)
524
575
return RevisionTree(self, inv, revision_id)
578
def revision_trees(self, revision_ids):
579
"""Return Tree for a revision on this branch.
581
`revision_id` may not be None or 'null:'"""
582
assert None not in revision_ids
583
assert NULL_REVISION not in revision_ids
584
texts = self.get_inventory_weave().get_texts(revision_ids)
585
for text, revision_id in zip(texts, revision_ids):
586
inv = self.deserialise_inventory(revision_id, text)
587
yield RevisionTree(self, inv, revision_id)
527
590
def get_ancestry(self, revision_id):
528
591
"""Return a list of revision-ids integrated by a revision.
593
The first element of the list is always None, indicating the origin
594
revision. This might change when we have history horizons, or
595
perhaps we should have a new API.
530
597
This is topologically sorted.
594
661
return self._revision_store.get_signature_text(revision_id,
595
662
self.get_transaction())
665
def check(self, revision_ids):
666
"""Check consistency of all history of given revision_ids.
668
Different repository implementations should override _check().
670
:param revision_ids: A non-empty list of revision_ids whose ancestry
671
will be checked. Typically the last revision_id of a branch.
674
raise ValueError("revision_ids must be non-empty in %s.check"
676
return self._check(revision_ids)
678
def _check(self, revision_ids):
679
result = check.Check(self)
598
684
class AllInOneRepository(Repository):
599
685
"""Legacy support - the repository behaviour for all-in-one branches."""
659
745
present_parents.append(p_id)
660
746
parent_trees[p_id] = repository.revision_tree(p_id)
662
parent_trees[p_id] = EmptyTree()
748
parent_trees[p_id] = repository.revision_tree(None)
664
750
inv = revision_tree.inventory
670
756
if ie.revision not in w:
671
757
text_parents = []
672
758
# FIXME: TODO: The following loop *may* be overlapping/duplicate
673
# with inventoryEntry.find_previous_heads(). if it is, then there
759
# with InventoryEntry.find_previous_heads(). if it is, then there
674
760
# is a latent bug here where the parents may have ancestors of each
676
762
for revision, tree in parent_trees.iteritems():
742
828
inv_vf.add_lines_with_ghosts(revid, parents, lines)
745
def all_revision_ids(self):
831
def _all_revision_ids(self):
746
832
"""See Repository.all_revision_ids()."""
833
# Knits get the revision graph from the index of the revision knit, so
834
# it's always possible even if they're on an unlistable transport.
747
835
return self._revision_store.all_revision_ids(self.get_transaction())
749
837
def fileid_involved_between_revs(self, from_revid, to_revid):
794
882
def get_revision_graph(self, revision_id=None):
795
883
"""Return a dictionary containing the revision graph.
885
:param revision_id: The revision_id to get a graph from. If None, then
886
the entire revision graph is returned. This is a deprecated mode of
887
operation and will be removed in the future.
797
888
:return: a dictionary of revision_id->revision_parents_list.
890
# special case NULL_REVISION
891
if revision_id == NULL_REVISION:
799
893
weave = self._get_revision_vf()
800
894
entire_graph = weave.get_graph()
801
895
if revision_id is None:
829
923
required = set([])
831
925
pending = set(revision_ids)
832
required = set(revision_ids)
926
# special case NULL_REVISION
927
if NULL_REVISION in pending:
928
pending.remove(NULL_REVISION)
929
required = set(pending)
834
931
while len(pending):
835
932
revision_id = pending.pop()
838
935
raise errors.NoSuchRevision(self, revision_id)
840
937
result.add_ghost(revision_id)
841
# mark it as done so we dont try for it again.
938
# mark it as done so we don't try for it again.
842
939
done.add(revision_id)
844
941
parent_ids = vf.get_parents_with_ghosts(revision_id)
865
962
reconciler.reconcile()
866
963
return reconciler
868
def revision_parents(self, revid):
869
return self._get_revision_vf().get_parents(rev_id)
965
def revision_parents(self, revision_id):
966
return self._get_revision_vf().get_parents(revision_id)
871
969
class RepositoryFormat(object):
872
970
"""A repository format.
908
1006
except errors.NoSuchFile:
909
1007
raise errors.NoRepositoryPresent(a_bzrdir)
910
1008
except KeyError:
911
raise errors.UnknownFormatError(format_string)
1009
raise errors.UnknownFormatError(format=format_string)
913
1011
def _get_control_store(self, repo_transport, control_files):
914
1012
"""Return the control store for this repository."""
928
1026
raise NotImplementedError(self.get_format_string)
930
1028
def get_format_description(self):
931
"""Return the short desciption for this format."""
1029
"""Return the short description for this format."""
932
1030
raise NotImplementedError(self.get_format_description)
934
1032
def _get_revision_store(self, repo_transport, control_files):
1036
1134
# Create an empty weave
1037
1135
sio = StringIO()
1038
bzrlib.weavefile.write_weave_v5(Weave(), sio)
1136
write_weave_v5(Weave(), sio)
1039
1137
empty_weave = sio.getvalue()
1041
1139
mutter('creating repository in %s.', a_bzrdir.transport.base)
1043
1141
files = [('inventory.weave', StringIO(empty_weave)),
1046
# FIXME: RBC 20060125 dont peek under the covers
1144
# FIXME: RBC 20060125 don't peek under the covers
1047
1145
# NB: no need to escape relative paths that are url safe.
1048
1146
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock',
1095
1193
- TextStores for texts, inventories,revisions.
1097
1195
This format is deprecated: it indexes texts using a text id which is
1098
removed in format 5; initializationa and write support for this format
1196
removed in format 5; initialization and write support for this format
1099
1197
has been removed.
1102
1200
def __init__(self):
1103
1201
super(RepositoryFormat4, self).__init__()
1104
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat4()
1202
self._matchingbzrdir = bzrdir.BzrDirFormat4()
1106
1204
def get_format_description(self):
1107
1205
"""See RepositoryFormat.get_format_description()."""
1151
1249
def __init__(self):
1152
1250
super(RepositoryFormat5, self).__init__()
1153
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat5()
1251
self._matchingbzrdir = bzrdir.BzrDirFormat5()
1155
1253
def get_format_description(self):
1156
1254
"""See RepositoryFormat.get_format_description()."""
1181
1279
def __init__(self):
1182
1280
super(RepositoryFormat6, self).__init__()
1183
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat6()
1281
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1185
1283
def get_format_description(self):
1186
1284
"""See RepositoryFormat.get_format_description()."""
1202
1300
class MetaDirRepositoryFormat(RepositoryFormat):
1203
"""Common base class for the new repositories using the metadir layour."""
1301
"""Common base class for the new repositories using the metadir layout."""
1205
1303
def __init__(self):
1206
1304
super(MetaDirRepositoryFormat, self).__init__()
1207
self._matchingbzrdir = bzrlib.bzrdir.BzrDirMetaFormat1()
1305
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1209
1307
def _create_control_files(self, a_bzrdir):
1210
1308
"""Create the required files and the initial control_files object."""
1211
# FIXME: RBC 20060125 dont peek under the covers
1309
# FIXME: RBC 20060125 don't peek under the covers
1212
1310
# NB: no need to escape relative paths that are url safe.
1213
1311
repository_transport = a_bzrdir.get_repository_transport(self)
1214
1312
control_files = LockableFiles(repository_transport, 'lock', LockDir)
1286
1384
# Create an empty weave
1287
1385
sio = StringIO()
1288
bzrlib.weavefile.write_weave_v5(Weave(), sio)
1386
write_weave_v5(Weave(), sio)
1289
1387
empty_weave = sio.getvalue()
1291
1389
mutter('creating repository in %s.', a_bzrdir.transport.base)
1396
1494
repo_transport = a_bzrdir.get_repository_transport(None)
1397
1495
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1398
1496
control_store = self._get_control_store(repo_transport, control_files)
1399
transaction = bzrlib.transactions.WriteTransaction()
1497
transaction = transactions.WriteTransaction()
1400
1498
# trigger a write of the inventory store.
1401
1499
control_store.get_weave_or_empty('inventory', transaction)
1402
1500
_revision_store = self._get_revision_store(repo_transport, control_files)
1474
1572
# grab the basis available data
1475
1573
if basis is not None:
1476
1574
self.target.fetch(basis, revision_id=revision_id)
1477
# but dont bother fetching if we have the needed data now.
1575
# but don't bother fetching if we have the needed data now.
1478
1576
if (revision_id not in (None, NULL_REVISION) and
1479
1577
self.target.has_revision(revision_id)):
1571
1669
def is_compatible(source, target):
1572
1670
"""Be compatible with known Weave formats.
1574
We dont test for the stores being of specific types becase that
1672
We don't test for the stores being of specific types because that
1575
1673
could lead to confusing results, and there is no need to be
1576
1674
overly general.
1592
1690
if basis is not None:
1593
1691
# copy the basis in, then fetch remaining data.
1594
1692
basis.copy_content_into(self.target, revision_id)
1595
# the basis copy_content_into could misset this.
1693
# the basis copy_content_into could miss-set this.
1597
1695
self.target.set_make_working_trees(self.source.make_working_trees())
1598
1696
except NotImplementedError:
1641
1739
def missing_revision_ids(self, revision_id=None):
1642
1740
"""See InterRepository.missing_revision_ids()."""
1643
1741
# we want all revisions to satisfy revision_id in source.
1644
# but we dont want to stat every file here and there.
1742
# but we don't want to stat every file here and there.
1645
1743
# we want then, all revisions other needs to satisfy revision_id
1646
1744
# checked, but not those that we have locally.
1647
1745
# so the first thing is to get a subset of the revisions to
1660
1758
source_ids_set = set(source_ids)
1661
1759
# source_ids is the worst possible case we may need to pull.
1662
1760
# now we want to filter source_ids against what we actually
1663
# have in target, but dont try to check for existence where we know
1761
# have in target, but don't try to check for existence where we know
1664
1762
# we do not have a revision as that would be pointless.
1665
1763
target_ids = set(self.target._all_possible_ids())
1666
1764
possibly_present_revisions = target_ids.intersection(source_ids_set)
1689
1787
def is_compatible(source, target):
1690
1788
"""Be compatible with known Knit formats.
1692
We dont test for the stores being of specific types becase that
1790
We don't test for the stores being of specific types because that
1693
1791
could lead to confusing results, and there is no need to be
1694
1792
overly general.
1723
1821
source_ids_set = set(source_ids)
1724
1822
# source_ids is the worst possible case we may need to pull.
1725
1823
# now we want to filter source_ids against what we actually
1726
# have in target, but dont try to check for existence where we know
1824
# have in target, but don't try to check for existence where we know
1727
1825
# we do not have a revision as that would be pointless.
1728
1826
target_ids = set(self.target._all_possible_ids())
1729
1827
possibly_present_revisions = target_ids.intersection(source_ids_set)
1915
2013
self._revprops.update(revprops)
1917
2015
if timestamp is None:
1918
self._timestamp = time.time()
1920
self._timestamp = long(timestamp)
2016
timestamp = time.time()
2017
# Restrict resolution to 1ms
2018
self._timestamp = round(timestamp, 3)
1922
2020
if timezone is None:
1923
2021
self._timezone = local_time_offset()
2034
2132
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2035
2133
# should return the SHA1 and size
2036
2134
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2037
return bzrlib.osutils.sha_strings(new_lines), \
2135
return osutils.sha_strings(new_lines), \
2038
2136
sum(map(len, new_lines))
2040
2138
def modified_link(self, file_id, file_parents, link_target):
2053
2151
versionedfile.clear_cache()
2056
# Copied from xml.sax.saxutils
2163
def _unescaper(match, _map=_unescape_map):
2164
return _map[match.group(1)]
2057
2170
def _unescape_xml(data):
2058
"""Unescape &, <, and > in a string of data.
2060
data = data.replace("<", "<")
2061
data = data.replace(">", ">")
2062
# must do ampersand last
2063
return data.replace("&", "&")
2171
"""Unescape predefined XML entities in a string of data."""
2173
if _unescape_re is None:
2174
_unescape_re = re.compile('\&([^;]*);')
2175
return _unescape_re.sub(_unescaper, data)