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 (
40
revision as _mod_revision,
49
from bzrlib.osutils import (
54
from bzrlib.revisiontree import RevisionTree
55
from bzrlib.store.versioned import VersionedFileStore
56
from bzrlib.store.text import TextStore
57
from bzrlib.testament import Testament
36
60
from bzrlib.decorators import needs_read_lock, needs_write_lock
37
from bzrlib.errors import InvalidRevisionId
38
from bzrlib.graph import Graph
39
61
from bzrlib.inter import InterObject
40
62
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,
63
from bzrlib.symbol_versioning import (
54
from bzrlib.testament import Testament
55
67
from bzrlib.trace import mutter, note, warning
56
from bzrlib.tsort import topo_sort
57
from bzrlib.weave import WeaveFile
60
70
# Old formats display a warning, but only once
86
_file_ids_altered_regex = lazy_regex.lazy_compile(
87
r'file_id="(?P<file_id>[^"]+)"'
88
r'.*revision="(?P<revision_id>[^"]+)"'
77
92
def add_inventory(self, revid, inv, parents):
78
93
"""Add the inventory inv to the repository as revid.
83
98
returns the sha1 of the serialized inventory.
100
_mod_revision.check_not_reserved_id(revid)
85
101
assert inv.revision_id is None or inv.revision_id == revid, \
86
102
"Mismatch between inventory revision" \
87
103
" id and insertion revid (%r, %r)" % (inv.revision_id, revid)
317
334
or testing the revision graph.
319
336
if not revision_id or not isinstance(revision_id, basestring):
320
raise InvalidRevisionId(revision_id=revision_id, branch=self)
337
raise errors.InvalidRevisionId(revision_id=revision_id,
321
339
return self._revision_store.get_revisions([revision_id],
322
340
self.get_transaction())[0]
426
444
# revisions. We don't need to see all lines in the inventory because
427
445
# only those added in an inventory in rev X can contain a revision=X
447
unescape_revid_cache = {}
448
unescape_fileid_cache = {}
450
# jam 20061218 In a big fetch, this handles hundreds of thousands
451
# of lines, so it has had a lot of inlining and optimizing done.
452
# Sorry that it is a little bit messy.
453
# Move several functions to be local variables, since this is a long
455
search = self._file_ids_altered_regex.search
456
unescape = _unescape_xml
457
setdefault = result.setdefault
429
458
pb = ui.ui_factory.nested_progress_bar()
431
460
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])
461
selected_revision_ids, pb=pb):
465
# One call to match.group() returning multiple items is quite a
466
# bit faster than 2 calls to match.group() each returning 1
467
file_id, revision_id = match.group('file_id', 'revision_id')
469
# Inlining the cache lookups helps a lot when you make 170,000
470
# lines and 350k ids, versus 8.4 unique ids.
471
# Using a cache helps in 2 ways:
472
# 1) Avoids unnecessary decoding calls
473
# 2) Re-uses cached strings, which helps in future set and
475
# (2) is enough that removing encoding entirely along with
476
# the cache (so we are using plain strings) results in no
477
# performance improvement.
479
revision_id = unescape_revid_cache[revision_id]
481
unescaped = unescape(revision_id)
482
unescape_revid_cache[revision_id] = unescaped
483
revision_id = unescaped
444
485
if revision_id in selected_revision_ids:
445
result.setdefault(file_id, set()).add(revision_id)
487
file_id = unescape_fileid_cache[file_id]
489
unescaped = unescape(file_id)
490
unescape_fileid_cache[file_id] = unescaped
492
setdefault(file_id, set()).add(revision_id)
497
544
:return: a dictionary of revision_id->revision_parents_list.
499
546
# special case NULL_REVISION
500
if revision_id == NULL_REVISION:
547
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
549
a_weave = self.get_inventory_weave()
550
all_revisions = self._eliminate_revisions_not_present(
552
entire_graph = dict([(node, a_weave.get_parents(node)) for
505
553
node in all_revisions])
506
554
if revision_id is None:
507
555
return entire_graph
526
574
:param revision_ids: an iterable of revisions to graph or None for all.
527
575
:return: a Graph object with the graph reachable from revision_ids.
577
result = graph.Graph()
530
578
if not revision_ids:
531
579
pending = set(self.all_revision_ids())
532
580
required = set([])
534
582
pending = set(revision_ids)
535
583
# special case NULL_REVISION
536
if NULL_REVISION in pending:
537
pending.remove(NULL_REVISION)
584
if _mod_revision.NULL_REVISION in pending:
585
pending.remove(_mod_revision.NULL_REVISION)
538
586
required = set(pending)
540
588
while len(pending):
595
643
# TODO: refactor this to use an existing revision object
596
644
# 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)
645
if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
646
return RevisionTree(self, Inventory(root_id=None),
647
_mod_revision.NULL_REVISION)
600
649
inv = self.get_revision_inventory(revision_id)
601
650
return RevisionTree(self, inv, revision_id)
607
656
`revision_id` may not be None or 'null:'"""
608
657
assert None not in revision_ids
609
assert NULL_REVISION not in revision_ids
658
assert _mod_revision.NULL_REVISION not in revision_ids
610
659
texts = self.get_inventory_weave().get_texts(revision_ids)
611
660
for text, revision_id in zip(texts, revision_ids):
612
661
inv = self.deserialise_inventory(revision_id, text)
717
766
def supports_rich_root(self):
718
767
return self._format.rich_root_data
769
def _check_ascii_revisionid(self, revision_id, method):
770
"""Private helper for ascii-only repositories."""
771
# weave repositories refuse to store revisionids that are non-ascii.
772
if revision_id is not None:
773
# weaves require ascii revision ids.
774
if isinstance(revision_id, unicode):
776
revision_id.encode('ascii')
777
except UnicodeEncodeError:
778
raise errors.NonAsciiRevisionId(method, self)
721
781
class AllInOneRepository(Repository):
722
782
"""Legacy support - the repository behaviour for all-in-one branches."""
751
811
text_store = get_store('text-store')
752
812
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
814
def get_commit_builder(self, branch, parents, config, timestamp=None,
815
timezone=None, committer=None, revprops=None,
817
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
818
return Repository.get_commit_builder(self, branch, parents, config,
819
timestamp, timezone, committer, revprops, revision_id)
755
822
def is_shared(self):
756
823
"""AllInOne repositories cannot be shared."""
862
929
return not self.control_files._transport.has('no-working-trees')
932
class WeaveMetaDirRepository(MetaDirRepository):
933
"""A subclass of MetaDirRepository to set weave specific policy."""
935
def get_commit_builder(self, branch, parents, config, timestamp=None,
936
timezone=None, committer=None, revprops=None,
938
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
939
return MetaDirRepository.get_commit_builder(self, branch, parents,
940
config, timestamp, timezone, committer, revprops, revision_id)
865
943
class KnitRepository(MetaDirRepository):
866
944
"""Knit format repository."""
933
1011
:return: a dictionary of revision_id->revision_parents_list.
935
1013
# special case NULL_REVISION
936
if revision_id == NULL_REVISION:
1014
if revision_id == _mod_revision.NULL_REVISION:
938
weave = self._get_revision_vf()
939
entire_graph = weave.get_graph()
1016
a_weave = self._get_revision_vf()
1017
entire_graph = a_weave.get_graph()
940
1018
if revision_id is None:
941
return weave.get_graph()
942
elif revision_id not in weave:
1019
return a_weave.get_graph()
1020
elif revision_id not in a_weave:
943
1021
raise errors.NoSuchRevision(self, revision_id)
945
1023
# add what can be reached from revision_id
1202
1280
klass._formats[format.get_format_string()] = format
1283
@deprecated_method(symbol_versioning.zero_fourteen)
1205
1284
def set_default_format(klass, format):
1285
klass._set_default_format(format)
1288
def _set_default_format(klass, format):
1206
1289
klass._default_format = format
1222
1305
TODO: when creating split out bzr branch formats, move this to a common
1223
1306
base for Format5, Format6. or something like that.
1225
from bzrlib.weavefile import write_weave_v5
1226
from bzrlib.weave import Weave
1229
1309
raise errors.IncompatibleFormat(self, a_bzrdir._format)
1245
1325
# FIXME: RBC 20060125 don't peek under the covers
1246
1326
# NB: no need to escape relative paths that are url safe.
1247
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock',
1327
control_files = lockable_files.LockableFiles(a_bzrdir.transport,
1328
'branch-lock', lockable_files.TransportLock)
1249
1329
control_files.create_lock()
1250
1330
control_files.lock_write()
1251
1331
control_files._transport.mkdir_multi(dirs,
1415
1495
# FIXME: RBC 20060125 don't peek under the covers
1416
1496
# NB: no need to escape relative paths that are url safe.
1417
1497
repository_transport = a_bzrdir.get_repository_transport(self)
1418
control_files = LockableFiles(repository_transport, 'lock', LockDir)
1498
control_files = lockable_files.LockableFiles(repository_transport,
1499
'lock', lockdir.LockDir)
1419
1500
control_files.create_lock()
1420
1501
return control_files
1487
1568
: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
1571
# Create an empty weave
1494
1572
sio = StringIO()
1495
write_weave_v5(Weave(), sio)
1573
weavefile.write_weave_v5(weave.Weave(), sio)
1496
1574
empty_weave = sio.getvalue()
1498
1576
mutter('creating repository in %s.', a_bzrdir.transport.base)
1518
1596
repo_transport = _override_transport
1520
1598
repo_transport = a_bzrdir.get_repository_transport(None)
1521
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1599
control_files = lockable_files.LockableFiles(repo_transport,
1600
'lock', lockdir.LockDir)
1522
1601
text_store = self._get_text_store(repo_transport, control_files)
1523
1602
control_store = self._get_control_store(repo_transport, control_files)
1524
1603
_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)
1604
return WeaveMetaDirRepository(_format=self,
1606
control_files=control_files,
1607
_revision_store=_revision_store,
1608
control_store=control_store,
1609
text_store=text_store)
1533
1612
class RepositoryFormatKnit(MetaDirRepositoryFormat):
1550
1629
repo_transport,
1551
1630
prefixed=False,
1552
1631
file_mode=control_files._file_mode,
1553
versionedfile_class=KnitVersionedFile,
1554
versionedfile_kwargs={'factory':KnitPlainFactory()},
1632
versionedfile_class=knit.KnitVersionedFile,
1633
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
1557
1636
def _get_revision_store(self, repo_transport, control_files):
1562
1641
file_mode=control_files._file_mode,
1563
1642
prefixed=False,
1565
versionedfile_class=KnitVersionedFile,
1566
versionedfile_kwargs={'delta':False, 'factory':KnitPlainFactory(),},
1644
versionedfile_class=knit.KnitVersionedFile,
1645
versionedfile_kwargs={'delta':False,
1646
'factory':knit.KnitPlainFactory(),
1569
1650
return KnitRevisionStore(versioned_file_store)
1571
1652
def _get_text_store(self, transport, control_files):
1572
1653
"""See RepositoryFormat._get_text_store()."""
1573
1654
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,
1657
versionedfile_class=knit.KnitVersionedFile,
1658
versionedfile_kwargs={
1659
'create_parent_dir':True,
1660
'delay_create':True,
1661
'dir_mode':control_files._dir_mode,
1584
1665
def initialize(self, a_bzrdir, shared=False):
1585
1666
"""Create a knit format 1 repository.
1597
1678
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1598
1679
repo_transport = a_bzrdir.get_repository_transport(None)
1599
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1680
control_files = lockable_files.LockableFiles(repo_transport,
1681
'lock', lockdir.LockDir)
1600
1682
control_store = self._get_control_store(repo_transport, control_files)
1601
1683
transaction = transactions.WriteTransaction()
1602
1684
# trigger a write of the inventory store.
1603
1685
control_store.get_weave_or_empty('inventory', transaction)
1604
1686
_revision_store = self._get_revision_store(repo_transport, control_files)
1687
# the revision id here is irrelevant: it will not be stored, and cannot
1605
1689
_revision_store.has_revision_id('A', transaction)
1606
1690
_revision_store.get_signature_file(transaction)
1607
1691
return self.open(a_bzrdir=a_bzrdir, _found=True)
1620
1704
repo_transport = _override_transport
1622
1706
repo_transport = a_bzrdir.get_repository_transport(None)
1623
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1707
control_files = lockable_files.LockableFiles(repo_transport,
1708
'lock', lockdir.LockDir)
1624
1709
text_store = self._get_text_store(repo_transport, control_files)
1625
1710
control_store = self._get_control_store(repo_transport, control_files)
1626
1711
_revision_store = self._get_revision_store(repo_transport, control_files)
1705
1790
repo_transport = _override_transport
1707
1792
repo_transport = a_bzrdir.get_repository_transport(None)
1708
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1793
control_files = lockable_files.LockableFiles(repo_transport, 'lock',
1709
1795
text_store = self._get_text_store(repo_transport, control_files)
1710
1796
control_store = self._get_control_store(repo_transport, control_files)
1711
1797
_revision_store = self._get_revision_store(repo_transport, control_files)
1721
1807
# formats which have no format string are not discoverable
1722
1808
# and not independently creatable, so are not registered.
1723
1809
RepositoryFormat.register_format(RepositoryFormat7())
1810
# KEEP in sync with bzrdir.format_registry default
1724
1811
_default_format = RepositoryFormatKnit1()
1725
1812
RepositoryFormat.register_format(_default_format)
1726
1813
RepositoryFormat.register_format(RepositoryFormatKnit2())
1727
RepositoryFormat.set_default_format(_default_format)
1814
RepositoryFormat._set_default_format(_default_format)
1728
1815
_legacy_formats = [RepositoryFormat4(),
1729
1816
RepositoryFormat5(),
1730
1817
RepositoryFormat6()]
1826
1913
if basis is not None:
1827
1914
self.target.fetch(basis, revision_id=revision_id)
1828
1915
# but don't bother fetching if we have the needed data now.
1829
if (revision_id not in (None, NULL_REVISION) and
1916
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1830
1917
self.target.has_revision(revision_id)):
1832
1919
self.target.fetch(self.source, revision_id=revision_id)
2070
2157
if basis is not None:
2071
2158
self.target.fetch(basis, revision_id=revision_id)
2072
2159
# but don't bother fetching if we have the needed data now.
2073
if (revision_id not in (None, NULL_REVISION) and
2160
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
2074
2161
self.target.has_revision(revision_id)):
2076
2163
self.target.fetch(self.source, revision_id=revision_id)
2305
2392
:return: The revision id of the recorded revision.
2307
rev = Revision(timestamp=self._timestamp,
2394
rev = _mod_revision.Revision(
2395
timestamp=self._timestamp,
2308
2396
timezone=self._timezone,
2309
2397
committer=self._committer,
2310
2398
message=message,
2345
2433
def _gen_revision_id(self):
2346
2434
"""Return new revision-id."""
2347
s = '%s-%s-' % (self._config.user_email(),
2348
compact_date(self._timestamp))
2349
s += hexlify(rand_bytes(8))
2435
return generate_ids.gen_revision_id(self._config.username(),
2352
2438
def _generate_revision_if_needed(self):
2353
2439
"""Create a revision id if None was supplied.
2355
2441
If the repository can not support user-specified revision ids
2356
they should override this function and raise UnsupportedOperation
2442
they should override this function and raise CannotSetRevisionId
2357
2443
if _new_revision_id is not None.
2359
:raises: UnsupportedOperation
2445
:raises: CannotSetRevisionId
2361
2447
if self._new_revision_id is None:
2362
2448
self._new_revision_id = self._gen_revision_id()