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, token=None):
237
"""Lock this repository for writing.
239
:param token: if this is already locked, then lock_write will fail
240
unless the token matches the existing lock.
241
:returns: a token if this instance supports tokens, otherwise None.
242
:raises TokenLockingNotSupported: when a token is given but this
243
instance doesn't support using token locks.
244
:raises MismatchedToken: if the specified token doesn't match the token
245
of the existing lock.
247
XXX: this docstring is duplicated in many places, e.g. lockable_files.py
249
return self.control_files.lock_write(token=token)
252
self.control_files.lock_read()
254
def get_physical_lock_status(self):
255
return self.control_files.get_physical_lock_status()
257
def leave_lock_in_place(self):
258
"""Tell this repository not to release the physical lock when this
261
If lock_write doesn't return a token, then this method is not supported.
263
self.control_files.leave_in_place()
265
def dont_leave_lock_in_place(self):
266
"""Tell this repository to release the physical lock when this
267
object is unlocked, even if it didn't originally acquire it.
269
If lock_write doesn't return a token, then this method is not supported.
271
self.control_files.dont_leave_in_place()
274
def gather_stats(self, revid=None, committers=None):
275
"""Gather statistics from a revision id.
277
:param revid: The revision id to gather statistics from, if None, then
278
no revision specific statistics are gathered.
279
:param committers: Optional parameter controlling whether to grab
280
a count of committers from the revision specific statistics.
281
:return: A dictionary of statistics. Currently this contains:
282
committers: The number of committers if requested.
283
firstrev: A tuple with timestamp, timezone for the penultimate left
284
most ancestor of revid, if revid is not the NULL_REVISION.
285
latestrev: A tuple with timestamp, timezone for revid, if revid is
286
not the NULL_REVISION.
287
revisions: The total revision count in the repository.
288
size: An estimate disk size of the repository in bytes.
291
if revid and committers:
292
result['committers'] = 0
293
if revid and revid != _mod_revision.NULL_REVISION:
295
all_committers = set()
296
revisions = self.get_ancestry(revid)
297
# pop the leading None
299
first_revision = None
301
# ignore the revisions in the middle - just grab first and last
302
revisions = revisions[0], revisions[-1]
303
for revision in self.get_revisions(revisions):
304
if not first_revision:
305
first_revision = revision
307
all_committers.add(revision.committer)
308
last_revision = revision
310
result['committers'] = len(all_committers)
311
result['firstrev'] = (first_revision.timestamp,
312
first_revision.timezone)
313
result['latestrev'] = (last_revision.timestamp,
314
last_revision.timezone)
316
# now gather global repository information
317
if self.bzrdir.root_transport.listable():
318
c, t = self._revision_store.total_size(self.get_transaction())
319
result['revisions'] = c
324
def missing_revision_ids(self, other, revision_id=None):
325
"""Return the revision ids that other has that this does not.
327
These are returned in topological order.
329
revision_id: only return revision ids included by revision_id.
331
return InterRepository.get(other, self).missing_revision_ids(revision_id)
335
"""Open the repository rooted at base.
337
For instance, if the repository is at URL/.bzr/repository,
338
Repository.open(URL) -> a Repository instance.
340
control = bzrdir.BzrDir.open(base)
341
return control.open_repository()
343
def copy_content_into(self, destination, revision_id=None, basis=None):
344
"""Make a complete copy of the content in self into destination.
346
This is a destructive operation! Do not use it on existing
349
return InterRepository.get(self, destination).copy_content(revision_id, basis)
351
def fetch(self, source, revision_id=None, pb=None):
352
"""Fetch the content required to construct revision_id from source.
354
If revision_id is None all content is copied.
356
return InterRepository.get(source, self).fetch(revision_id=revision_id,
359
def get_commit_builder(self, branch, parents, config, timestamp=None,
360
timezone=None, committer=None, revprops=None,
362
"""Obtain a CommitBuilder for this repository.
364
:param branch: Branch to commit to.
365
:param parents: Revision ids of the parents of the new revision.
366
:param config: Configuration to use.
367
:param timestamp: Optional timestamp recorded for commit.
368
:param timezone: Optional timezone for timestamp.
369
:param committer: Optional committer to set for commit.
370
:param revprops: Optional dictionary of revision properties.
371
:param revision_id: Optional revision id.
373
return _CommitBuilder(self, parents, config, timestamp, timezone,
374
committer, revprops, revision_id)
377
self.control_files.unlock()
380
def clone(self, a_bzrdir, revision_id=None, basis=None):
381
"""Clone this repository into a_bzrdir using the current format.
383
Currently no check is made that the format of this repository and
384
the bzrdir format are compatible. FIXME RBC 20060201.
386
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
387
# use target default format.
388
result = a_bzrdir.create_repository()
389
# FIXME RBC 20060209 split out the repository type to avoid this check ?
390
elif isinstance(a_bzrdir._format,
391
(bzrdir.BzrDirFormat4,
392
bzrdir.BzrDirFormat5,
393
bzrdir.BzrDirFormat6)):
394
result = a_bzrdir.open_repository()
396
result = self._format.initialize(a_bzrdir, shared=self.is_shared())
397
self.copy_content_into(result, revision_id, basis)
401
def has_revision(self, revision_id):
402
"""True if this repository has a copy of the revision."""
403
return self._revision_store.has_revision_id(revision_id,
404
self.get_transaction())
407
def get_revision_reconcile(self, revision_id):
408
"""'reconcile' helper routine that allows access to a revision always.
410
This variant of get_revision does not cross check the weave graph
411
against the revision one as get_revision does: but it should only
412
be used by reconcile, or reconcile-alike commands that are correcting
413
or testing the revision graph.
415
if not revision_id or not isinstance(revision_id, basestring):
416
raise errors.InvalidRevisionId(revision_id=revision_id,
418
return self._revision_store.get_revisions([revision_id],
419
self.get_transaction())[0]
421
def get_revisions(self, revision_ids):
422
return self._revision_store.get_revisions(revision_ids,
423
self.get_transaction())
426
def get_revision_xml(self, revision_id):
427
rev = self.get_revision(revision_id)
429
# the current serializer..
430
self._revision_store._serializer.write_revision(rev, rev_tmp)
432
return rev_tmp.getvalue()
435
def get_revision(self, revision_id):
436
"""Return the Revision object for a named revision"""
437
r = self.get_revision_reconcile(revision_id)
438
# weave corruption can lead to absent revision markers that should be
440
# the following test is reasonably cheap (it needs a single weave read)
441
# and the weave is cached in read transactions. In write transactions
442
# it is not cached but typically we only read a small number of
443
# revisions. For knits when they are introduced we will probably want
444
# to ensure that caching write transactions are in use.
445
inv = self.get_inventory_weave()
446
self._check_revision_parents(r, inv)
450
def get_deltas_for_revisions(self, revisions):
451
"""Produce a generator of revision deltas.
453
Note that the input is a sequence of REVISIONS, not revision_ids.
454
Trees will be held in memory until the generator exits.
455
Each delta is relative to the revision's lefthand predecessor.
457
required_trees = set()
458
for revision in revisions:
459
required_trees.add(revision.revision_id)
460
required_trees.update(revision.parent_ids[:1])
461
trees = dict((t.get_revision_id(), t) for
462
t in self.revision_trees(required_trees))
463
for revision in revisions:
464
if not revision.parent_ids:
465
old_tree = self.revision_tree(None)
467
old_tree = trees[revision.parent_ids[0]]
468
yield trees[revision.revision_id].changes_from(old_tree)
471
def get_revision_delta(self, revision_id):
472
"""Return the delta for one revision.
474
The delta is relative to the left-hand predecessor of the
477
r = self.get_revision(revision_id)
478
return list(self.get_deltas_for_revisions([r]))[0]
480
def _check_revision_parents(self, revision, inventory):
481
"""Private to Repository and Fetch.
483
This checks the parentage of revision in an inventory weave for
484
consistency and is only applicable to inventory-weave-for-ancestry
485
using repository formats & fetchers.
487
weave_parents = inventory.get_parents(revision.revision_id)
488
weave_names = inventory.versions()
489
for parent_id in revision.parent_ids:
490
if parent_id in weave_names:
491
# this parent must not be a ghost.
492
if not parent_id in weave_parents:
494
raise errors.CorruptRepository(self)
497
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
498
signature = gpg_strategy.sign(plaintext)
499
self._revision_store.add_revision_signature_text(revision_id,
501
self.get_transaction())
503
def fileids_altered_by_revision_ids(self, revision_ids):
504
"""Find the file ids and versions affected by revisions.
506
:param revisions: an iterable containing revision ids.
507
:return: a dictionary mapping altered file-ids to an iterable of
508
revision_ids. Each altered file-ids has the exact revision_ids that
509
altered it listed explicitly.
511
assert self._serializer.support_altered_by_hack, \
512
("fileids_altered_by_revision_ids only supported for branches "
513
"which store inventory as unnested xml, not on %r" % self)
514
selected_revision_ids = set(revision_ids)
515
w = self.get_inventory_weave()
518
# this code needs to read every new line in every inventory for the
519
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
520
# not present in one of those inventories is unnecessary but not
521
# harmful because we are filtering by the revision id marker in the
522
# inventory lines : we only select file ids altered in one of those
523
# revisions. We don't need to see all lines in the inventory because
524
# only those added in an inventory in rev X can contain a revision=X
526
unescape_revid_cache = {}
527
unescape_fileid_cache = {}
529
# jam 20061218 In a big fetch, this handles hundreds of thousands
530
# of lines, so it has had a lot of inlining and optimizing done.
531
# Sorry that it is a little bit messy.
532
# Move several functions to be local variables, since this is a long
534
search = self._file_ids_altered_regex.search
535
unescape = _unescape_xml
536
setdefault = result.setdefault
537
pb = ui.ui_factory.nested_progress_bar()
539
for line in w.iter_lines_added_or_present_in_versions(
540
selected_revision_ids, pb=pb):
544
# One call to match.group() returning multiple items is quite a
545
# bit faster than 2 calls to match.group() each returning 1
546
file_id, revision_id = match.group('file_id', 'revision_id')
548
# Inlining the cache lookups helps a lot when you make 170,000
549
# lines and 350k ids, versus 8.4 unique ids.
550
# Using a cache helps in 2 ways:
551
# 1) Avoids unnecessary decoding calls
552
# 2) Re-uses cached strings, which helps in future set and
554
# (2) is enough that removing encoding entirely along with
555
# the cache (so we are using plain strings) results in no
556
# performance improvement.
558
revision_id = unescape_revid_cache[revision_id]
560
unescaped = unescape(revision_id)
561
unescape_revid_cache[revision_id] = unescaped
562
revision_id = unescaped
564
if revision_id in selected_revision_ids:
566
file_id = unescape_fileid_cache[file_id]
568
unescaped = unescape(file_id)
569
unescape_fileid_cache[file_id] = unescaped
571
setdefault(file_id, set()).add(revision_id)
577
def get_inventory_weave(self):
578
return self.control_weaves.get_weave('inventory',
579
self.get_transaction())
582
def get_inventory(self, revision_id):
583
"""Get Inventory object by hash."""
584
return self.deserialise_inventory(
585
revision_id, self.get_inventory_xml(revision_id))
587
def deserialise_inventory(self, revision_id, xml):
588
"""Transform the xml into an inventory object.
590
:param revision_id: The expected revision id of the inventory.
591
:param xml: A serialised inventory.
593
result = self._serializer.read_inventory_from_string(xml)
594
result.root.revision = revision_id
597
def serialise_inventory(self, inv):
598
return self._serializer.write_inventory_to_string(inv)
601
def get_inventory_xml(self, revision_id):
602
"""Get inventory XML as a file object."""
604
assert isinstance(revision_id, basestring), type(revision_id)
605
iw = self.get_inventory_weave()
606
return iw.get_text(revision_id)
608
raise errors.HistoryMissing(self, 'inventory', revision_id)
611
def get_inventory_sha1(self, revision_id):
612
"""Return the sha1 hash of the inventory entry
614
return self.get_revision(revision_id).inventory_sha1
617
def get_revision_graph(self, revision_id=None):
618
"""Return a dictionary containing the revision graph.
620
:param revision_id: The revision_id to get a graph from. If None, then
621
the entire revision graph is returned. This is a deprecated mode of
622
operation and will be removed in the future.
623
:return: a dictionary of revision_id->revision_parents_list.
625
# special case NULL_REVISION
626
if revision_id == _mod_revision.NULL_REVISION:
628
a_weave = self.get_inventory_weave()
629
all_revisions = self._eliminate_revisions_not_present(
631
entire_graph = dict([(node, a_weave.get_parents(node)) for
632
node in all_revisions])
633
if revision_id is None:
635
elif revision_id not in entire_graph:
636
raise errors.NoSuchRevision(self, revision_id)
638
# add what can be reached from revision_id
640
pending = set([revision_id])
641
while len(pending) > 0:
643
result[node] = entire_graph[node]
644
for revision_id in result[node]:
645
if revision_id not in result:
646
pending.add(revision_id)
650
def get_revision_graph_with_ghosts(self, revision_ids=None):
651
"""Return a graph of the revisions with ghosts marked as applicable.
653
:param revision_ids: an iterable of revisions to graph or None for all.
654
:return: a Graph object with the graph reachable from revision_ids.
656
result = graph.Graph()
658
pending = set(self.all_revision_ids())
661
pending = set(revision_ids)
662
# special case NULL_REVISION
663
if _mod_revision.NULL_REVISION in pending:
664
pending.remove(_mod_revision.NULL_REVISION)
665
required = set(pending)
668
revision_id = pending.pop()
670
rev = self.get_revision(revision_id)
671
except errors.NoSuchRevision:
672
if revision_id in required:
675
result.add_ghost(revision_id)
677
for parent_id in rev.parent_ids:
678
# is this queued or done ?
679
if (parent_id not in pending and
680
parent_id not in done):
682
pending.add(parent_id)
683
result.add_node(revision_id, rev.parent_ids)
684
done.add(revision_id)
688
def get_revision_inventory(self, revision_id):
689
"""Return inventory of a past revision."""
690
# TODO: Unify this with get_inventory()
691
# bzr 0.0.6 and later imposes the constraint that the inventory_id
692
# must be the same as its revision, so this is trivial.
693
if revision_id is None:
694
# This does not make sense: if there is no revision,
695
# then it is the current tree inventory surely ?!
696
# and thus get_root_id() is something that looks at the last
697
# commit on the branch, and the get_root_id is an inventory check.
698
raise NotImplementedError
699
# return Inventory(self.get_root_id())
701
return self.get_inventory(revision_id)
705
"""Return True if this repository is flagged as a shared repository."""
706
raise NotImplementedError(self.is_shared)
709
def reconcile(self, other=None, thorough=False):
710
"""Reconcile this repository."""
711
from bzrlib.reconcile import RepoReconciler
712
reconciler = RepoReconciler(self, thorough=thorough)
713
reconciler.reconcile()
717
def revision_tree(self, revision_id):
718
"""Return Tree for a revision on this branch.
720
`revision_id` may be None for the empty tree revision.
722
# TODO: refactor this to use an existing revision object
723
# so we don't need to read it in twice.
724
if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
725
return RevisionTree(self, Inventory(root_id=None),
726
_mod_revision.NULL_REVISION)
728
inv = self.get_revision_inventory(revision_id)
729
return RevisionTree(self, inv, revision_id)
732
def revision_trees(self, revision_ids):
733
"""Return Tree for a revision on this branch.
735
`revision_id` may not be None or 'null:'"""
736
assert None not in revision_ids
737
assert _mod_revision.NULL_REVISION not in revision_ids
738
texts = self.get_inventory_weave().get_texts(revision_ids)
739
for text, revision_id in zip(texts, revision_ids):
740
inv = self.deserialise_inventory(revision_id, text)
741
yield RevisionTree(self, inv, revision_id)
744
def get_ancestry(self, revision_id):
745
"""Return a list of revision-ids integrated by a revision.
747
The first element of the list is always None, indicating the origin
748
revision. This might change when we have history horizons, or
749
perhaps we should have a new API.
751
This is topologically sorted.
753
if revision_id is None:
755
if not self.has_revision(revision_id):
756
raise errors.NoSuchRevision(self, revision_id)
757
w = self.get_inventory_weave()
758
candidates = w.get_ancestry(revision_id)
759
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
762
def print_file(self, file, revision_id):
763
"""Print `file` to stdout.
765
FIXME RBC 20060125 as John Meinel points out this is a bad api
766
- it writes to stdout, it assumes that that is valid etc. Fix
767
by creating a new more flexible convenience function.
769
tree = self.revision_tree(revision_id)
770
# use inventory as it was in that revision
771
file_id = tree.inventory.path2id(file)
773
# TODO: jam 20060427 Write a test for this code path
774
# it had a bug in it, and was raising the wrong
776
raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
777
tree.print_file(file_id)
779
def get_transaction(self):
780
return self.control_files.get_transaction()
782
def revision_parents(self, revid):
783
return self.get_inventory_weave().parent_names(revid)
786
def set_make_working_trees(self, new_value):
787
"""Set the policy flag for making working trees when creating branches.
789
This only applies to branches that use this repository.
791
The default is 'True'.
792
:param new_value: True to restore the default, False to disable making
795
raise NotImplementedError(self.set_make_working_trees)
797
def make_working_trees(self):
798
"""Returns the policy for making working trees on new branches."""
799
raise NotImplementedError(self.make_working_trees)
802
def sign_revision(self, revision_id, gpg_strategy):
803
plaintext = Testament.from_revision(self, revision_id).as_short_text()
804
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
807
def has_signature_for_revision_id(self, revision_id):
808
"""Query for a revision signature for revision_id in the repository."""
809
return self._revision_store.has_signature(revision_id,
810
self.get_transaction())
813
def get_signature_text(self, revision_id):
814
"""Return the text for a signature."""
815
return self._revision_store.get_signature_text(revision_id,
816
self.get_transaction())
819
def check(self, revision_ids):
820
"""Check consistency of all history of given revision_ids.
822
Different repository implementations should override _check().
824
:param revision_ids: A non-empty list of revision_ids whose ancestry
825
will be checked. Typically the last revision_id of a branch.
828
raise ValueError("revision_ids must be non-empty in %s.check"
830
return self._check(revision_ids)
832
def _check(self, revision_ids):
833
result = check.Check(self)
837
def _warn_if_deprecated(self):
838
global _deprecation_warning_done
839
if _deprecation_warning_done:
841
_deprecation_warning_done = True
842
warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
843
% (self._format, self.bzrdir.transport.base))
845
def supports_rich_root(self):
846
return self._format.rich_root_data
848
def _check_ascii_revisionid(self, revision_id, method):
849
"""Private helper for ascii-only repositories."""
850
# weave repositories refuse to store revisionids that are non-ascii.
851
if revision_id is not None:
852
# weaves require ascii revision ids.
853
if isinstance(revision_id, unicode):
855
revision_id.encode('ascii')
856
except UnicodeEncodeError:
857
raise errors.NonAsciiRevisionId(method, self)
860
class AllInOneRepository(Repository):
861
"""Legacy support - the repository behaviour for all-in-one branches."""
863
def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
864
# we reuse one control files instance.
865
dir_mode = a_bzrdir._control_files._dir_mode
866
file_mode = a_bzrdir._control_files._file_mode
868
def get_store(name, compressed=True, prefixed=False):
869
# FIXME: This approach of assuming stores are all entirely compressed
870
# or entirely uncompressed is tidy, but breaks upgrade from
871
# some existing branches where there's a mixture; we probably
872
# still want the option to look for both.
873
relpath = a_bzrdir._control_files._escape(name)
874
store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
875
prefixed=prefixed, compressed=compressed,
878
#if self._transport.should_cache():
879
# cache_path = os.path.join(self.cache_root, name)
880
# os.mkdir(cache_path)
881
# store = bzrlib.store.CachedStore(store, cache_path)
884
# not broken out yet because the controlweaves|inventory_store
885
# and text_store | weave_store bits are still different.
886
if isinstance(_format, RepositoryFormat4):
887
# cannot remove these - there is still no consistent api
888
# which allows access to this old info.
889
self.inventory_store = get_store('inventory-store')
890
text_store = get_store('text-store')
891
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
893
def get_commit_builder(self, branch, parents, config, timestamp=None,
894
timezone=None, committer=None, revprops=None,
896
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
897
return Repository.get_commit_builder(self, branch, parents, config,
898
timestamp, timezone, committer, revprops, revision_id)
902
"""AllInOne repositories cannot be shared."""
906
def set_make_working_trees(self, new_value):
907
"""Set the policy flag for making working trees when creating branches.
909
This only applies to branches that use this repository.
911
The default is 'True'.
912
:param new_value: True to restore the default, False to disable making
915
raise NotImplementedError(self.set_make_working_trees)
917
def make_working_trees(self):
918
"""Returns the policy for making working trees on new branches."""
922
def install_revision(repository, rev, revision_tree):
923
"""Install all revision data into a repository."""
926
for p_id in rev.parent_ids:
927
if repository.has_revision(p_id):
928
present_parents.append(p_id)
929
parent_trees[p_id] = repository.revision_tree(p_id)
931
parent_trees[p_id] = repository.revision_tree(None)
933
inv = revision_tree.inventory
934
entries = inv.iter_entries()
935
# backwards compatability hack: skip the root id.
936
if not repository.supports_rich_root():
937
path, root = entries.next()
938
if root.revision != rev.revision_id:
939
raise errors.IncompatibleRevision(repr(repository))
940
# Add the texts that are not already present
941
for path, ie in entries:
942
w = repository.weave_store.get_weave_or_empty(ie.file_id,
943
repository.get_transaction())
944
if ie.revision not in w:
946
# FIXME: TODO: The following loop *may* be overlapping/duplicate
947
# with InventoryEntry.find_previous_heads(). if it is, then there
948
# is a latent bug here where the parents may have ancestors of each
950
for revision, tree in parent_trees.iteritems():
951
if ie.file_id not in tree:
953
parent_id = tree.inventory[ie.file_id].revision
954
if parent_id in text_parents:
956
text_parents.append(parent_id)
958
vfile = repository.weave_store.get_weave_or_empty(ie.file_id,
959
repository.get_transaction())
960
lines = revision_tree.get_file(ie.file_id).readlines()
961
vfile.add_lines(rev.revision_id, text_parents, lines)
963
# install the inventory
964
repository.add_inventory(rev.revision_id, inv, present_parents)
965
except errors.RevisionAlreadyPresent:
967
repository.add_revision(rev.revision_id, rev, inv)
970
class MetaDirRepository(Repository):
971
"""Repositories in the new meta-dir layout."""
973
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
974
super(MetaDirRepository, self).__init__(_format,
980
dir_mode = self.control_files._dir_mode
981
file_mode = self.control_files._file_mode
985
"""Return True if this repository is flagged as a shared repository."""
986
return self.control_files._transport.has('shared-storage')
989
def set_make_working_trees(self, new_value):
990
"""Set the policy flag for making working trees when creating branches.
992
This only applies to branches that use this repository.
994
The default is 'True'.
995
:param new_value: True to restore the default, False to disable making
1000
self.control_files._transport.delete('no-working-trees')
1001
except errors.NoSuchFile:
1004
self.control_files.put_utf8('no-working-trees', '')
1006
def make_working_trees(self):
1007
"""Returns the policy for making working trees on new branches."""
1008
return not self.control_files._transport.has('no-working-trees')
1011
class WeaveMetaDirRepository(MetaDirRepository):
1012
"""A subclass of MetaDirRepository to set weave specific policy."""
1014
def get_commit_builder(self, branch, parents, config, timestamp=None,
1015
timezone=None, committer=None, revprops=None,
1017
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
1018
return MetaDirRepository.get_commit_builder(self, branch, parents,
1019
config, timestamp, timezone, committer, revprops, revision_id)
1022
class KnitRepository(MetaDirRepository):
1023
"""Knit format repository."""
1025
def _warn_if_deprecated(self):
1026
# This class isn't deprecated
1029
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
1030
inv_vf.add_lines_with_ghosts(revid, parents, lines)
1033
def _all_revision_ids(self):
1034
"""See Repository.all_revision_ids()."""
1035
# Knits get the revision graph from the index of the revision knit, so
1036
# it's always possible even if they're on an unlistable transport.
1037
return self._revision_store.all_revision_ids(self.get_transaction())
1039
def fileid_involved_between_revs(self, from_revid, to_revid):
1040
"""Find file_id(s) which are involved in the changes between revisions.
1042
This determines the set of revisions which are involved, and then
1043
finds all file ids affected by those revisions.
1045
vf = self._get_revision_vf()
1046
from_set = set(vf.get_ancestry(from_revid))
1047
to_set = set(vf.get_ancestry(to_revid))
1048
changed = to_set.difference(from_set)
1049
return self._fileid_involved_by_set(changed)
1051
def fileid_involved(self, last_revid=None):
1052
"""Find all file_ids modified in the ancestry of last_revid.
1054
:param last_revid: If None, last_revision() will be used.
1057
changed = set(self.all_revision_ids())
1059
changed = set(self.get_ancestry(last_revid))
1061
changed.remove(None)
1062
return self._fileid_involved_by_set(changed)
1065
def get_ancestry(self, revision_id):
1066
"""Return a list of revision-ids integrated by a revision.
1068
This is topologically sorted.
1070
if revision_id is None:
1072
vf = self._get_revision_vf()
1074
return [None] + vf.get_ancestry(revision_id)
1075
except errors.RevisionNotPresent:
1076
raise errors.NoSuchRevision(self, revision_id)
1079
def get_revision(self, revision_id):
1080
"""Return the Revision object for a named revision"""
1081
return self.get_revision_reconcile(revision_id)
1084
def get_revision_graph(self, revision_id=None):
1085
"""Return a dictionary containing the revision graph.
1087
:param revision_id: The revision_id to get a graph from. If None, then
1088
the entire revision graph is returned. This is a deprecated mode of
1089
operation and will be removed in the future.
1090
:return: a dictionary of revision_id->revision_parents_list.
1092
# special case NULL_REVISION
1093
if revision_id == _mod_revision.NULL_REVISION:
1095
a_weave = self._get_revision_vf()
1096
entire_graph = a_weave.get_graph()
1097
if revision_id is None:
1098
return a_weave.get_graph()
1099
elif revision_id not in a_weave:
1100
raise errors.NoSuchRevision(self, revision_id)
1102
# add what can be reached from revision_id
1104
pending = set([revision_id])
1105
while len(pending) > 0:
1106
node = pending.pop()
1107
result[node] = a_weave.get_parents(node)
1108
for revision_id in result[node]:
1109
if revision_id not in result:
1110
pending.add(revision_id)
1114
def get_revision_graph_with_ghosts(self, revision_ids=None):
1115
"""Return a graph of the revisions with ghosts marked as applicable.
1117
:param revision_ids: an iterable of revisions to graph or None for all.
1118
:return: a Graph object with the graph reachable from revision_ids.
1120
result = graph.Graph()
1121
vf = self._get_revision_vf()
1122
versions = set(vf.versions())
1123
if not revision_ids:
1124
pending = set(self.all_revision_ids())
1127
pending = set(revision_ids)
1128
# special case NULL_REVISION
1129
if _mod_revision.NULL_REVISION in pending:
1130
pending.remove(_mod_revision.NULL_REVISION)
1131
required = set(pending)
1134
revision_id = pending.pop()
1135
if not revision_id in versions:
1136
if revision_id in required:
1137
raise errors.NoSuchRevision(self, revision_id)
1139
result.add_ghost(revision_id)
1140
# mark it as done so we don't try for it again.
1141
done.add(revision_id)
1143
parent_ids = vf.get_parents_with_ghosts(revision_id)
1144
for parent_id in parent_ids:
1145
# is this queued or done ?
1146
if (parent_id not in pending and
1147
parent_id not in done):
1149
pending.add(parent_id)
1150
result.add_node(revision_id, parent_ids)
1151
done.add(revision_id)
1154
def _get_revision_vf(self):
1155
""":return: a versioned file containing the revisions."""
1156
vf = self._revision_store.get_revision_file(self.get_transaction())
1160
def reconcile(self, other=None, thorough=False):
1161
"""Reconcile this repository."""
1162
from bzrlib.reconcile import KnitReconciler
1163
reconciler = KnitReconciler(self, thorough=thorough)
1164
reconciler.reconcile()
1167
def revision_parents(self, revision_id):
1168
return self._get_revision_vf().get_parents(revision_id)
1171
class KnitRepository2(KnitRepository):
1173
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
1174
control_store, text_store):
1175
KnitRepository.__init__(self, _format, a_bzrdir, control_files,
1176
_revision_store, control_store, text_store)
1177
self._serializer = xml6.serializer_v6
1179
def deserialise_inventory(self, revision_id, xml):
1180
"""Transform the xml into an inventory object.
1182
:param revision_id: The expected revision id of the inventory.
1183
:param xml: A serialised inventory.
1185
result = self._serializer.read_inventory_from_string(xml)
1186
assert result.root.revision is not None
1189
def serialise_inventory(self, inv):
1190
"""Transform the inventory object into XML text.
1192
:param revision_id: The expected revision id of the inventory.
1193
:param xml: A serialised inventory.
1195
assert inv.revision_id is not None
1196
assert inv.root.revision is not None
1197
return KnitRepository.serialise_inventory(self, inv)
1199
def get_commit_builder(self, branch, parents, config, timestamp=None,
1200
timezone=None, committer=None, revprops=None,
1202
"""Obtain a CommitBuilder for this repository.
1204
:param branch: Branch to commit to.
1205
:param parents: Revision ids of the parents of the new revision.
1206
:param config: Configuration to use.
1207
:param timestamp: Optional timestamp recorded for commit.
1208
:param timezone: Optional timezone for timestamp.
1209
:param committer: Optional committer to set for commit.
1210
:param revprops: Optional dictionary of revision properties.
1211
:param revision_id: Optional revision id.
1213
return RootCommitBuilder(self, parents, config, timestamp, timezone,
1214
committer, revprops, revision_id)
1217
class RepositoryFormatRegistry(registry.Registry):
1218
"""Registry of RepositoryFormats.
1222
format_registry = RepositoryFormatRegistry()
1223
"""Registry of formats, indexed by their identifying format string."""
1226
class RepositoryFormat(object):
1227
"""A repository format.
1229
Formats provide three things:
1230
* An initialization routine to construct repository data on disk.
1231
* a format string which is used when the BzrDir supports versioned
1233
* an open routine which returns a Repository instance.
1235
Formats are placed in an dict by their format string for reference
1236
during opening. These should be subclasses of RepositoryFormat
1239
Once a format is deprecated, just deprecate the initialize and open
1240
methods on the format class. Do not deprecate the object, as the
1241
object will be created every system load.
1243
Common instance attributes:
1244
_matchingbzrdir - the bzrdir format that the repository format was
1245
originally written to work with. This can be used if manually
1246
constructing a bzrdir and repository, or more commonly for test suite
1251
return "<%s>" % self.__class__.__name__
1254
def find_format(klass, a_bzrdir):
1255
"""Return the format for the repository object in a_bzrdir.
1257
This is used by bzr native formats that have a "format" file in
1258
the repository. Other methods may be used by different types of
1262
transport = a_bzrdir.get_repository_transport(None)
1263
format_string = transport.get("format").read()
1264
return format_registry.get(format_string)
1265
except errors.NoSuchFile:
1266
raise errors.NoRepositoryPresent(a_bzrdir)
1268
raise errors.UnknownFormatError(format=format_string)
1271
def register_format(klass, format):
1272
format_registry.register(format.get_format_string(), format)
1275
def unregister_format(klass, format):
1276
format_registry.remove(format.get_format_string())
1279
def get_default_format(klass):
1280
"""Return the current default format."""
1281
from bzrlib import bzrdir
1282
return bzrdir.format_registry.make_bzrdir('default').repository_format
1284
def _get_control_store(self, repo_transport, control_files):
1285
"""Return the control store for this repository."""
1286
raise NotImplementedError(self._get_control_store)
1288
def get_format_string(self):
1289
"""Return the ASCII format string that identifies this format.
1291
Note that in pre format ?? repositories the format string is
1292
not permitted nor written to disk.
1294
raise NotImplementedError(self.get_format_string)
1296
def get_format_description(self):
1297
"""Return the short description for this format."""
1298
raise NotImplementedError(self.get_format_description)
1300
def _get_revision_store(self, repo_transport, control_files):
1301
"""Return the revision store object for this a_bzrdir."""
1302
raise NotImplementedError(self._get_revision_store)
1304
def _get_text_rev_store(self,
1311
"""Common logic for getting a revision store for a repository.
1313
see self._get_revision_store for the subclass-overridable method to
1314
get the store for a repository.
1316
from bzrlib.store.revision.text import TextRevisionStore
1317
dir_mode = control_files._dir_mode
1318
file_mode = control_files._file_mode
1319
text_store =TextStore(transport.clone(name),
1321
compressed=compressed,
1323
file_mode=file_mode)
1324
_revision_store = TextRevisionStore(text_store, serializer)
1325
return _revision_store
1327
def _get_versioned_file_store(self,
1332
versionedfile_class=weave.WeaveFile,
1333
versionedfile_kwargs={},
1335
weave_transport = control_files._transport.clone(name)
1336
dir_mode = control_files._dir_mode
1337
file_mode = control_files._file_mode
1338
return VersionedFileStore(weave_transport, prefixed=prefixed,
1340
file_mode=file_mode,
1341
versionedfile_class=versionedfile_class,
1342
versionedfile_kwargs=versionedfile_kwargs,
1345
def initialize(self, a_bzrdir, shared=False):
1346
"""Initialize a repository of this format in a_bzrdir.
1348
:param a_bzrdir: The bzrdir to put the new repository in it.
1349
:param shared: The repository should be initialized as a sharable one.
1350
:returns: The new repository object.
1352
This may raise UninitializableFormat if shared repository are not
1353
compatible the a_bzrdir.
1355
raise NotImplementedError(self.initialize)
1357
def is_supported(self):
1358
"""Is this format supported?
1360
Supported formats must be initializable and openable.
1361
Unsupported formats may not support initialization or committing or
1362
some other features depending on the reason for not being supported.
1366
def check_conversion_target(self, target_format):
1367
raise NotImplementedError(self.check_conversion_target)
1369
def open(self, a_bzrdir, _found=False):
1370
"""Return an instance of this format for the bzrdir a_bzrdir.
1372
_found is a private parameter, do not use it.
1374
raise NotImplementedError(self.open)
1377
class PreSplitOutRepositoryFormat(RepositoryFormat):
1378
"""Base class for the pre split out repository formats."""
1380
rich_root_data = False
1382
def initialize(self, a_bzrdir, shared=False, _internal=False):
1383
"""Create a weave repository.
1385
TODO: when creating split out bzr branch formats, move this to a common
1386
base for Format5, Format6. or something like that.
1389
raise errors.IncompatibleFormat(self, a_bzrdir._format)
1392
# always initialized when the bzrdir is.
1393
return self.open(a_bzrdir, _found=True)
1395
# Create an empty weave
1397
weavefile.write_weave_v5(weave.Weave(), sio)
1398
empty_weave = sio.getvalue()
1400
mutter('creating repository in %s.', a_bzrdir.transport.base)
1401
dirs = ['revision-store', 'weaves']
1402
files = [('inventory.weave', StringIO(empty_weave)),
1405
# FIXME: RBC 20060125 don't peek under the covers
1406
# NB: no need to escape relative paths that are url safe.
1407
control_files = lockable_files.LockableFiles(a_bzrdir.transport,
1408
'branch-lock', lockable_files.TransportLock)
1409
control_files.create_lock()
1410
control_files.lock_write()
1411
control_files._transport.mkdir_multi(dirs,
1412
mode=control_files._dir_mode)
1414
for file, content in files:
1415
control_files.put(file, content)
1417
control_files.unlock()
1418
return self.open(a_bzrdir, _found=True)
1420
def _get_control_store(self, repo_transport, control_files):
1421
"""Return the control store for this repository."""
1422
return self._get_versioned_file_store('',
1427
def _get_text_store(self, transport, control_files):
1428
"""Get a store for file texts for this format."""
1429
raise NotImplementedError(self._get_text_store)
1431
def open(self, a_bzrdir, _found=False):
1432
"""See RepositoryFormat.open()."""
1434
# we are being called directly and must probe.
1435
raise NotImplementedError
1437
repo_transport = a_bzrdir.get_repository_transport(None)
1438
control_files = a_bzrdir._control_files
1439
text_store = self._get_text_store(repo_transport, control_files)
1440
control_store = self._get_control_store(repo_transport, control_files)
1441
_revision_store = self._get_revision_store(repo_transport, control_files)
1442
return AllInOneRepository(_format=self,
1444
_revision_store=_revision_store,
1445
control_store=control_store,
1446
text_store=text_store)
1448
def check_conversion_target(self, target_format):
1452
class RepositoryFormat4(PreSplitOutRepositoryFormat):
1453
"""Bzr repository format 4.
1455
This repository format has:
1457
- TextStores for texts, inventories,revisions.
1459
This format is deprecated: it indexes texts using a text id which is
1460
removed in format 5; initialization and write support for this format
1465
super(RepositoryFormat4, self).__init__()
1466
self._matchingbzrdir = bzrdir.BzrDirFormat4()
1468
def get_format_description(self):
1469
"""See RepositoryFormat.get_format_description()."""
1470
return "Repository format 4"
1472
def initialize(self, url, shared=False, _internal=False):
1473
"""Format 4 branches cannot be created."""
1474
raise errors.UninitializableFormat(self)
1476
def is_supported(self):
1477
"""Format 4 is not supported.
1479
It is not supported because the model changed from 4 to 5 and the
1480
conversion logic is expensive - so doing it on the fly was not
1485
def _get_control_store(self, repo_transport, control_files):
1486
"""Format 4 repositories have no formal control store at this point.
1488
This will cause any control-file-needing apis to fail - this is desired.
1492
def _get_revision_store(self, repo_transport, control_files):
1493
"""See RepositoryFormat._get_revision_store()."""
1494
from bzrlib.xml4 import serializer_v4
1495
return self._get_text_rev_store(repo_transport,
1498
serializer=serializer_v4)
1500
def _get_text_store(self, transport, control_files):
1501
"""See RepositoryFormat._get_text_store()."""
1504
class RepositoryFormat5(PreSplitOutRepositoryFormat):
1505
"""Bzr control format 5.
1507
This repository format has:
1508
- weaves for file texts and inventory
1510
- TextStores for revisions and signatures.
1514
super(RepositoryFormat5, self).__init__()
1515
self._matchingbzrdir = bzrdir.BzrDirFormat5()
1517
def get_format_description(self):
1518
"""See RepositoryFormat.get_format_description()."""
1519
return "Weave repository format 5"
1521
def _get_revision_store(self, repo_transport, control_files):
1522
"""See RepositoryFormat._get_revision_store()."""
1523
"""Return the revision store object for this a_bzrdir."""
1524
return self._get_text_rev_store(repo_transport,
1529
def _get_text_store(self, transport, control_files):
1530
"""See RepositoryFormat._get_text_store()."""
1531
return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
1534
class RepositoryFormat6(PreSplitOutRepositoryFormat):
1535
"""Bzr control format 6.
1537
This repository format has:
1538
- weaves for file texts and inventory
1539
- hash subdirectory based stores.
1540
- TextStores for revisions and signatures.
1544
super(RepositoryFormat6, self).__init__()
1545
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1547
def get_format_description(self):
1548
"""See RepositoryFormat.get_format_description()."""
1549
return "Weave repository format 6"
1551
def _get_revision_store(self, repo_transport, control_files):
1552
"""See RepositoryFormat._get_revision_store()."""
1553
return self._get_text_rev_store(repo_transport,
1559
def _get_text_store(self, transport, control_files):
1560
"""See RepositoryFormat._get_text_store()."""
1561
return self._get_versioned_file_store('weaves', transport, control_files)
1564
class MetaDirRepositoryFormat(RepositoryFormat):
1565
"""Common base class for the new repositories using the metadir layout."""
1567
rich_root_data = False
1570
super(MetaDirRepositoryFormat, self).__init__()
1571
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1573
def _create_control_files(self, a_bzrdir):
1574
"""Create the required files and the initial control_files object."""
1575
# FIXME: RBC 20060125 don't peek under the covers
1576
# NB: no need to escape relative paths that are url safe.
1577
repository_transport = a_bzrdir.get_repository_transport(self)
1578
control_files = lockable_files.LockableFiles(repository_transport,
1579
'lock', lockdir.LockDir)
1580
control_files.create_lock()
1581
return control_files
1583
def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
1584
"""Upload the initial blank content."""
1585
control_files = self._create_control_files(a_bzrdir)
1586
control_files.lock_write()
1588
control_files._transport.mkdir_multi(dirs,
1589
mode=control_files._dir_mode)
1590
for file, content in files:
1591
control_files.put(file, content)
1592
for file, content in utf8_files:
1593
control_files.put_utf8(file, content)
1595
control_files.put_utf8('shared-storage', '')
1597
control_files.unlock()
1600
class RepositoryFormat7(MetaDirRepositoryFormat):
1601
"""Bzr repository 7.
1603
This repository format has:
1604
- weaves for file texts and inventory
1605
- hash subdirectory based stores.
1606
- TextStores for revisions and signatures.
1607
- a format marker of its own
1608
- an optional 'shared-storage' flag
1609
- an optional 'no-working-trees' flag
1612
def _get_control_store(self, repo_transport, control_files):
1613
"""Return the control store for this repository."""
1614
return self._get_versioned_file_store('',
1619
def get_format_string(self):
1620
"""See RepositoryFormat.get_format_string()."""
1621
return "Bazaar-NG Repository format 7"
1623
def get_format_description(self):
1624
"""See RepositoryFormat.get_format_description()."""
1625
return "Weave repository format 7"
1627
def check_conversion_target(self, target_format):
1630
def _get_revision_store(self, repo_transport, control_files):
1631
"""See RepositoryFormat._get_revision_store()."""
1632
return self._get_text_rev_store(repo_transport,
1639
def _get_text_store(self, transport, control_files):
1640
"""See RepositoryFormat._get_text_store()."""
1641
return self._get_versioned_file_store('weaves',
1645
def initialize(self, a_bzrdir, shared=False):
1646
"""Create a weave repository.
1648
:param shared: If true the repository will be initialized as a shared
1651
# Create an empty weave
1653
weavefile.write_weave_v5(weave.Weave(), sio)
1654
empty_weave = sio.getvalue()
1656
mutter('creating repository in %s.', a_bzrdir.transport.base)
1657
dirs = ['revision-store', 'weaves']
1658
files = [('inventory.weave', StringIO(empty_weave)),
1660
utf8_files = [('format', self.get_format_string())]
1662
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1663
return self.open(a_bzrdir=a_bzrdir, _found=True)
1665
def open(self, a_bzrdir, _found=False, _override_transport=None):
1666
"""See RepositoryFormat.open().
1668
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1669
repository at a slightly different url
1670
than normal. I.e. during 'upgrade'.
1673
format = RepositoryFormat.find_format(a_bzrdir)
1674
assert format.__class__ == self.__class__
1675
if _override_transport is not None:
1676
repo_transport = _override_transport
1678
repo_transport = a_bzrdir.get_repository_transport(None)
1679
control_files = lockable_files.LockableFiles(repo_transport,
1680
'lock', lockdir.LockDir)
1681
text_store = self._get_text_store(repo_transport, control_files)
1682
control_store = self._get_control_store(repo_transport, control_files)
1683
_revision_store = self._get_revision_store(repo_transport, control_files)
1684
return WeaveMetaDirRepository(_format=self,
1686
control_files=control_files,
1687
_revision_store=_revision_store,
1688
control_store=control_store,
1689
text_store=text_store)
1692
class RepositoryFormatKnit(MetaDirRepositoryFormat):
1693
"""Bzr repository knit format (generalized).
1695
This repository format has:
1696
- knits for file texts and inventory
1697
- hash subdirectory based stores.
1698
- knits for revisions and signatures
1699
- TextStores for revisions and signatures.
1700
- a format marker of its own
1701
- an optional 'shared-storage' flag
1702
- an optional 'no-working-trees' flag
1706
def _get_control_store(self, repo_transport, control_files):
1707
"""Return the control store for this repository."""
1708
return VersionedFileStore(
1711
file_mode=control_files._file_mode,
1712
versionedfile_class=knit.KnitVersionedFile,
1713
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
1716
def _get_revision_store(self, repo_transport, control_files):
1717
"""See RepositoryFormat._get_revision_store()."""
1718
from bzrlib.store.revision.knit import KnitRevisionStore
1719
versioned_file_store = VersionedFileStore(
1721
file_mode=control_files._file_mode,
1724
versionedfile_class=knit.KnitVersionedFile,
1725
versionedfile_kwargs={'delta':False,
1726
'factory':knit.KnitPlainFactory(),
1730
return KnitRevisionStore(versioned_file_store)
1732
def _get_text_store(self, transport, control_files):
1733
"""See RepositoryFormat._get_text_store()."""
1734
return self._get_versioned_file_store('knits',
1737
versionedfile_class=knit.KnitVersionedFile,
1738
versionedfile_kwargs={
1739
'create_parent_dir':True,
1740
'delay_create':True,
1741
'dir_mode':control_files._dir_mode,
1745
def initialize(self, a_bzrdir, shared=False):
1746
"""Create a knit format 1 repository.
1748
:param a_bzrdir: bzrdir to contain the new repository; must already
1750
:param shared: If true the repository will be initialized as a shared
1753
mutter('creating repository in %s.', a_bzrdir.transport.base)
1754
dirs = ['revision-store', 'knits']
1756
utf8_files = [('format', self.get_format_string())]
1758
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1759
repo_transport = a_bzrdir.get_repository_transport(None)
1760
control_files = lockable_files.LockableFiles(repo_transport,
1761
'lock', lockdir.LockDir)
1762
control_store = self._get_control_store(repo_transport, control_files)
1763
transaction = transactions.WriteTransaction()
1764
# trigger a write of the inventory store.
1765
control_store.get_weave_or_empty('inventory', transaction)
1766
_revision_store = self._get_revision_store(repo_transport, control_files)
1767
# the revision id here is irrelevant: it will not be stored, and cannot
1769
_revision_store.has_revision_id('A', transaction)
1770
_revision_store.get_signature_file(transaction)
1771
return self.open(a_bzrdir=a_bzrdir, _found=True)
1773
def open(self, a_bzrdir, _found=False, _override_transport=None):
1774
"""See RepositoryFormat.open().
1776
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1777
repository at a slightly different url
1778
than normal. I.e. during 'upgrade'.
1781
format = RepositoryFormat.find_format(a_bzrdir)
1782
assert format.__class__ == self.__class__
1783
if _override_transport is not None:
1784
repo_transport = _override_transport
1786
repo_transport = a_bzrdir.get_repository_transport(None)
1787
control_files = lockable_files.LockableFiles(repo_transport,
1788
'lock', lockdir.LockDir)
1789
text_store = self._get_text_store(repo_transport, control_files)
1790
control_store = self._get_control_store(repo_transport, control_files)
1791
_revision_store = self._get_revision_store(repo_transport, control_files)
1792
return KnitRepository(_format=self,
1794
control_files=control_files,
1795
_revision_store=_revision_store,
1796
control_store=control_store,
1797
text_store=text_store)
1800
class RepositoryFormatKnit1(RepositoryFormatKnit):
1801
"""Bzr repository knit format 1.
1803
This repository format has:
1804
- knits for file texts and inventory
1805
- hash subdirectory based stores.
1806
- knits for revisions and signatures
1807
- TextStores for revisions and signatures.
1808
- a format marker of its own
1809
- an optional 'shared-storage' flag
1810
- an optional 'no-working-trees' flag
1813
This format was introduced in bzr 0.8.
1815
def get_format_string(self):
1816
"""See RepositoryFormat.get_format_string()."""
1817
return "Bazaar-NG Knit Repository Format 1"
1819
def get_format_description(self):
1820
"""See RepositoryFormat.get_format_description()."""
1821
return "Knit repository format 1"
1823
def check_conversion_target(self, target_format):
1827
class RepositoryFormatKnit2(RepositoryFormatKnit):
1828
"""Bzr repository knit format 2.
1830
THIS FORMAT IS EXPERIMENTAL
1831
This repository format has:
1832
- knits for file texts and inventory
1833
- hash subdirectory based stores.
1834
- knits for revisions and signatures
1835
- TextStores for revisions and signatures.
1836
- a format marker of its own
1837
- an optional 'shared-storage' flag
1838
- an optional 'no-working-trees' flag
1840
- Support for recording full info about the tree root
1844
rich_root_data = True
1846
def get_format_string(self):
1847
"""See RepositoryFormat.get_format_string()."""
1848
return "Bazaar Knit Repository Format 2\n"
1850
def get_format_description(self):
1851
"""See RepositoryFormat.get_format_description()."""
1852
return "Knit repository format 2"
1854
def check_conversion_target(self, target_format):
1855
if not target_format.rich_root_data:
1856
raise errors.BadConversionTarget(
1857
'Does not support rich root data.', target_format)
1859
def open(self, a_bzrdir, _found=False, _override_transport=None):
1860
"""See RepositoryFormat.open().
1862
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1863
repository at a slightly different url
1864
than normal. I.e. during 'upgrade'.
1867
format = RepositoryFormat.find_format(a_bzrdir)
1868
assert format.__class__ == self.__class__
1869
if _override_transport is not None:
1870
repo_transport = _override_transport
1872
repo_transport = a_bzrdir.get_repository_transport(None)
1873
control_files = lockable_files.LockableFiles(repo_transport, 'lock',
1875
text_store = self._get_text_store(repo_transport, control_files)
1876
control_store = self._get_control_store(repo_transport, control_files)
1877
_revision_store = self._get_revision_store(repo_transport, control_files)
1878
return KnitRepository2(_format=self,
1880
control_files=control_files,
1881
_revision_store=_revision_store,
1882
control_store=control_store,
1883
text_store=text_store)
1887
# formats which have no format string are not discoverable
1888
# and not independently creatable, so are not registered.
1889
RepositoryFormat.register_format(RepositoryFormat7())
1890
# KEEP in sync with bzrdir.format_registry default
1891
RepositoryFormat.register_format(RepositoryFormatKnit1())
1892
RepositoryFormat.register_format(RepositoryFormatKnit2())
1893
_legacy_formats = [RepositoryFormat4(),
1894
RepositoryFormat5(),
1895
RepositoryFormat6()]
1898
class InterRepository(InterObject):
1899
"""This class represents operations taking place between two repositories.
1901
Its instances have methods like copy_content and fetch, and contain
1902
references to the source and target repositories these operations can be
1905
Often we will provide convenience methods on 'repository' which carry out
1906
operations with another repository - they will always forward to
1907
InterRepository.get(other).method_name(parameters).
1911
"""The available optimised InterRepository types."""
1913
def copy_content(self, revision_id=None, basis=None):
1914
raise NotImplementedError(self.copy_content)
1916
def fetch(self, revision_id=None, pb=None):
1917
"""Fetch the content required to construct revision_id.
1919
The content is copied from self.source to self.target.
1921
:param revision_id: if None all content is copied, if NULL_REVISION no
1923
:param pb: optional progress bar to use for progress reports. If not
1924
provided a default one will be created.
1926
Returns the copied revision count and the failed revisions in a tuple:
1929
raise NotImplementedError(self.fetch)
1932
def missing_revision_ids(self, revision_id=None):
1933
"""Return the revision ids that source has that target does not.
1935
These are returned in topological order.
1937
:param revision_id: only return revision ids included by this
1940
# generic, possibly worst case, slow code path.
1941
target_ids = set(self.target.all_revision_ids())
1942
if revision_id is not None:
1943
source_ids = self.source.get_ancestry(revision_id)
1944
assert source_ids[0] is None
1947
source_ids = self.source.all_revision_ids()
1948
result_set = set(source_ids).difference(target_ids)
1949
# this may look like a no-op: its not. It preserves the ordering
1950
# other_ids had while only returning the members from other_ids
1951
# that we've decided we need.
1952
return [rev_id for rev_id in source_ids if rev_id in result_set]
1955
class InterSameDataRepository(InterRepository):
1956
"""Code for converting between repositories that represent the same data.
1958
Data format and model must match for this to work.
1961
_matching_repo_format = RepositoryFormat4()
1962
"""Repository format for testing with."""
1965
def is_compatible(source, target):
1966
if source._format.rich_root_data == target._format.rich_root_data:
1972
def copy_content(self, revision_id=None, basis=None):
1973
"""Make a complete copy of the content in self into destination.
1975
This is a destructive operation! Do not use it on existing
1978
:param revision_id: Only copy the content needed to construct
1979
revision_id and its parents.
1980
:param basis: Copy the needed data preferentially from basis.
1983
self.target.set_make_working_trees(self.source.make_working_trees())
1984
except NotImplementedError:
1986
# grab the basis available data
1987
if basis is not None:
1988
self.target.fetch(basis, revision_id=revision_id)
1989
# but don't bother fetching if we have the needed data now.
1990
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1991
self.target.has_revision(revision_id)):
1993
self.target.fetch(self.source, revision_id=revision_id)
1996
def fetch(self, revision_id=None, pb=None):
1997
"""See InterRepository.fetch()."""
1998
from bzrlib.fetch import GenericRepoFetcher
1999
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2000
self.source, self.source._format, self.target,
2001
self.target._format)
2002
f = GenericRepoFetcher(to_repository=self.target,
2003
from_repository=self.source,
2004
last_revision=revision_id,
2006
return f.count_copied, f.failed_revisions
2009
class InterWeaveRepo(InterSameDataRepository):
2010
"""Optimised code paths between Weave based repositories."""
2012
_matching_repo_format = RepositoryFormat7()
2013
"""Repository format for testing with."""
2016
def is_compatible(source, target):
2017
"""Be compatible with known Weave formats.
2019
We don't test for the stores being of specific types because that
2020
could lead to confusing results, and there is no need to be
2024
return (isinstance(source._format, (RepositoryFormat5,
2026
RepositoryFormat7)) and
2027
isinstance(target._format, (RepositoryFormat5,
2029
RepositoryFormat7)))
2030
except AttributeError:
2034
def copy_content(self, revision_id=None, basis=None):
2035
"""See InterRepository.copy_content()."""
2036
# weave specific optimised path:
2037
if basis is not None:
2038
# copy the basis in, then fetch remaining data.
2039
basis.copy_content_into(self.target, revision_id)
2040
# the basis copy_content_into could miss-set this.
2042
self.target.set_make_working_trees(self.source.make_working_trees())
2043
except NotImplementedError:
2045
self.target.fetch(self.source, revision_id=revision_id)
2048
self.target.set_make_working_trees(self.source.make_working_trees())
2049
except NotImplementedError:
2051
# FIXME do not peek!
2052
if self.source.control_files._transport.listable():
2053
pb = ui.ui_factory.nested_progress_bar()
2055
self.target.weave_store.copy_all_ids(
2056
self.source.weave_store,
2058
from_transaction=self.source.get_transaction(),
2059
to_transaction=self.target.get_transaction())
2060
pb.update('copying inventory', 0, 1)
2061
self.target.control_weaves.copy_multi(
2062
self.source.control_weaves, ['inventory'],
2063
from_transaction=self.source.get_transaction(),
2064
to_transaction=self.target.get_transaction())
2065
self.target._revision_store.text_store.copy_all_ids(
2066
self.source._revision_store.text_store,
2071
self.target.fetch(self.source, revision_id=revision_id)
2074
def fetch(self, revision_id=None, pb=None):
2075
"""See InterRepository.fetch()."""
2076
from bzrlib.fetch import GenericRepoFetcher
2077
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2078
self.source, self.source._format, self.target, self.target._format)
2079
f = GenericRepoFetcher(to_repository=self.target,
2080
from_repository=self.source,
2081
last_revision=revision_id,
2083
return f.count_copied, f.failed_revisions
2086
def missing_revision_ids(self, revision_id=None):
2087
"""See InterRepository.missing_revision_ids()."""
2088
# we want all revisions to satisfy revision_id in source.
2089
# but we don't want to stat every file here and there.
2090
# we want then, all revisions other needs to satisfy revision_id
2091
# checked, but not those that we have locally.
2092
# so the first thing is to get a subset of the revisions to
2093
# satisfy revision_id in source, and then eliminate those that
2094
# we do already have.
2095
# this is slow on high latency connection to self, but as as this
2096
# disk format scales terribly for push anyway due to rewriting
2097
# inventory.weave, this is considered acceptable.
2099
if revision_id is not None:
2100
source_ids = self.source.get_ancestry(revision_id)
2101
assert source_ids[0] is None
2104
source_ids = self.source._all_possible_ids()
2105
source_ids_set = set(source_ids)
2106
# source_ids is the worst possible case we may need to pull.
2107
# now we want to filter source_ids against what we actually
2108
# have in target, but don't try to check for existence where we know
2109
# we do not have a revision as that would be pointless.
2110
target_ids = set(self.target._all_possible_ids())
2111
possibly_present_revisions = target_ids.intersection(source_ids_set)
2112
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
2113
required_revisions = source_ids_set.difference(actually_present_revisions)
2114
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
2115
if revision_id is not None:
2116
# we used get_ancestry to determine source_ids then we are assured all
2117
# revisions referenced are present as they are installed in topological order.
2118
# and the tip revision was validated by get_ancestry.
2119
return required_topo_revisions
2121
# if we just grabbed the possibly available ids, then
2122
# we only have an estimate of whats available and need to validate
2123
# that against the revision records.
2124
return self.source._eliminate_revisions_not_present(required_topo_revisions)
2127
class InterKnitRepo(InterSameDataRepository):
2128
"""Optimised code paths between Knit based repositories."""
2130
_matching_repo_format = RepositoryFormatKnit1()
2131
"""Repository format for testing with."""
2134
def is_compatible(source, target):
2135
"""Be compatible with known Knit formats.
2137
We don't test for the stores being of specific types because that
2138
could lead to confusing results, and there is no need to be
2142
return (isinstance(source._format, (RepositoryFormatKnit1)) and
2143
isinstance(target._format, (RepositoryFormatKnit1)))
2144
except AttributeError:
2148
def fetch(self, revision_id=None, pb=None):
2149
"""See InterRepository.fetch()."""
2150
from bzrlib.fetch import KnitRepoFetcher
2151
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2152
self.source, self.source._format, self.target, self.target._format)
2153
f = KnitRepoFetcher(to_repository=self.target,
2154
from_repository=self.source,
2155
last_revision=revision_id,
2157
return f.count_copied, f.failed_revisions
2160
def missing_revision_ids(self, revision_id=None):
2161
"""See InterRepository.missing_revision_ids()."""
2162
if revision_id is not None:
2163
source_ids = self.source.get_ancestry(revision_id)
2164
assert source_ids[0] is None
2167
source_ids = self.source._all_possible_ids()
2168
source_ids_set = set(source_ids)
2169
# source_ids is the worst possible case we may need to pull.
2170
# now we want to filter source_ids against what we actually
2171
# have in target, but don't try to check for existence where we know
2172
# we do not have a revision as that would be pointless.
2173
target_ids = set(self.target._all_possible_ids())
2174
possibly_present_revisions = target_ids.intersection(source_ids_set)
2175
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
2176
required_revisions = source_ids_set.difference(actually_present_revisions)
2177
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
2178
if revision_id is not None:
2179
# we used get_ancestry to determine source_ids then we are assured all
2180
# revisions referenced are present as they are installed in topological order.
2181
# and the tip revision was validated by get_ancestry.
2182
return required_topo_revisions
2184
# if we just grabbed the possibly available ids, then
2185
# we only have an estimate of whats available and need to validate
2186
# that against the revision records.
2187
return self.source._eliminate_revisions_not_present(required_topo_revisions)
2190
class InterModel1and2(InterRepository):
2192
_matching_repo_format = None
2195
def is_compatible(source, target):
2196
if not isinstance(source, Repository):
2198
if not isinstance(target, Repository):
2200
if not source._format.rich_root_data and target._format.rich_root_data:
2206
def fetch(self, revision_id=None, pb=None):
2207
"""See InterRepository.fetch()."""
2208
from bzrlib.fetch import Model1toKnit2Fetcher
2209
f = Model1toKnit2Fetcher(to_repository=self.target,
2210
from_repository=self.source,
2211
last_revision=revision_id,
2213
return f.count_copied, f.failed_revisions
2216
def copy_content(self, revision_id=None, basis=None):
2217
"""Make a complete copy of the content in self into destination.
2219
This is a destructive operation! Do not use it on existing
2222
:param revision_id: Only copy the content needed to construct
2223
revision_id and its parents.
2224
:param basis: Copy the needed data preferentially from basis.
2227
self.target.set_make_working_trees(self.source.make_working_trees())
2228
except NotImplementedError:
2230
# grab the basis available data
2231
if basis is not None:
2232
self.target.fetch(basis, revision_id=revision_id)
2233
# but don't bother fetching if we have the needed data now.
2234
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
2235
self.target.has_revision(revision_id)):
2237
self.target.fetch(self.source, revision_id=revision_id)
2240
class InterKnit1and2(InterKnitRepo):
2242
_matching_repo_format = None
2245
def is_compatible(source, target):
2246
"""Be compatible with Knit1 source and Knit2 target"""
2248
return (isinstance(source._format, (RepositoryFormatKnit1)) and
2249
isinstance(target._format, (RepositoryFormatKnit2)))
2250
except AttributeError:
2254
def fetch(self, revision_id=None, pb=None):
2255
"""See InterRepository.fetch()."""
2256
from bzrlib.fetch import Knit1to2Fetcher
2257
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2258
self.source, self.source._format, self.target,
2259
self.target._format)
2260
f = Knit1to2Fetcher(to_repository=self.target,
2261
from_repository=self.source,
2262
last_revision=revision_id,
2264
return f.count_copied, f.failed_revisions
2267
InterRepository.register_optimiser(InterSameDataRepository)
2268
InterRepository.register_optimiser(InterWeaveRepo)
2269
InterRepository.register_optimiser(InterKnitRepo)
2270
InterRepository.register_optimiser(InterModel1and2)
2271
InterRepository.register_optimiser(InterKnit1and2)
2274
class RepositoryTestProviderAdapter(object):
2275
"""A tool to generate a suite testing multiple repository formats at once.
2277
This is done by copying the test once for each transport and injecting
2278
the transport_server, transport_readonly_server, and bzrdir_format and
2279
repository_format classes into each copy. Each copy is also given a new id()
2280
to make it easy to identify.
2283
def __init__(self, transport_server, transport_readonly_server, formats,
2284
vfs_transport_factory=None):
2285
self._transport_server = transport_server
2286
self._transport_readonly_server = transport_readonly_server
2287
self._vfs_transport_factory = vfs_transport_factory
2288
self._formats = formats
2290
def adapt(self, test):
2291
result = unittest.TestSuite()
2292
for repository_format, bzrdir_format in self._formats:
2293
new_test = deepcopy(test)
2294
new_test.transport_server = self._transport_server
2295
new_test.transport_readonly_server = self._transport_readonly_server
2296
if self._vfs_transport_factory:
2297
new_test.vfs_transport_factory = self._vfs_transport_factory
2298
new_test.bzrdir_format = bzrdir_format
2299
new_test.repository_format = repository_format
2300
def make_new_test_id():
2301
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
2302
return lambda: new_id
2303
new_test.id = make_new_test_id()
2304
result.addTest(new_test)
2308
class InterRepositoryTestProviderAdapter(object):
2309
"""A tool to generate a suite testing multiple inter repository formats.
2311
This is done by copying the test once for each interrepo provider and injecting
2312
the transport_server, transport_readonly_server, repository_format and
2313
repository_to_format classes into each copy.
2314
Each copy is also given a new id() to make it easy to identify.
2317
def __init__(self, transport_server, transport_readonly_server, formats):
2318
self._transport_server = transport_server
2319
self._transport_readonly_server = transport_readonly_server
2320
self._formats = formats
2322
def adapt(self, test):
2323
result = unittest.TestSuite()
2324
for interrepo_class, repository_format, repository_format_to in self._formats:
2325
new_test = deepcopy(test)
2326
new_test.transport_server = self._transport_server
2327
new_test.transport_readonly_server = self._transport_readonly_server
2328
new_test.interrepo_class = interrepo_class
2329
new_test.repository_format = repository_format
2330
new_test.repository_format_to = repository_format_to
2331
def make_new_test_id():
2332
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
2333
return lambda: new_id
2334
new_test.id = make_new_test_id()
2335
result.addTest(new_test)
2339
def default_test_list():
2340
"""Generate the default list of interrepo permutations to test."""
2342
# test the default InterRepository between format 6 and the current
2344
# XXX: robertc 20060220 reinstate this when there are two supported
2345
# formats which do not have an optimal code path between them.
2346
#result.append((InterRepository,
2347
# RepositoryFormat6(),
2348
# RepositoryFormatKnit1()))
2349
for optimiser in InterRepository._optimisers:
2350
if optimiser._matching_repo_format is not None:
2351
result.append((optimiser,
2352
optimiser._matching_repo_format,
2353
optimiser._matching_repo_format
2355
# if there are specific combinations we want to use, we can add them
2357
result.append((InterModel1and2, RepositoryFormat5(),
2358
RepositoryFormatKnit2()))
2359
result.append((InterKnit1and2, RepositoryFormatKnit1(),
2360
RepositoryFormatKnit2()))
2364
class CopyConverter(object):
2365
"""A repository conversion tool which just performs a copy of the content.
2367
This is slow but quite reliable.
2370
def __init__(self, target_format):
2371
"""Create a CopyConverter.
2373
:param target_format: The format the resulting repository should be.
2375
self.target_format = target_format
2377
def convert(self, repo, pb):
2378
"""Perform the conversion of to_convert, giving feedback via pb.
2380
:param to_convert: The disk object to convert.
2381
:param pb: a progress bar to use for progress information.
2386
# this is only useful with metadir layouts - separated repo content.
2387
# trigger an assertion if not such
2388
repo._format.get_format_string()
2389
self.repo_dir = repo.bzrdir
2390
self.step('Moving repository to repository.backup')
2391
self.repo_dir.transport.move('repository', 'repository.backup')
2392
backup_transport = self.repo_dir.transport.clone('repository.backup')
2393
repo._format.check_conversion_target(self.target_format)
2394
self.source_repo = repo._format.open(self.repo_dir,
2396
_override_transport=backup_transport)
2397
self.step('Creating new repository')
2398
converted = self.target_format.initialize(self.repo_dir,
2399
self.source_repo.is_shared())
2400
converted.lock_write()
2402
self.step('Copying content into repository.')
2403
self.source_repo.copy_content_into(converted)
2406
self.step('Deleting old repository content.')
2407
self.repo_dir.transport.delete_tree('repository.backup')
2408
self.pb.note('repository converted')
2410
def step(self, message):
2411
"""Update the pb by a step."""
2413
self.pb.update(message, self.count, self.total)
2416
class CommitBuilder(object):
2417
"""Provides an interface to build up a commit.
2419
This allows describing a tree to be committed without needing to
2420
know the internals of the format of the repository.
2423
record_root_entry = False
2424
def __init__(self, repository, parents, config, timestamp=None,
2425
timezone=None, committer=None, revprops=None,
2427
"""Initiate a CommitBuilder.
2429
:param repository: Repository to commit to.
2430
:param parents: Revision ids of the parents of the new revision.
2431
:param config: Configuration to use.
2432
:param timestamp: Optional timestamp recorded for commit.
2433
:param timezone: Optional timezone for timestamp.
2434
:param committer: Optional committer to set for commit.
2435
:param revprops: Optional dictionary of revision properties.
2436
:param revision_id: Optional revision id.
2438
self._config = config
2440
if committer is None:
2441
self._committer = self._config.username()
2443
assert isinstance(committer, basestring), type(committer)
2444
self._committer = committer
2446
self.new_inventory = Inventory(None)
2447
self._new_revision_id = revision_id
2448
self.parents = parents
2449
self.repository = repository
2452
if revprops is not None:
2453
self._revprops.update(revprops)
2455
if timestamp is None:
2456
timestamp = time.time()
2457
# Restrict resolution to 1ms
2458
self._timestamp = round(timestamp, 3)
2460
if timezone is None:
2461
self._timezone = local_time_offset()
2463
self._timezone = int(timezone)
2465
self._generate_revision_if_needed()
2467
def commit(self, message):
2468
"""Make the actual commit.
2470
:return: The revision id of the recorded revision.
2472
rev = _mod_revision.Revision(
2473
timestamp=self._timestamp,
2474
timezone=self._timezone,
2475
committer=self._committer,
2477
inventory_sha1=self.inv_sha1,
2478
revision_id=self._new_revision_id,
2479
properties=self._revprops)
2480
rev.parent_ids = self.parents
2481
self.repository.add_revision(self._new_revision_id, rev,
2482
self.new_inventory, self._config)
2483
return self._new_revision_id
2485
def revision_tree(self):
2486
"""Return the tree that was just committed.
2488
After calling commit() this can be called to get a RevisionTree
2489
representing the newly committed tree. This is preferred to
2490
calling Repository.revision_tree() because that may require
2491
deserializing the inventory, while we already have a copy in
2494
return RevisionTree(self.repository, self.new_inventory,
2495
self._new_revision_id)
2497
def finish_inventory(self):
2498
"""Tell the builder that the inventory is finished."""
2499
if self.new_inventory.root is 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.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
2504
self.new_inventory.revision_id = self._new_revision_id
2505
self.inv_sha1 = self.repository.add_inventory(
2506
self._new_revision_id,
2511
def _gen_revision_id(self):
2512
"""Return new revision-id."""
2513
return generate_ids.gen_revision_id(self._config.username(),
2516
def _generate_revision_if_needed(self):
2517
"""Create a revision id if None was supplied.
2519
If the repository can not support user-specified revision ids
2520
they should override this function and raise CannotSetRevisionId
2521
if _new_revision_id is not None.
2523
:raises: CannotSetRevisionId
2525
if self._new_revision_id is None:
2526
self._new_revision_id = self._gen_revision_id()
2528
def record_entry_contents(self, ie, parent_invs, path, tree):
2529
"""Record the content of ie from tree into the commit if needed.
2531
Side effect: sets ie.revision when unchanged
2533
:param ie: An inventory entry present in the commit.
2534
:param parent_invs: The inventories of the parent revisions of the
2536
:param path: The path the entry is at in the tree.
2537
:param tree: The tree which contains this entry and should be used to
2540
if self.new_inventory.root is None and ie.parent_id is not None:
2541
symbol_versioning.warn('Root entry should be supplied to'
2542
' record_entry_contents, as of bzr 0.10.',
2543
DeprecationWarning, stacklevel=2)
2544
self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
2546
self.new_inventory.add(ie)
2548
# ie.revision is always None if the InventoryEntry is considered
2549
# for committing. ie.snapshot will record the correct revision
2550
# which may be the sole parent if it is untouched.
2551
if ie.revision is not None:
2554
# In this revision format, root entries have no knit or weave
2555
if ie is self.new_inventory.root:
2556
# When serializing out to disk and back in
2557
# root.revision is always _new_revision_id
2558
ie.revision = self._new_revision_id
2560
previous_entries = ie.find_previous_heads(
2562
self.repository.weave_store,
2563
self.repository.get_transaction())
2564
# we are creating a new revision for ie in the history store
2566
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2568
def modified_directory(self, file_id, file_parents):
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.
2574
self._add_text_to_weave(file_id, [], file_parents.keys())
2576
def modified_file_text(self, file_id, file_parents,
2577
get_content_byte_lines, text_sha1=None,
2579
"""Record the text of file file_id
2581
:param file_id: The file_id of the file to record the text of.
2582
:param file_parents: The per-file parent revision ids.
2583
:param get_content_byte_lines: A callable which will return the byte
2585
:param text_sha1: Optional SHA1 of the file contents.
2586
:param text_size: Optional size of the file contents.
2588
# mutter('storing text of file {%s} in revision {%s} into %r',
2589
# file_id, self._new_revision_id, self.repository.weave_store)
2590
# special case to avoid diffing on renames or
2592
if (len(file_parents) == 1
2593
and text_sha1 == file_parents.values()[0].text_sha1
2594
and text_size == file_parents.values()[0].text_size):
2595
previous_ie = file_parents.values()[0]
2596
versionedfile = self.repository.weave_store.get_weave(file_id,
2597
self.repository.get_transaction())
2598
versionedfile.clone_text(self._new_revision_id,
2599
previous_ie.revision, file_parents.keys())
2600
return text_sha1, text_size
2602
new_lines = get_content_byte_lines()
2603
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2604
# should return the SHA1 and size
2605
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2606
return osutils.sha_strings(new_lines), \
2607
sum(map(len, new_lines))
2609
def modified_link(self, file_id, file_parents, link_target):
2610
"""Record the presence of a symbolic link.
2612
:param file_id: The file_id of the link to record.
2613
:param file_parents: The per-file parent revision ids.
2614
:param link_target: Target location of this link.
2616
self._add_text_to_weave(file_id, [], file_parents.keys())
2618
def _add_text_to_weave(self, file_id, new_lines, parents):
2619
versionedfile = self.repository.weave_store.get_weave_or_empty(
2620
file_id, self.repository.get_transaction())
2621
versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2622
versionedfile.clear_cache()
2625
class _CommitBuilder(CommitBuilder):
2626
"""Temporary class so old CommitBuilders are detected properly
2628
Note: CommitBuilder works whether or not root entry is recorded.
2631
record_root_entry = True
2634
class RootCommitBuilder(CommitBuilder):
2635
"""This commitbuilder actually records the root id"""
2637
record_root_entry = True
2639
def record_entry_contents(self, ie, parent_invs, path, tree):
2640
"""Record the content of ie from tree into the commit if needed.
2642
Side effect: sets ie.revision when unchanged
2644
:param ie: An inventory entry present in the commit.
2645
:param parent_invs: The inventories of the parent revisions of the
2647
:param path: The path the entry is at in the tree.
2648
:param tree: The tree which contains this entry and should be used to
2651
assert self.new_inventory.root is not None or ie.parent_id is None
2652
self.new_inventory.add(ie)
2654
# ie.revision is always None if the InventoryEntry is considered
2655
# for committing. ie.snapshot will record the correct revision
2656
# which may be the sole parent if it is untouched.
2657
if ie.revision is not None:
2660
previous_entries = ie.find_previous_heads(
2662
self.repository.weave_store,
2663
self.repository.get_transaction())
2664
# we are creating a new revision for ie in the history store
2666
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2678
def _unescaper(match, _map=_unescape_map):
2679
return _map[match.group(1)]
2685
def _unescape_xml(data):
2686
"""Unescape predefined XML entities in a string of data."""
2688
if _unescape_re is None:
2689
_unescape_re = re.compile('\&([^;]*);')
2690
return _unescape_re.sub(_unescaper, data)