1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
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
from binascii import hexlify
22
from copy import deepcopy
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
61
from bzrlib.decorators import needs_read_lock, needs_write_lock
62
from bzrlib.inter import InterObject
63
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
64
from bzrlib.symbol_versioning import (
68
from bzrlib.trace import mutter, note, warning
71
# Old formats display a warning, but only once
72
_deprecation_warning_done = False
75
class Repository(object):
76
"""Repository holding history for one or more branches.
78
The repository holds and retrieves historical information including
79
revisions and file history. It's normally accessed only by the Branch,
80
which views a particular line of development through that history.
82
The Repository builds on top of Stores and a Transport, which respectively
83
describe the disk data format and the way of accessing the (possibly
87
_file_ids_altered_regex = lazy_regex.lazy_compile(
88
r'file_id="(?P<file_id>[^"]+)"'
89
r'.*revision="(?P<revision_id>[^"]+)"'
93
def add_inventory(self, revid, inv, parents):
94
"""Add the inventory inv to the repository as revid.
96
:param parents: The revision ids of the parents that revid
97
is known to have and are in the repository already.
99
returns the sha1 of the serialized inventory.
101
_mod_revision.check_not_reserved_id(revid)
102
assert inv.revision_id is None or inv.revision_id == revid, \
103
"Mismatch between inventory revision" \
104
" id and insertion revid (%r, %r)" % (inv.revision_id, revid)
105
assert inv.root is not None
106
inv_text = self.serialise_inventory(inv)
107
inv_sha1 = osutils.sha_string(inv_text)
108
inv_vf = self.control_weaves.get_weave('inventory',
109
self.get_transaction())
110
self._inventory_add_lines(inv_vf, revid, parents, osutils.split_lines(inv_text))
113
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
115
for parent in parents:
117
final_parents.append(parent)
119
inv_vf.add_lines(revid, final_parents, lines)
122
def add_revision(self, rev_id, rev, inv=None, config=None):
123
"""Add rev to the revision store as rev_id.
125
:param rev_id: the revision id to use.
126
:param rev: The revision object.
127
:param inv: The inventory for the revision. if None, it will be looked
128
up in the inventory storer
129
:param config: If None no digital signature will be created.
130
If supplied its signature_needed method will be used
131
to determine if a signature should be made.
133
_mod_revision.check_not_reserved_id(rev_id)
134
if config is not None and config.signature_needed():
136
inv = self.get_inventory(rev_id)
137
plaintext = Testament(rev, inv).as_short_text()
138
self.store_revision_signature(
139
gpg.GPGStrategy(config), plaintext, rev_id)
140
if not rev_id in self.get_inventory_weave():
142
raise errors.WeaveRevisionNotPresent(rev_id,
143
self.get_inventory_weave())
145
# yes, this is not suitable for adding with ghosts.
146
self.add_inventory(rev_id, inv, rev.parent_ids)
147
self._revision_store.add_revision(rev, self.get_transaction())
150
def _all_possible_ids(self):
151
"""Return all the possible revisions that we could find."""
152
return self.get_inventory_weave().versions()
154
def all_revision_ids(self):
155
"""Returns a list of all the revision ids in the repository.
157
This is deprecated because code should generally work on the graph
158
reachable from a particular revision, and ignore any other revisions
159
that might be present. There is no direct replacement method.
161
return self._all_revision_ids()
164
def _all_revision_ids(self):
165
"""Returns a list of all the revision ids in the repository.
167
These are in as much topological order as the underlying store can
168
present: for weaves ghosts may lead to a lack of correctness until
169
the reweave updates the parents list.
171
if self._revision_store.text_store.listable():
172
return self._revision_store.all_revision_ids(self.get_transaction())
173
result = self._all_possible_ids()
174
return self._eliminate_revisions_not_present(result)
176
def break_lock(self):
177
"""Break a lock if one is present from another instance.
179
Uses the ui factory to ask for confirmation if the lock may be from
182
self.control_files.break_lock()
185
def _eliminate_revisions_not_present(self, revision_ids):
186
"""Check every revision id in revision_ids to see if we have it.
188
Returns a set of the present revisions.
191
for id in revision_ids:
192
if self.has_revision(id):
197
def create(a_bzrdir):
198
"""Construct the current default format repository in a_bzrdir."""
199
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
201
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
202
"""instantiate a Repository.
204
:param _format: The format of the repository on disk.
205
:param a_bzrdir: The BzrDir of the repository.
207
In the future we will have a single api for all stores for
208
getting file texts, inventories and revisions, then
209
this construct will accept instances of those things.
211
super(Repository, self).__init__()
212
self._format = _format
213
# the following are part of the public API for Repository:
214
self.bzrdir = a_bzrdir
215
self.control_files = control_files
216
self._revision_store = _revision_store
217
self.text_store = text_store
218
# backwards compatibility
219
self.weave_store = text_store
220
# not right yet - should be more semantically clear ?
222
self.control_store = control_store
223
self.control_weaves = control_store
224
# TODO: make sure to construct the right store classes, etc, depending
225
# on whether escaping is required.
226
self._warn_if_deprecated()
227
self._serializer = xml5.serializer_v5
230
return '%s(%r)' % (self.__class__.__name__,
231
self.bzrdir.transport.base)
234
return self.control_files.is_locked()
236
def lock_write(self):
237
self.control_files.lock_write()
240
self.control_files.lock_read()
242
def get_physical_lock_status(self):
243
return self.control_files.get_physical_lock_status()
246
def gather_stats(self, revid, committers=None):
247
"""Gather statistics from a revision id.
249
:param revid: The revision id to gather statistics from.
250
:param committers: Optional parameter controlling whether to grab
251
a count of committers.
252
:return: A dictionary of statistics. Currently this contains:
253
committers: The number of committers if requested.
254
firstrev: A tuple with timestamp, timezone for the penultimate left
255
most ancestor of revid, if revid is not the NULL_REVISION.
256
latestrev: A tuple with timestamp, timezone for revid, if revid is
257
not the NULL_REVISION.
261
result['committers'] = 0
262
if revid == _mod_revision.NULL_REVISION:
264
all_committers = set()
265
revisions = self.get_ancestry(revid)
266
# pop the leading None
268
first_revision = None
270
# ignore the revisions in the middle - just grab first and last
271
revisions = revisions[0], revisions[-1]
272
for revision in self.get_revisions(revisions):
273
if not first_revision:
274
first_revision = revision
276
all_committers.add(revision.committer)
277
last_revision = revision
279
result['committers'] = len(all_committers)
280
result['firstrev'] = first_revision.timestamp, first_revision.timezone
281
result['latestrev'] = last_revision.timestamp, last_revision.timezone
285
def missing_revision_ids(self, other, revision_id=None):
286
"""Return the revision ids that other has that this does not.
288
These are returned in topological order.
290
revision_id: only return revision ids included by revision_id.
292
return InterRepository.get(other, self).missing_revision_ids(revision_id)
296
"""Open the repository rooted at base.
298
For instance, if the repository is at URL/.bzr/repository,
299
Repository.open(URL) -> a Repository instance.
301
control = bzrdir.BzrDir.open(base)
302
return control.open_repository()
304
def copy_content_into(self, destination, revision_id=None, basis=None):
305
"""Make a complete copy of the content in self into destination.
307
This is a destructive operation! Do not use it on existing
310
return InterRepository.get(self, destination).copy_content(revision_id, basis)
312
def fetch(self, source, revision_id=None, pb=None):
313
"""Fetch the content required to construct revision_id from source.
315
If revision_id is None all content is copied.
317
return InterRepository.get(source, self).fetch(revision_id=revision_id,
320
def get_commit_builder(self, branch, parents, config, timestamp=None,
321
timezone=None, committer=None, revprops=None,
323
"""Obtain a CommitBuilder for this repository.
325
:param branch: Branch to commit to.
326
:param parents: Revision ids of the parents of the new revision.
327
:param config: Configuration to use.
328
:param timestamp: Optional timestamp recorded for commit.
329
:param timezone: Optional timezone for timestamp.
330
:param committer: Optional committer to set for commit.
331
:param revprops: Optional dictionary of revision properties.
332
:param revision_id: Optional revision id.
334
return _CommitBuilder(self, parents, config, timestamp, timezone,
335
committer, revprops, revision_id)
338
self.control_files.unlock()
341
def clone(self, a_bzrdir, revision_id=None, basis=None):
342
"""Clone this repository into a_bzrdir using the current format.
344
Currently no check is made that the format of this repository and
345
the bzrdir format are compatible. FIXME RBC 20060201.
347
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
348
# use target default format.
349
result = a_bzrdir.create_repository()
350
# FIXME RBC 20060209 split out the repository type to avoid this check ?
351
elif isinstance(a_bzrdir._format,
352
(bzrdir.BzrDirFormat4,
353
bzrdir.BzrDirFormat5,
354
bzrdir.BzrDirFormat6)):
355
result = a_bzrdir.open_repository()
357
result = self._format.initialize(a_bzrdir, shared=self.is_shared())
358
self.copy_content_into(result, revision_id, basis)
362
def has_revision(self, revision_id):
363
"""True if this repository has a copy of the revision."""
364
return self._revision_store.has_revision_id(revision_id,
365
self.get_transaction())
368
def get_revision_reconcile(self, revision_id):
369
"""'reconcile' helper routine that allows access to a revision always.
371
This variant of get_revision does not cross check the weave graph
372
against the revision one as get_revision does: but it should only
373
be used by reconcile, or reconcile-alike commands that are correcting
374
or testing the revision graph.
376
if not revision_id or not isinstance(revision_id, basestring):
377
raise errors.InvalidRevisionId(revision_id=revision_id,
379
return self._revision_store.get_revisions([revision_id],
380
self.get_transaction())[0]
382
def get_revisions(self, revision_ids):
383
return self._revision_store.get_revisions(revision_ids,
384
self.get_transaction())
387
def get_revision_xml(self, revision_id):
388
rev = self.get_revision(revision_id)
390
# the current serializer..
391
self._revision_store._serializer.write_revision(rev, rev_tmp)
393
return rev_tmp.getvalue()
396
def get_revision(self, revision_id):
397
"""Return the Revision object for a named revision"""
398
r = self.get_revision_reconcile(revision_id)
399
# weave corruption can lead to absent revision markers that should be
401
# the following test is reasonably cheap (it needs a single weave read)
402
# and the weave is cached in read transactions. In write transactions
403
# it is not cached but typically we only read a small number of
404
# revisions. For knits when they are introduced we will probably want
405
# to ensure that caching write transactions are in use.
406
inv = self.get_inventory_weave()
407
self._check_revision_parents(r, inv)
411
def get_deltas_for_revisions(self, revisions):
412
"""Produce a generator of revision deltas.
414
Note that the input is a sequence of REVISIONS, not revision_ids.
415
Trees will be held in memory until the generator exits.
416
Each delta is relative to the revision's lefthand predecessor.
418
required_trees = set()
419
for revision in revisions:
420
required_trees.add(revision.revision_id)
421
required_trees.update(revision.parent_ids[:1])
422
trees = dict((t.get_revision_id(), t) for
423
t in self.revision_trees(required_trees))
424
for revision in revisions:
425
if not revision.parent_ids:
426
old_tree = self.revision_tree(None)
428
old_tree = trees[revision.parent_ids[0]]
429
yield trees[revision.revision_id].changes_from(old_tree)
432
def get_revision_delta(self, revision_id):
433
"""Return the delta for one revision.
435
The delta is relative to the left-hand predecessor of the
438
r = self.get_revision(revision_id)
439
return list(self.get_deltas_for_revisions([r]))[0]
441
def _check_revision_parents(self, revision, inventory):
442
"""Private to Repository and Fetch.
444
This checks the parentage of revision in an inventory weave for
445
consistency and is only applicable to inventory-weave-for-ancestry
446
using repository formats & fetchers.
448
weave_parents = inventory.get_parents(revision.revision_id)
449
weave_names = inventory.versions()
450
for parent_id in revision.parent_ids:
451
if parent_id in weave_names:
452
# this parent must not be a ghost.
453
if not parent_id in weave_parents:
455
raise errors.CorruptRepository(self)
458
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
459
signature = gpg_strategy.sign(plaintext)
460
self._revision_store.add_revision_signature_text(revision_id,
462
self.get_transaction())
464
def fileids_altered_by_revision_ids(self, revision_ids):
465
"""Find the file ids and versions affected by revisions.
467
:param revisions: an iterable containing revision ids.
468
:return: a dictionary mapping altered file-ids to an iterable of
469
revision_ids. Each altered file-ids has the exact revision_ids that
470
altered it listed explicitly.
472
assert self._serializer.support_altered_by_hack, \
473
("fileids_altered_by_revision_ids only supported for branches "
474
"which store inventory as unnested xml, not on %r" % self)
475
selected_revision_ids = set(revision_ids)
476
w = self.get_inventory_weave()
479
# this code needs to read every new line in every inventory for the
480
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
481
# not present in one of those inventories is unnecessary but not
482
# harmful because we are filtering by the revision id marker in the
483
# inventory lines : we only select file ids altered in one of those
484
# revisions. We don't need to see all lines in the inventory because
485
# only those added in an inventory in rev X can contain a revision=X
487
unescape_revid_cache = {}
488
unescape_fileid_cache = {}
490
# jam 20061218 In a big fetch, this handles hundreds of thousands
491
# of lines, so it has had a lot of inlining and optimizing done.
492
# Sorry that it is a little bit messy.
493
# Move several functions to be local variables, since this is a long
495
search = self._file_ids_altered_regex.search
496
unescape = _unescape_xml
497
setdefault = result.setdefault
498
pb = ui.ui_factory.nested_progress_bar()
500
for line in w.iter_lines_added_or_present_in_versions(
501
selected_revision_ids, pb=pb):
505
# One call to match.group() returning multiple items is quite a
506
# bit faster than 2 calls to match.group() each returning 1
507
file_id, revision_id = match.group('file_id', 'revision_id')
509
# Inlining the cache lookups helps a lot when you make 170,000
510
# lines and 350k ids, versus 8.4 unique ids.
511
# Using a cache helps in 2 ways:
512
# 1) Avoids unnecessary decoding calls
513
# 2) Re-uses cached strings, which helps in future set and
515
# (2) is enough that removing encoding entirely along with
516
# the cache (so we are using plain strings) results in no
517
# performance improvement.
519
revision_id = unescape_revid_cache[revision_id]
521
unescaped = unescape(revision_id)
522
unescape_revid_cache[revision_id] = unescaped
523
revision_id = unescaped
525
if revision_id in selected_revision_ids:
527
file_id = unescape_fileid_cache[file_id]
529
unescaped = unescape(file_id)
530
unescape_fileid_cache[file_id] = unescaped
532
setdefault(file_id, set()).add(revision_id)
538
def get_inventory_weave(self):
539
return self.control_weaves.get_weave('inventory',
540
self.get_transaction())
543
def get_inventory(self, revision_id):
544
"""Get Inventory object by hash."""
545
return self.deserialise_inventory(
546
revision_id, self.get_inventory_xml(revision_id))
548
def deserialise_inventory(self, revision_id, xml):
549
"""Transform the xml into an inventory object.
551
:param revision_id: The expected revision id of the inventory.
552
:param xml: A serialised inventory.
554
result = self._serializer.read_inventory_from_string(xml)
555
result.root.revision = revision_id
558
def serialise_inventory(self, inv):
559
return self._serializer.write_inventory_to_string(inv)
562
def get_inventory_xml(self, revision_id):
563
"""Get inventory XML as a file object."""
565
assert isinstance(revision_id, basestring), type(revision_id)
566
iw = self.get_inventory_weave()
567
return iw.get_text(revision_id)
569
raise errors.HistoryMissing(self, 'inventory', revision_id)
572
def get_inventory_sha1(self, revision_id):
573
"""Return the sha1 hash of the inventory entry
575
return self.get_revision(revision_id).inventory_sha1
578
def get_revision_graph(self, revision_id=None):
579
"""Return a dictionary containing the revision graph.
581
:param revision_id: The revision_id to get a graph from. If None, then
582
the entire revision graph is returned. This is a deprecated mode of
583
operation and will be removed in the future.
584
:return: a dictionary of revision_id->revision_parents_list.
586
# special case NULL_REVISION
587
if revision_id == _mod_revision.NULL_REVISION:
589
a_weave = self.get_inventory_weave()
590
all_revisions = self._eliminate_revisions_not_present(
592
entire_graph = dict([(node, a_weave.get_parents(node)) for
593
node in all_revisions])
594
if revision_id is None:
596
elif revision_id not in entire_graph:
597
raise errors.NoSuchRevision(self, revision_id)
599
# add what can be reached from revision_id
601
pending = set([revision_id])
602
while len(pending) > 0:
604
result[node] = entire_graph[node]
605
for revision_id in result[node]:
606
if revision_id not in result:
607
pending.add(revision_id)
611
def get_revision_graph_with_ghosts(self, revision_ids=None):
612
"""Return a graph of the revisions with ghosts marked as applicable.
614
:param revision_ids: an iterable of revisions to graph or None for all.
615
:return: a Graph object with the graph reachable from revision_ids.
617
result = graph.Graph()
619
pending = set(self.all_revision_ids())
622
pending = set(revision_ids)
623
# special case NULL_REVISION
624
if _mod_revision.NULL_REVISION in pending:
625
pending.remove(_mod_revision.NULL_REVISION)
626
required = set(pending)
629
revision_id = pending.pop()
631
rev = self.get_revision(revision_id)
632
except errors.NoSuchRevision:
633
if revision_id in required:
636
result.add_ghost(revision_id)
638
for parent_id in rev.parent_ids:
639
# is this queued or done ?
640
if (parent_id not in pending and
641
parent_id not in done):
643
pending.add(parent_id)
644
result.add_node(revision_id, rev.parent_ids)
645
done.add(revision_id)
649
def get_revision_inventory(self, revision_id):
650
"""Return inventory of a past revision."""
651
# TODO: Unify this with get_inventory()
652
# bzr 0.0.6 and later imposes the constraint that the inventory_id
653
# must be the same as its revision, so this is trivial.
654
if revision_id is None:
655
# This does not make sense: if there is no revision,
656
# then it is the current tree inventory surely ?!
657
# and thus get_root_id() is something that looks at the last
658
# commit on the branch, and the get_root_id is an inventory check.
659
raise NotImplementedError
660
# return Inventory(self.get_root_id())
662
return self.get_inventory(revision_id)
666
"""Return True if this repository is flagged as a shared repository."""
667
raise NotImplementedError(self.is_shared)
670
def reconcile(self, other=None, thorough=False):
671
"""Reconcile this repository."""
672
from bzrlib.reconcile import RepoReconciler
673
reconciler = RepoReconciler(self, thorough=thorough)
674
reconciler.reconcile()
678
def revision_tree(self, revision_id):
679
"""Return Tree for a revision on this branch.
681
`revision_id` may be None for the empty tree revision.
683
# TODO: refactor this to use an existing revision object
684
# so we don't need to read it in twice.
685
if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
686
return RevisionTree(self, Inventory(root_id=None),
687
_mod_revision.NULL_REVISION)
689
inv = self.get_revision_inventory(revision_id)
690
return RevisionTree(self, inv, revision_id)
693
def revision_trees(self, revision_ids):
694
"""Return Tree for a revision on this branch.
696
`revision_id` may not be None or 'null:'"""
697
assert None not in revision_ids
698
assert _mod_revision.NULL_REVISION not in revision_ids
699
texts = self.get_inventory_weave().get_texts(revision_ids)
700
for text, revision_id in zip(texts, revision_ids):
701
inv = self.deserialise_inventory(revision_id, text)
702
yield RevisionTree(self, inv, revision_id)
705
def get_ancestry(self, revision_id):
706
"""Return a list of revision-ids integrated by a revision.
708
The first element of the list is always None, indicating the origin
709
revision. This might change when we have history horizons, or
710
perhaps we should have a new API.
712
This is topologically sorted.
714
if revision_id is None:
716
if not self.has_revision(revision_id):
717
raise errors.NoSuchRevision(self, revision_id)
718
w = self.get_inventory_weave()
719
candidates = w.get_ancestry(revision_id)
720
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
723
def print_file(self, file, revision_id):
724
"""Print `file` to stdout.
726
FIXME RBC 20060125 as John Meinel points out this is a bad api
727
- it writes to stdout, it assumes that that is valid etc. Fix
728
by creating a new more flexible convenience function.
730
tree = self.revision_tree(revision_id)
731
# use inventory as it was in that revision
732
file_id = tree.inventory.path2id(file)
734
# TODO: jam 20060427 Write a test for this code path
735
# it had a bug in it, and was raising the wrong
737
raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
738
tree.print_file(file_id)
740
def get_transaction(self):
741
return self.control_files.get_transaction()
743
def revision_parents(self, revid):
744
return self.get_inventory_weave().parent_names(revid)
747
def set_make_working_trees(self, new_value):
748
"""Set the policy flag for making working trees when creating branches.
750
This only applies to branches that use this repository.
752
The default is 'True'.
753
:param new_value: True to restore the default, False to disable making
756
raise NotImplementedError(self.set_make_working_trees)
758
def make_working_trees(self):
759
"""Returns the policy for making working trees on new branches."""
760
raise NotImplementedError(self.make_working_trees)
763
def sign_revision(self, revision_id, gpg_strategy):
764
plaintext = Testament.from_revision(self, revision_id).as_short_text()
765
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
768
def has_signature_for_revision_id(self, revision_id):
769
"""Query for a revision signature for revision_id in the repository."""
770
return self._revision_store.has_signature(revision_id,
771
self.get_transaction())
774
def get_signature_text(self, revision_id):
775
"""Return the text for a signature."""
776
return self._revision_store.get_signature_text(revision_id,
777
self.get_transaction())
780
def check(self, revision_ids):
781
"""Check consistency of all history of given revision_ids.
783
Different repository implementations should override _check().
785
:param revision_ids: A non-empty list of revision_ids whose ancestry
786
will be checked. Typically the last revision_id of a branch.
789
raise ValueError("revision_ids must be non-empty in %s.check"
791
return self._check(revision_ids)
793
def _check(self, revision_ids):
794
result = check.Check(self)
798
def _warn_if_deprecated(self):
799
global _deprecation_warning_done
800
if _deprecation_warning_done:
802
_deprecation_warning_done = True
803
warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
804
% (self._format, self.bzrdir.transport.base))
806
def supports_rich_root(self):
807
return self._format.rich_root_data
809
def _check_ascii_revisionid(self, revision_id, method):
810
"""Private helper for ascii-only repositories."""
811
# weave repositories refuse to store revisionids that are non-ascii.
812
if revision_id is not None:
813
# weaves require ascii revision ids.
814
if isinstance(revision_id, unicode):
816
revision_id.encode('ascii')
817
except UnicodeEncodeError:
818
raise errors.NonAsciiRevisionId(method, self)
821
class AllInOneRepository(Repository):
822
"""Legacy support - the repository behaviour for all-in-one branches."""
824
def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
825
# we reuse one control files instance.
826
dir_mode = a_bzrdir._control_files._dir_mode
827
file_mode = a_bzrdir._control_files._file_mode
829
def get_store(name, compressed=True, prefixed=False):
830
# FIXME: This approach of assuming stores are all entirely compressed
831
# or entirely uncompressed is tidy, but breaks upgrade from
832
# some existing branches where there's a mixture; we probably
833
# still want the option to look for both.
834
relpath = a_bzrdir._control_files._escape(name)
835
store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
836
prefixed=prefixed, compressed=compressed,
839
#if self._transport.should_cache():
840
# cache_path = os.path.join(self.cache_root, name)
841
# os.mkdir(cache_path)
842
# store = bzrlib.store.CachedStore(store, cache_path)
845
# not broken out yet because the controlweaves|inventory_store
846
# and text_store | weave_store bits are still different.
847
if isinstance(_format, RepositoryFormat4):
848
# cannot remove these - there is still no consistent api
849
# which allows access to this old info.
850
self.inventory_store = get_store('inventory-store')
851
text_store = get_store('text-store')
852
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
854
def get_commit_builder(self, branch, parents, config, timestamp=None,
855
timezone=None, committer=None, revprops=None,
857
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
858
return Repository.get_commit_builder(self, branch, parents, config,
859
timestamp, timezone, committer, revprops, revision_id)
863
"""AllInOne repositories cannot be shared."""
867
def set_make_working_trees(self, new_value):
868
"""Set the policy flag for making working trees when creating branches.
870
This only applies to branches that use this repository.
872
The default is 'True'.
873
:param new_value: True to restore the default, False to disable making
876
raise NotImplementedError(self.set_make_working_trees)
878
def make_working_trees(self):
879
"""Returns the policy for making working trees on new branches."""
883
def install_revision(repository, rev, revision_tree):
884
"""Install all revision data into a repository."""
887
for p_id in rev.parent_ids:
888
if repository.has_revision(p_id):
889
present_parents.append(p_id)
890
parent_trees[p_id] = repository.revision_tree(p_id)
892
parent_trees[p_id] = repository.revision_tree(None)
894
inv = revision_tree.inventory
895
entries = inv.iter_entries()
896
# backwards compatability hack: skip the root id.
897
if not repository.supports_rich_root():
898
path, root = entries.next()
899
if root.revision != rev.revision_id:
900
raise errors.IncompatibleRevision(repr(repository))
901
# Add the texts that are not already present
902
for path, ie in entries:
903
w = repository.weave_store.get_weave_or_empty(ie.file_id,
904
repository.get_transaction())
905
if ie.revision not in w:
907
# FIXME: TODO: The following loop *may* be overlapping/duplicate
908
# with InventoryEntry.find_previous_heads(). if it is, then there
909
# is a latent bug here where the parents may have ancestors of each
911
for revision, tree in parent_trees.iteritems():
912
if ie.file_id not in tree:
914
parent_id = tree.inventory[ie.file_id].revision
915
if parent_id in text_parents:
917
text_parents.append(parent_id)
919
vfile = repository.weave_store.get_weave_or_empty(ie.file_id,
920
repository.get_transaction())
921
lines = revision_tree.get_file(ie.file_id).readlines()
922
vfile.add_lines(rev.revision_id, text_parents, lines)
924
# install the inventory
925
repository.add_inventory(rev.revision_id, inv, present_parents)
926
except errors.RevisionAlreadyPresent:
928
repository.add_revision(rev.revision_id, rev, inv)
931
class MetaDirRepository(Repository):
932
"""Repositories in the new meta-dir layout."""
934
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
935
super(MetaDirRepository, self).__init__(_format,
941
dir_mode = self.control_files._dir_mode
942
file_mode = self.control_files._file_mode
946
"""Return True if this repository is flagged as a shared repository."""
947
return self.control_files._transport.has('shared-storage')
950
def set_make_working_trees(self, new_value):
951
"""Set the policy flag for making working trees when creating branches.
953
This only applies to branches that use this repository.
955
The default is 'True'.
956
:param new_value: True to restore the default, False to disable making
961
self.control_files._transport.delete('no-working-trees')
962
except errors.NoSuchFile:
965
self.control_files.put_utf8('no-working-trees', '')
967
def make_working_trees(self):
968
"""Returns the policy for making working trees on new branches."""
969
return not self.control_files._transport.has('no-working-trees')
972
class WeaveMetaDirRepository(MetaDirRepository):
973
"""A subclass of MetaDirRepository to set weave specific policy."""
975
def get_commit_builder(self, branch, parents, config, timestamp=None,
976
timezone=None, committer=None, revprops=None,
978
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
979
return MetaDirRepository.get_commit_builder(self, branch, parents,
980
config, timestamp, timezone, committer, revprops, revision_id)
983
class KnitRepository(MetaDirRepository):
984
"""Knit format repository."""
986
def _warn_if_deprecated(self):
987
# This class isn't deprecated
990
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
991
inv_vf.add_lines_with_ghosts(revid, parents, lines)
994
def _all_revision_ids(self):
995
"""See Repository.all_revision_ids()."""
996
# Knits get the revision graph from the index of the revision knit, so
997
# it's always possible even if they're on an unlistable transport.
998
return self._revision_store.all_revision_ids(self.get_transaction())
1000
def fileid_involved_between_revs(self, from_revid, to_revid):
1001
"""Find file_id(s) which are involved in the changes between revisions.
1003
This determines the set of revisions which are involved, and then
1004
finds all file ids affected by those revisions.
1006
vf = self._get_revision_vf()
1007
from_set = set(vf.get_ancestry(from_revid))
1008
to_set = set(vf.get_ancestry(to_revid))
1009
changed = to_set.difference(from_set)
1010
return self._fileid_involved_by_set(changed)
1012
def fileid_involved(self, last_revid=None):
1013
"""Find all file_ids modified in the ancestry of last_revid.
1015
:param last_revid: If None, last_revision() will be used.
1018
changed = set(self.all_revision_ids())
1020
changed = set(self.get_ancestry(last_revid))
1022
changed.remove(None)
1023
return self._fileid_involved_by_set(changed)
1026
def get_ancestry(self, revision_id):
1027
"""Return a list of revision-ids integrated by a revision.
1029
This is topologically sorted.
1031
if revision_id is None:
1033
vf = self._get_revision_vf()
1035
return [None] + vf.get_ancestry(revision_id)
1036
except errors.RevisionNotPresent:
1037
raise errors.NoSuchRevision(self, revision_id)
1040
def get_revision(self, revision_id):
1041
"""Return the Revision object for a named revision"""
1042
return self.get_revision_reconcile(revision_id)
1045
def get_revision_graph(self, revision_id=None):
1046
"""Return a dictionary containing the revision graph.
1048
:param revision_id: The revision_id to get a graph from. If None, then
1049
the entire revision graph is returned. This is a deprecated mode of
1050
operation and will be removed in the future.
1051
:return: a dictionary of revision_id->revision_parents_list.
1053
# special case NULL_REVISION
1054
if revision_id == _mod_revision.NULL_REVISION:
1056
a_weave = self._get_revision_vf()
1057
entire_graph = a_weave.get_graph()
1058
if revision_id is None:
1059
return a_weave.get_graph()
1060
elif revision_id not in a_weave:
1061
raise errors.NoSuchRevision(self, revision_id)
1063
# add what can be reached from revision_id
1065
pending = set([revision_id])
1066
while len(pending) > 0:
1067
node = pending.pop()
1068
result[node] = a_weave.get_parents(node)
1069
for revision_id in result[node]:
1070
if revision_id not in result:
1071
pending.add(revision_id)
1075
def get_revision_graph_with_ghosts(self, revision_ids=None):
1076
"""Return a graph of the revisions with ghosts marked as applicable.
1078
:param revision_ids: an iterable of revisions to graph or None for all.
1079
:return: a Graph object with the graph reachable from revision_ids.
1081
result = graph.Graph()
1082
vf = self._get_revision_vf()
1083
versions = set(vf.versions())
1084
if not revision_ids:
1085
pending = set(self.all_revision_ids())
1088
pending = set(revision_ids)
1089
# special case NULL_REVISION
1090
if _mod_revision.NULL_REVISION in pending:
1091
pending.remove(_mod_revision.NULL_REVISION)
1092
required = set(pending)
1095
revision_id = pending.pop()
1096
if not revision_id in versions:
1097
if revision_id in required:
1098
raise errors.NoSuchRevision(self, revision_id)
1100
result.add_ghost(revision_id)
1101
# mark it as done so we don't try for it again.
1102
done.add(revision_id)
1104
parent_ids = vf.get_parents_with_ghosts(revision_id)
1105
for parent_id in parent_ids:
1106
# is this queued or done ?
1107
if (parent_id not in pending and
1108
parent_id not in done):
1110
pending.add(parent_id)
1111
result.add_node(revision_id, parent_ids)
1112
done.add(revision_id)
1115
def _get_revision_vf(self):
1116
""":return: a versioned file containing the revisions."""
1117
vf = self._revision_store.get_revision_file(self.get_transaction())
1121
def reconcile(self, other=None, thorough=False):
1122
"""Reconcile this repository."""
1123
from bzrlib.reconcile import KnitReconciler
1124
reconciler = KnitReconciler(self, thorough=thorough)
1125
reconciler.reconcile()
1128
def revision_parents(self, revision_id):
1129
return self._get_revision_vf().get_parents(revision_id)
1132
class KnitRepository2(KnitRepository):
1134
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
1135
control_store, text_store):
1136
KnitRepository.__init__(self, _format, a_bzrdir, control_files,
1137
_revision_store, control_store, text_store)
1138
self._serializer = xml6.serializer_v6
1140
def deserialise_inventory(self, revision_id, xml):
1141
"""Transform the xml into an inventory object.
1143
:param revision_id: The expected revision id of the inventory.
1144
:param xml: A serialised inventory.
1146
result = self._serializer.read_inventory_from_string(xml)
1147
assert result.root.revision is not None
1150
def serialise_inventory(self, inv):
1151
"""Transform the inventory object into XML text.
1153
:param revision_id: The expected revision id of the inventory.
1154
:param xml: A serialised inventory.
1156
assert inv.revision_id is not None
1157
assert inv.root.revision is not None
1158
return KnitRepository.serialise_inventory(self, inv)
1160
def get_commit_builder(self, branch, parents, config, timestamp=None,
1161
timezone=None, committer=None, revprops=None,
1163
"""Obtain a CommitBuilder for this repository.
1165
:param branch: Branch to commit to.
1166
:param parents: Revision ids of the parents of the new revision.
1167
:param config: Configuration to use.
1168
:param timestamp: Optional timestamp recorded for commit.
1169
:param timezone: Optional timezone for timestamp.
1170
:param committer: Optional committer to set for commit.
1171
:param revprops: Optional dictionary of revision properties.
1172
:param revision_id: Optional revision id.
1174
return RootCommitBuilder(self, parents, config, timestamp, timezone,
1175
committer, revprops, revision_id)
1178
class RepositoryFormatRegistry(registry.Registry):
1179
"""Registry of RepositoryFormats.
1183
format_registry = RepositoryFormatRegistry()
1184
"""Registry of formats, indexed by their identifying format string."""
1187
class RepositoryFormat(object):
1188
"""A repository format.
1190
Formats provide three things:
1191
* An initialization routine to construct repository data on disk.
1192
* a format string which is used when the BzrDir supports versioned
1194
* an open routine which returns a Repository instance.
1196
Formats are placed in an dict by their format string for reference
1197
during opening. These should be subclasses of RepositoryFormat
1200
Once a format is deprecated, just deprecate the initialize and open
1201
methods on the format class. Do not deprecate the object, as the
1202
object will be created every system load.
1204
Common instance attributes:
1205
_matchingbzrdir - the bzrdir format that the repository format was
1206
originally written to work with. This can be used if manually
1207
constructing a bzrdir and repository, or more commonly for test suite
1212
return "<%s>" % self.__class__.__name__
1215
def find_format(klass, a_bzrdir):
1216
"""Return the format for the repository object in a_bzrdir.
1218
This is used by bzr native formats that have a "format" file in
1219
the repository. Other methods may be used by different types of
1223
transport = a_bzrdir.get_repository_transport(None)
1224
format_string = transport.get("format").read()
1225
return format_registry.get(format_string)
1226
except errors.NoSuchFile:
1227
raise errors.NoRepositoryPresent(a_bzrdir)
1229
raise errors.UnknownFormatError(format=format_string)
1232
def register_format(klass, format):
1233
format_registry.register(format.get_format_string(), format)
1236
def unregister_format(klass, format):
1237
format_registry.remove(format.get_format_string())
1240
def get_default_format(klass):
1241
"""Return the current default format."""
1242
from bzrlib import bzrdir
1243
return bzrdir.format_registry.make_bzrdir('default').repository_format
1245
def _get_control_store(self, repo_transport, control_files):
1246
"""Return the control store for this repository."""
1247
raise NotImplementedError(self._get_control_store)
1249
def get_format_string(self):
1250
"""Return the ASCII format string that identifies this format.
1252
Note that in pre format ?? repositories the format string is
1253
not permitted nor written to disk.
1255
raise NotImplementedError(self.get_format_string)
1257
def get_format_description(self):
1258
"""Return the short description for this format."""
1259
raise NotImplementedError(self.get_format_description)
1261
def _get_revision_store(self, repo_transport, control_files):
1262
"""Return the revision store object for this a_bzrdir."""
1263
raise NotImplementedError(self._get_revision_store)
1265
def _get_text_rev_store(self,
1272
"""Common logic for getting a revision store for a repository.
1274
see self._get_revision_store for the subclass-overridable method to
1275
get the store for a repository.
1277
from bzrlib.store.revision.text import TextRevisionStore
1278
dir_mode = control_files._dir_mode
1279
file_mode = control_files._file_mode
1280
text_store =TextStore(transport.clone(name),
1282
compressed=compressed,
1284
file_mode=file_mode)
1285
_revision_store = TextRevisionStore(text_store, serializer)
1286
return _revision_store
1288
def _get_versioned_file_store(self,
1293
versionedfile_class=weave.WeaveFile,
1294
versionedfile_kwargs={},
1296
weave_transport = control_files._transport.clone(name)
1297
dir_mode = control_files._dir_mode
1298
file_mode = control_files._file_mode
1299
return VersionedFileStore(weave_transport, prefixed=prefixed,
1301
file_mode=file_mode,
1302
versionedfile_class=versionedfile_class,
1303
versionedfile_kwargs=versionedfile_kwargs,
1306
def initialize(self, a_bzrdir, shared=False):
1307
"""Initialize a repository of this format in a_bzrdir.
1309
:param a_bzrdir: The bzrdir to put the new repository in it.
1310
:param shared: The repository should be initialized as a sharable one.
1312
This may raise UninitializableFormat if shared repository are not
1313
compatible the a_bzrdir.
1316
def is_supported(self):
1317
"""Is this format supported?
1319
Supported formats must be initializable and openable.
1320
Unsupported formats may not support initialization or committing or
1321
some other features depending on the reason for not being supported.
1325
def check_conversion_target(self, target_format):
1326
raise NotImplementedError(self.check_conversion_target)
1328
def open(self, a_bzrdir, _found=False):
1329
"""Return an instance of this format for the bzrdir a_bzrdir.
1331
_found is a private parameter, do not use it.
1333
raise NotImplementedError(self.open)
1336
class PreSplitOutRepositoryFormat(RepositoryFormat):
1337
"""Base class for the pre split out repository formats."""
1339
rich_root_data = False
1341
def initialize(self, a_bzrdir, shared=False, _internal=False):
1342
"""Create a weave repository.
1344
TODO: when creating split out bzr branch formats, move this to a common
1345
base for Format5, Format6. or something like that.
1348
raise errors.IncompatibleFormat(self, a_bzrdir._format)
1351
# always initialized when the bzrdir is.
1352
return self.open(a_bzrdir, _found=True)
1354
# Create an empty weave
1356
weavefile.write_weave_v5(weave.Weave(), sio)
1357
empty_weave = sio.getvalue()
1359
mutter('creating repository in %s.', a_bzrdir.transport.base)
1360
dirs = ['revision-store', 'weaves']
1361
files = [('inventory.weave', StringIO(empty_weave)),
1364
# FIXME: RBC 20060125 don't peek under the covers
1365
# NB: no need to escape relative paths that are url safe.
1366
control_files = lockable_files.LockableFiles(a_bzrdir.transport,
1367
'branch-lock', lockable_files.TransportLock)
1368
control_files.create_lock()
1369
control_files.lock_write()
1370
control_files._transport.mkdir_multi(dirs,
1371
mode=control_files._dir_mode)
1373
for file, content in files:
1374
control_files.put(file, content)
1376
control_files.unlock()
1377
return self.open(a_bzrdir, _found=True)
1379
def _get_control_store(self, repo_transport, control_files):
1380
"""Return the control store for this repository."""
1381
return self._get_versioned_file_store('',
1386
def _get_text_store(self, transport, control_files):
1387
"""Get a store for file texts for this format."""
1388
raise NotImplementedError(self._get_text_store)
1390
def open(self, a_bzrdir, _found=False):
1391
"""See RepositoryFormat.open()."""
1393
# we are being called directly and must probe.
1394
raise NotImplementedError
1396
repo_transport = a_bzrdir.get_repository_transport(None)
1397
control_files = a_bzrdir._control_files
1398
text_store = self._get_text_store(repo_transport, control_files)
1399
control_store = self._get_control_store(repo_transport, control_files)
1400
_revision_store = self._get_revision_store(repo_transport, control_files)
1401
return AllInOneRepository(_format=self,
1403
_revision_store=_revision_store,
1404
control_store=control_store,
1405
text_store=text_store)
1407
def check_conversion_target(self, target_format):
1411
class RepositoryFormat4(PreSplitOutRepositoryFormat):
1412
"""Bzr repository format 4.
1414
This repository format has:
1416
- TextStores for texts, inventories,revisions.
1418
This format is deprecated: it indexes texts using a text id which is
1419
removed in format 5; initialization and write support for this format
1424
super(RepositoryFormat4, self).__init__()
1425
self._matchingbzrdir = bzrdir.BzrDirFormat4()
1427
def get_format_description(self):
1428
"""See RepositoryFormat.get_format_description()."""
1429
return "Repository format 4"
1431
def initialize(self, url, shared=False, _internal=False):
1432
"""Format 4 branches cannot be created."""
1433
raise errors.UninitializableFormat(self)
1435
def is_supported(self):
1436
"""Format 4 is not supported.
1438
It is not supported because the model changed from 4 to 5 and the
1439
conversion logic is expensive - so doing it on the fly was not
1444
def _get_control_store(self, repo_transport, control_files):
1445
"""Format 4 repositories have no formal control store at this point.
1447
This will cause any control-file-needing apis to fail - this is desired.
1451
def _get_revision_store(self, repo_transport, control_files):
1452
"""See RepositoryFormat._get_revision_store()."""
1453
from bzrlib.xml4 import serializer_v4
1454
return self._get_text_rev_store(repo_transport,
1457
serializer=serializer_v4)
1459
def _get_text_store(self, transport, control_files):
1460
"""See RepositoryFormat._get_text_store()."""
1463
class RepositoryFormat5(PreSplitOutRepositoryFormat):
1464
"""Bzr control format 5.
1466
This repository format has:
1467
- weaves for file texts and inventory
1469
- TextStores for revisions and signatures.
1473
super(RepositoryFormat5, self).__init__()
1474
self._matchingbzrdir = bzrdir.BzrDirFormat5()
1476
def get_format_description(self):
1477
"""See RepositoryFormat.get_format_description()."""
1478
return "Weave repository format 5"
1480
def _get_revision_store(self, repo_transport, control_files):
1481
"""See RepositoryFormat._get_revision_store()."""
1482
"""Return the revision store object for this a_bzrdir."""
1483
return self._get_text_rev_store(repo_transport,
1488
def _get_text_store(self, transport, control_files):
1489
"""See RepositoryFormat._get_text_store()."""
1490
return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
1493
class RepositoryFormat6(PreSplitOutRepositoryFormat):
1494
"""Bzr control format 6.
1496
This repository format has:
1497
- weaves for file texts and inventory
1498
- hash subdirectory based stores.
1499
- TextStores for revisions and signatures.
1503
super(RepositoryFormat6, self).__init__()
1504
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1506
def get_format_description(self):
1507
"""See RepositoryFormat.get_format_description()."""
1508
return "Weave repository format 6"
1510
def _get_revision_store(self, repo_transport, control_files):
1511
"""See RepositoryFormat._get_revision_store()."""
1512
return self._get_text_rev_store(repo_transport,
1518
def _get_text_store(self, transport, control_files):
1519
"""See RepositoryFormat._get_text_store()."""
1520
return self._get_versioned_file_store('weaves', transport, control_files)
1523
class MetaDirRepositoryFormat(RepositoryFormat):
1524
"""Common base class for the new repositories using the metadir layout."""
1526
rich_root_data = False
1529
super(MetaDirRepositoryFormat, self).__init__()
1530
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1532
def _create_control_files(self, a_bzrdir):
1533
"""Create the required files and the initial control_files object."""
1534
# FIXME: RBC 20060125 don't peek under the covers
1535
# NB: no need to escape relative paths that are url safe.
1536
repository_transport = a_bzrdir.get_repository_transport(self)
1537
control_files = lockable_files.LockableFiles(repository_transport,
1538
'lock', lockdir.LockDir)
1539
control_files.create_lock()
1540
return control_files
1542
def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
1543
"""Upload the initial blank content."""
1544
control_files = self._create_control_files(a_bzrdir)
1545
control_files.lock_write()
1547
control_files._transport.mkdir_multi(dirs,
1548
mode=control_files._dir_mode)
1549
for file, content in files:
1550
control_files.put(file, content)
1551
for file, content in utf8_files:
1552
control_files.put_utf8(file, content)
1554
control_files.put_utf8('shared-storage', '')
1556
control_files.unlock()
1559
class RepositoryFormat7(MetaDirRepositoryFormat):
1560
"""Bzr repository 7.
1562
This repository format has:
1563
- weaves for file texts and inventory
1564
- hash subdirectory based stores.
1565
- TextStores for revisions and signatures.
1566
- a format marker of its own
1567
- an optional 'shared-storage' flag
1568
- an optional 'no-working-trees' flag
1571
def _get_control_store(self, repo_transport, control_files):
1572
"""Return the control store for this repository."""
1573
return self._get_versioned_file_store('',
1578
def get_format_string(self):
1579
"""See RepositoryFormat.get_format_string()."""
1580
return "Bazaar-NG Repository format 7"
1582
def get_format_description(self):
1583
"""See RepositoryFormat.get_format_description()."""
1584
return "Weave repository format 7"
1586
def check_conversion_target(self, target_format):
1589
def _get_revision_store(self, repo_transport, control_files):
1590
"""See RepositoryFormat._get_revision_store()."""
1591
return self._get_text_rev_store(repo_transport,
1598
def _get_text_store(self, transport, control_files):
1599
"""See RepositoryFormat._get_text_store()."""
1600
return self._get_versioned_file_store('weaves',
1604
def initialize(self, a_bzrdir, shared=False):
1605
"""Create a weave repository.
1607
:param shared: If true the repository will be initialized as a shared
1610
# Create an empty weave
1612
weavefile.write_weave_v5(weave.Weave(), sio)
1613
empty_weave = sio.getvalue()
1615
mutter('creating repository in %s.', a_bzrdir.transport.base)
1616
dirs = ['revision-store', 'weaves']
1617
files = [('inventory.weave', StringIO(empty_weave)),
1619
utf8_files = [('format', self.get_format_string())]
1621
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1622
return self.open(a_bzrdir=a_bzrdir, _found=True)
1624
def open(self, a_bzrdir, _found=False, _override_transport=None):
1625
"""See RepositoryFormat.open().
1627
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1628
repository at a slightly different url
1629
than normal. I.e. during 'upgrade'.
1632
format = RepositoryFormat.find_format(a_bzrdir)
1633
assert format.__class__ == self.__class__
1634
if _override_transport is not None:
1635
repo_transport = _override_transport
1637
repo_transport = a_bzrdir.get_repository_transport(None)
1638
control_files = lockable_files.LockableFiles(repo_transport,
1639
'lock', lockdir.LockDir)
1640
text_store = self._get_text_store(repo_transport, control_files)
1641
control_store = self._get_control_store(repo_transport, control_files)
1642
_revision_store = self._get_revision_store(repo_transport, control_files)
1643
return WeaveMetaDirRepository(_format=self,
1645
control_files=control_files,
1646
_revision_store=_revision_store,
1647
control_store=control_store,
1648
text_store=text_store)
1651
class RepositoryFormatKnit(MetaDirRepositoryFormat):
1652
"""Bzr repository knit format (generalized).
1654
This repository format has:
1655
- knits for file texts and inventory
1656
- hash subdirectory based stores.
1657
- knits for revisions and signatures
1658
- TextStores for revisions and signatures.
1659
- a format marker of its own
1660
- an optional 'shared-storage' flag
1661
- an optional 'no-working-trees' flag
1665
def _get_control_store(self, repo_transport, control_files):
1666
"""Return the control store for this repository."""
1667
return VersionedFileStore(
1670
file_mode=control_files._file_mode,
1671
versionedfile_class=knit.KnitVersionedFile,
1672
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
1675
def _get_revision_store(self, repo_transport, control_files):
1676
"""See RepositoryFormat._get_revision_store()."""
1677
from bzrlib.store.revision.knit import KnitRevisionStore
1678
versioned_file_store = VersionedFileStore(
1680
file_mode=control_files._file_mode,
1683
versionedfile_class=knit.KnitVersionedFile,
1684
versionedfile_kwargs={'delta':False,
1685
'factory':knit.KnitPlainFactory(),
1689
return KnitRevisionStore(versioned_file_store)
1691
def _get_text_store(self, transport, control_files):
1692
"""See RepositoryFormat._get_text_store()."""
1693
return self._get_versioned_file_store('knits',
1696
versionedfile_class=knit.KnitVersionedFile,
1697
versionedfile_kwargs={
1698
'create_parent_dir':True,
1699
'delay_create':True,
1700
'dir_mode':control_files._dir_mode,
1704
def initialize(self, a_bzrdir, shared=False):
1705
"""Create a knit format 1 repository.
1707
:param a_bzrdir: bzrdir to contain the new repository; must already
1709
:param shared: If true the repository will be initialized as a shared
1712
mutter('creating repository in %s.', a_bzrdir.transport.base)
1713
dirs = ['revision-store', 'knits']
1715
utf8_files = [('format', self.get_format_string())]
1717
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1718
repo_transport = a_bzrdir.get_repository_transport(None)
1719
control_files = lockable_files.LockableFiles(repo_transport,
1720
'lock', lockdir.LockDir)
1721
control_store = self._get_control_store(repo_transport, control_files)
1722
transaction = transactions.WriteTransaction()
1723
# trigger a write of the inventory store.
1724
control_store.get_weave_or_empty('inventory', transaction)
1725
_revision_store = self._get_revision_store(repo_transport, control_files)
1726
# the revision id here is irrelevant: it will not be stored, and cannot
1728
_revision_store.has_revision_id('A', transaction)
1729
_revision_store.get_signature_file(transaction)
1730
return self.open(a_bzrdir=a_bzrdir, _found=True)
1732
def open(self, a_bzrdir, _found=False, _override_transport=None):
1733
"""See RepositoryFormat.open().
1735
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1736
repository at a slightly different url
1737
than normal. I.e. during 'upgrade'.
1740
format = RepositoryFormat.find_format(a_bzrdir)
1741
assert format.__class__ == self.__class__
1742
if _override_transport is not None:
1743
repo_transport = _override_transport
1745
repo_transport = a_bzrdir.get_repository_transport(None)
1746
control_files = lockable_files.LockableFiles(repo_transport,
1747
'lock', lockdir.LockDir)
1748
text_store = self._get_text_store(repo_transport, control_files)
1749
control_store = self._get_control_store(repo_transport, control_files)
1750
_revision_store = self._get_revision_store(repo_transport, control_files)
1751
return KnitRepository(_format=self,
1753
control_files=control_files,
1754
_revision_store=_revision_store,
1755
control_store=control_store,
1756
text_store=text_store)
1759
class RepositoryFormatKnit1(RepositoryFormatKnit):
1760
"""Bzr repository knit format 1.
1762
This repository format has:
1763
- knits for file texts and inventory
1764
- hash subdirectory based stores.
1765
- knits for revisions and signatures
1766
- TextStores for revisions and signatures.
1767
- a format marker of its own
1768
- an optional 'shared-storage' flag
1769
- an optional 'no-working-trees' flag
1772
This format was introduced in bzr 0.8.
1774
def get_format_string(self):
1775
"""See RepositoryFormat.get_format_string()."""
1776
return "Bazaar-NG Knit Repository Format 1"
1778
def get_format_description(self):
1779
"""See RepositoryFormat.get_format_description()."""
1780
return "Knit repository format 1"
1782
def check_conversion_target(self, target_format):
1786
class RepositoryFormatKnit2(RepositoryFormatKnit):
1787
"""Bzr repository knit format 2.
1789
THIS FORMAT IS EXPERIMENTAL
1790
This repository format has:
1791
- knits for file texts and inventory
1792
- hash subdirectory based stores.
1793
- knits for revisions and signatures
1794
- TextStores for revisions and signatures.
1795
- a format marker of its own
1796
- an optional 'shared-storage' flag
1797
- an optional 'no-working-trees' flag
1799
- Support for recording full info about the tree root
1803
rich_root_data = True
1805
def get_format_string(self):
1806
"""See RepositoryFormat.get_format_string()."""
1807
return "Bazaar Knit Repository Format 2\n"
1809
def get_format_description(self):
1810
"""See RepositoryFormat.get_format_description()."""
1811
return "Knit repository format 2"
1813
def check_conversion_target(self, target_format):
1814
if not target_format.rich_root_data:
1815
raise errors.BadConversionTarget(
1816
'Does not support rich root data.', target_format)
1818
def open(self, a_bzrdir, _found=False, _override_transport=None):
1819
"""See RepositoryFormat.open().
1821
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1822
repository at a slightly different url
1823
than normal. I.e. during 'upgrade'.
1826
format = RepositoryFormat.find_format(a_bzrdir)
1827
assert format.__class__ == self.__class__
1828
if _override_transport is not None:
1829
repo_transport = _override_transport
1831
repo_transport = a_bzrdir.get_repository_transport(None)
1832
control_files = lockable_files.LockableFiles(repo_transport, 'lock',
1834
text_store = self._get_text_store(repo_transport, control_files)
1835
control_store = self._get_control_store(repo_transport, control_files)
1836
_revision_store = self._get_revision_store(repo_transport, control_files)
1837
return KnitRepository2(_format=self,
1839
control_files=control_files,
1840
_revision_store=_revision_store,
1841
control_store=control_store,
1842
text_store=text_store)
1846
# formats which have no format string are not discoverable
1847
# and not independently creatable, so are not registered.
1848
RepositoryFormat.register_format(RepositoryFormat7())
1849
# KEEP in sync with bzrdir.format_registry default
1850
RepositoryFormat.register_format(RepositoryFormatKnit1())
1851
RepositoryFormat.register_format(RepositoryFormatKnit2())
1852
_legacy_formats = [RepositoryFormat4(),
1853
RepositoryFormat5(),
1854
RepositoryFormat6()]
1857
class InterRepository(InterObject):
1858
"""This class represents operations taking place between two repositories.
1860
Its instances have methods like copy_content and fetch, and contain
1861
references to the source and target repositories these operations can be
1864
Often we will provide convenience methods on 'repository' which carry out
1865
operations with another repository - they will always forward to
1866
InterRepository.get(other).method_name(parameters).
1870
"""The available optimised InterRepository types."""
1872
def copy_content(self, revision_id=None, basis=None):
1873
raise NotImplementedError(self.copy_content)
1875
def fetch(self, revision_id=None, pb=None):
1876
"""Fetch the content required to construct revision_id.
1878
The content is copied from self.source to self.target.
1880
:param revision_id: if None all content is copied, if NULL_REVISION no
1882
:param pb: optional progress bar to use for progress reports. If not
1883
provided a default one will be created.
1885
Returns the copied revision count and the failed revisions in a tuple:
1888
raise NotImplementedError(self.fetch)
1891
def missing_revision_ids(self, revision_id=None):
1892
"""Return the revision ids that source has that target does not.
1894
These are returned in topological order.
1896
:param revision_id: only return revision ids included by this
1899
# generic, possibly worst case, slow code path.
1900
target_ids = set(self.target.all_revision_ids())
1901
if revision_id is not None:
1902
source_ids = self.source.get_ancestry(revision_id)
1903
assert source_ids[0] is None
1906
source_ids = self.source.all_revision_ids()
1907
result_set = set(source_ids).difference(target_ids)
1908
# this may look like a no-op: its not. It preserves the ordering
1909
# other_ids had while only returning the members from other_ids
1910
# that we've decided we need.
1911
return [rev_id for rev_id in source_ids if rev_id in result_set]
1914
class InterSameDataRepository(InterRepository):
1915
"""Code for converting between repositories that represent the same data.
1917
Data format and model must match for this to work.
1920
_matching_repo_format = RepositoryFormat4()
1921
"""Repository format for testing with."""
1924
def is_compatible(source, target):
1925
if not isinstance(source, Repository):
1927
if not isinstance(target, Repository):
1929
if source._format.rich_root_data == target._format.rich_root_data:
1935
def copy_content(self, revision_id=None, basis=None):
1936
"""Make a complete copy of the content in self into destination.
1938
This is a destructive operation! Do not use it on existing
1941
:param revision_id: Only copy the content needed to construct
1942
revision_id and its parents.
1943
:param basis: Copy the needed data preferentially from basis.
1946
self.target.set_make_working_trees(self.source.make_working_trees())
1947
except NotImplementedError:
1949
# grab the basis available data
1950
if basis is not None:
1951
self.target.fetch(basis, revision_id=revision_id)
1952
# but don't bother fetching if we have the needed data now.
1953
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1954
self.target.has_revision(revision_id)):
1956
self.target.fetch(self.source, revision_id=revision_id)
1959
def fetch(self, revision_id=None, pb=None):
1960
"""See InterRepository.fetch()."""
1961
from bzrlib.fetch import GenericRepoFetcher
1962
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1963
self.source, self.source._format, self.target,
1964
self.target._format)
1965
f = GenericRepoFetcher(to_repository=self.target,
1966
from_repository=self.source,
1967
last_revision=revision_id,
1969
return f.count_copied, f.failed_revisions
1972
class InterWeaveRepo(InterSameDataRepository):
1973
"""Optimised code paths between Weave based repositories."""
1975
_matching_repo_format = RepositoryFormat7()
1976
"""Repository format for testing with."""
1979
def is_compatible(source, target):
1980
"""Be compatible with known Weave formats.
1982
We don't test for the stores being of specific types because that
1983
could lead to confusing results, and there is no need to be
1987
return (isinstance(source._format, (RepositoryFormat5,
1989
RepositoryFormat7)) and
1990
isinstance(target._format, (RepositoryFormat5,
1992
RepositoryFormat7)))
1993
except AttributeError:
1997
def copy_content(self, revision_id=None, basis=None):
1998
"""See InterRepository.copy_content()."""
1999
# weave specific optimised path:
2000
if basis is not None:
2001
# copy the basis in, then fetch remaining data.
2002
basis.copy_content_into(self.target, revision_id)
2003
# the basis copy_content_into could miss-set this.
2005
self.target.set_make_working_trees(self.source.make_working_trees())
2006
except NotImplementedError:
2008
self.target.fetch(self.source, revision_id=revision_id)
2011
self.target.set_make_working_trees(self.source.make_working_trees())
2012
except NotImplementedError:
2014
# FIXME do not peek!
2015
if self.source.control_files._transport.listable():
2016
pb = ui.ui_factory.nested_progress_bar()
2018
self.target.weave_store.copy_all_ids(
2019
self.source.weave_store,
2021
from_transaction=self.source.get_transaction(),
2022
to_transaction=self.target.get_transaction())
2023
pb.update('copying inventory', 0, 1)
2024
self.target.control_weaves.copy_multi(
2025
self.source.control_weaves, ['inventory'],
2026
from_transaction=self.source.get_transaction(),
2027
to_transaction=self.target.get_transaction())
2028
self.target._revision_store.text_store.copy_all_ids(
2029
self.source._revision_store.text_store,
2034
self.target.fetch(self.source, revision_id=revision_id)
2037
def fetch(self, revision_id=None, pb=None):
2038
"""See InterRepository.fetch()."""
2039
from bzrlib.fetch import GenericRepoFetcher
2040
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2041
self.source, self.source._format, self.target, self.target._format)
2042
f = GenericRepoFetcher(to_repository=self.target,
2043
from_repository=self.source,
2044
last_revision=revision_id,
2046
return f.count_copied, f.failed_revisions
2049
def missing_revision_ids(self, revision_id=None):
2050
"""See InterRepository.missing_revision_ids()."""
2051
# we want all revisions to satisfy revision_id in source.
2052
# but we don't want to stat every file here and there.
2053
# we want then, all revisions other needs to satisfy revision_id
2054
# checked, but not those that we have locally.
2055
# so the first thing is to get a subset of the revisions to
2056
# satisfy revision_id in source, and then eliminate those that
2057
# we do already have.
2058
# this is slow on high latency connection to self, but as as this
2059
# disk format scales terribly for push anyway due to rewriting
2060
# inventory.weave, this is considered acceptable.
2062
if revision_id is not None:
2063
source_ids = self.source.get_ancestry(revision_id)
2064
assert source_ids[0] is None
2067
source_ids = self.source._all_possible_ids()
2068
source_ids_set = set(source_ids)
2069
# source_ids is the worst possible case we may need to pull.
2070
# now we want to filter source_ids against what we actually
2071
# have in target, but don't try to check for existence where we know
2072
# we do not have a revision as that would be pointless.
2073
target_ids = set(self.target._all_possible_ids())
2074
possibly_present_revisions = target_ids.intersection(source_ids_set)
2075
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
2076
required_revisions = source_ids_set.difference(actually_present_revisions)
2077
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
2078
if revision_id is not None:
2079
# we used get_ancestry to determine source_ids then we are assured all
2080
# revisions referenced are present as they are installed in topological order.
2081
# and the tip revision was validated by get_ancestry.
2082
return required_topo_revisions
2084
# if we just grabbed the possibly available ids, then
2085
# we only have an estimate of whats available and need to validate
2086
# that against the revision records.
2087
return self.source._eliminate_revisions_not_present(required_topo_revisions)
2090
class InterKnitRepo(InterSameDataRepository):
2091
"""Optimised code paths between Knit based repositories."""
2093
_matching_repo_format = RepositoryFormatKnit1()
2094
"""Repository format for testing with."""
2097
def is_compatible(source, target):
2098
"""Be compatible with known Knit formats.
2100
We don't test for the stores being of specific types because that
2101
could lead to confusing results, and there is no need to be
2105
return (isinstance(source._format, (RepositoryFormatKnit1)) and
2106
isinstance(target._format, (RepositoryFormatKnit1)))
2107
except AttributeError:
2111
def fetch(self, revision_id=None, pb=None):
2112
"""See InterRepository.fetch()."""
2113
from bzrlib.fetch import KnitRepoFetcher
2114
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2115
self.source, self.source._format, self.target, self.target._format)
2116
f = KnitRepoFetcher(to_repository=self.target,
2117
from_repository=self.source,
2118
last_revision=revision_id,
2120
return f.count_copied, f.failed_revisions
2123
def missing_revision_ids(self, revision_id=None):
2124
"""See InterRepository.missing_revision_ids()."""
2125
if revision_id is not None:
2126
source_ids = self.source.get_ancestry(revision_id)
2127
assert source_ids[0] is None
2130
source_ids = self.source._all_possible_ids()
2131
source_ids_set = set(source_ids)
2132
# source_ids is the worst possible case we may need to pull.
2133
# now we want to filter source_ids against what we actually
2134
# have in target, but don't try to check for existence where we know
2135
# we do not have a revision as that would be pointless.
2136
target_ids = set(self.target._all_possible_ids())
2137
possibly_present_revisions = target_ids.intersection(source_ids_set)
2138
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
2139
required_revisions = source_ids_set.difference(actually_present_revisions)
2140
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
2141
if revision_id is not None:
2142
# we used get_ancestry to determine source_ids then we are assured all
2143
# revisions referenced are present as they are installed in topological order.
2144
# and the tip revision was validated by get_ancestry.
2145
return required_topo_revisions
2147
# if we just grabbed the possibly available ids, then
2148
# we only have an estimate of whats available and need to validate
2149
# that against the revision records.
2150
return self.source._eliminate_revisions_not_present(required_topo_revisions)
2153
class InterModel1and2(InterRepository):
2155
_matching_repo_format = None
2158
def is_compatible(source, target):
2159
if not isinstance(source, Repository):
2161
if not isinstance(target, Repository):
2163
if not source._format.rich_root_data and target._format.rich_root_data:
2169
def fetch(self, revision_id=None, pb=None):
2170
"""See InterRepository.fetch()."""
2171
from bzrlib.fetch import Model1toKnit2Fetcher
2172
f = Model1toKnit2Fetcher(to_repository=self.target,
2173
from_repository=self.source,
2174
last_revision=revision_id,
2176
return f.count_copied, f.failed_revisions
2179
def copy_content(self, revision_id=None, basis=None):
2180
"""Make a complete copy of the content in self into destination.
2182
This is a destructive operation! Do not use it on existing
2185
:param revision_id: Only copy the content needed to construct
2186
revision_id and its parents.
2187
:param basis: Copy the needed data preferentially from basis.
2190
self.target.set_make_working_trees(self.source.make_working_trees())
2191
except NotImplementedError:
2193
# grab the basis available data
2194
if basis is not None:
2195
self.target.fetch(basis, revision_id=revision_id)
2196
# but don't bother fetching if we have the needed data now.
2197
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
2198
self.target.has_revision(revision_id)):
2200
self.target.fetch(self.source, revision_id=revision_id)
2203
class InterKnit1and2(InterKnitRepo):
2205
_matching_repo_format = None
2208
def is_compatible(source, target):
2209
"""Be compatible with Knit1 source and Knit2 target"""
2211
return (isinstance(source._format, (RepositoryFormatKnit1)) and
2212
isinstance(target._format, (RepositoryFormatKnit2)))
2213
except AttributeError:
2217
def fetch(self, revision_id=None, pb=None):
2218
"""See InterRepository.fetch()."""
2219
from bzrlib.fetch import Knit1to2Fetcher
2220
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2221
self.source, self.source._format, self.target,
2222
self.target._format)
2223
f = Knit1to2Fetcher(to_repository=self.target,
2224
from_repository=self.source,
2225
last_revision=revision_id,
2227
return f.count_copied, f.failed_revisions
2230
InterRepository.register_optimiser(InterSameDataRepository)
2231
InterRepository.register_optimiser(InterWeaveRepo)
2232
InterRepository.register_optimiser(InterKnitRepo)
2233
InterRepository.register_optimiser(InterModel1and2)
2234
InterRepository.register_optimiser(InterKnit1and2)
2237
class RepositoryTestProviderAdapter(object):
2238
"""A tool to generate a suite testing multiple repository formats at once.
2240
This is done by copying the test once for each transport and injecting
2241
the transport_server, transport_readonly_server, and bzrdir_format and
2242
repository_format classes into each copy. Each copy is also given a new id()
2243
to make it easy to identify.
2246
def __init__(self, transport_server, transport_readonly_server, formats):
2247
self._transport_server = transport_server
2248
self._transport_readonly_server = transport_readonly_server
2249
self._formats = formats
2251
def adapt(self, test):
2252
result = unittest.TestSuite()
2253
for repository_format, bzrdir_format in self._formats:
2254
new_test = deepcopy(test)
2255
new_test.transport_server = self._transport_server
2256
new_test.transport_readonly_server = self._transport_readonly_server
2257
new_test.bzrdir_format = bzrdir_format
2258
new_test.repository_format = repository_format
2259
def make_new_test_id():
2260
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
2261
return lambda: new_id
2262
new_test.id = make_new_test_id()
2263
result.addTest(new_test)
2267
class InterRepositoryTestProviderAdapter(object):
2268
"""A tool to generate a suite testing multiple inter repository formats.
2270
This is done by copying the test once for each interrepo provider and injecting
2271
the transport_server, transport_readonly_server, repository_format and
2272
repository_to_format classes into each copy.
2273
Each copy is also given a new id() to make it easy to identify.
2276
def __init__(self, transport_server, transport_readonly_server, formats):
2277
self._transport_server = transport_server
2278
self._transport_readonly_server = transport_readonly_server
2279
self._formats = formats
2281
def adapt(self, test):
2282
result = unittest.TestSuite()
2283
for interrepo_class, repository_format, repository_format_to in self._formats:
2284
new_test = deepcopy(test)
2285
new_test.transport_server = self._transport_server
2286
new_test.transport_readonly_server = self._transport_readonly_server
2287
new_test.interrepo_class = interrepo_class
2288
new_test.repository_format = repository_format
2289
new_test.repository_format_to = repository_format_to
2290
def make_new_test_id():
2291
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
2292
return lambda: new_id
2293
new_test.id = make_new_test_id()
2294
result.addTest(new_test)
2298
def default_test_list():
2299
"""Generate the default list of interrepo permutations to test."""
2301
# test the default InterRepository between format 6 and the current
2303
# XXX: robertc 20060220 reinstate this when there are two supported
2304
# formats which do not have an optimal code path between them.
2305
#result.append((InterRepository,
2306
# RepositoryFormat6(),
2307
# RepositoryFormatKnit1()))
2308
for optimiser in InterRepository._optimisers:
2309
if optimiser._matching_repo_format is not None:
2310
result.append((optimiser,
2311
optimiser._matching_repo_format,
2312
optimiser._matching_repo_format
2314
# if there are specific combinations we want to use, we can add them
2316
result.append((InterModel1and2, RepositoryFormat5(),
2317
RepositoryFormatKnit2()))
2318
result.append((InterKnit1and2, RepositoryFormatKnit1(),
2319
RepositoryFormatKnit2()))
2323
class CopyConverter(object):
2324
"""A repository conversion tool which just performs a copy of the content.
2326
This is slow but quite reliable.
2329
def __init__(self, target_format):
2330
"""Create a CopyConverter.
2332
:param target_format: The format the resulting repository should be.
2334
self.target_format = target_format
2336
def convert(self, repo, pb):
2337
"""Perform the conversion of to_convert, giving feedback via pb.
2339
:param to_convert: The disk object to convert.
2340
:param pb: a progress bar to use for progress information.
2345
# this is only useful with metadir layouts - separated repo content.
2346
# trigger an assertion if not such
2347
repo._format.get_format_string()
2348
self.repo_dir = repo.bzrdir
2349
self.step('Moving repository to repository.backup')
2350
self.repo_dir.transport.move('repository', 'repository.backup')
2351
backup_transport = self.repo_dir.transport.clone('repository.backup')
2352
repo._format.check_conversion_target(self.target_format)
2353
self.source_repo = repo._format.open(self.repo_dir,
2355
_override_transport=backup_transport)
2356
self.step('Creating new repository')
2357
converted = self.target_format.initialize(self.repo_dir,
2358
self.source_repo.is_shared())
2359
converted.lock_write()
2361
self.step('Copying content into repository.')
2362
self.source_repo.copy_content_into(converted)
2365
self.step('Deleting old repository content.')
2366
self.repo_dir.transport.delete_tree('repository.backup')
2367
self.pb.note('repository converted')
2369
def step(self, message):
2370
"""Update the pb by a step."""
2372
self.pb.update(message, self.count, self.total)
2375
class CommitBuilder(object):
2376
"""Provides an interface to build up a commit.
2378
This allows describing a tree to be committed without needing to
2379
know the internals of the format of the repository.
2382
record_root_entry = False
2383
def __init__(self, repository, parents, config, timestamp=None,
2384
timezone=None, committer=None, revprops=None,
2386
"""Initiate a CommitBuilder.
2388
:param repository: Repository to commit to.
2389
:param parents: Revision ids of the parents of the new revision.
2390
:param config: Configuration to use.
2391
:param timestamp: Optional timestamp recorded for commit.
2392
:param timezone: Optional timezone for timestamp.
2393
:param committer: Optional committer to set for commit.
2394
:param revprops: Optional dictionary of revision properties.
2395
:param revision_id: Optional revision id.
2397
self._config = config
2399
if committer is None:
2400
self._committer = self._config.username()
2402
assert isinstance(committer, basestring), type(committer)
2403
self._committer = committer
2405
self.new_inventory = Inventory(None)
2406
self._new_revision_id = revision_id
2407
self.parents = parents
2408
self.repository = repository
2411
if revprops is not None:
2412
self._revprops.update(revprops)
2414
if timestamp is None:
2415
timestamp = time.time()
2416
# Restrict resolution to 1ms
2417
self._timestamp = round(timestamp, 3)
2419
if timezone is None:
2420
self._timezone = local_time_offset()
2422
self._timezone = int(timezone)
2424
self._generate_revision_if_needed()
2426
def commit(self, message):
2427
"""Make the actual commit.
2429
:return: The revision id of the recorded revision.
2431
rev = _mod_revision.Revision(
2432
timestamp=self._timestamp,
2433
timezone=self._timezone,
2434
committer=self._committer,
2436
inventory_sha1=self.inv_sha1,
2437
revision_id=self._new_revision_id,
2438
properties=self._revprops)
2439
rev.parent_ids = self.parents
2440
self.repository.add_revision(self._new_revision_id, rev,
2441
self.new_inventory, self._config)
2442
return self._new_revision_id
2444
def revision_tree(self):
2445
"""Return the tree that was just committed.
2447
After calling commit() this can be called to get a RevisionTree
2448
representing the newly committed tree. This is preferred to
2449
calling Repository.revision_tree() because that may require
2450
deserializing the inventory, while we already have a copy in
2453
return RevisionTree(self.repository, self.new_inventory,
2454
self._new_revision_id)
2456
def finish_inventory(self):
2457
"""Tell the builder that the inventory is finished."""
2458
if self.new_inventory.root is None:
2459
symbol_versioning.warn('Root entry should be supplied to'
2460
' record_entry_contents, as of bzr 0.10.',
2461
DeprecationWarning, stacklevel=2)
2462
self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
2463
self.new_inventory.revision_id = self._new_revision_id
2464
self.inv_sha1 = self.repository.add_inventory(
2465
self._new_revision_id,
2470
def _gen_revision_id(self):
2471
"""Return new revision-id."""
2472
return generate_ids.gen_revision_id(self._config.username(),
2475
def _generate_revision_if_needed(self):
2476
"""Create a revision id if None was supplied.
2478
If the repository can not support user-specified revision ids
2479
they should override this function and raise CannotSetRevisionId
2480
if _new_revision_id is not None.
2482
:raises: CannotSetRevisionId
2484
if self._new_revision_id is None:
2485
self._new_revision_id = self._gen_revision_id()
2487
def record_entry_contents(self, ie, parent_invs, path, tree):
2488
"""Record the content of ie from tree into the commit if needed.
2490
Side effect: sets ie.revision when unchanged
2492
:param ie: An inventory entry present in the commit.
2493
:param parent_invs: The inventories of the parent revisions of the
2495
:param path: The path the entry is at in the tree.
2496
:param tree: The tree which contains this entry and should be used to
2499
if self.new_inventory.root is None and ie.parent_id is not None:
2500
symbol_versioning.warn('Root entry should be supplied to'
2501
' record_entry_contents, as of bzr 0.10.',
2502
DeprecationWarning, stacklevel=2)
2503
self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
2505
self.new_inventory.add(ie)
2507
# ie.revision is always None if the InventoryEntry is considered
2508
# for committing. ie.snapshot will record the correct revision
2509
# which may be the sole parent if it is untouched.
2510
if ie.revision is not None:
2513
# In this revision format, root entries have no knit or weave
2514
if ie is self.new_inventory.root:
2515
# When serializing out to disk and back in
2516
# root.revision is always _new_revision_id
2517
ie.revision = self._new_revision_id
2519
previous_entries = ie.find_previous_heads(
2521
self.repository.weave_store,
2522
self.repository.get_transaction())
2523
# we are creating a new revision for ie in the history store
2525
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2527
def modified_directory(self, file_id, file_parents):
2528
"""Record the presence of a symbolic link.
2530
:param file_id: The file_id of the link to record.
2531
:param file_parents: The per-file parent revision ids.
2533
self._add_text_to_weave(file_id, [], file_parents.keys())
2535
def modified_file_text(self, file_id, file_parents,
2536
get_content_byte_lines, text_sha1=None,
2538
"""Record the text of file file_id
2540
:param file_id: The file_id of the file to record the text of.
2541
:param file_parents: The per-file parent revision ids.
2542
:param get_content_byte_lines: A callable which will return the byte
2544
:param text_sha1: Optional SHA1 of the file contents.
2545
:param text_size: Optional size of the file contents.
2547
# mutter('storing text of file {%s} in revision {%s} into %r',
2548
# file_id, self._new_revision_id, self.repository.weave_store)
2549
# special case to avoid diffing on renames or
2551
if (len(file_parents) == 1
2552
and text_sha1 == file_parents.values()[0].text_sha1
2553
and text_size == file_parents.values()[0].text_size):
2554
previous_ie = file_parents.values()[0]
2555
versionedfile = self.repository.weave_store.get_weave(file_id,
2556
self.repository.get_transaction())
2557
versionedfile.clone_text(self._new_revision_id,
2558
previous_ie.revision, file_parents.keys())
2559
return text_sha1, text_size
2561
new_lines = get_content_byte_lines()
2562
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2563
# should return the SHA1 and size
2564
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2565
return osutils.sha_strings(new_lines), \
2566
sum(map(len, new_lines))
2568
def modified_link(self, file_id, file_parents, link_target):
2569
"""Record the presence of a symbolic link.
2571
:param file_id: The file_id of the link to record.
2572
:param file_parents: The per-file parent revision ids.
2573
:param link_target: Target location of this link.
2575
self._add_text_to_weave(file_id, [], file_parents.keys())
2577
def _add_text_to_weave(self, file_id, new_lines, parents):
2578
versionedfile = self.repository.weave_store.get_weave_or_empty(
2579
file_id, self.repository.get_transaction())
2580
versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2581
versionedfile.clear_cache()
2584
class _CommitBuilder(CommitBuilder):
2585
"""Temporary class so old CommitBuilders are detected properly
2587
Note: CommitBuilder works whether or not root entry is recorded.
2590
record_root_entry = True
2593
class RootCommitBuilder(CommitBuilder):
2594
"""This commitbuilder actually records the root id"""
2596
record_root_entry = True
2598
def record_entry_contents(self, ie, parent_invs, path, tree):
2599
"""Record the content of ie from tree into the commit if needed.
2601
Side effect: sets ie.revision when unchanged
2603
:param ie: An inventory entry present in the commit.
2604
:param parent_invs: The inventories of the parent revisions of the
2606
:param path: The path the entry is at in the tree.
2607
:param tree: The tree which contains this entry and should be used to
2610
assert self.new_inventory.root is not None or ie.parent_id is None
2611
self.new_inventory.add(ie)
2613
# ie.revision is always None if the InventoryEntry is considered
2614
# for committing. ie.snapshot will record the correct revision
2615
# which may be the sole parent if it is untouched.
2616
if ie.revision is not None:
2619
previous_entries = ie.find_previous_heads(
2621
self.repository.weave_store,
2622
self.repository.get_transaction())
2623
# we are creating a new revision for ie in the history store
2625
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2637
def _unescaper(match, _map=_unescape_map):
2638
return _map[match.group(1)]
2644
def _unescape_xml(data):
2645
"""Unescape predefined XML entities in a string of data."""
2647
if _unescape_re is None:
2648
_unescape_re = re.compile('\&([^;]*);')
2649
return _unescape_re.sub(_unescaper, data)