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(), """
17
21
from binascii import hexlify
18
22
from copy import deepcopy
19
from cStringIO import StringIO
22
from unittest import TestSuite
24
27
from bzrlib import (
41
revision as _mod_revision,
50
from bzrlib.osutils import (
55
from bzrlib.revisiontree import RevisionTree
56
from bzrlib.store.versioned import VersionedFileStore
57
from bzrlib.store.text import TextStore
58
from bzrlib.testament import Testament
36
61
from bzrlib.decorators import needs_read_lock, needs_write_lock
37
from bzrlib.errors import InvalidRevisionId
38
from bzrlib.graph import Graph
39
62
from bzrlib.inter import InterObject
40
63
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
41
from bzrlib.knit import KnitVersionedFile, KnitPlainFactory
42
from bzrlib.lockable_files import LockableFiles, TransportLock
43
from bzrlib.lockdir import LockDir
44
from bzrlib.osutils import (safe_unicode, rand_bytes, compact_date,
46
from bzrlib.revision import NULL_REVISION, Revision
47
from bzrlib.revisiontree import RevisionTree
48
from bzrlib.store.versioned import VersionedFileStore, WeaveStore
49
from bzrlib.store.text import TextStore
50
from bzrlib import symbol_versioning
51
from bzrlib.symbol_versioning import (deprecated_method,
64
from bzrlib.symbol_versioning import (
54
from bzrlib.testament import Testament
55
68
from bzrlib.trace import mutter, note, warning
56
from bzrlib.tsort import topo_sort
57
from bzrlib.weave import WeaveFile
60
71
# Old formats display a warning, but only once
87
_file_ids_altered_regex = lazy_regex.lazy_compile(
88
r'file_id="(?P<file_id>[^"]+)"'
89
r'.*revision="(?P<revision_id>[^"]+)"'
77
93
def add_inventory(self, revid, inv, parents):
78
94
"""Add the inventory inv to the repository as revid.
83
99
returns the sha1 of the serialized inventory.
101
_mod_revision.check_not_reserved_id(revid)
85
102
assert inv.revision_id is None or inv.revision_id == revid, \
86
103
"Mismatch between inventory revision" \
87
104
" id and insertion revid (%r, %r)" % (inv.revision_id, revid)
317
335
or testing the revision graph.
319
337
if not revision_id or not isinstance(revision_id, basestring):
320
raise InvalidRevisionId(revision_id=revision_id, branch=self)
338
raise errors.InvalidRevisionId(revision_id=revision_id,
321
340
return self._revision_store.get_revisions([revision_id],
322
341
self.get_transaction())[0]
426
445
# revisions. We don't need to see all lines in the inventory because
427
446
# only those added in an inventory in rev X can contain a revision=X
448
unescape_revid_cache = {}
449
unescape_fileid_cache = {}
451
# jam 20061218 In a big fetch, this handles hundreds of thousands
452
# of lines, so it has had a lot of inlining and optimizing done.
453
# Sorry that it is a little bit messy.
454
# Move several functions to be local variables, since this is a long
456
search = self._file_ids_altered_regex.search
457
unescape = _unescape_xml
458
setdefault = result.setdefault
429
459
pb = ui.ui_factory.nested_progress_bar()
431
461
for line in w.iter_lines_added_or_present_in_versions(
432
selected_revision_ids, pb=pb):
433
start = line.find('file_id="')+9
434
if start < 9: continue
435
end = line.find('"', start)
437
file_id = _unescape_xml(line[start:end])
439
start = line.find('revision="')+10
440
if start < 10: continue
441
end = line.find('"', start)
443
revision_id = _unescape_xml(line[start:end])
462
selected_revision_ids, pb=pb):
466
# One call to match.group() returning multiple items is quite a
467
# bit faster than 2 calls to match.group() each returning 1
468
file_id, revision_id = match.group('file_id', 'revision_id')
470
# Inlining the cache lookups helps a lot when you make 170,000
471
# lines and 350k ids, versus 8.4 unique ids.
472
# Using a cache helps in 2 ways:
473
# 1) Avoids unnecessary decoding calls
474
# 2) Re-uses cached strings, which helps in future set and
476
# (2) is enough that removing encoding entirely along with
477
# the cache (so we are using plain strings) results in no
478
# performance improvement.
480
revision_id = unescape_revid_cache[revision_id]
482
unescaped = unescape(revision_id)
483
unescape_revid_cache[revision_id] = unescaped
484
revision_id = unescaped
444
486
if revision_id in selected_revision_ids:
445
result.setdefault(file_id, set()).add(revision_id)
488
file_id = unescape_fileid_cache[file_id]
490
unescaped = unescape(file_id)
491
unescape_fileid_cache[file_id] = unescaped
493
setdefault(file_id, set()).add(revision_id)
497
545
:return: a dictionary of revision_id->revision_parents_list.
499
547
# special case NULL_REVISION
500
if revision_id == NULL_REVISION:
548
if revision_id == _mod_revision.NULL_REVISION:
502
weave = self.get_inventory_weave()
503
all_revisions = self._eliminate_revisions_not_present(weave.versions())
504
entire_graph = dict([(node, weave.get_parents(node)) for
550
a_weave = self.get_inventory_weave()
551
all_revisions = self._eliminate_revisions_not_present(
553
entire_graph = dict([(node, a_weave.get_parents(node)) for
505
554
node in all_revisions])
506
555
if revision_id is None:
507
556
return entire_graph
526
575
:param revision_ids: an iterable of revisions to graph or None for all.
527
576
:return: a Graph object with the graph reachable from revision_ids.
578
result = graph.Graph()
530
579
if not revision_ids:
531
580
pending = set(self.all_revision_ids())
532
581
required = set([])
534
583
pending = set(revision_ids)
535
584
# special case NULL_REVISION
536
if NULL_REVISION in pending:
537
pending.remove(NULL_REVISION)
585
if _mod_revision.NULL_REVISION in pending:
586
pending.remove(_mod_revision.NULL_REVISION)
538
587
required = set(pending)
540
589
while len(pending):
595
644
# TODO: refactor this to use an existing revision object
596
645
# so we don't need to read it in twice.
597
if revision_id is None or revision_id == NULL_REVISION:
598
return RevisionTree(self, Inventory(), NULL_REVISION)
646
if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
647
return RevisionTree(self, Inventory(root_id=None),
648
_mod_revision.NULL_REVISION)
600
650
inv = self.get_revision_inventory(revision_id)
601
651
return RevisionTree(self, inv, revision_id)
607
657
`revision_id` may not be None or 'null:'"""
608
658
assert None not in revision_ids
609
assert NULL_REVISION not in revision_ids
659
assert _mod_revision.NULL_REVISION not in revision_ids
610
660
texts = self.get_inventory_weave().get_texts(revision_ids)
611
661
for text, revision_id in zip(texts, revision_ids):
612
662
inv = self.deserialise_inventory(revision_id, text)
717
767
def supports_rich_root(self):
718
768
return self._format.rich_root_data
770
def _check_ascii_revisionid(self, revision_id, method):
771
"""Private helper for ascii-only repositories."""
772
# weave repositories refuse to store revisionids that are non-ascii.
773
if revision_id is not None:
774
# weaves require ascii revision ids.
775
if isinstance(revision_id, unicode):
777
revision_id.encode('ascii')
778
except UnicodeEncodeError:
779
raise errors.NonAsciiRevisionId(method, self)
721
782
class AllInOneRepository(Repository):
722
783
"""Legacy support - the repository behaviour for all-in-one branches."""
751
812
text_store = get_store('text-store')
752
813
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
815
def get_commit_builder(self, branch, parents, config, timestamp=None,
816
timezone=None, committer=None, revprops=None,
818
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
819
return Repository.get_commit_builder(self, branch, parents, config,
820
timestamp, timezone, committer, revprops, revision_id)
755
823
def is_shared(self):
756
824
"""AllInOne repositories cannot be shared."""
862
930
return not self.control_files._transport.has('no-working-trees')
933
class WeaveMetaDirRepository(MetaDirRepository):
934
"""A subclass of MetaDirRepository to set weave specific policy."""
936
def get_commit_builder(self, branch, parents, config, timestamp=None,
937
timezone=None, committer=None, revprops=None,
939
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
940
return MetaDirRepository.get_commit_builder(self, branch, parents,
941
config, timestamp, timezone, committer, revprops, revision_id)
865
944
class KnitRepository(MetaDirRepository):
866
945
"""Knit format repository."""
933
1012
:return: a dictionary of revision_id->revision_parents_list.
935
1014
# special case NULL_REVISION
936
if revision_id == NULL_REVISION:
1015
if revision_id == _mod_revision.NULL_REVISION:
938
weave = self._get_revision_vf()
939
entire_graph = weave.get_graph()
1017
a_weave = self._get_revision_vf()
1018
entire_graph = a_weave.get_graph()
940
1019
if revision_id is None:
941
return weave.get_graph()
942
elif revision_id not in weave:
1020
return a_weave.get_graph()
1021
elif revision_id not in a_weave:
943
1022
raise errors.NoSuchRevision(self, revision_id)
945
1024
# add what can be reached from revision_id
1057
1136
committer, revprops, revision_id)
1139
class RepositoryFormatRegistry(registry.Registry):
1140
"""Registry of RepositoryFormats.
1144
format_registry = RepositoryFormatRegistry()
1145
"""Registry of formats, indexed by their identifying format string."""
1060
1148
class RepositoryFormat(object):
1061
1149
"""A repository format.
1081
1169
parameterisation.
1084
_default_format = None
1085
"""The default format used for new repositories."""
1088
"""The known formats."""
1090
1172
def __str__(self):
1091
1173
return "<%s>" % self.__class__.__name__
1094
1176
def find_format(klass, a_bzrdir):
1095
"""Return the format for the repository object in a_bzrdir."""
1177
"""Return the format for the repository object in a_bzrdir.
1179
This is used by bzr native formats that have a "format" file in
1180
the repository. Other methods may be used by different types of
1097
1184
transport = a_bzrdir.get_repository_transport(None)
1098
1185
format_string = transport.get("format").read()
1099
return klass._formats[format_string]
1186
return format_registry.get(format_string)
1100
1187
except errors.NoSuchFile:
1101
1188
raise errors.NoRepositoryPresent(a_bzrdir)
1102
1189
except KeyError:
1103
1190
raise errors.UnknownFormatError(format=format_string)
1105
def _get_control_store(self, repo_transport, control_files):
1106
"""Return the control store for this repository."""
1107
raise NotImplementedError(self._get_control_store)
1193
@deprecated_method(symbol_versioning.zero_fourteen)
1194
def set_default_format(klass, format):
1195
klass._set_default_format(format)
1198
def _set_default_format(klass, format):
1199
"""Set the default format for new Repository creation.
1201
The format must already be registered.
1203
format_registry.default_key = format.get_format_string()
1206
def register_format(klass, format):
1207
format_registry.register(format.get_format_string(), format)
1210
def unregister_format(klass, format):
1211
format_registry.remove(format.get_format_string())
1110
1214
def get_default_format(klass):
1111
1215
"""Return the current default format."""
1112
return klass._default_format
1216
return format_registry.get(format_registry.default_key)
1218
def _get_control_store(self, repo_transport, control_files):
1219
"""Return the control store for this repository."""
1220
raise NotImplementedError(self._get_control_store)
1114
1222
def get_format_string(self):
1115
1223
"""Return the ASCII format string that identifies this format.
1198
1306
raise NotImplementedError(self.open)
1201
def register_format(klass, format):
1202
klass._formats[format.get_format_string()] = format
1205
def set_default_format(klass, format):
1206
klass._default_format = format
1209
def unregister_format(klass, format):
1210
assert klass._formats[format.get_format_string()] is format
1211
del klass._formats[format.get_format_string()]
1214
1309
class PreSplitOutRepositoryFormat(RepositoryFormat):
1215
1310
"""Base class for the pre split out repository formats."""
1222
1317
TODO: when creating split out bzr branch formats, move this to a common
1223
1318
base for Format5, Format6. or something like that.
1225
from bzrlib.weavefile import write_weave_v5
1226
from bzrlib.weave import Weave
1229
1321
raise errors.IncompatibleFormat(self, a_bzrdir._format)
1245
1337
# FIXME: RBC 20060125 don't peek under the covers
1246
1338
# NB: no need to escape relative paths that are url safe.
1247
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock',
1339
control_files = lockable_files.LockableFiles(a_bzrdir.transport,
1340
'branch-lock', lockable_files.TransportLock)
1249
1341
control_files.create_lock()
1250
1342
control_files.lock_write()
1251
1343
control_files._transport.mkdir_multi(dirs,
1415
1507
# FIXME: RBC 20060125 don't peek under the covers
1416
1508
# NB: no need to escape relative paths that are url safe.
1417
1509
repository_transport = a_bzrdir.get_repository_transport(self)
1418
control_files = LockableFiles(repository_transport, 'lock', LockDir)
1510
control_files = lockable_files.LockableFiles(repository_transport,
1511
'lock', lockdir.LockDir)
1419
1512
control_files.create_lock()
1420
1513
return control_files
1487
1580
:param shared: If true the repository will be initialized as a shared
1490
from bzrlib.weavefile import write_weave_v5
1491
from bzrlib.weave import Weave
1493
1583
# Create an empty weave
1494
1584
sio = StringIO()
1495
write_weave_v5(Weave(), sio)
1585
weavefile.write_weave_v5(weave.Weave(), sio)
1496
1586
empty_weave = sio.getvalue()
1498
1588
mutter('creating repository in %s.', a_bzrdir.transport.base)
1518
1608
repo_transport = _override_transport
1520
1610
repo_transport = a_bzrdir.get_repository_transport(None)
1521
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1611
control_files = lockable_files.LockableFiles(repo_transport,
1612
'lock', lockdir.LockDir)
1522
1613
text_store = self._get_text_store(repo_transport, control_files)
1523
1614
control_store = self._get_control_store(repo_transport, control_files)
1524
1615
_revision_store = self._get_revision_store(repo_transport, control_files)
1525
return MetaDirRepository(_format=self,
1527
control_files=control_files,
1528
_revision_store=_revision_store,
1529
control_store=control_store,
1530
text_store=text_store)
1616
return WeaveMetaDirRepository(_format=self,
1618
control_files=control_files,
1619
_revision_store=_revision_store,
1620
control_store=control_store,
1621
text_store=text_store)
1533
1624
class RepositoryFormatKnit(MetaDirRepositoryFormat):
1550
1641
repo_transport,
1551
1642
prefixed=False,
1552
1643
file_mode=control_files._file_mode,
1553
versionedfile_class=KnitVersionedFile,
1554
versionedfile_kwargs={'factory':KnitPlainFactory()},
1644
versionedfile_class=knit.KnitVersionedFile,
1645
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
1557
1648
def _get_revision_store(self, repo_transport, control_files):
1562
1653
file_mode=control_files._file_mode,
1563
1654
prefixed=False,
1565
versionedfile_class=KnitVersionedFile,
1566
versionedfile_kwargs={'delta':False, 'factory':KnitPlainFactory(),},
1656
versionedfile_class=knit.KnitVersionedFile,
1657
versionedfile_kwargs={'delta':False,
1658
'factory':knit.KnitPlainFactory(),
1569
1662
return KnitRevisionStore(versioned_file_store)
1571
1664
def _get_text_store(self, transport, control_files):
1572
1665
"""See RepositoryFormat._get_text_store()."""
1573
1666
return self._get_versioned_file_store('knits',
1576
versionedfile_class=KnitVersionedFile,
1577
versionedfile_kwargs={
1578
'create_parent_dir':True,
1579
'delay_create':True,
1580
'dir_mode':control_files._dir_mode,
1669
versionedfile_class=knit.KnitVersionedFile,
1670
versionedfile_kwargs={
1671
'create_parent_dir':True,
1672
'delay_create':True,
1673
'dir_mode':control_files._dir_mode,
1584
1677
def initialize(self, a_bzrdir, shared=False):
1585
1678
"""Create a knit format 1 repository.
1597
1690
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1598
1691
repo_transport = a_bzrdir.get_repository_transport(None)
1599
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1692
control_files = lockable_files.LockableFiles(repo_transport,
1693
'lock', lockdir.LockDir)
1600
1694
control_store = self._get_control_store(repo_transport, control_files)
1601
1695
transaction = transactions.WriteTransaction()
1602
1696
# trigger a write of the inventory store.
1603
1697
control_store.get_weave_or_empty('inventory', transaction)
1604
1698
_revision_store = self._get_revision_store(repo_transport, control_files)
1699
# the revision id here is irrelevant: it will not be stored, and cannot
1605
1701
_revision_store.has_revision_id('A', transaction)
1606
1702
_revision_store.get_signature_file(transaction)
1607
1703
return self.open(a_bzrdir=a_bzrdir, _found=True)
1620
1716
repo_transport = _override_transport
1622
1718
repo_transport = a_bzrdir.get_repository_transport(None)
1623
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1719
control_files = lockable_files.LockableFiles(repo_transport,
1720
'lock', lockdir.LockDir)
1624
1721
text_store = self._get_text_store(repo_transport, control_files)
1625
1722
control_store = self._get_control_store(repo_transport, control_files)
1626
1723
_revision_store = self._get_revision_store(repo_transport, control_files)
1705
1802
repo_transport = _override_transport
1707
1804
repo_transport = a_bzrdir.get_repository_transport(None)
1708
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1805
control_files = lockable_files.LockableFiles(repo_transport, 'lock',
1709
1807
text_store = self._get_text_store(repo_transport, control_files)
1710
1808
control_store = self._get_control_store(repo_transport, control_files)
1711
1809
_revision_store = self._get_revision_store(repo_transport, control_files)
1721
1819
# formats which have no format string are not discoverable
1722
1820
# and not independently creatable, so are not registered.
1723
1821
RepositoryFormat.register_format(RepositoryFormat7())
1822
# KEEP in sync with bzrdir.format_registry default, which controls the overall
1823
# default control directory format
1724
1824
_default_format = RepositoryFormatKnit1()
1725
1825
RepositoryFormat.register_format(_default_format)
1726
1826
RepositoryFormat.register_format(RepositoryFormatKnit2())
1727
RepositoryFormat.set_default_format(_default_format)
1827
RepositoryFormat._set_default_format(_default_format)
1728
1828
_legacy_formats = [RepositoryFormat4(),
1729
1829
RepositoryFormat5(),
1730
1830
RepositoryFormat6()]
1826
1926
if basis is not None:
1827
1927
self.target.fetch(basis, revision_id=revision_id)
1828
1928
# but don't bother fetching if we have the needed data now.
1829
if (revision_id not in (None, NULL_REVISION) and
1929
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1830
1930
self.target.has_revision(revision_id)):
1832
1932
self.target.fetch(self.source, revision_id=revision_id)
2070
2170
if basis is not None:
2071
2171
self.target.fetch(basis, revision_id=revision_id)
2072
2172
# but don't bother fetching if we have the needed data now.
2073
if (revision_id not in (None, NULL_REVISION) and
2173
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
2074
2174
self.target.has_revision(revision_id)):
2076
2176
self.target.fetch(self.source, revision_id=revision_id)
2305
2405
:return: The revision id of the recorded revision.
2307
rev = Revision(timestamp=self._timestamp,
2407
rev = _mod_revision.Revision(
2408
timestamp=self._timestamp,
2308
2409
timezone=self._timezone,
2309
2410
committer=self._committer,
2310
2411
message=message,
2345
2446
def _gen_revision_id(self):
2346
2447
"""Return new revision-id."""
2347
s = '%s-%s-' % (self._config.user_email(),
2348
compact_date(self._timestamp))
2349
s += hexlify(rand_bytes(8))
2448
return generate_ids.gen_revision_id(self._config.username(),
2352
2451
def _generate_revision_if_needed(self):
2353
2452
"""Create a revision id if None was supplied.
2355
2454
If the repository can not support user-specified revision ids
2356
they should override this function and raise UnsupportedOperation
2455
they should override this function and raise CannotSetRevisionId
2357
2456
if _new_revision_id is not None.
2359
:raises: UnsupportedOperation
2458
:raises: CannotSetRevisionId
2361
2460
if self._new_revision_id is None:
2362
2461
self._new_revision_id = self._gen_revision_id()