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
self.control_files.leave_in_place()
263
def dont_leave_lock_in_place(self):
264
"""Tell this repository to release the physical lock when this
265
object is unlocked, even if it didn't originally acquire it.
267
self.control_files.dont_leave_in_place()
270
def gather_stats(self, revid=None, committers=None):
271
"""Gather statistics from a revision id.
273
:param revid: The revision id to gather statistics from, if None, then
274
no revision specific statistics are gathered.
275
:param committers: Optional parameter controlling whether to grab
276
a count of committers from the revision specific statistics.
277
:return: A dictionary of statistics. Currently this contains:
278
committers: The number of committers if requested.
279
firstrev: A tuple with timestamp, timezone for the penultimate left
280
most ancestor of revid, if revid is not the NULL_REVISION.
281
latestrev: A tuple with timestamp, timezone for revid, if revid is
282
not the NULL_REVISION.
283
revisions: The total revision count in the repository.
284
size: An estimate disk size of the repository in bytes.
287
if revid and committers:
288
result['committers'] = 0
289
if revid and revid != _mod_revision.NULL_REVISION:
291
all_committers = set()
292
revisions = self.get_ancestry(revid)
293
# pop the leading None
295
first_revision = None
297
# ignore the revisions in the middle - just grab first and last
298
revisions = revisions[0], revisions[-1]
299
for revision in self.get_revisions(revisions):
300
if not first_revision:
301
first_revision = revision
303
all_committers.add(revision.committer)
304
last_revision = revision
306
result['committers'] = len(all_committers)
307
result['firstrev'] = (first_revision.timestamp,
308
first_revision.timezone)
309
result['latestrev'] = (last_revision.timestamp,
310
last_revision.timezone)
312
# now gather global repository information
313
if self.bzrdir.root_transport.listable():
314
c, t = self._revision_store.total_size(self.get_transaction())
315
result['revisions'] = c
320
def missing_revision_ids(self, other, revision_id=None):
321
"""Return the revision ids that other has that this does not.
323
These are returned in topological order.
325
revision_id: only return revision ids included by revision_id.
327
return InterRepository.get(other, self).missing_revision_ids(revision_id)
331
"""Open the repository rooted at base.
333
For instance, if the repository is at URL/.bzr/repository,
334
Repository.open(URL) -> a Repository instance.
336
control = bzrdir.BzrDir.open(base)
337
return control.open_repository()
339
def copy_content_into(self, destination, revision_id=None, basis=None):
340
"""Make a complete copy of the content in self into destination.
342
This is a destructive operation! Do not use it on existing
345
return InterRepository.get(self, destination).copy_content(revision_id, basis)
347
def fetch(self, source, revision_id=None, pb=None):
348
"""Fetch the content required to construct revision_id from source.
350
If revision_id is None all content is copied.
352
return InterRepository.get(source, self).fetch(revision_id=revision_id,
355
def get_commit_builder(self, branch, parents, config, timestamp=None,
356
timezone=None, committer=None, revprops=None,
358
"""Obtain a CommitBuilder for this repository.
360
:param branch: Branch to commit to.
361
:param parents: Revision ids of the parents of the new revision.
362
:param config: Configuration to use.
363
:param timestamp: Optional timestamp recorded for commit.
364
:param timezone: Optional timezone for timestamp.
365
:param committer: Optional committer to set for commit.
366
:param revprops: Optional dictionary of revision properties.
367
:param revision_id: Optional revision id.
369
return _CommitBuilder(self, parents, config, timestamp, timezone,
370
committer, revprops, revision_id)
373
self.control_files.unlock()
376
def clone(self, a_bzrdir, revision_id=None, basis=None):
377
"""Clone this repository into a_bzrdir using the current format.
379
Currently no check is made that the format of this repository and
380
the bzrdir format are compatible. FIXME RBC 20060201.
382
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
383
# use target default format.
384
result = a_bzrdir.create_repository()
385
# FIXME RBC 20060209 split out the repository type to avoid this check ?
386
elif isinstance(a_bzrdir._format,
387
(bzrdir.BzrDirFormat4,
388
bzrdir.BzrDirFormat5,
389
bzrdir.BzrDirFormat6)):
390
result = a_bzrdir.open_repository()
392
result = self._format.initialize(a_bzrdir, shared=self.is_shared())
393
self.copy_content_into(result, revision_id, basis)
397
def has_revision(self, revision_id):
398
"""True if this repository has a copy of the revision."""
399
return self._revision_store.has_revision_id(revision_id,
400
self.get_transaction())
403
def get_revision_reconcile(self, revision_id):
404
"""'reconcile' helper routine that allows access to a revision always.
406
This variant of get_revision does not cross check the weave graph
407
against the revision one as get_revision does: but it should only
408
be used by reconcile, or reconcile-alike commands that are correcting
409
or testing the revision graph.
411
if not revision_id or not isinstance(revision_id, basestring):
412
raise errors.InvalidRevisionId(revision_id=revision_id,
414
return self._revision_store.get_revisions([revision_id],
415
self.get_transaction())[0]
417
def get_revisions(self, revision_ids):
418
return self._revision_store.get_revisions(revision_ids,
419
self.get_transaction())
422
def get_revision_xml(self, revision_id):
423
rev = self.get_revision(revision_id)
425
# the current serializer..
426
self._revision_store._serializer.write_revision(rev, rev_tmp)
428
return rev_tmp.getvalue()
431
def get_revision(self, revision_id):
432
"""Return the Revision object for a named revision"""
433
r = self.get_revision_reconcile(revision_id)
434
# weave corruption can lead to absent revision markers that should be
436
# the following test is reasonably cheap (it needs a single weave read)
437
# and the weave is cached in read transactions. In write transactions
438
# it is not cached but typically we only read a small number of
439
# revisions. For knits when they are introduced we will probably want
440
# to ensure that caching write transactions are in use.
441
inv = self.get_inventory_weave()
442
self._check_revision_parents(r, inv)
446
def get_deltas_for_revisions(self, revisions):
447
"""Produce a generator of revision deltas.
449
Note that the input is a sequence of REVISIONS, not revision_ids.
450
Trees will be held in memory until the generator exits.
451
Each delta is relative to the revision's lefthand predecessor.
453
required_trees = set()
454
for revision in revisions:
455
required_trees.add(revision.revision_id)
456
required_trees.update(revision.parent_ids[:1])
457
trees = dict((t.get_revision_id(), t) for
458
t in self.revision_trees(required_trees))
459
for revision in revisions:
460
if not revision.parent_ids:
461
old_tree = self.revision_tree(None)
463
old_tree = trees[revision.parent_ids[0]]
464
yield trees[revision.revision_id].changes_from(old_tree)
467
def get_revision_delta(self, revision_id):
468
"""Return the delta for one revision.
470
The delta is relative to the left-hand predecessor of the
473
r = self.get_revision(revision_id)
474
return list(self.get_deltas_for_revisions([r]))[0]
476
def _check_revision_parents(self, revision, inventory):
477
"""Private to Repository and Fetch.
479
This checks the parentage of revision in an inventory weave for
480
consistency and is only applicable to inventory-weave-for-ancestry
481
using repository formats & fetchers.
483
weave_parents = inventory.get_parents(revision.revision_id)
484
weave_names = inventory.versions()
485
for parent_id in revision.parent_ids:
486
if parent_id in weave_names:
487
# this parent must not be a ghost.
488
if not parent_id in weave_parents:
490
raise errors.CorruptRepository(self)
493
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
494
signature = gpg_strategy.sign(plaintext)
495
self._revision_store.add_revision_signature_text(revision_id,
497
self.get_transaction())
499
def fileids_altered_by_revision_ids(self, revision_ids):
500
"""Find the file ids and versions affected by revisions.
502
:param revisions: an iterable containing revision ids.
503
:return: a dictionary mapping altered file-ids to an iterable of
504
revision_ids. Each altered file-ids has the exact revision_ids that
505
altered it listed explicitly.
507
assert self._serializer.support_altered_by_hack, \
508
("fileids_altered_by_revision_ids only supported for branches "
509
"which store inventory as unnested xml, not on %r" % self)
510
selected_revision_ids = set(revision_ids)
511
w = self.get_inventory_weave()
514
# this code needs to read every new line in every inventory for the
515
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
516
# not present in one of those inventories is unnecessary but not
517
# harmful because we are filtering by the revision id marker in the
518
# inventory lines : we only select file ids altered in one of those
519
# revisions. We don't need to see all lines in the inventory because
520
# only those added in an inventory in rev X can contain a revision=X
522
unescape_revid_cache = {}
523
unescape_fileid_cache = {}
525
# jam 20061218 In a big fetch, this handles hundreds of thousands
526
# of lines, so it has had a lot of inlining and optimizing done.
527
# Sorry that it is a little bit messy.
528
# Move several functions to be local variables, since this is a long
530
search = self._file_ids_altered_regex.search
531
unescape = _unescape_xml
532
setdefault = result.setdefault
533
pb = ui.ui_factory.nested_progress_bar()
535
for line in w.iter_lines_added_or_present_in_versions(
536
selected_revision_ids, pb=pb):
540
# One call to match.group() returning multiple items is quite a
541
# bit faster than 2 calls to match.group() each returning 1
542
file_id, revision_id = match.group('file_id', 'revision_id')
544
# Inlining the cache lookups helps a lot when you make 170,000
545
# lines and 350k ids, versus 8.4 unique ids.
546
# Using a cache helps in 2 ways:
547
# 1) Avoids unnecessary decoding calls
548
# 2) Re-uses cached strings, which helps in future set and
550
# (2) is enough that removing encoding entirely along with
551
# the cache (so we are using plain strings) results in no
552
# performance improvement.
554
revision_id = unescape_revid_cache[revision_id]
556
unescaped = unescape(revision_id)
557
unescape_revid_cache[revision_id] = unescaped
558
revision_id = unescaped
560
if revision_id in selected_revision_ids:
562
file_id = unescape_fileid_cache[file_id]
564
unescaped = unescape(file_id)
565
unescape_fileid_cache[file_id] = unescaped
567
setdefault(file_id, set()).add(revision_id)
573
def get_inventory_weave(self):
574
return self.control_weaves.get_weave('inventory',
575
self.get_transaction())
578
def get_inventory(self, revision_id):
579
"""Get Inventory object by hash."""
580
return self.deserialise_inventory(
581
revision_id, self.get_inventory_xml(revision_id))
583
def deserialise_inventory(self, revision_id, xml):
584
"""Transform the xml into an inventory object.
586
:param revision_id: The expected revision id of the inventory.
587
:param xml: A serialised inventory.
589
result = self._serializer.read_inventory_from_string(xml)
590
result.root.revision = revision_id
593
def serialise_inventory(self, inv):
594
return self._serializer.write_inventory_to_string(inv)
597
def get_inventory_xml(self, revision_id):
598
"""Get inventory XML as a file object."""
600
assert isinstance(revision_id, basestring), type(revision_id)
601
iw = self.get_inventory_weave()
602
return iw.get_text(revision_id)
604
raise errors.HistoryMissing(self, 'inventory', revision_id)
607
def get_inventory_sha1(self, revision_id):
608
"""Return the sha1 hash of the inventory entry
610
return self.get_revision(revision_id).inventory_sha1
613
def get_revision_graph(self, revision_id=None):
614
"""Return a dictionary containing the revision graph.
616
:param revision_id: The revision_id to get a graph from. If None, then
617
the entire revision graph is returned. This is a deprecated mode of
618
operation and will be removed in the future.
619
:return: a dictionary of revision_id->revision_parents_list.
621
# special case NULL_REVISION
622
if revision_id == _mod_revision.NULL_REVISION:
624
a_weave = self.get_inventory_weave()
625
all_revisions = self._eliminate_revisions_not_present(
627
entire_graph = dict([(node, a_weave.get_parents(node)) for
628
node in all_revisions])
629
if revision_id is None:
631
elif revision_id not in entire_graph:
632
raise errors.NoSuchRevision(self, revision_id)
634
# add what can be reached from revision_id
636
pending = set([revision_id])
637
while len(pending) > 0:
639
result[node] = entire_graph[node]
640
for revision_id in result[node]:
641
if revision_id not in result:
642
pending.add(revision_id)
646
def get_revision_graph_with_ghosts(self, revision_ids=None):
647
"""Return a graph of the revisions with ghosts marked as applicable.
649
:param revision_ids: an iterable of revisions to graph or None for all.
650
:return: a Graph object with the graph reachable from revision_ids.
652
result = graph.Graph()
654
pending = set(self.all_revision_ids())
657
pending = set(revision_ids)
658
# special case NULL_REVISION
659
if _mod_revision.NULL_REVISION in pending:
660
pending.remove(_mod_revision.NULL_REVISION)
661
required = set(pending)
664
revision_id = pending.pop()
666
rev = self.get_revision(revision_id)
667
except errors.NoSuchRevision:
668
if revision_id in required:
671
result.add_ghost(revision_id)
673
for parent_id in rev.parent_ids:
674
# is this queued or done ?
675
if (parent_id not in pending and
676
parent_id not in done):
678
pending.add(parent_id)
679
result.add_node(revision_id, rev.parent_ids)
680
done.add(revision_id)
684
def get_revision_inventory(self, revision_id):
685
"""Return inventory of a past revision."""
686
# TODO: Unify this with get_inventory()
687
# bzr 0.0.6 and later imposes the constraint that the inventory_id
688
# must be the same as its revision, so this is trivial.
689
if revision_id is None:
690
# This does not make sense: if there is no revision,
691
# then it is the current tree inventory surely ?!
692
# and thus get_root_id() is something that looks at the last
693
# commit on the branch, and the get_root_id is an inventory check.
694
raise NotImplementedError
695
# return Inventory(self.get_root_id())
697
return self.get_inventory(revision_id)
701
"""Return True if this repository is flagged as a shared repository."""
702
raise NotImplementedError(self.is_shared)
705
def reconcile(self, other=None, thorough=False):
706
"""Reconcile this repository."""
707
from bzrlib.reconcile import RepoReconciler
708
reconciler = RepoReconciler(self, thorough=thorough)
709
reconciler.reconcile()
713
def revision_tree(self, revision_id):
714
"""Return Tree for a revision on this branch.
716
`revision_id` may be None for the empty tree revision.
718
# TODO: refactor this to use an existing revision object
719
# so we don't need to read it in twice.
720
if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
721
return RevisionTree(self, Inventory(root_id=None),
722
_mod_revision.NULL_REVISION)
724
inv = self.get_revision_inventory(revision_id)
725
return RevisionTree(self, inv, revision_id)
728
def revision_trees(self, revision_ids):
729
"""Return Tree for a revision on this branch.
731
`revision_id` may not be None or 'null:'"""
732
assert None not in revision_ids
733
assert _mod_revision.NULL_REVISION not in revision_ids
734
texts = self.get_inventory_weave().get_texts(revision_ids)
735
for text, revision_id in zip(texts, revision_ids):
736
inv = self.deserialise_inventory(revision_id, text)
737
yield RevisionTree(self, inv, revision_id)
740
def get_ancestry(self, revision_id):
741
"""Return a list of revision-ids integrated by a revision.
743
The first element of the list is always None, indicating the origin
744
revision. This might change when we have history horizons, or
745
perhaps we should have a new API.
747
This is topologically sorted.
749
if revision_id is None:
751
if not self.has_revision(revision_id):
752
raise errors.NoSuchRevision(self, revision_id)
753
w = self.get_inventory_weave()
754
candidates = w.get_ancestry(revision_id)
755
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
758
def print_file(self, file, revision_id):
759
"""Print `file` to stdout.
761
FIXME RBC 20060125 as John Meinel points out this is a bad api
762
- it writes to stdout, it assumes that that is valid etc. Fix
763
by creating a new more flexible convenience function.
765
tree = self.revision_tree(revision_id)
766
# use inventory as it was in that revision
767
file_id = tree.inventory.path2id(file)
769
# TODO: jam 20060427 Write a test for this code path
770
# it had a bug in it, and was raising the wrong
772
raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
773
tree.print_file(file_id)
775
def get_transaction(self):
776
return self.control_files.get_transaction()
778
def revision_parents(self, revid):
779
return self.get_inventory_weave().parent_names(revid)
782
def set_make_working_trees(self, new_value):
783
"""Set the policy flag for making working trees when creating branches.
785
This only applies to branches that use this repository.
787
The default is 'True'.
788
:param new_value: True to restore the default, False to disable making
791
raise NotImplementedError(self.set_make_working_trees)
793
def make_working_trees(self):
794
"""Returns the policy for making working trees on new branches."""
795
raise NotImplementedError(self.make_working_trees)
798
def sign_revision(self, revision_id, gpg_strategy):
799
plaintext = Testament.from_revision(self, revision_id).as_short_text()
800
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
803
def has_signature_for_revision_id(self, revision_id):
804
"""Query for a revision signature for revision_id in the repository."""
805
return self._revision_store.has_signature(revision_id,
806
self.get_transaction())
809
def get_signature_text(self, revision_id):
810
"""Return the text for a signature."""
811
return self._revision_store.get_signature_text(revision_id,
812
self.get_transaction())
815
def check(self, revision_ids):
816
"""Check consistency of all history of given revision_ids.
818
Different repository implementations should override _check().
820
:param revision_ids: A non-empty list of revision_ids whose ancestry
821
will be checked. Typically the last revision_id of a branch.
824
raise ValueError("revision_ids must be non-empty in %s.check"
826
return self._check(revision_ids)
828
def _check(self, revision_ids):
829
result = check.Check(self)
833
def _warn_if_deprecated(self):
834
global _deprecation_warning_done
835
if _deprecation_warning_done:
837
_deprecation_warning_done = True
838
warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
839
% (self._format, self.bzrdir.transport.base))
841
def supports_rich_root(self):
842
return self._format.rich_root_data
844
def _check_ascii_revisionid(self, revision_id, method):
845
"""Private helper for ascii-only repositories."""
846
# weave repositories refuse to store revisionids that are non-ascii.
847
if revision_id is not None:
848
# weaves require ascii revision ids.
849
if isinstance(revision_id, unicode):
851
revision_id.encode('ascii')
852
except UnicodeEncodeError:
853
raise errors.NonAsciiRevisionId(method, self)
856
class AllInOneRepository(Repository):
857
"""Legacy support - the repository behaviour for all-in-one branches."""
859
def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
860
# we reuse one control files instance.
861
dir_mode = a_bzrdir._control_files._dir_mode
862
file_mode = a_bzrdir._control_files._file_mode
864
def get_store(name, compressed=True, prefixed=False):
865
# FIXME: This approach of assuming stores are all entirely compressed
866
# or entirely uncompressed is tidy, but breaks upgrade from
867
# some existing branches where there's a mixture; we probably
868
# still want the option to look for both.
869
relpath = a_bzrdir._control_files._escape(name)
870
store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
871
prefixed=prefixed, compressed=compressed,
874
#if self._transport.should_cache():
875
# cache_path = os.path.join(self.cache_root, name)
876
# os.mkdir(cache_path)
877
# store = bzrlib.store.CachedStore(store, cache_path)
880
# not broken out yet because the controlweaves|inventory_store
881
# and text_store | weave_store bits are still different.
882
if isinstance(_format, RepositoryFormat4):
883
# cannot remove these - there is still no consistent api
884
# which allows access to this old info.
885
self.inventory_store = get_store('inventory-store')
886
text_store = get_store('text-store')
887
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
889
def get_commit_builder(self, branch, parents, config, timestamp=None,
890
timezone=None, committer=None, revprops=None,
892
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
893
return Repository.get_commit_builder(self, branch, parents, config,
894
timestamp, timezone, committer, revprops, revision_id)
898
"""AllInOne repositories cannot be shared."""
902
def set_make_working_trees(self, new_value):
903
"""Set the policy flag for making working trees when creating branches.
905
This only applies to branches that use this repository.
907
The default is 'True'.
908
:param new_value: True to restore the default, False to disable making
911
raise NotImplementedError(self.set_make_working_trees)
913
def make_working_trees(self):
914
"""Returns the policy for making working trees on new branches."""
918
def install_revision(repository, rev, revision_tree):
919
"""Install all revision data into a repository."""
922
for p_id in rev.parent_ids:
923
if repository.has_revision(p_id):
924
present_parents.append(p_id)
925
parent_trees[p_id] = repository.revision_tree(p_id)
927
parent_trees[p_id] = repository.revision_tree(None)
929
inv = revision_tree.inventory
930
entries = inv.iter_entries()
931
# backwards compatability hack: skip the root id.
932
if not repository.supports_rich_root():
933
path, root = entries.next()
934
if root.revision != rev.revision_id:
935
raise errors.IncompatibleRevision(repr(repository))
936
# Add the texts that are not already present
937
for path, ie in entries:
938
w = repository.weave_store.get_weave_or_empty(ie.file_id,
939
repository.get_transaction())
940
if ie.revision not in w:
942
# FIXME: TODO: The following loop *may* be overlapping/duplicate
943
# with InventoryEntry.find_previous_heads(). if it is, then there
944
# is a latent bug here where the parents may have ancestors of each
946
for revision, tree in parent_trees.iteritems():
947
if ie.file_id not in tree:
949
parent_id = tree.inventory[ie.file_id].revision
950
if parent_id in text_parents:
952
text_parents.append(parent_id)
954
vfile = repository.weave_store.get_weave_or_empty(ie.file_id,
955
repository.get_transaction())
956
lines = revision_tree.get_file(ie.file_id).readlines()
957
vfile.add_lines(rev.revision_id, text_parents, lines)
959
# install the inventory
960
repository.add_inventory(rev.revision_id, inv, present_parents)
961
except errors.RevisionAlreadyPresent:
963
repository.add_revision(rev.revision_id, rev, inv)
966
class MetaDirRepository(Repository):
967
"""Repositories in the new meta-dir layout."""
969
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
970
super(MetaDirRepository, self).__init__(_format,
976
dir_mode = self.control_files._dir_mode
977
file_mode = self.control_files._file_mode
981
"""Return True if this repository is flagged as a shared repository."""
982
return self.control_files._transport.has('shared-storage')
985
def set_make_working_trees(self, new_value):
986
"""Set the policy flag for making working trees when creating branches.
988
This only applies to branches that use this repository.
990
The default is 'True'.
991
:param new_value: True to restore the default, False to disable making
996
self.control_files._transport.delete('no-working-trees')
997
except errors.NoSuchFile:
1000
self.control_files.put_utf8('no-working-trees', '')
1002
def make_working_trees(self):
1003
"""Returns the policy for making working trees on new branches."""
1004
return not self.control_files._transport.has('no-working-trees')
1007
class WeaveMetaDirRepository(MetaDirRepository):
1008
"""A subclass of MetaDirRepository to set weave specific policy."""
1010
def get_commit_builder(self, branch, parents, config, timestamp=None,
1011
timezone=None, committer=None, revprops=None,
1013
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
1014
return MetaDirRepository.get_commit_builder(self, branch, parents,
1015
config, timestamp, timezone, committer, revprops, revision_id)
1018
class KnitRepository(MetaDirRepository):
1019
"""Knit format repository."""
1021
def _warn_if_deprecated(self):
1022
# This class isn't deprecated
1025
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
1026
inv_vf.add_lines_with_ghosts(revid, parents, lines)
1029
def _all_revision_ids(self):
1030
"""See Repository.all_revision_ids()."""
1031
# Knits get the revision graph from the index of the revision knit, so
1032
# it's always possible even if they're on an unlistable transport.
1033
return self._revision_store.all_revision_ids(self.get_transaction())
1035
def fileid_involved_between_revs(self, from_revid, to_revid):
1036
"""Find file_id(s) which are involved in the changes between revisions.
1038
This determines the set of revisions which are involved, and then
1039
finds all file ids affected by those revisions.
1041
vf = self._get_revision_vf()
1042
from_set = set(vf.get_ancestry(from_revid))
1043
to_set = set(vf.get_ancestry(to_revid))
1044
changed = to_set.difference(from_set)
1045
return self._fileid_involved_by_set(changed)
1047
def fileid_involved(self, last_revid=None):
1048
"""Find all file_ids modified in the ancestry of last_revid.
1050
:param last_revid: If None, last_revision() will be used.
1053
changed = set(self.all_revision_ids())
1055
changed = set(self.get_ancestry(last_revid))
1057
changed.remove(None)
1058
return self._fileid_involved_by_set(changed)
1061
def get_ancestry(self, revision_id):
1062
"""Return a list of revision-ids integrated by a revision.
1064
This is topologically sorted.
1066
if revision_id is None:
1068
vf = self._get_revision_vf()
1070
return [None] + vf.get_ancestry(revision_id)
1071
except errors.RevisionNotPresent:
1072
raise errors.NoSuchRevision(self, revision_id)
1075
def get_revision(self, revision_id):
1076
"""Return the Revision object for a named revision"""
1077
return self.get_revision_reconcile(revision_id)
1080
def get_revision_graph(self, revision_id=None):
1081
"""Return a dictionary containing the revision graph.
1083
:param revision_id: The revision_id to get a graph from. If None, then
1084
the entire revision graph is returned. This is a deprecated mode of
1085
operation and will be removed in the future.
1086
:return: a dictionary of revision_id->revision_parents_list.
1088
# special case NULL_REVISION
1089
if revision_id == _mod_revision.NULL_REVISION:
1091
a_weave = self._get_revision_vf()
1092
entire_graph = a_weave.get_graph()
1093
if revision_id is None:
1094
return a_weave.get_graph()
1095
elif revision_id not in a_weave:
1096
raise errors.NoSuchRevision(self, revision_id)
1098
# add what can be reached from revision_id
1100
pending = set([revision_id])
1101
while len(pending) > 0:
1102
node = pending.pop()
1103
result[node] = a_weave.get_parents(node)
1104
for revision_id in result[node]:
1105
if revision_id not in result:
1106
pending.add(revision_id)
1110
def get_revision_graph_with_ghosts(self, revision_ids=None):
1111
"""Return a graph of the revisions with ghosts marked as applicable.
1113
:param revision_ids: an iterable of revisions to graph or None for all.
1114
:return: a Graph object with the graph reachable from revision_ids.
1116
result = graph.Graph()
1117
vf = self._get_revision_vf()
1118
versions = set(vf.versions())
1119
if not revision_ids:
1120
pending = set(self.all_revision_ids())
1123
pending = set(revision_ids)
1124
# special case NULL_REVISION
1125
if _mod_revision.NULL_REVISION in pending:
1126
pending.remove(_mod_revision.NULL_REVISION)
1127
required = set(pending)
1130
revision_id = pending.pop()
1131
if not revision_id in versions:
1132
if revision_id in required:
1133
raise errors.NoSuchRevision(self, revision_id)
1135
result.add_ghost(revision_id)
1136
# mark it as done so we don't try for it again.
1137
done.add(revision_id)
1139
parent_ids = vf.get_parents_with_ghosts(revision_id)
1140
for parent_id in parent_ids:
1141
# is this queued or done ?
1142
if (parent_id not in pending and
1143
parent_id not in done):
1145
pending.add(parent_id)
1146
result.add_node(revision_id, parent_ids)
1147
done.add(revision_id)
1150
def _get_revision_vf(self):
1151
""":return: a versioned file containing the revisions."""
1152
vf = self._revision_store.get_revision_file(self.get_transaction())
1156
def reconcile(self, other=None, thorough=False):
1157
"""Reconcile this repository."""
1158
from bzrlib.reconcile import KnitReconciler
1159
reconciler = KnitReconciler(self, thorough=thorough)
1160
reconciler.reconcile()
1163
def revision_parents(self, revision_id):
1164
return self._get_revision_vf().get_parents(revision_id)
1167
class KnitRepository2(KnitRepository):
1169
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
1170
control_store, text_store):
1171
KnitRepository.__init__(self, _format, a_bzrdir, control_files,
1172
_revision_store, control_store, text_store)
1173
self._serializer = xml6.serializer_v6
1175
def deserialise_inventory(self, revision_id, xml):
1176
"""Transform the xml into an inventory object.
1178
:param revision_id: The expected revision id of the inventory.
1179
:param xml: A serialised inventory.
1181
result = self._serializer.read_inventory_from_string(xml)
1182
assert result.root.revision is not None
1185
def serialise_inventory(self, inv):
1186
"""Transform the inventory object into XML text.
1188
:param revision_id: The expected revision id of the inventory.
1189
:param xml: A serialised inventory.
1191
assert inv.revision_id is not None
1192
assert inv.root.revision is not None
1193
return KnitRepository.serialise_inventory(self, inv)
1195
def get_commit_builder(self, branch, parents, config, timestamp=None,
1196
timezone=None, committer=None, revprops=None,
1198
"""Obtain a CommitBuilder for this repository.
1200
:param branch: Branch to commit to.
1201
:param parents: Revision ids of the parents of the new revision.
1202
:param config: Configuration to use.
1203
:param timestamp: Optional timestamp recorded for commit.
1204
:param timezone: Optional timezone for timestamp.
1205
:param committer: Optional committer to set for commit.
1206
:param revprops: Optional dictionary of revision properties.
1207
:param revision_id: Optional revision id.
1209
return RootCommitBuilder(self, parents, config, timestamp, timezone,
1210
committer, revprops, revision_id)
1213
class RepositoryFormatRegistry(registry.Registry):
1214
"""Registry of RepositoryFormats.
1218
format_registry = RepositoryFormatRegistry()
1219
"""Registry of formats, indexed by their identifying format string."""
1222
class RepositoryFormat(object):
1223
"""A repository format.
1225
Formats provide three things:
1226
* An initialization routine to construct repository data on disk.
1227
* a format string which is used when the BzrDir supports versioned
1229
* an open routine which returns a Repository instance.
1231
Formats are placed in an dict by their format string for reference
1232
during opening. These should be subclasses of RepositoryFormat
1235
Once a format is deprecated, just deprecate the initialize and open
1236
methods on the format class. Do not deprecate the object, as the
1237
object will be created every system load.
1239
Common instance attributes:
1240
_matchingbzrdir - the bzrdir format that the repository format was
1241
originally written to work with. This can be used if manually
1242
constructing a bzrdir and repository, or more commonly for test suite
1247
return "<%s>" % self.__class__.__name__
1250
def find_format(klass, a_bzrdir):
1251
"""Return the format for the repository object in a_bzrdir.
1253
This is used by bzr native formats that have a "format" file in
1254
the repository. Other methods may be used by different types of
1258
transport = a_bzrdir.get_repository_transport(None)
1259
format_string = transport.get("format").read()
1260
return format_registry.get(format_string)
1261
except errors.NoSuchFile:
1262
raise errors.NoRepositoryPresent(a_bzrdir)
1264
raise errors.UnknownFormatError(format=format_string)
1267
def register_format(klass, format):
1268
format_registry.register(format.get_format_string(), format)
1271
def unregister_format(klass, format):
1272
format_registry.remove(format.get_format_string())
1275
def get_default_format(klass):
1276
"""Return the current default format."""
1277
from bzrlib import bzrdir
1278
return bzrdir.format_registry.make_bzrdir('default').repository_format
1280
def _get_control_store(self, repo_transport, control_files):
1281
"""Return the control store for this repository."""
1282
raise NotImplementedError(self._get_control_store)
1284
def get_format_string(self):
1285
"""Return the ASCII format string that identifies this format.
1287
Note that in pre format ?? repositories the format string is
1288
not permitted nor written to disk.
1290
raise NotImplementedError(self.get_format_string)
1292
def get_format_description(self):
1293
"""Return the short description for this format."""
1294
raise NotImplementedError(self.get_format_description)
1296
def _get_revision_store(self, repo_transport, control_files):
1297
"""Return the revision store object for this a_bzrdir."""
1298
raise NotImplementedError(self._get_revision_store)
1300
def _get_text_rev_store(self,
1307
"""Common logic for getting a revision store for a repository.
1309
see self._get_revision_store for the subclass-overridable method to
1310
get the store for a repository.
1312
from bzrlib.store.revision.text import TextRevisionStore
1313
dir_mode = control_files._dir_mode
1314
file_mode = control_files._file_mode
1315
text_store =TextStore(transport.clone(name),
1317
compressed=compressed,
1319
file_mode=file_mode)
1320
_revision_store = TextRevisionStore(text_store, serializer)
1321
return _revision_store
1323
def _get_versioned_file_store(self,
1328
versionedfile_class=weave.WeaveFile,
1329
versionedfile_kwargs={},
1331
weave_transport = control_files._transport.clone(name)
1332
dir_mode = control_files._dir_mode
1333
file_mode = control_files._file_mode
1334
return VersionedFileStore(weave_transport, prefixed=prefixed,
1336
file_mode=file_mode,
1337
versionedfile_class=versionedfile_class,
1338
versionedfile_kwargs=versionedfile_kwargs,
1341
def initialize(self, a_bzrdir, shared=False):
1342
"""Initialize a repository of this format in a_bzrdir.
1344
:param a_bzrdir: The bzrdir to put the new repository in it.
1345
:param shared: The repository should be initialized as a sharable one.
1346
:returns: The new repository object.
1348
This may raise UninitializableFormat if shared repository are not
1349
compatible the a_bzrdir.
1351
raise NotImplementedError(self.initialize)
1353
def is_supported(self):
1354
"""Is this format supported?
1356
Supported formats must be initializable and openable.
1357
Unsupported formats may not support initialization or committing or
1358
some other features depending on the reason for not being supported.
1362
def check_conversion_target(self, target_format):
1363
raise NotImplementedError(self.check_conversion_target)
1365
def open(self, a_bzrdir, _found=False):
1366
"""Return an instance of this format for the bzrdir a_bzrdir.
1368
_found is a private parameter, do not use it.
1370
raise NotImplementedError(self.open)
1373
class PreSplitOutRepositoryFormat(RepositoryFormat):
1374
"""Base class for the pre split out repository formats."""
1376
rich_root_data = False
1378
def initialize(self, a_bzrdir, shared=False, _internal=False):
1379
"""Create a weave repository.
1381
TODO: when creating split out bzr branch formats, move this to a common
1382
base for Format5, Format6. or something like that.
1385
raise errors.IncompatibleFormat(self, a_bzrdir._format)
1388
# always initialized when the bzrdir is.
1389
return self.open(a_bzrdir, _found=True)
1391
# Create an empty weave
1393
weavefile.write_weave_v5(weave.Weave(), sio)
1394
empty_weave = sio.getvalue()
1396
mutter('creating repository in %s.', a_bzrdir.transport.base)
1397
dirs = ['revision-store', 'weaves']
1398
files = [('inventory.weave', StringIO(empty_weave)),
1401
# FIXME: RBC 20060125 don't peek under the covers
1402
# NB: no need to escape relative paths that are url safe.
1403
control_files = lockable_files.LockableFiles(a_bzrdir.transport,
1404
'branch-lock', lockable_files.TransportLock)
1405
control_files.create_lock()
1406
control_files.lock_write()
1407
control_files._transport.mkdir_multi(dirs,
1408
mode=control_files._dir_mode)
1410
for file, content in files:
1411
control_files.put(file, content)
1413
control_files.unlock()
1414
return self.open(a_bzrdir, _found=True)
1416
def _get_control_store(self, repo_transport, control_files):
1417
"""Return the control store for this repository."""
1418
return self._get_versioned_file_store('',
1423
def _get_text_store(self, transport, control_files):
1424
"""Get a store for file texts for this format."""
1425
raise NotImplementedError(self._get_text_store)
1427
def open(self, a_bzrdir, _found=False):
1428
"""See RepositoryFormat.open()."""
1430
# we are being called directly and must probe.
1431
raise NotImplementedError
1433
repo_transport = a_bzrdir.get_repository_transport(None)
1434
control_files = a_bzrdir._control_files
1435
text_store = self._get_text_store(repo_transport, control_files)
1436
control_store = self._get_control_store(repo_transport, control_files)
1437
_revision_store = self._get_revision_store(repo_transport, control_files)
1438
return AllInOneRepository(_format=self,
1440
_revision_store=_revision_store,
1441
control_store=control_store,
1442
text_store=text_store)
1444
def check_conversion_target(self, target_format):
1448
class RepositoryFormat4(PreSplitOutRepositoryFormat):
1449
"""Bzr repository format 4.
1451
This repository format has:
1453
- TextStores for texts, inventories,revisions.
1455
This format is deprecated: it indexes texts using a text id which is
1456
removed in format 5; initialization and write support for this format
1461
super(RepositoryFormat4, self).__init__()
1462
self._matchingbzrdir = bzrdir.BzrDirFormat4()
1464
def get_format_description(self):
1465
"""See RepositoryFormat.get_format_description()."""
1466
return "Repository format 4"
1468
def initialize(self, url, shared=False, _internal=False):
1469
"""Format 4 branches cannot be created."""
1470
raise errors.UninitializableFormat(self)
1472
def is_supported(self):
1473
"""Format 4 is not supported.
1475
It is not supported because the model changed from 4 to 5 and the
1476
conversion logic is expensive - so doing it on the fly was not
1481
def _get_control_store(self, repo_transport, control_files):
1482
"""Format 4 repositories have no formal control store at this point.
1484
This will cause any control-file-needing apis to fail - this is desired.
1488
def _get_revision_store(self, repo_transport, control_files):
1489
"""See RepositoryFormat._get_revision_store()."""
1490
from bzrlib.xml4 import serializer_v4
1491
return self._get_text_rev_store(repo_transport,
1494
serializer=serializer_v4)
1496
def _get_text_store(self, transport, control_files):
1497
"""See RepositoryFormat._get_text_store()."""
1500
class RepositoryFormat5(PreSplitOutRepositoryFormat):
1501
"""Bzr control format 5.
1503
This repository format has:
1504
- weaves for file texts and inventory
1506
- TextStores for revisions and signatures.
1510
super(RepositoryFormat5, self).__init__()
1511
self._matchingbzrdir = bzrdir.BzrDirFormat5()
1513
def get_format_description(self):
1514
"""See RepositoryFormat.get_format_description()."""
1515
return "Weave repository format 5"
1517
def _get_revision_store(self, repo_transport, control_files):
1518
"""See RepositoryFormat._get_revision_store()."""
1519
"""Return the revision store object for this a_bzrdir."""
1520
return self._get_text_rev_store(repo_transport,
1525
def _get_text_store(self, transport, control_files):
1526
"""See RepositoryFormat._get_text_store()."""
1527
return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
1530
class RepositoryFormat6(PreSplitOutRepositoryFormat):
1531
"""Bzr control format 6.
1533
This repository format has:
1534
- weaves for file texts and inventory
1535
- hash subdirectory based stores.
1536
- TextStores for revisions and signatures.
1540
super(RepositoryFormat6, self).__init__()
1541
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1543
def get_format_description(self):
1544
"""See RepositoryFormat.get_format_description()."""
1545
return "Weave repository format 6"
1547
def _get_revision_store(self, repo_transport, control_files):
1548
"""See RepositoryFormat._get_revision_store()."""
1549
return self._get_text_rev_store(repo_transport,
1555
def _get_text_store(self, transport, control_files):
1556
"""See RepositoryFormat._get_text_store()."""
1557
return self._get_versioned_file_store('weaves', transport, control_files)
1560
class MetaDirRepositoryFormat(RepositoryFormat):
1561
"""Common base class for the new repositories using the metadir layout."""
1563
rich_root_data = False
1566
super(MetaDirRepositoryFormat, self).__init__()
1567
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1569
def _create_control_files(self, a_bzrdir):
1570
"""Create the required files and the initial control_files object."""
1571
# FIXME: RBC 20060125 don't peek under the covers
1572
# NB: no need to escape relative paths that are url safe.
1573
repository_transport = a_bzrdir.get_repository_transport(self)
1574
control_files = lockable_files.LockableFiles(repository_transport,
1575
'lock', lockdir.LockDir)
1576
control_files.create_lock()
1577
return control_files
1579
def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
1580
"""Upload the initial blank content."""
1581
control_files = self._create_control_files(a_bzrdir)
1582
control_files.lock_write()
1584
control_files._transport.mkdir_multi(dirs,
1585
mode=control_files._dir_mode)
1586
for file, content in files:
1587
control_files.put(file, content)
1588
for file, content in utf8_files:
1589
control_files.put_utf8(file, content)
1591
control_files.put_utf8('shared-storage', '')
1593
control_files.unlock()
1596
class RepositoryFormat7(MetaDirRepositoryFormat):
1597
"""Bzr repository 7.
1599
This repository format has:
1600
- weaves for file texts and inventory
1601
- hash subdirectory based stores.
1602
- TextStores for revisions and signatures.
1603
- a format marker of its own
1604
- an optional 'shared-storage' flag
1605
- an optional 'no-working-trees' flag
1608
def _get_control_store(self, repo_transport, control_files):
1609
"""Return the control store for this repository."""
1610
return self._get_versioned_file_store('',
1615
def get_format_string(self):
1616
"""See RepositoryFormat.get_format_string()."""
1617
return "Bazaar-NG Repository format 7"
1619
def get_format_description(self):
1620
"""See RepositoryFormat.get_format_description()."""
1621
return "Weave repository format 7"
1623
def check_conversion_target(self, target_format):
1626
def _get_revision_store(self, repo_transport, control_files):
1627
"""See RepositoryFormat._get_revision_store()."""
1628
return self._get_text_rev_store(repo_transport,
1635
def _get_text_store(self, transport, control_files):
1636
"""See RepositoryFormat._get_text_store()."""
1637
return self._get_versioned_file_store('weaves',
1641
def initialize(self, a_bzrdir, shared=False):
1642
"""Create a weave repository.
1644
:param shared: If true the repository will be initialized as a shared
1647
# Create an empty weave
1649
weavefile.write_weave_v5(weave.Weave(), sio)
1650
empty_weave = sio.getvalue()
1652
mutter('creating repository in %s.', a_bzrdir.transport.base)
1653
dirs = ['revision-store', 'weaves']
1654
files = [('inventory.weave', StringIO(empty_weave)),
1656
utf8_files = [('format', self.get_format_string())]
1658
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1659
return self.open(a_bzrdir=a_bzrdir, _found=True)
1661
def open(self, a_bzrdir, _found=False, _override_transport=None):
1662
"""See RepositoryFormat.open().
1664
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1665
repository at a slightly different url
1666
than normal. I.e. during 'upgrade'.
1669
format = RepositoryFormat.find_format(a_bzrdir)
1670
assert format.__class__ == self.__class__
1671
if _override_transport is not None:
1672
repo_transport = _override_transport
1674
repo_transport = a_bzrdir.get_repository_transport(None)
1675
control_files = lockable_files.LockableFiles(repo_transport,
1676
'lock', lockdir.LockDir)
1677
text_store = self._get_text_store(repo_transport, control_files)
1678
control_store = self._get_control_store(repo_transport, control_files)
1679
_revision_store = self._get_revision_store(repo_transport, control_files)
1680
return WeaveMetaDirRepository(_format=self,
1682
control_files=control_files,
1683
_revision_store=_revision_store,
1684
control_store=control_store,
1685
text_store=text_store)
1688
class RepositoryFormatKnit(MetaDirRepositoryFormat):
1689
"""Bzr repository knit format (generalized).
1691
This repository format has:
1692
- knits for file texts and inventory
1693
- hash subdirectory based stores.
1694
- knits for revisions and signatures
1695
- TextStores for revisions and signatures.
1696
- a format marker of its own
1697
- an optional 'shared-storage' flag
1698
- an optional 'no-working-trees' flag
1702
def _get_control_store(self, repo_transport, control_files):
1703
"""Return the control store for this repository."""
1704
return VersionedFileStore(
1707
file_mode=control_files._file_mode,
1708
versionedfile_class=knit.KnitVersionedFile,
1709
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
1712
def _get_revision_store(self, repo_transport, control_files):
1713
"""See RepositoryFormat._get_revision_store()."""
1714
from bzrlib.store.revision.knit import KnitRevisionStore
1715
versioned_file_store = VersionedFileStore(
1717
file_mode=control_files._file_mode,
1720
versionedfile_class=knit.KnitVersionedFile,
1721
versionedfile_kwargs={'delta':False,
1722
'factory':knit.KnitPlainFactory(),
1726
return KnitRevisionStore(versioned_file_store)
1728
def _get_text_store(self, transport, control_files):
1729
"""See RepositoryFormat._get_text_store()."""
1730
return self._get_versioned_file_store('knits',
1733
versionedfile_class=knit.KnitVersionedFile,
1734
versionedfile_kwargs={
1735
'create_parent_dir':True,
1736
'delay_create':True,
1737
'dir_mode':control_files._dir_mode,
1741
def initialize(self, a_bzrdir, shared=False):
1742
"""Create a knit format 1 repository.
1744
:param a_bzrdir: bzrdir to contain the new repository; must already
1746
:param shared: If true the repository will be initialized as a shared
1749
mutter('creating repository in %s.', a_bzrdir.transport.base)
1750
dirs = ['revision-store', 'knits']
1752
utf8_files = [('format', self.get_format_string())]
1754
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1755
repo_transport = a_bzrdir.get_repository_transport(None)
1756
control_files = lockable_files.LockableFiles(repo_transport,
1757
'lock', lockdir.LockDir)
1758
control_store = self._get_control_store(repo_transport, control_files)
1759
transaction = transactions.WriteTransaction()
1760
# trigger a write of the inventory store.
1761
control_store.get_weave_or_empty('inventory', transaction)
1762
_revision_store = self._get_revision_store(repo_transport, control_files)
1763
# the revision id here is irrelevant: it will not be stored, and cannot
1765
_revision_store.has_revision_id('A', transaction)
1766
_revision_store.get_signature_file(transaction)
1767
return self.open(a_bzrdir=a_bzrdir, _found=True)
1769
def open(self, a_bzrdir, _found=False, _override_transport=None):
1770
"""See RepositoryFormat.open().
1772
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1773
repository at a slightly different url
1774
than normal. I.e. during 'upgrade'.
1777
format = RepositoryFormat.find_format(a_bzrdir)
1778
assert format.__class__ == self.__class__
1779
if _override_transport is not None:
1780
repo_transport = _override_transport
1782
repo_transport = a_bzrdir.get_repository_transport(None)
1783
control_files = lockable_files.LockableFiles(repo_transport,
1784
'lock', lockdir.LockDir)
1785
text_store = self._get_text_store(repo_transport, control_files)
1786
control_store = self._get_control_store(repo_transport, control_files)
1787
_revision_store = self._get_revision_store(repo_transport, control_files)
1788
return KnitRepository(_format=self,
1790
control_files=control_files,
1791
_revision_store=_revision_store,
1792
control_store=control_store,
1793
text_store=text_store)
1796
class RepositoryFormatKnit1(RepositoryFormatKnit):
1797
"""Bzr repository knit format 1.
1799
This repository format has:
1800
- knits for file texts and inventory
1801
- hash subdirectory based stores.
1802
- knits for revisions and signatures
1803
- TextStores for revisions and signatures.
1804
- a format marker of its own
1805
- an optional 'shared-storage' flag
1806
- an optional 'no-working-trees' flag
1809
This format was introduced in bzr 0.8.
1811
def get_format_string(self):
1812
"""See RepositoryFormat.get_format_string()."""
1813
return "Bazaar-NG Knit Repository Format 1"
1815
def get_format_description(self):
1816
"""See RepositoryFormat.get_format_description()."""
1817
return "Knit repository format 1"
1819
def check_conversion_target(self, target_format):
1823
class RepositoryFormatKnit2(RepositoryFormatKnit):
1824
"""Bzr repository knit format 2.
1826
THIS FORMAT IS EXPERIMENTAL
1827
This repository format has:
1828
- knits for file texts and inventory
1829
- hash subdirectory based stores.
1830
- knits for revisions and signatures
1831
- TextStores for revisions and signatures.
1832
- a format marker of its own
1833
- an optional 'shared-storage' flag
1834
- an optional 'no-working-trees' flag
1836
- Support for recording full info about the tree root
1840
rich_root_data = True
1842
def get_format_string(self):
1843
"""See RepositoryFormat.get_format_string()."""
1844
return "Bazaar Knit Repository Format 2\n"
1846
def get_format_description(self):
1847
"""See RepositoryFormat.get_format_description()."""
1848
return "Knit repository format 2"
1850
def check_conversion_target(self, target_format):
1851
if not target_format.rich_root_data:
1852
raise errors.BadConversionTarget(
1853
'Does not support rich root data.', target_format)
1855
def open(self, a_bzrdir, _found=False, _override_transport=None):
1856
"""See RepositoryFormat.open().
1858
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1859
repository at a slightly different url
1860
than normal. I.e. during 'upgrade'.
1863
format = RepositoryFormat.find_format(a_bzrdir)
1864
assert format.__class__ == self.__class__
1865
if _override_transport is not None:
1866
repo_transport = _override_transport
1868
repo_transport = a_bzrdir.get_repository_transport(None)
1869
control_files = lockable_files.LockableFiles(repo_transport, 'lock',
1871
text_store = self._get_text_store(repo_transport, control_files)
1872
control_store = self._get_control_store(repo_transport, control_files)
1873
_revision_store = self._get_revision_store(repo_transport, control_files)
1874
return KnitRepository2(_format=self,
1876
control_files=control_files,
1877
_revision_store=_revision_store,
1878
control_store=control_store,
1879
text_store=text_store)
1883
# formats which have no format string are not discoverable
1884
# and not independently creatable, so are not registered.
1885
RepositoryFormat.register_format(RepositoryFormat7())
1886
# KEEP in sync with bzrdir.format_registry default
1887
RepositoryFormat.register_format(RepositoryFormatKnit1())
1888
RepositoryFormat.register_format(RepositoryFormatKnit2())
1889
_legacy_formats = [RepositoryFormat4(),
1890
RepositoryFormat5(),
1891
RepositoryFormat6()]
1894
class InterRepository(InterObject):
1895
"""This class represents operations taking place between two repositories.
1897
Its instances have methods like copy_content and fetch, and contain
1898
references to the source and target repositories these operations can be
1901
Often we will provide convenience methods on 'repository' which carry out
1902
operations with another repository - they will always forward to
1903
InterRepository.get(other).method_name(parameters).
1907
"""The available optimised InterRepository types."""
1909
def copy_content(self, revision_id=None, basis=None):
1910
raise NotImplementedError(self.copy_content)
1912
def fetch(self, revision_id=None, pb=None):
1913
"""Fetch the content required to construct revision_id.
1915
The content is copied from self.source to self.target.
1917
:param revision_id: if None all content is copied, if NULL_REVISION no
1919
:param pb: optional progress bar to use for progress reports. If not
1920
provided a default one will be created.
1922
Returns the copied revision count and the failed revisions in a tuple:
1925
raise NotImplementedError(self.fetch)
1928
def missing_revision_ids(self, revision_id=None):
1929
"""Return the revision ids that source has that target does not.
1931
These are returned in topological order.
1933
:param revision_id: only return revision ids included by this
1936
# generic, possibly worst case, slow code path.
1937
target_ids = set(self.target.all_revision_ids())
1938
if revision_id is not None:
1939
source_ids = self.source.get_ancestry(revision_id)
1940
assert source_ids[0] is None
1943
source_ids = self.source.all_revision_ids()
1944
result_set = set(source_ids).difference(target_ids)
1945
# this may look like a no-op: its not. It preserves the ordering
1946
# other_ids had while only returning the members from other_ids
1947
# that we've decided we need.
1948
return [rev_id for rev_id in source_ids if rev_id in result_set]
1951
class InterSameDataRepository(InterRepository):
1952
"""Code for converting between repositories that represent the same data.
1954
Data format and model must match for this to work.
1957
_matching_repo_format = RepositoryFormat4()
1958
"""Repository format for testing with."""
1961
def is_compatible(source, target):
1962
if source._format.rich_root_data == target._format.rich_root_data:
1968
def copy_content(self, revision_id=None, basis=None):
1969
"""Make a complete copy of the content in self into destination.
1971
This is a destructive operation! Do not use it on existing
1974
:param revision_id: Only copy the content needed to construct
1975
revision_id and its parents.
1976
:param basis: Copy the needed data preferentially from basis.
1979
self.target.set_make_working_trees(self.source.make_working_trees())
1980
except NotImplementedError:
1982
# grab the basis available data
1983
if basis is not None:
1984
self.target.fetch(basis, revision_id=revision_id)
1985
# but don't bother fetching if we have the needed data now.
1986
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1987
self.target.has_revision(revision_id)):
1989
self.target.fetch(self.source, revision_id=revision_id)
1992
def fetch(self, revision_id=None, pb=None):
1993
"""See InterRepository.fetch()."""
1994
from bzrlib.fetch import GenericRepoFetcher
1995
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1996
self.source, self.source._format, self.target,
1997
self.target._format)
1998
f = GenericRepoFetcher(to_repository=self.target,
1999
from_repository=self.source,
2000
last_revision=revision_id,
2002
return f.count_copied, f.failed_revisions
2005
class InterWeaveRepo(InterSameDataRepository):
2006
"""Optimised code paths between Weave based repositories."""
2008
_matching_repo_format = RepositoryFormat7()
2009
"""Repository format for testing with."""
2012
def is_compatible(source, target):
2013
"""Be compatible with known Weave formats.
2015
We don't test for the stores being of specific types because that
2016
could lead to confusing results, and there is no need to be
2020
return (isinstance(source._format, (RepositoryFormat5,
2022
RepositoryFormat7)) and
2023
isinstance(target._format, (RepositoryFormat5,
2025
RepositoryFormat7)))
2026
except AttributeError:
2030
def copy_content(self, revision_id=None, basis=None):
2031
"""See InterRepository.copy_content()."""
2032
# weave specific optimised path:
2033
if basis is not None:
2034
# copy the basis in, then fetch remaining data.
2035
basis.copy_content_into(self.target, revision_id)
2036
# the basis copy_content_into could miss-set this.
2038
self.target.set_make_working_trees(self.source.make_working_trees())
2039
except NotImplementedError:
2041
self.target.fetch(self.source, revision_id=revision_id)
2044
self.target.set_make_working_trees(self.source.make_working_trees())
2045
except NotImplementedError:
2047
# FIXME do not peek!
2048
if self.source.control_files._transport.listable():
2049
pb = ui.ui_factory.nested_progress_bar()
2051
self.target.weave_store.copy_all_ids(
2052
self.source.weave_store,
2054
from_transaction=self.source.get_transaction(),
2055
to_transaction=self.target.get_transaction())
2056
pb.update('copying inventory', 0, 1)
2057
self.target.control_weaves.copy_multi(
2058
self.source.control_weaves, ['inventory'],
2059
from_transaction=self.source.get_transaction(),
2060
to_transaction=self.target.get_transaction())
2061
self.target._revision_store.text_store.copy_all_ids(
2062
self.source._revision_store.text_store,
2067
self.target.fetch(self.source, revision_id=revision_id)
2070
def fetch(self, revision_id=None, pb=None):
2071
"""See InterRepository.fetch()."""
2072
from bzrlib.fetch import GenericRepoFetcher
2073
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2074
self.source, self.source._format, self.target, self.target._format)
2075
f = GenericRepoFetcher(to_repository=self.target,
2076
from_repository=self.source,
2077
last_revision=revision_id,
2079
return f.count_copied, f.failed_revisions
2082
def missing_revision_ids(self, revision_id=None):
2083
"""See InterRepository.missing_revision_ids()."""
2084
# we want all revisions to satisfy revision_id in source.
2085
# but we don't want to stat every file here and there.
2086
# we want then, all revisions other needs to satisfy revision_id
2087
# checked, but not those that we have locally.
2088
# so the first thing is to get a subset of the revisions to
2089
# satisfy revision_id in source, and then eliminate those that
2090
# we do already have.
2091
# this is slow on high latency connection to self, but as as this
2092
# disk format scales terribly for push anyway due to rewriting
2093
# inventory.weave, this is considered acceptable.
2095
if revision_id is not None:
2096
source_ids = self.source.get_ancestry(revision_id)
2097
assert source_ids[0] is None
2100
source_ids = self.source._all_possible_ids()
2101
source_ids_set = set(source_ids)
2102
# source_ids is the worst possible case we may need to pull.
2103
# now we want to filter source_ids against what we actually
2104
# have in target, but don't try to check for existence where we know
2105
# we do not have a revision as that would be pointless.
2106
target_ids = set(self.target._all_possible_ids())
2107
possibly_present_revisions = target_ids.intersection(source_ids_set)
2108
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
2109
required_revisions = source_ids_set.difference(actually_present_revisions)
2110
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
2111
if revision_id is not None:
2112
# we used get_ancestry to determine source_ids then we are assured all
2113
# revisions referenced are present as they are installed in topological order.
2114
# and the tip revision was validated by get_ancestry.
2115
return required_topo_revisions
2117
# if we just grabbed the possibly available ids, then
2118
# we only have an estimate of whats available and need to validate
2119
# that against the revision records.
2120
return self.source._eliminate_revisions_not_present(required_topo_revisions)
2123
class InterKnitRepo(InterSameDataRepository):
2124
"""Optimised code paths between Knit based repositories."""
2126
_matching_repo_format = RepositoryFormatKnit1()
2127
"""Repository format for testing with."""
2130
def is_compatible(source, target):
2131
"""Be compatible with known Knit formats.
2133
We don't test for the stores being of specific types because that
2134
could lead to confusing results, and there is no need to be
2138
return (isinstance(source._format, (RepositoryFormatKnit1)) and
2139
isinstance(target._format, (RepositoryFormatKnit1)))
2140
except AttributeError:
2144
def fetch(self, revision_id=None, pb=None):
2145
"""See InterRepository.fetch()."""
2146
from bzrlib.fetch import KnitRepoFetcher
2147
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2148
self.source, self.source._format, self.target, self.target._format)
2149
f = KnitRepoFetcher(to_repository=self.target,
2150
from_repository=self.source,
2151
last_revision=revision_id,
2153
return f.count_copied, f.failed_revisions
2156
def missing_revision_ids(self, revision_id=None):
2157
"""See InterRepository.missing_revision_ids()."""
2158
if revision_id is not None:
2159
source_ids = self.source.get_ancestry(revision_id)
2160
assert source_ids[0] is None
2163
source_ids = self.source._all_possible_ids()
2164
source_ids_set = set(source_ids)
2165
# source_ids is the worst possible case we may need to pull.
2166
# now we want to filter source_ids against what we actually
2167
# have in target, but don't try to check for existence where we know
2168
# we do not have a revision as that would be pointless.
2169
target_ids = set(self.target._all_possible_ids())
2170
possibly_present_revisions = target_ids.intersection(source_ids_set)
2171
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
2172
required_revisions = source_ids_set.difference(actually_present_revisions)
2173
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
2174
if revision_id is not None:
2175
# we used get_ancestry to determine source_ids then we are assured all
2176
# revisions referenced are present as they are installed in topological order.
2177
# and the tip revision was validated by get_ancestry.
2178
return required_topo_revisions
2180
# if we just grabbed the possibly available ids, then
2181
# we only have an estimate of whats available and need to validate
2182
# that against the revision records.
2183
return self.source._eliminate_revisions_not_present(required_topo_revisions)
2186
class InterModel1and2(InterRepository):
2188
_matching_repo_format = None
2191
def is_compatible(source, target):
2192
if not isinstance(source, Repository):
2194
if not isinstance(target, Repository):
2196
if not source._format.rich_root_data and target._format.rich_root_data:
2202
def fetch(self, revision_id=None, pb=None):
2203
"""See InterRepository.fetch()."""
2204
from bzrlib.fetch import Model1toKnit2Fetcher
2205
f = Model1toKnit2Fetcher(to_repository=self.target,
2206
from_repository=self.source,
2207
last_revision=revision_id,
2209
return f.count_copied, f.failed_revisions
2212
def copy_content(self, revision_id=None, basis=None):
2213
"""Make a complete copy of the content in self into destination.
2215
This is a destructive operation! Do not use it on existing
2218
:param revision_id: Only copy the content needed to construct
2219
revision_id and its parents.
2220
:param basis: Copy the needed data preferentially from basis.
2223
self.target.set_make_working_trees(self.source.make_working_trees())
2224
except NotImplementedError:
2226
# grab the basis available data
2227
if basis is not None:
2228
self.target.fetch(basis, revision_id=revision_id)
2229
# but don't bother fetching if we have the needed data now.
2230
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
2231
self.target.has_revision(revision_id)):
2233
self.target.fetch(self.source, revision_id=revision_id)
2236
class InterKnit1and2(InterKnitRepo):
2238
_matching_repo_format = None
2241
def is_compatible(source, target):
2242
"""Be compatible with Knit1 source and Knit2 target"""
2244
return (isinstance(source._format, (RepositoryFormatKnit1)) and
2245
isinstance(target._format, (RepositoryFormatKnit2)))
2246
except AttributeError:
2250
def fetch(self, revision_id=None, pb=None):
2251
"""See InterRepository.fetch()."""
2252
from bzrlib.fetch import Knit1to2Fetcher
2253
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2254
self.source, self.source._format, self.target,
2255
self.target._format)
2256
f = Knit1to2Fetcher(to_repository=self.target,
2257
from_repository=self.source,
2258
last_revision=revision_id,
2260
return f.count_copied, f.failed_revisions
2263
InterRepository.register_optimiser(InterSameDataRepository)
2264
InterRepository.register_optimiser(InterWeaveRepo)
2265
InterRepository.register_optimiser(InterKnitRepo)
2266
InterRepository.register_optimiser(InterModel1and2)
2267
InterRepository.register_optimiser(InterKnit1and2)
2270
class RepositoryTestProviderAdapter(object):
2271
"""A tool to generate a suite testing multiple repository formats at once.
2273
This is done by copying the test once for each transport and injecting
2274
the transport_server, transport_readonly_server, and bzrdir_format and
2275
repository_format classes into each copy. Each copy is also given a new id()
2276
to make it easy to identify.
2279
def __init__(self, transport_server, transport_readonly_server, formats,
2280
vfs_transport_factory=None):
2281
self._transport_server = transport_server
2282
self._transport_readonly_server = transport_readonly_server
2283
self._vfs_transport_factory = vfs_transport_factory
2284
self._formats = formats
2286
def adapt(self, test):
2287
result = unittest.TestSuite()
2288
for repository_format, bzrdir_format in self._formats:
2289
new_test = deepcopy(test)
2290
new_test.transport_server = self._transport_server
2291
new_test.transport_readonly_server = self._transport_readonly_server
2292
if self._vfs_transport_factory:
2293
new_test.vfs_transport_factory = self._vfs_transport_factory
2294
new_test.bzrdir_format = bzrdir_format
2295
new_test.repository_format = repository_format
2296
def make_new_test_id():
2297
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
2298
return lambda: new_id
2299
new_test.id = make_new_test_id()
2300
result.addTest(new_test)
2304
class InterRepositoryTestProviderAdapter(object):
2305
"""A tool to generate a suite testing multiple inter repository formats.
2307
This is done by copying the test once for each interrepo provider and injecting
2308
the transport_server, transport_readonly_server, repository_format and
2309
repository_to_format classes into each copy.
2310
Each copy is also given a new id() to make it easy to identify.
2313
def __init__(self, transport_server, transport_readonly_server, formats):
2314
self._transport_server = transport_server
2315
self._transport_readonly_server = transport_readonly_server
2316
self._formats = formats
2318
def adapt(self, test):
2319
result = unittest.TestSuite()
2320
for interrepo_class, repository_format, repository_format_to in self._formats:
2321
new_test = deepcopy(test)
2322
new_test.transport_server = self._transport_server
2323
new_test.transport_readonly_server = self._transport_readonly_server
2324
new_test.interrepo_class = interrepo_class
2325
new_test.repository_format = repository_format
2326
new_test.repository_format_to = repository_format_to
2327
def make_new_test_id():
2328
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
2329
return lambda: new_id
2330
new_test.id = make_new_test_id()
2331
result.addTest(new_test)
2335
def default_test_list():
2336
"""Generate the default list of interrepo permutations to test."""
2338
# test the default InterRepository between format 6 and the current
2340
# XXX: robertc 20060220 reinstate this when there are two supported
2341
# formats which do not have an optimal code path between them.
2342
#result.append((InterRepository,
2343
# RepositoryFormat6(),
2344
# RepositoryFormatKnit1()))
2345
for optimiser in InterRepository._optimisers:
2346
if optimiser._matching_repo_format is not None:
2347
result.append((optimiser,
2348
optimiser._matching_repo_format,
2349
optimiser._matching_repo_format
2351
# if there are specific combinations we want to use, we can add them
2353
result.append((InterModel1and2, RepositoryFormat5(),
2354
RepositoryFormatKnit2()))
2355
result.append((InterKnit1and2, RepositoryFormatKnit1(),
2356
RepositoryFormatKnit2()))
2360
class CopyConverter(object):
2361
"""A repository conversion tool which just performs a copy of the content.
2363
This is slow but quite reliable.
2366
def __init__(self, target_format):
2367
"""Create a CopyConverter.
2369
:param target_format: The format the resulting repository should be.
2371
self.target_format = target_format
2373
def convert(self, repo, pb):
2374
"""Perform the conversion of to_convert, giving feedback via pb.
2376
:param to_convert: The disk object to convert.
2377
:param pb: a progress bar to use for progress information.
2382
# this is only useful with metadir layouts - separated repo content.
2383
# trigger an assertion if not such
2384
repo._format.get_format_string()
2385
self.repo_dir = repo.bzrdir
2386
self.step('Moving repository to repository.backup')
2387
self.repo_dir.transport.move('repository', 'repository.backup')
2388
backup_transport = self.repo_dir.transport.clone('repository.backup')
2389
repo._format.check_conversion_target(self.target_format)
2390
self.source_repo = repo._format.open(self.repo_dir,
2392
_override_transport=backup_transport)
2393
self.step('Creating new repository')
2394
converted = self.target_format.initialize(self.repo_dir,
2395
self.source_repo.is_shared())
2396
converted.lock_write()
2398
self.step('Copying content into repository.')
2399
self.source_repo.copy_content_into(converted)
2402
self.step('Deleting old repository content.')
2403
self.repo_dir.transport.delete_tree('repository.backup')
2404
self.pb.note('repository converted')
2406
def step(self, message):
2407
"""Update the pb by a step."""
2409
self.pb.update(message, self.count, self.total)
2412
class CommitBuilder(object):
2413
"""Provides an interface to build up a commit.
2415
This allows describing a tree to be committed without needing to
2416
know the internals of the format of the repository.
2419
record_root_entry = False
2420
def __init__(self, repository, parents, config, timestamp=None,
2421
timezone=None, committer=None, revprops=None,
2423
"""Initiate a CommitBuilder.
2425
:param repository: Repository to commit to.
2426
:param parents: Revision ids of the parents of the new revision.
2427
:param config: Configuration to use.
2428
:param timestamp: Optional timestamp recorded for commit.
2429
:param timezone: Optional timezone for timestamp.
2430
:param committer: Optional committer to set for commit.
2431
:param revprops: Optional dictionary of revision properties.
2432
:param revision_id: Optional revision id.
2434
self._config = config
2436
if committer is None:
2437
self._committer = self._config.username()
2439
assert isinstance(committer, basestring), type(committer)
2440
self._committer = committer
2442
self.new_inventory = Inventory(None)
2443
self._new_revision_id = revision_id
2444
self.parents = parents
2445
self.repository = repository
2448
if revprops is not None:
2449
self._revprops.update(revprops)
2451
if timestamp is None:
2452
timestamp = time.time()
2453
# Restrict resolution to 1ms
2454
self._timestamp = round(timestamp, 3)
2456
if timezone is None:
2457
self._timezone = local_time_offset()
2459
self._timezone = int(timezone)
2461
self._generate_revision_if_needed()
2463
def commit(self, message):
2464
"""Make the actual commit.
2466
:return: The revision id of the recorded revision.
2468
rev = _mod_revision.Revision(
2469
timestamp=self._timestamp,
2470
timezone=self._timezone,
2471
committer=self._committer,
2473
inventory_sha1=self.inv_sha1,
2474
revision_id=self._new_revision_id,
2475
properties=self._revprops)
2476
rev.parent_ids = self.parents
2477
self.repository.add_revision(self._new_revision_id, rev,
2478
self.new_inventory, self._config)
2479
return self._new_revision_id
2481
def revision_tree(self):
2482
"""Return the tree that was just committed.
2484
After calling commit() this can be called to get a RevisionTree
2485
representing the newly committed tree. This is preferred to
2486
calling Repository.revision_tree() because that may require
2487
deserializing the inventory, while we already have a copy in
2490
return RevisionTree(self.repository, self.new_inventory,
2491
self._new_revision_id)
2493
def finish_inventory(self):
2494
"""Tell the builder that the inventory is finished."""
2495
if self.new_inventory.root is None:
2496
symbol_versioning.warn('Root entry should be supplied to'
2497
' record_entry_contents, as of bzr 0.10.',
2498
DeprecationWarning, stacklevel=2)
2499
self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
2500
self.new_inventory.revision_id = self._new_revision_id
2501
self.inv_sha1 = self.repository.add_inventory(
2502
self._new_revision_id,
2507
def _gen_revision_id(self):
2508
"""Return new revision-id."""
2509
return generate_ids.gen_revision_id(self._config.username(),
2512
def _generate_revision_if_needed(self):
2513
"""Create a revision id if None was supplied.
2515
If the repository can not support user-specified revision ids
2516
they should override this function and raise CannotSetRevisionId
2517
if _new_revision_id is not None.
2519
:raises: CannotSetRevisionId
2521
if self._new_revision_id is None:
2522
self._new_revision_id = self._gen_revision_id()
2524
def record_entry_contents(self, ie, parent_invs, path, tree):
2525
"""Record the content of ie from tree into the commit if needed.
2527
Side effect: sets ie.revision when unchanged
2529
:param ie: An inventory entry present in the commit.
2530
:param parent_invs: The inventories of the parent revisions of the
2532
:param path: The path the entry is at in the tree.
2533
:param tree: The tree which contains this entry and should be used to
2536
if self.new_inventory.root is None and ie.parent_id is not None:
2537
symbol_versioning.warn('Root entry should be supplied to'
2538
' record_entry_contents, as of bzr 0.10.',
2539
DeprecationWarning, stacklevel=2)
2540
self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
2542
self.new_inventory.add(ie)
2544
# ie.revision is always None if the InventoryEntry is considered
2545
# for committing. ie.snapshot will record the correct revision
2546
# which may be the sole parent if it is untouched.
2547
if ie.revision is not None:
2550
# In this revision format, root entries have no knit or weave
2551
if ie is self.new_inventory.root:
2552
# When serializing out to disk and back in
2553
# root.revision is always _new_revision_id
2554
ie.revision = self._new_revision_id
2556
previous_entries = ie.find_previous_heads(
2558
self.repository.weave_store,
2559
self.repository.get_transaction())
2560
# we are creating a new revision for ie in the history store
2562
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2564
def modified_directory(self, file_id, file_parents):
2565
"""Record the presence of a symbolic link.
2567
:param file_id: The file_id of the link to record.
2568
:param file_parents: The per-file parent revision ids.
2570
self._add_text_to_weave(file_id, [], file_parents.keys())
2572
def modified_file_text(self, file_id, file_parents,
2573
get_content_byte_lines, text_sha1=None,
2575
"""Record the text of file file_id
2577
:param file_id: The file_id of the file to record the text of.
2578
:param file_parents: The per-file parent revision ids.
2579
:param get_content_byte_lines: A callable which will return the byte
2581
:param text_sha1: Optional SHA1 of the file contents.
2582
:param text_size: Optional size of the file contents.
2584
# mutter('storing text of file {%s} in revision {%s} into %r',
2585
# file_id, self._new_revision_id, self.repository.weave_store)
2586
# special case to avoid diffing on renames or
2588
if (len(file_parents) == 1
2589
and text_sha1 == file_parents.values()[0].text_sha1
2590
and text_size == file_parents.values()[0].text_size):
2591
previous_ie = file_parents.values()[0]
2592
versionedfile = self.repository.weave_store.get_weave(file_id,
2593
self.repository.get_transaction())
2594
versionedfile.clone_text(self._new_revision_id,
2595
previous_ie.revision, file_parents.keys())
2596
return text_sha1, text_size
2598
new_lines = get_content_byte_lines()
2599
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2600
# should return the SHA1 and size
2601
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2602
return osutils.sha_strings(new_lines), \
2603
sum(map(len, new_lines))
2605
def modified_link(self, file_id, file_parents, link_target):
2606
"""Record the presence of a symbolic link.
2608
:param file_id: The file_id of the link to record.
2609
:param file_parents: The per-file parent revision ids.
2610
:param link_target: Target location of this link.
2612
self._add_text_to_weave(file_id, [], file_parents.keys())
2614
def _add_text_to_weave(self, file_id, new_lines, parents):
2615
versionedfile = self.repository.weave_store.get_weave_or_empty(
2616
file_id, self.repository.get_transaction())
2617
versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2618
versionedfile.clear_cache()
2621
class _CommitBuilder(CommitBuilder):
2622
"""Temporary class so old CommitBuilders are detected properly
2624
Note: CommitBuilder works whether or not root entry is recorded.
2627
record_root_entry = True
2630
class RootCommitBuilder(CommitBuilder):
2631
"""This commitbuilder actually records the root id"""
2633
record_root_entry = True
2635
def record_entry_contents(self, ie, parent_invs, path, tree):
2636
"""Record the content of ie from tree into the commit if needed.
2638
Side effect: sets ie.revision when unchanged
2640
:param ie: An inventory entry present in the commit.
2641
:param parent_invs: The inventories of the parent revisions of the
2643
:param path: The path the entry is at in the tree.
2644
:param tree: The tree which contains this entry and should be used to
2647
assert self.new_inventory.root is not None or ie.parent_id is None
2648
self.new_inventory.add(ie)
2650
# ie.revision is always None if the InventoryEntry is considered
2651
# for committing. ie.snapshot will record the correct revision
2652
# which may be the sole parent if it is untouched.
2653
if ie.revision is not None:
2656
previous_entries = ie.find_previous_heads(
2658
self.repository.weave_store,
2659
self.repository.get_transaction())
2660
# we are creating a new revision for ie in the history store
2662
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2674
def _unescaper(match, _map=_unescape_map):
2675
return _map[match.group(1)]
2681
def _unescape_xml(data):
2682
"""Unescape predefined XML entities in a string of data."""
2684
if _unescape_re is None:
2685
_unescape_re = re.compile('\&([^;]*);')
2686
return _unescape_re.sub(_unescaper, data)