1
# Copyright (C) 2005, 2006 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
40
revision as _mod_revision,
49
from bzrlib.osutils import (
54
from bzrlib.revisiontree import RevisionTree
55
from bzrlib.store.versioned import VersionedFileStore
56
from bzrlib.store.text import TextStore
57
from bzrlib.testament import Testament
60
from bzrlib.decorators import needs_read_lock, needs_write_lock
61
from bzrlib.inter import InterObject
62
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
63
from bzrlib.symbol_versioning import (
67
from bzrlib.trace import mutter, note, warning
70
# Old formats display a warning, but only once
71
_deprecation_warning_done = False
74
class Repository(object):
75
"""Repository holding history for one or more branches.
77
The repository holds and retrieves historical information including
78
revisions and file history. It's normally accessed only by the Branch,
79
which views a particular line of development through that history.
81
The Repository builds on top of Stores and a Transport, which respectively
82
describe the disk data format and the way of accessing the (possibly
86
_file_ids_altered_regex = lazy_regex.lazy_compile(
87
r'file_id="(?P<file_id>[^"]+)"'
88
r'.*revision="(?P<revision_id>[^"]+)"'
92
def add_inventory(self, revid, inv, parents):
93
"""Add the inventory inv to the repository as revid.
95
:param parents: The revision ids of the parents that revid
96
is known to have and are in the repository already.
98
returns the sha1 of the serialized inventory.
100
if _mod_revision.reserved_id(revid):
101
raise errors.ReservedId(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
if _mod_revision.reserved_id(rev_id):
134
raise errors.ReservedId(rev_id)
135
if config is not None and config.signature_needed():
137
inv = self.get_inventory(rev_id)
138
plaintext = Testament(rev, inv).as_short_text()
139
self.store_revision_signature(
140
gpg.GPGStrategy(config), plaintext, rev_id)
141
if not rev_id in self.get_inventory_weave():
143
raise errors.WeaveRevisionNotPresent(rev_id,
144
self.get_inventory_weave())
146
# yes, this is not suitable for adding with ghosts.
147
self.add_inventory(rev_id, inv, rev.parent_ids)
148
self._revision_store.add_revision(rev, self.get_transaction())
151
def _all_possible_ids(self):
152
"""Return all the possible revisions that we could find."""
153
return self.get_inventory_weave().versions()
155
def all_revision_ids(self):
156
"""Returns a list of all the revision ids in the repository.
158
This is deprecated because code should generally work on the graph
159
reachable from a particular revision, and ignore any other revisions
160
that might be present. There is no direct replacement method.
162
return self._all_revision_ids()
165
def _all_revision_ids(self):
166
"""Returns a list of all the revision ids in the repository.
168
These are in as much topological order as the underlying store can
169
present: for weaves ghosts may lead to a lack of correctness until
170
the reweave updates the parents list.
172
if self._revision_store.text_store.listable():
173
return self._revision_store.all_revision_ids(self.get_transaction())
174
result = self._all_possible_ids()
175
return self._eliminate_revisions_not_present(result)
177
def break_lock(self):
178
"""Break a lock if one is present from another instance.
180
Uses the ui factory to ask for confirmation if the lock may be from
183
self.control_files.break_lock()
186
def _eliminate_revisions_not_present(self, revision_ids):
187
"""Check every revision id in revision_ids to see if we have it.
189
Returns a set of the present revisions.
192
for id in revision_ids:
193
if self.has_revision(id):
198
def create(a_bzrdir):
199
"""Construct the current default format repository in a_bzrdir."""
200
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
202
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
203
"""instantiate a Repository.
205
:param _format: The format of the repository on disk.
206
:param a_bzrdir: The BzrDir of the repository.
208
In the future we will have a single api for all stores for
209
getting file texts, inventories and revisions, then
210
this construct will accept instances of those things.
212
super(Repository, self).__init__()
213
self._format = _format
214
# the following are part of the public API for Repository:
215
self.bzrdir = a_bzrdir
216
self.control_files = control_files
217
self._revision_store = _revision_store
218
self.text_store = text_store
219
# backwards compatibility
220
self.weave_store = text_store
221
# not right yet - should be more semantically clear ?
223
self.control_store = control_store
224
self.control_weaves = control_store
225
# TODO: make sure to construct the right store classes, etc, depending
226
# on whether escaping is required.
227
self._warn_if_deprecated()
228
self._serializer = xml5.serializer_v5
231
return '%s(%r)' % (self.__class__.__name__,
232
self.bzrdir.transport.base)
235
return self.control_files.is_locked()
237
def lock_write(self):
238
self.control_files.lock_write()
241
self.control_files.lock_read()
243
def get_physical_lock_status(self):
244
return self.control_files.get_physical_lock_status()
247
def missing_revision_ids(self, other, revision_id=None):
248
"""Return the revision ids that other has that this does not.
250
These are returned in topological order.
252
revision_id: only return revision ids included by revision_id.
254
return InterRepository.get(other, self).missing_revision_ids(revision_id)
258
"""Open the repository rooted at base.
260
For instance, if the repository is at URL/.bzr/repository,
261
Repository.open(URL) -> a Repository instance.
263
control = bzrdir.BzrDir.open(base)
264
return control.open_repository()
266
def copy_content_into(self, destination, revision_id=None, basis=None):
267
"""Make a complete copy of the content in self into destination.
269
This is a destructive operation! Do not use it on existing
272
return InterRepository.get(self, destination).copy_content(revision_id, basis)
274
def fetch(self, source, revision_id=None, pb=None):
275
"""Fetch the content required to construct revision_id from source.
277
If revision_id is None all content is copied.
279
return InterRepository.get(source, self).fetch(revision_id=revision_id,
282
def get_commit_builder(self, branch, parents, config, timestamp=None,
283
timezone=None, committer=None, revprops=None,
285
"""Obtain a CommitBuilder for this repository.
287
:param branch: Branch to commit to.
288
:param parents: Revision ids of the parents of the new revision.
289
:param config: Configuration to use.
290
:param timestamp: Optional timestamp recorded for commit.
291
:param timezone: Optional timezone for timestamp.
292
:param committer: Optional committer to set for commit.
293
:param revprops: Optional dictionary of revision properties.
294
:param revision_id: Optional revision id.
296
return _CommitBuilder(self, parents, config, timestamp, timezone,
297
committer, revprops, revision_id)
300
self.control_files.unlock()
303
def clone(self, a_bzrdir, revision_id=None, basis=None):
304
"""Clone this repository into a_bzrdir using the current format.
306
Currently no check is made that the format of this repository and
307
the bzrdir format are compatible. FIXME RBC 20060201.
309
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
310
# use target default format.
311
result = a_bzrdir.create_repository()
312
# FIXME RBC 20060209 split out the repository type to avoid this check ?
313
elif isinstance(a_bzrdir._format,
314
(bzrdir.BzrDirFormat4,
315
bzrdir.BzrDirFormat5,
316
bzrdir.BzrDirFormat6)):
317
result = a_bzrdir.open_repository()
319
result = self._format.initialize(a_bzrdir, shared=self.is_shared())
320
self.copy_content_into(result, revision_id, basis)
324
def has_revision(self, revision_id):
325
"""True if this repository has a copy of the revision."""
326
return self._revision_store.has_revision_id(revision_id,
327
self.get_transaction())
330
def get_revision_reconcile(self, revision_id):
331
"""'reconcile' helper routine that allows access to a revision always.
333
This variant of get_revision does not cross check the weave graph
334
against the revision one as get_revision does: but it should only
335
be used by reconcile, or reconcile-alike commands that are correcting
336
or testing the revision graph.
338
if not revision_id or not isinstance(revision_id, basestring):
339
raise errors.InvalidRevisionId(revision_id=revision_id,
341
return self._revision_store.get_revisions([revision_id],
342
self.get_transaction())[0]
344
def get_revisions(self, revision_ids):
345
return self._revision_store.get_revisions(revision_ids,
346
self.get_transaction())
349
def get_revision_xml(self, revision_id):
350
rev = self.get_revision(revision_id)
352
# the current serializer..
353
self._revision_store._serializer.write_revision(rev, rev_tmp)
355
return rev_tmp.getvalue()
358
def get_revision(self, revision_id):
359
"""Return the Revision object for a named revision"""
360
r = self.get_revision_reconcile(revision_id)
361
# weave corruption can lead to absent revision markers that should be
363
# the following test is reasonably cheap (it needs a single weave read)
364
# and the weave is cached in read transactions. In write transactions
365
# it is not cached but typically we only read a small number of
366
# revisions. For knits when they are introduced we will probably want
367
# to ensure that caching write transactions are in use.
368
inv = self.get_inventory_weave()
369
self._check_revision_parents(r, inv)
373
def get_deltas_for_revisions(self, revisions):
374
"""Produce a generator of revision deltas.
376
Note that the input is a sequence of REVISIONS, not revision_ids.
377
Trees will be held in memory until the generator exits.
378
Each delta is relative to the revision's lefthand predecessor.
380
required_trees = set()
381
for revision in revisions:
382
required_trees.add(revision.revision_id)
383
required_trees.update(revision.parent_ids[:1])
384
trees = dict((t.get_revision_id(), t) for
385
t in self.revision_trees(required_trees))
386
for revision in revisions:
387
if not revision.parent_ids:
388
old_tree = self.revision_tree(None)
390
old_tree = trees[revision.parent_ids[0]]
391
yield trees[revision.revision_id].changes_from(old_tree)
394
def get_revision_delta(self, revision_id):
395
"""Return the delta for one revision.
397
The delta is relative to the left-hand predecessor of the
400
r = self.get_revision(revision_id)
401
return list(self.get_deltas_for_revisions([r]))[0]
403
def _check_revision_parents(self, revision, inventory):
404
"""Private to Repository and Fetch.
406
This checks the parentage of revision in an inventory weave for
407
consistency and is only applicable to inventory-weave-for-ancestry
408
using repository formats & fetchers.
410
weave_parents = inventory.get_parents(revision.revision_id)
411
weave_names = inventory.versions()
412
for parent_id in revision.parent_ids:
413
if parent_id in weave_names:
414
# this parent must not be a ghost.
415
if not parent_id in weave_parents:
417
raise errors.CorruptRepository(self)
420
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
421
signature = gpg_strategy.sign(plaintext)
422
self._revision_store.add_revision_signature_text(revision_id,
424
self.get_transaction())
426
def fileids_altered_by_revision_ids(self, revision_ids):
427
"""Find the file ids and versions affected by revisions.
429
:param revisions: an iterable containing revision ids.
430
:return: a dictionary mapping altered file-ids to an iterable of
431
revision_ids. Each altered file-ids has the exact revision_ids that
432
altered it listed explicitly.
434
assert self._serializer.support_altered_by_hack, \
435
("fileids_altered_by_revision_ids only supported for branches "
436
"which store inventory as unnested xml, not on %r" % self)
437
selected_revision_ids = set(revision_ids)
438
w = self.get_inventory_weave()
441
# this code needs to read every new line in every inventory for the
442
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
443
# not present in one of those inventories is unnecessary but not
444
# harmful because we are filtering by the revision id marker in the
445
# inventory lines : we only select file ids altered in one of those
446
# revisions. We don't need to see all lines in the inventory because
447
# only those added in an inventory in rev X can contain a revision=X
449
unescape_revid_cache = {}
450
unescape_fileid_cache = {}
452
# jam 20061218 In a big fetch, this handles hundreds of thousands
453
# of lines, so it has had a lot of inlining and optimizing done.
454
# Sorry that it is a little bit messy.
455
# Move several functions to be local variables, since this is a long
457
search = self._file_ids_altered_regex.search
458
unescape = _unescape_xml
459
setdefault = result.setdefault
460
pb = ui.ui_factory.nested_progress_bar()
462
for line in w.iter_lines_added_or_present_in_versions(
463
selected_revision_ids, pb=pb):
467
# One call to match.group() returning multiple items is quite a
468
# bit faster than 2 calls to match.group() each returning 1
469
file_id, revision_id = match.group('file_id', 'revision_id')
471
# Inlining the cache lookups helps a lot when you make 170,000
472
# lines and 350k ids, versus 8.4 unique ids.
473
# Using a cache helps in 2 ways:
474
# 1) Avoids unnecessary decoding calls
475
# 2) Re-uses cached strings, which helps in future set and
477
# (2) is enough that removing encoding entirely along with
478
# the cache (so we are using plain strings) results in no
479
# performance improvement.
481
revision_id = unescape_revid_cache[revision_id]
483
unescaped = unescape(revision_id)
484
unescape_revid_cache[revision_id] = unescaped
485
revision_id = unescaped
487
if revision_id in selected_revision_ids:
489
file_id = unescape_fileid_cache[file_id]
491
unescaped = unescape(file_id)
492
unescape_fileid_cache[file_id] = unescaped
494
setdefault(file_id, set()).add(revision_id)
500
def get_inventory_weave(self):
501
return self.control_weaves.get_weave('inventory',
502
self.get_transaction())
505
def get_inventory(self, revision_id):
506
"""Get Inventory object by hash."""
507
return self.deserialise_inventory(
508
revision_id, self.get_inventory_xml(revision_id))
510
def deserialise_inventory(self, revision_id, xml):
511
"""Transform the xml into an inventory object.
513
:param revision_id: The expected revision id of the inventory.
514
:param xml: A serialised inventory.
516
result = self._serializer.read_inventory_from_string(xml)
517
result.root.revision = revision_id
520
def serialise_inventory(self, inv):
521
return self._serializer.write_inventory_to_string(inv)
524
def get_inventory_xml(self, revision_id):
525
"""Get inventory XML as a file object."""
527
assert isinstance(revision_id, basestring), type(revision_id)
528
iw = self.get_inventory_weave()
529
return iw.get_text(revision_id)
531
raise errors.HistoryMissing(self, 'inventory', revision_id)
534
def get_inventory_sha1(self, revision_id):
535
"""Return the sha1 hash of the inventory entry
537
return self.get_revision(revision_id).inventory_sha1
540
def get_revision_graph(self, revision_id=None):
541
"""Return a dictionary containing the revision graph.
543
:param revision_id: The revision_id to get a graph from. If None, then
544
the entire revision graph is returned. This is a deprecated mode of
545
operation and will be removed in the future.
546
:return: a dictionary of revision_id->revision_parents_list.
548
# special case NULL_REVISION
549
if revision_id == _mod_revision.NULL_REVISION:
551
a_weave = self.get_inventory_weave()
552
all_revisions = self._eliminate_revisions_not_present(
554
entire_graph = dict([(node, a_weave.get_parents(node)) for
555
node in all_revisions])
556
if revision_id is None:
558
elif revision_id not in entire_graph:
559
raise errors.NoSuchRevision(self, revision_id)
561
# add what can be reached from revision_id
563
pending = set([revision_id])
564
while len(pending) > 0:
566
result[node] = entire_graph[node]
567
for revision_id in result[node]:
568
if revision_id not in result:
569
pending.add(revision_id)
573
def get_revision_graph_with_ghosts(self, revision_ids=None):
574
"""Return a graph of the revisions with ghosts marked as applicable.
576
:param revision_ids: an iterable of revisions to graph or None for all.
577
:return: a Graph object with the graph reachable from revision_ids.
579
result = graph.Graph()
581
pending = set(self.all_revision_ids())
584
pending = set(revision_ids)
585
# special case NULL_REVISION
586
if _mod_revision.NULL_REVISION in pending:
587
pending.remove(_mod_revision.NULL_REVISION)
588
required = set(pending)
591
revision_id = pending.pop()
593
rev = self.get_revision(revision_id)
594
except errors.NoSuchRevision:
595
if revision_id in required:
598
result.add_ghost(revision_id)
600
for parent_id in rev.parent_ids:
601
# is this queued or done ?
602
if (parent_id not in pending and
603
parent_id not in done):
605
pending.add(parent_id)
606
result.add_node(revision_id, rev.parent_ids)
607
done.add(revision_id)
611
def get_revision_inventory(self, revision_id):
612
"""Return inventory of a past revision."""
613
# TODO: Unify this with get_inventory()
614
# bzr 0.0.6 and later imposes the constraint that the inventory_id
615
# must be the same as its revision, so this is trivial.
616
if revision_id is None:
617
# This does not make sense: if there is no revision,
618
# then it is the current tree inventory surely ?!
619
# and thus get_root_id() is something that looks at the last
620
# commit on the branch, and the get_root_id is an inventory check.
621
raise NotImplementedError
622
# return Inventory(self.get_root_id())
624
return self.get_inventory(revision_id)
628
"""Return True if this repository is flagged as a shared repository."""
629
raise NotImplementedError(self.is_shared)
632
def reconcile(self, other=None, thorough=False):
633
"""Reconcile this repository."""
634
from bzrlib.reconcile import RepoReconciler
635
reconciler = RepoReconciler(self, thorough=thorough)
636
reconciler.reconcile()
640
def revision_tree(self, revision_id):
641
"""Return Tree for a revision on this branch.
643
`revision_id` may be None for the empty tree revision.
645
# TODO: refactor this to use an existing revision object
646
# so we don't need to read it in twice.
647
if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
648
return RevisionTree(self, Inventory(root_id=None),
649
_mod_revision.NULL_REVISION)
651
inv = self.get_revision_inventory(revision_id)
652
return RevisionTree(self, inv, revision_id)
655
def revision_trees(self, revision_ids):
656
"""Return Tree for a revision on this branch.
658
`revision_id` may not be None or 'null:'"""
659
assert None not in revision_ids
660
assert _mod_revision.NULL_REVISION not in revision_ids
661
texts = self.get_inventory_weave().get_texts(revision_ids)
662
for text, revision_id in zip(texts, revision_ids):
663
inv = self.deserialise_inventory(revision_id, text)
664
yield RevisionTree(self, inv, revision_id)
667
def get_ancestry(self, revision_id):
668
"""Return a list of revision-ids integrated by a revision.
670
The first element of the list is always None, indicating the origin
671
revision. This might change when we have history horizons, or
672
perhaps we should have a new API.
674
This is topologically sorted.
676
if revision_id is None:
678
if not self.has_revision(revision_id):
679
raise errors.NoSuchRevision(self, revision_id)
680
w = self.get_inventory_weave()
681
candidates = w.get_ancestry(revision_id)
682
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
685
def print_file(self, file, revision_id):
686
"""Print `file` to stdout.
688
FIXME RBC 20060125 as John Meinel points out this is a bad api
689
- it writes to stdout, it assumes that that is valid etc. Fix
690
by creating a new more flexible convenience function.
692
tree = self.revision_tree(revision_id)
693
# use inventory as it was in that revision
694
file_id = tree.inventory.path2id(file)
696
# TODO: jam 20060427 Write a test for this code path
697
# it had a bug in it, and was raising the wrong
699
raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
700
tree.print_file(file_id)
702
def get_transaction(self):
703
return self.control_files.get_transaction()
705
def revision_parents(self, revid):
706
return self.get_inventory_weave().parent_names(revid)
709
def set_make_working_trees(self, new_value):
710
"""Set the policy flag for making working trees when creating branches.
712
This only applies to branches that use this repository.
714
The default is 'True'.
715
:param new_value: True to restore the default, False to disable making
718
raise NotImplementedError(self.set_make_working_trees)
720
def make_working_trees(self):
721
"""Returns the policy for making working trees on new branches."""
722
raise NotImplementedError(self.make_working_trees)
725
def sign_revision(self, revision_id, gpg_strategy):
726
plaintext = Testament.from_revision(self, revision_id).as_short_text()
727
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
730
def has_signature_for_revision_id(self, revision_id):
731
"""Query for a revision signature for revision_id in the repository."""
732
return self._revision_store.has_signature(revision_id,
733
self.get_transaction())
736
def get_signature_text(self, revision_id):
737
"""Return the text for a signature."""
738
return self._revision_store.get_signature_text(revision_id,
739
self.get_transaction())
742
def check(self, revision_ids):
743
"""Check consistency of all history of given revision_ids.
745
Different repository implementations should override _check().
747
:param revision_ids: A non-empty list of revision_ids whose ancestry
748
will be checked. Typically the last revision_id of a branch.
751
raise ValueError("revision_ids must be non-empty in %s.check"
753
return self._check(revision_ids)
755
def _check(self, revision_ids):
756
result = check.Check(self)
760
def _warn_if_deprecated(self):
761
global _deprecation_warning_done
762
if _deprecation_warning_done:
764
_deprecation_warning_done = True
765
warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
766
% (self._format, self.bzrdir.transport.base))
768
def supports_rich_root(self):
769
return self._format.rich_root_data
771
def _check_ascii_revisionid(self, revision_id, method):
772
"""Private helper for ascii-only repositories."""
773
# weave repositories refuse to store revisionids that are non-ascii.
774
if revision_id is not None:
775
# weaves require ascii revision ids.
776
if isinstance(revision_id, unicode):
778
revision_id.encode('ascii')
779
except UnicodeEncodeError:
780
raise errors.NonAsciiRevisionId(method, self)
783
class AllInOneRepository(Repository):
784
"""Legacy support - the repository behaviour for all-in-one branches."""
786
def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
787
# we reuse one control files instance.
788
dir_mode = a_bzrdir._control_files._dir_mode
789
file_mode = a_bzrdir._control_files._file_mode
791
def get_store(name, compressed=True, prefixed=False):
792
# FIXME: This approach of assuming stores are all entirely compressed
793
# or entirely uncompressed is tidy, but breaks upgrade from
794
# some existing branches where there's a mixture; we probably
795
# still want the option to look for both.
796
relpath = a_bzrdir._control_files._escape(name)
797
store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
798
prefixed=prefixed, compressed=compressed,
801
#if self._transport.should_cache():
802
# cache_path = os.path.join(self.cache_root, name)
803
# os.mkdir(cache_path)
804
# store = bzrlib.store.CachedStore(store, cache_path)
807
# not broken out yet because the controlweaves|inventory_store
808
# and text_store | weave_store bits are still different.
809
if isinstance(_format, RepositoryFormat4):
810
# cannot remove these - there is still no consistent api
811
# which allows access to this old info.
812
self.inventory_store = get_store('inventory-store')
813
text_store = get_store('text-store')
814
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
816
def get_commit_builder(self, branch, parents, config, timestamp=None,
817
timezone=None, committer=None, revprops=None,
819
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
820
return Repository.get_commit_builder(self, branch, parents, config,
821
timestamp, timezone, committer, revprops, revision_id)
825
"""AllInOne repositories cannot be shared."""
829
def set_make_working_trees(self, new_value):
830
"""Set the policy flag for making working trees when creating branches.
832
This only applies to branches that use this repository.
834
The default is 'True'.
835
:param new_value: True to restore the default, False to disable making
838
raise NotImplementedError(self.set_make_working_trees)
840
def make_working_trees(self):
841
"""Returns the policy for making working trees on new branches."""
845
def install_revision(repository, rev, revision_tree):
846
"""Install all revision data into a repository."""
849
for p_id in rev.parent_ids:
850
if repository.has_revision(p_id):
851
present_parents.append(p_id)
852
parent_trees[p_id] = repository.revision_tree(p_id)
854
parent_trees[p_id] = repository.revision_tree(None)
856
inv = revision_tree.inventory
857
entries = inv.iter_entries()
858
# backwards compatability hack: skip the root id.
859
if not repository.supports_rich_root():
860
path, root = entries.next()
861
if root.revision != rev.revision_id:
862
raise errors.IncompatibleRevision(repr(repository))
863
# Add the texts that are not already present
864
for path, ie in entries:
865
w = repository.weave_store.get_weave_or_empty(ie.file_id,
866
repository.get_transaction())
867
if ie.revision not in w:
869
# FIXME: TODO: The following loop *may* be overlapping/duplicate
870
# with InventoryEntry.find_previous_heads(). if it is, then there
871
# is a latent bug here where the parents may have ancestors of each
873
for revision, tree in parent_trees.iteritems():
874
if ie.file_id not in tree:
876
parent_id = tree.inventory[ie.file_id].revision
877
if parent_id in text_parents:
879
text_parents.append(parent_id)
881
vfile = repository.weave_store.get_weave_or_empty(ie.file_id,
882
repository.get_transaction())
883
lines = revision_tree.get_file(ie.file_id).readlines()
884
vfile.add_lines(rev.revision_id, text_parents, lines)
886
# install the inventory
887
repository.add_inventory(rev.revision_id, inv, present_parents)
888
except errors.RevisionAlreadyPresent:
890
repository.add_revision(rev.revision_id, rev, inv)
893
class MetaDirRepository(Repository):
894
"""Repositories in the new meta-dir layout."""
896
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
897
super(MetaDirRepository, self).__init__(_format,
903
dir_mode = self.control_files._dir_mode
904
file_mode = self.control_files._file_mode
908
"""Return True if this repository is flagged as a shared repository."""
909
return self.control_files._transport.has('shared-storage')
912
def set_make_working_trees(self, new_value):
913
"""Set the policy flag for making working trees when creating branches.
915
This only applies to branches that use this repository.
917
The default is 'True'.
918
:param new_value: True to restore the default, False to disable making
923
self.control_files._transport.delete('no-working-trees')
924
except errors.NoSuchFile:
927
self.control_files.put_utf8('no-working-trees', '')
929
def make_working_trees(self):
930
"""Returns the policy for making working trees on new branches."""
931
return not self.control_files._transport.has('no-working-trees')
934
class WeaveMetaDirRepository(MetaDirRepository):
935
"""A subclass of MetaDirRepository to set weave specific policy."""
937
def get_commit_builder(self, branch, parents, config, timestamp=None,
938
timezone=None, committer=None, revprops=None,
940
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
941
return MetaDirRepository.get_commit_builder(self, branch, parents,
942
config, timestamp, timezone, committer, revprops, revision_id)
945
class KnitRepository(MetaDirRepository):
946
"""Knit format repository."""
948
def _warn_if_deprecated(self):
949
# This class isn't deprecated
952
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
953
inv_vf.add_lines_with_ghosts(revid, parents, lines)
956
def _all_revision_ids(self):
957
"""See Repository.all_revision_ids()."""
958
# Knits get the revision graph from the index of the revision knit, so
959
# it's always possible even if they're on an unlistable transport.
960
return self._revision_store.all_revision_ids(self.get_transaction())
962
def fileid_involved_between_revs(self, from_revid, to_revid):
963
"""Find file_id(s) which are involved in the changes between revisions.
965
This determines the set of revisions which are involved, and then
966
finds all file ids affected by those revisions.
968
vf = self._get_revision_vf()
969
from_set = set(vf.get_ancestry(from_revid))
970
to_set = set(vf.get_ancestry(to_revid))
971
changed = to_set.difference(from_set)
972
return self._fileid_involved_by_set(changed)
974
def fileid_involved(self, last_revid=None):
975
"""Find all file_ids modified in the ancestry of last_revid.
977
:param last_revid: If None, last_revision() will be used.
980
changed = set(self.all_revision_ids())
982
changed = set(self.get_ancestry(last_revid))
985
return self._fileid_involved_by_set(changed)
988
def get_ancestry(self, revision_id):
989
"""Return a list of revision-ids integrated by a revision.
991
This is topologically sorted.
993
if revision_id is None:
995
vf = self._get_revision_vf()
997
return [None] + vf.get_ancestry(revision_id)
998
except errors.RevisionNotPresent:
999
raise errors.NoSuchRevision(self, revision_id)
1002
def get_revision(self, revision_id):
1003
"""Return the Revision object for a named revision"""
1004
return self.get_revision_reconcile(revision_id)
1007
def get_revision_graph(self, revision_id=None):
1008
"""Return a dictionary containing the revision graph.
1010
:param revision_id: The revision_id to get a graph from. If None, then
1011
the entire revision graph is returned. This is a deprecated mode of
1012
operation and will be removed in the future.
1013
:return: a dictionary of revision_id->revision_parents_list.
1015
# special case NULL_REVISION
1016
if revision_id == _mod_revision.NULL_REVISION:
1018
a_weave = self._get_revision_vf()
1019
entire_graph = a_weave.get_graph()
1020
if revision_id is None:
1021
return a_weave.get_graph()
1022
elif revision_id not in a_weave:
1023
raise errors.NoSuchRevision(self, revision_id)
1025
# add what can be reached from revision_id
1027
pending = set([revision_id])
1028
while len(pending) > 0:
1029
node = pending.pop()
1030
result[node] = a_weave.get_parents(node)
1031
for revision_id in result[node]:
1032
if revision_id not in result:
1033
pending.add(revision_id)
1037
def get_revision_graph_with_ghosts(self, revision_ids=None):
1038
"""Return a graph of the revisions with ghosts marked as applicable.
1040
:param revision_ids: an iterable of revisions to graph or None for all.
1041
:return: a Graph object with the graph reachable from revision_ids.
1043
result = graph.Graph()
1044
vf = self._get_revision_vf()
1045
versions = set(vf.versions())
1046
if not revision_ids:
1047
pending = set(self.all_revision_ids())
1050
pending = set(revision_ids)
1051
# special case NULL_REVISION
1052
if _mod_revision.NULL_REVISION in pending:
1053
pending.remove(_mod_revision.NULL_REVISION)
1054
required = set(pending)
1057
revision_id = pending.pop()
1058
if not revision_id in versions:
1059
if revision_id in required:
1060
raise errors.NoSuchRevision(self, revision_id)
1062
result.add_ghost(revision_id)
1063
# mark it as done so we don't try for it again.
1064
done.add(revision_id)
1066
parent_ids = vf.get_parents_with_ghosts(revision_id)
1067
for parent_id in parent_ids:
1068
# is this queued or done ?
1069
if (parent_id not in pending and
1070
parent_id not in done):
1072
pending.add(parent_id)
1073
result.add_node(revision_id, parent_ids)
1074
done.add(revision_id)
1077
def _get_revision_vf(self):
1078
""":return: a versioned file containing the revisions."""
1079
vf = self._revision_store.get_revision_file(self.get_transaction())
1083
def reconcile(self, other=None, thorough=False):
1084
"""Reconcile this repository."""
1085
from bzrlib.reconcile import KnitReconciler
1086
reconciler = KnitReconciler(self, thorough=thorough)
1087
reconciler.reconcile()
1090
def revision_parents(self, revision_id):
1091
return self._get_revision_vf().get_parents(revision_id)
1094
class KnitRepository2(KnitRepository):
1096
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
1097
control_store, text_store):
1098
KnitRepository.__init__(self, _format, a_bzrdir, control_files,
1099
_revision_store, control_store, text_store)
1100
self._serializer = xml6.serializer_v6
1102
def deserialise_inventory(self, revision_id, xml):
1103
"""Transform the xml into an inventory object.
1105
:param revision_id: The expected revision id of the inventory.
1106
:param xml: A serialised inventory.
1108
result = self._serializer.read_inventory_from_string(xml)
1109
assert result.root.revision is not None
1112
def serialise_inventory(self, inv):
1113
"""Transform the inventory object into XML text.
1115
:param revision_id: The expected revision id of the inventory.
1116
:param xml: A serialised inventory.
1118
assert inv.revision_id is not None
1119
assert inv.root.revision is not None
1120
return KnitRepository.serialise_inventory(self, inv)
1122
def get_commit_builder(self, branch, parents, config, timestamp=None,
1123
timezone=None, committer=None, revprops=None,
1125
"""Obtain a CommitBuilder for this repository.
1127
:param branch: Branch to commit to.
1128
:param parents: Revision ids of the parents of the new revision.
1129
:param config: Configuration to use.
1130
:param timestamp: Optional timestamp recorded for commit.
1131
:param timezone: Optional timezone for timestamp.
1132
:param committer: Optional committer to set for commit.
1133
:param revprops: Optional dictionary of revision properties.
1134
:param revision_id: Optional revision id.
1136
return RootCommitBuilder(self, parents, config, timestamp, timezone,
1137
committer, revprops, revision_id)
1140
class RepositoryFormat(object):
1141
"""A repository format.
1143
Formats provide three things:
1144
* An initialization routine to construct repository data on disk.
1145
* a format string which is used when the BzrDir supports versioned
1147
* an open routine which returns a Repository instance.
1149
Formats are placed in an dict by their format string for reference
1150
during opening. These should be subclasses of RepositoryFormat
1153
Once a format is deprecated, just deprecate the initialize and open
1154
methods on the format class. Do not deprecate the object, as the
1155
object will be created every system load.
1157
Common instance attributes:
1158
_matchingbzrdir - the bzrdir format that the repository format was
1159
originally written to work with. This can be used if manually
1160
constructing a bzrdir and repository, or more commonly for test suite
1164
_default_format = None
1165
"""The default format used for new repositories."""
1168
"""The known formats."""
1171
return "<%s>" % self.__class__.__name__
1174
def find_format(klass, a_bzrdir):
1175
"""Return the format for the repository object in a_bzrdir."""
1177
transport = a_bzrdir.get_repository_transport(None)
1178
format_string = transport.get("format").read()
1179
return klass._formats[format_string]
1180
except errors.NoSuchFile:
1181
raise errors.NoRepositoryPresent(a_bzrdir)
1183
raise errors.UnknownFormatError(format=format_string)
1185
def _get_control_store(self, repo_transport, control_files):
1186
"""Return the control store for this repository."""
1187
raise NotImplementedError(self._get_control_store)
1190
def get_default_format(klass):
1191
"""Return the current default format."""
1192
return klass._default_format
1194
def get_format_string(self):
1195
"""Return the ASCII format string that identifies this format.
1197
Note that in pre format ?? repositories the format string is
1198
not permitted nor written to disk.
1200
raise NotImplementedError(self.get_format_string)
1202
def get_format_description(self):
1203
"""Return the short description for this format."""
1204
raise NotImplementedError(self.get_format_description)
1206
def _get_revision_store(self, repo_transport, control_files):
1207
"""Return the revision store object for this a_bzrdir."""
1208
raise NotImplementedError(self._get_revision_store)
1210
def _get_text_rev_store(self,
1217
"""Common logic for getting a revision store for a repository.
1219
see self._get_revision_store for the subclass-overridable method to
1220
get the store for a repository.
1222
from bzrlib.store.revision.text import TextRevisionStore
1223
dir_mode = control_files._dir_mode
1224
file_mode = control_files._file_mode
1225
text_store =TextStore(transport.clone(name),
1227
compressed=compressed,
1229
file_mode=file_mode)
1230
_revision_store = TextRevisionStore(text_store, serializer)
1231
return _revision_store
1233
def _get_versioned_file_store(self,
1238
versionedfile_class=weave.WeaveFile,
1239
versionedfile_kwargs={},
1241
weave_transport = control_files._transport.clone(name)
1242
dir_mode = control_files._dir_mode
1243
file_mode = control_files._file_mode
1244
return VersionedFileStore(weave_transport, prefixed=prefixed,
1246
file_mode=file_mode,
1247
versionedfile_class=versionedfile_class,
1248
versionedfile_kwargs=versionedfile_kwargs,
1251
def initialize(self, a_bzrdir, shared=False):
1252
"""Initialize a repository of this format in a_bzrdir.
1254
:param a_bzrdir: The bzrdir to put the new repository in it.
1255
:param shared: The repository should be initialized as a sharable one.
1257
This may raise UninitializableFormat if shared repository are not
1258
compatible the a_bzrdir.
1261
def is_supported(self):
1262
"""Is this format supported?
1264
Supported formats must be initializable and openable.
1265
Unsupported formats may not support initialization or committing or
1266
some other features depending on the reason for not being supported.
1270
def check_conversion_target(self, target_format):
1271
raise NotImplementedError(self.check_conversion_target)
1273
def open(self, a_bzrdir, _found=False):
1274
"""Return an instance of this format for the bzrdir a_bzrdir.
1276
_found is a private parameter, do not use it.
1278
raise NotImplementedError(self.open)
1281
def register_format(klass, format):
1282
klass._formats[format.get_format_string()] = format
1285
@deprecated_method(symbol_versioning.zero_fourteen)
1286
def set_default_format(klass, format):
1287
klass._set_default_format(format)
1290
def _set_default_format(klass, format):
1291
klass._default_format = format
1294
def unregister_format(klass, format):
1295
assert klass._formats[format.get_format_string()] is format
1296
del klass._formats[format.get_format_string()]
1299
class PreSplitOutRepositoryFormat(RepositoryFormat):
1300
"""Base class for the pre split out repository formats."""
1302
rich_root_data = False
1304
def initialize(self, a_bzrdir, shared=False, _internal=False):
1305
"""Create a weave repository.
1307
TODO: when creating split out bzr branch formats, move this to a common
1308
base for Format5, Format6. or something like that.
1311
raise errors.IncompatibleFormat(self, a_bzrdir._format)
1314
# always initialized when the bzrdir is.
1315
return self.open(a_bzrdir, _found=True)
1317
# Create an empty weave
1319
weavefile.write_weave_v5(weave.Weave(), sio)
1320
empty_weave = sio.getvalue()
1322
mutter('creating repository in %s.', a_bzrdir.transport.base)
1323
dirs = ['revision-store', 'weaves']
1324
files = [('inventory.weave', StringIO(empty_weave)),
1327
# FIXME: RBC 20060125 don't peek under the covers
1328
# NB: no need to escape relative paths that are url safe.
1329
control_files = lockable_files.LockableFiles(a_bzrdir.transport,
1330
'branch-lock', lockable_files.TransportLock)
1331
control_files.create_lock()
1332
control_files.lock_write()
1333
control_files._transport.mkdir_multi(dirs,
1334
mode=control_files._dir_mode)
1336
for file, content in files:
1337
control_files.put(file, content)
1339
control_files.unlock()
1340
return self.open(a_bzrdir, _found=True)
1342
def _get_control_store(self, repo_transport, control_files):
1343
"""Return the control store for this repository."""
1344
return self._get_versioned_file_store('',
1349
def _get_text_store(self, transport, control_files):
1350
"""Get a store for file texts for this format."""
1351
raise NotImplementedError(self._get_text_store)
1353
def open(self, a_bzrdir, _found=False):
1354
"""See RepositoryFormat.open()."""
1356
# we are being called directly and must probe.
1357
raise NotImplementedError
1359
repo_transport = a_bzrdir.get_repository_transport(None)
1360
control_files = a_bzrdir._control_files
1361
text_store = self._get_text_store(repo_transport, control_files)
1362
control_store = self._get_control_store(repo_transport, control_files)
1363
_revision_store = self._get_revision_store(repo_transport, control_files)
1364
return AllInOneRepository(_format=self,
1366
_revision_store=_revision_store,
1367
control_store=control_store,
1368
text_store=text_store)
1370
def check_conversion_target(self, target_format):
1374
class RepositoryFormat4(PreSplitOutRepositoryFormat):
1375
"""Bzr repository format 4.
1377
This repository format has:
1379
- TextStores for texts, inventories,revisions.
1381
This format is deprecated: it indexes texts using a text id which is
1382
removed in format 5; initialization and write support for this format
1387
super(RepositoryFormat4, self).__init__()
1388
self._matchingbzrdir = bzrdir.BzrDirFormat4()
1390
def get_format_description(self):
1391
"""See RepositoryFormat.get_format_description()."""
1392
return "Repository format 4"
1394
def initialize(self, url, shared=False, _internal=False):
1395
"""Format 4 branches cannot be created."""
1396
raise errors.UninitializableFormat(self)
1398
def is_supported(self):
1399
"""Format 4 is not supported.
1401
It is not supported because the model changed from 4 to 5 and the
1402
conversion logic is expensive - so doing it on the fly was not
1407
def _get_control_store(self, repo_transport, control_files):
1408
"""Format 4 repositories have no formal control store at this point.
1410
This will cause any control-file-needing apis to fail - this is desired.
1414
def _get_revision_store(self, repo_transport, control_files):
1415
"""See RepositoryFormat._get_revision_store()."""
1416
from bzrlib.xml4 import serializer_v4
1417
return self._get_text_rev_store(repo_transport,
1420
serializer=serializer_v4)
1422
def _get_text_store(self, transport, control_files):
1423
"""See RepositoryFormat._get_text_store()."""
1426
class RepositoryFormat5(PreSplitOutRepositoryFormat):
1427
"""Bzr control format 5.
1429
This repository format has:
1430
- weaves for file texts and inventory
1432
- TextStores for revisions and signatures.
1436
super(RepositoryFormat5, self).__init__()
1437
self._matchingbzrdir = bzrdir.BzrDirFormat5()
1439
def get_format_description(self):
1440
"""See RepositoryFormat.get_format_description()."""
1441
return "Weave repository format 5"
1443
def _get_revision_store(self, repo_transport, control_files):
1444
"""See RepositoryFormat._get_revision_store()."""
1445
"""Return the revision store object for this a_bzrdir."""
1446
return self._get_text_rev_store(repo_transport,
1451
def _get_text_store(self, transport, control_files):
1452
"""See RepositoryFormat._get_text_store()."""
1453
return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
1456
class RepositoryFormat6(PreSplitOutRepositoryFormat):
1457
"""Bzr control format 6.
1459
This repository format has:
1460
- weaves for file texts and inventory
1461
- hash subdirectory based stores.
1462
- TextStores for revisions and signatures.
1466
super(RepositoryFormat6, self).__init__()
1467
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1469
def get_format_description(self):
1470
"""See RepositoryFormat.get_format_description()."""
1471
return "Weave repository format 6"
1473
def _get_revision_store(self, repo_transport, control_files):
1474
"""See RepositoryFormat._get_revision_store()."""
1475
return self._get_text_rev_store(repo_transport,
1481
def _get_text_store(self, transport, control_files):
1482
"""See RepositoryFormat._get_text_store()."""
1483
return self._get_versioned_file_store('weaves', transport, control_files)
1486
class MetaDirRepositoryFormat(RepositoryFormat):
1487
"""Common base class for the new repositories using the metadir layout."""
1489
rich_root_data = False
1492
super(MetaDirRepositoryFormat, self).__init__()
1493
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1495
def _create_control_files(self, a_bzrdir):
1496
"""Create the required files and the initial control_files object."""
1497
# FIXME: RBC 20060125 don't peek under the covers
1498
# NB: no need to escape relative paths that are url safe.
1499
repository_transport = a_bzrdir.get_repository_transport(self)
1500
control_files = lockable_files.LockableFiles(repository_transport,
1501
'lock', lockdir.LockDir)
1502
control_files.create_lock()
1503
return control_files
1505
def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
1506
"""Upload the initial blank content."""
1507
control_files = self._create_control_files(a_bzrdir)
1508
control_files.lock_write()
1510
control_files._transport.mkdir_multi(dirs,
1511
mode=control_files._dir_mode)
1512
for file, content in files:
1513
control_files.put(file, content)
1514
for file, content in utf8_files:
1515
control_files.put_utf8(file, content)
1517
control_files.put_utf8('shared-storage', '')
1519
control_files.unlock()
1522
class RepositoryFormat7(MetaDirRepositoryFormat):
1523
"""Bzr repository 7.
1525
This repository format has:
1526
- weaves for file texts and inventory
1527
- hash subdirectory based stores.
1528
- TextStores for revisions and signatures.
1529
- a format marker of its own
1530
- an optional 'shared-storage' flag
1531
- an optional 'no-working-trees' flag
1534
def _get_control_store(self, repo_transport, control_files):
1535
"""Return the control store for this repository."""
1536
return self._get_versioned_file_store('',
1541
def get_format_string(self):
1542
"""See RepositoryFormat.get_format_string()."""
1543
return "Bazaar-NG Repository format 7"
1545
def get_format_description(self):
1546
"""See RepositoryFormat.get_format_description()."""
1547
return "Weave repository format 7"
1549
def check_conversion_target(self, target_format):
1552
def _get_revision_store(self, repo_transport, control_files):
1553
"""See RepositoryFormat._get_revision_store()."""
1554
return self._get_text_rev_store(repo_transport,
1561
def _get_text_store(self, transport, control_files):
1562
"""See RepositoryFormat._get_text_store()."""
1563
return self._get_versioned_file_store('weaves',
1567
def initialize(self, a_bzrdir, shared=False):
1568
"""Create a weave repository.
1570
:param shared: If true the repository will be initialized as a shared
1573
# Create an empty weave
1575
weavefile.write_weave_v5(weave.Weave(), sio)
1576
empty_weave = sio.getvalue()
1578
mutter('creating repository in %s.', a_bzrdir.transport.base)
1579
dirs = ['revision-store', 'weaves']
1580
files = [('inventory.weave', StringIO(empty_weave)),
1582
utf8_files = [('format', self.get_format_string())]
1584
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1585
return self.open(a_bzrdir=a_bzrdir, _found=True)
1587
def open(self, a_bzrdir, _found=False, _override_transport=None):
1588
"""See RepositoryFormat.open().
1590
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1591
repository at a slightly different url
1592
than normal. I.e. during 'upgrade'.
1595
format = RepositoryFormat.find_format(a_bzrdir)
1596
assert format.__class__ == self.__class__
1597
if _override_transport is not None:
1598
repo_transport = _override_transport
1600
repo_transport = a_bzrdir.get_repository_transport(None)
1601
control_files = lockable_files.LockableFiles(repo_transport,
1602
'lock', lockdir.LockDir)
1603
text_store = self._get_text_store(repo_transport, control_files)
1604
control_store = self._get_control_store(repo_transport, control_files)
1605
_revision_store = self._get_revision_store(repo_transport, control_files)
1606
return WeaveMetaDirRepository(_format=self,
1608
control_files=control_files,
1609
_revision_store=_revision_store,
1610
control_store=control_store,
1611
text_store=text_store)
1614
class RepositoryFormatKnit(MetaDirRepositoryFormat):
1615
"""Bzr repository knit format (generalized).
1617
This repository format has:
1618
- knits for file texts and inventory
1619
- hash subdirectory based stores.
1620
- knits for revisions and signatures
1621
- TextStores for revisions and signatures.
1622
- a format marker of its own
1623
- an optional 'shared-storage' flag
1624
- an optional 'no-working-trees' flag
1628
def _get_control_store(self, repo_transport, control_files):
1629
"""Return the control store for this repository."""
1630
return VersionedFileStore(
1633
file_mode=control_files._file_mode,
1634
versionedfile_class=knit.KnitVersionedFile,
1635
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
1638
def _get_revision_store(self, repo_transport, control_files):
1639
"""See RepositoryFormat._get_revision_store()."""
1640
from bzrlib.store.revision.knit import KnitRevisionStore
1641
versioned_file_store = VersionedFileStore(
1643
file_mode=control_files._file_mode,
1646
versionedfile_class=knit.KnitVersionedFile,
1647
versionedfile_kwargs={'delta':False,
1648
'factory':knit.KnitPlainFactory(),
1652
return KnitRevisionStore(versioned_file_store)
1654
def _get_text_store(self, transport, control_files):
1655
"""See RepositoryFormat._get_text_store()."""
1656
return self._get_versioned_file_store('knits',
1659
versionedfile_class=knit.KnitVersionedFile,
1660
versionedfile_kwargs={
1661
'create_parent_dir':True,
1662
'delay_create':True,
1663
'dir_mode':control_files._dir_mode,
1667
def initialize(self, a_bzrdir, shared=False):
1668
"""Create a knit format 1 repository.
1670
:param a_bzrdir: bzrdir to contain the new repository; must already
1672
:param shared: If true the repository will be initialized as a shared
1675
mutter('creating repository in %s.', a_bzrdir.transport.base)
1676
dirs = ['revision-store', 'knits']
1678
utf8_files = [('format', self.get_format_string())]
1680
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1681
repo_transport = a_bzrdir.get_repository_transport(None)
1682
control_files = lockable_files.LockableFiles(repo_transport,
1683
'lock', lockdir.LockDir)
1684
control_store = self._get_control_store(repo_transport, control_files)
1685
transaction = transactions.WriteTransaction()
1686
# trigger a write of the inventory store.
1687
control_store.get_weave_or_empty('inventory', transaction)
1688
_revision_store = self._get_revision_store(repo_transport, control_files)
1689
# the revision id here is irrelevant: it will not be stored, and cannot
1691
_revision_store.has_revision_id('A', transaction)
1692
_revision_store.get_signature_file(transaction)
1693
return self.open(a_bzrdir=a_bzrdir, _found=True)
1695
def open(self, a_bzrdir, _found=False, _override_transport=None):
1696
"""See RepositoryFormat.open().
1698
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1699
repository at a slightly different url
1700
than normal. I.e. during 'upgrade'.
1703
format = RepositoryFormat.find_format(a_bzrdir)
1704
assert format.__class__ == self.__class__
1705
if _override_transport is not None:
1706
repo_transport = _override_transport
1708
repo_transport = a_bzrdir.get_repository_transport(None)
1709
control_files = lockable_files.LockableFiles(repo_transport,
1710
'lock', lockdir.LockDir)
1711
text_store = self._get_text_store(repo_transport, control_files)
1712
control_store = self._get_control_store(repo_transport, control_files)
1713
_revision_store = self._get_revision_store(repo_transport, control_files)
1714
return KnitRepository(_format=self,
1716
control_files=control_files,
1717
_revision_store=_revision_store,
1718
control_store=control_store,
1719
text_store=text_store)
1722
class RepositoryFormatKnit1(RepositoryFormatKnit):
1723
"""Bzr repository knit format 1.
1725
This repository format has:
1726
- knits for file texts and inventory
1727
- hash subdirectory based stores.
1728
- knits for revisions and signatures
1729
- TextStores for revisions and signatures.
1730
- a format marker of its own
1731
- an optional 'shared-storage' flag
1732
- an optional 'no-working-trees' flag
1735
This format was introduced in bzr 0.8.
1737
def get_format_string(self):
1738
"""See RepositoryFormat.get_format_string()."""
1739
return "Bazaar-NG Knit Repository Format 1"
1741
def get_format_description(self):
1742
"""See RepositoryFormat.get_format_description()."""
1743
return "Knit repository format 1"
1745
def check_conversion_target(self, target_format):
1749
class RepositoryFormatKnit2(RepositoryFormatKnit):
1750
"""Bzr repository knit format 2.
1752
THIS FORMAT IS EXPERIMENTAL
1753
This repository format has:
1754
- knits for file texts and inventory
1755
- hash subdirectory based stores.
1756
- knits for revisions and signatures
1757
- TextStores for revisions and signatures.
1758
- a format marker of its own
1759
- an optional 'shared-storage' flag
1760
- an optional 'no-working-trees' flag
1762
- Support for recording full info about the tree root
1766
rich_root_data = True
1768
def get_format_string(self):
1769
"""See RepositoryFormat.get_format_string()."""
1770
return "Bazaar Knit Repository Format 2\n"
1772
def get_format_description(self):
1773
"""See RepositoryFormat.get_format_description()."""
1774
return "Knit repository format 2"
1776
def check_conversion_target(self, target_format):
1777
if not target_format.rich_root_data:
1778
raise errors.BadConversionTarget(
1779
'Does not support rich root data.', target_format)
1781
def open(self, a_bzrdir, _found=False, _override_transport=None):
1782
"""See RepositoryFormat.open().
1784
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1785
repository at a slightly different url
1786
than normal. I.e. during 'upgrade'.
1789
format = RepositoryFormat.find_format(a_bzrdir)
1790
assert format.__class__ == self.__class__
1791
if _override_transport is not None:
1792
repo_transport = _override_transport
1794
repo_transport = a_bzrdir.get_repository_transport(None)
1795
control_files = lockable_files.LockableFiles(repo_transport, 'lock',
1797
text_store = self._get_text_store(repo_transport, control_files)
1798
control_store = self._get_control_store(repo_transport, control_files)
1799
_revision_store = self._get_revision_store(repo_transport, control_files)
1800
return KnitRepository2(_format=self,
1802
control_files=control_files,
1803
_revision_store=_revision_store,
1804
control_store=control_store,
1805
text_store=text_store)
1809
# formats which have no format string are not discoverable
1810
# and not independently creatable, so are not registered.
1811
RepositoryFormat.register_format(RepositoryFormat7())
1812
# KEEP in sync with bzrdir.format_registry default
1813
_default_format = RepositoryFormatKnit1()
1814
RepositoryFormat.register_format(_default_format)
1815
RepositoryFormat.register_format(RepositoryFormatKnit2())
1816
RepositoryFormat._set_default_format(_default_format)
1817
_legacy_formats = [RepositoryFormat4(),
1818
RepositoryFormat5(),
1819
RepositoryFormat6()]
1822
class InterRepository(InterObject):
1823
"""This class represents operations taking place between two repositories.
1825
Its instances have methods like copy_content and fetch, and contain
1826
references to the source and target repositories these operations can be
1829
Often we will provide convenience methods on 'repository' which carry out
1830
operations with another repository - they will always forward to
1831
InterRepository.get(other).method_name(parameters).
1835
"""The available optimised InterRepository types."""
1837
def copy_content(self, revision_id=None, basis=None):
1838
raise NotImplementedError(self.copy_content)
1840
def fetch(self, revision_id=None, pb=None):
1841
"""Fetch the content required to construct revision_id.
1843
The content is copied from self.source to self.target.
1845
:param revision_id: if None all content is copied, if NULL_REVISION no
1847
:param pb: optional progress bar to use for progress reports. If not
1848
provided a default one will be created.
1850
Returns the copied revision count and the failed revisions in a tuple:
1853
raise NotImplementedError(self.fetch)
1856
def missing_revision_ids(self, revision_id=None):
1857
"""Return the revision ids that source has that target does not.
1859
These are returned in topological order.
1861
:param revision_id: only return revision ids included by this
1864
# generic, possibly worst case, slow code path.
1865
target_ids = set(self.target.all_revision_ids())
1866
if revision_id is not None:
1867
source_ids = self.source.get_ancestry(revision_id)
1868
assert source_ids[0] is None
1871
source_ids = self.source.all_revision_ids()
1872
result_set = set(source_ids).difference(target_ids)
1873
# this may look like a no-op: its not. It preserves the ordering
1874
# other_ids had while only returning the members from other_ids
1875
# that we've decided we need.
1876
return [rev_id for rev_id in source_ids if rev_id in result_set]
1879
class InterSameDataRepository(InterRepository):
1880
"""Code for converting between repositories that represent the same data.
1882
Data format and model must match for this to work.
1885
_matching_repo_format = RepositoryFormat4()
1886
"""Repository format for testing with."""
1889
def is_compatible(source, target):
1890
if not isinstance(source, Repository):
1892
if not isinstance(target, Repository):
1894
if source._format.rich_root_data == target._format.rich_root_data:
1900
def copy_content(self, revision_id=None, basis=None):
1901
"""Make a complete copy of the content in self into destination.
1903
This is a destructive operation! Do not use it on existing
1906
:param revision_id: Only copy the content needed to construct
1907
revision_id and its parents.
1908
:param basis: Copy the needed data preferentially from basis.
1911
self.target.set_make_working_trees(self.source.make_working_trees())
1912
except NotImplementedError:
1914
# grab the basis available data
1915
if basis is not None:
1916
self.target.fetch(basis, revision_id=revision_id)
1917
# but don't bother fetching if we have the needed data now.
1918
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1919
self.target.has_revision(revision_id)):
1921
self.target.fetch(self.source, revision_id=revision_id)
1924
def fetch(self, revision_id=None, pb=None):
1925
"""See InterRepository.fetch()."""
1926
from bzrlib.fetch import GenericRepoFetcher
1927
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1928
self.source, self.source._format, self.target,
1929
self.target._format)
1930
f = GenericRepoFetcher(to_repository=self.target,
1931
from_repository=self.source,
1932
last_revision=revision_id,
1934
return f.count_copied, f.failed_revisions
1937
class InterWeaveRepo(InterSameDataRepository):
1938
"""Optimised code paths between Weave based repositories."""
1940
_matching_repo_format = RepositoryFormat7()
1941
"""Repository format for testing with."""
1944
def is_compatible(source, target):
1945
"""Be compatible with known Weave formats.
1947
We don't test for the stores being of specific types because that
1948
could lead to confusing results, and there is no need to be
1952
return (isinstance(source._format, (RepositoryFormat5,
1954
RepositoryFormat7)) and
1955
isinstance(target._format, (RepositoryFormat5,
1957
RepositoryFormat7)))
1958
except AttributeError:
1962
def copy_content(self, revision_id=None, basis=None):
1963
"""See InterRepository.copy_content()."""
1964
# weave specific optimised path:
1965
if basis is not None:
1966
# copy the basis in, then fetch remaining data.
1967
basis.copy_content_into(self.target, revision_id)
1968
# the basis copy_content_into could miss-set this.
1970
self.target.set_make_working_trees(self.source.make_working_trees())
1971
except NotImplementedError:
1973
self.target.fetch(self.source, revision_id=revision_id)
1976
self.target.set_make_working_trees(self.source.make_working_trees())
1977
except NotImplementedError:
1979
# FIXME do not peek!
1980
if self.source.control_files._transport.listable():
1981
pb = ui.ui_factory.nested_progress_bar()
1983
self.target.weave_store.copy_all_ids(
1984
self.source.weave_store,
1986
from_transaction=self.source.get_transaction(),
1987
to_transaction=self.target.get_transaction())
1988
pb.update('copying inventory', 0, 1)
1989
self.target.control_weaves.copy_multi(
1990
self.source.control_weaves, ['inventory'],
1991
from_transaction=self.source.get_transaction(),
1992
to_transaction=self.target.get_transaction())
1993
self.target._revision_store.text_store.copy_all_ids(
1994
self.source._revision_store.text_store,
1999
self.target.fetch(self.source, revision_id=revision_id)
2002
def fetch(self, revision_id=None, pb=None):
2003
"""See InterRepository.fetch()."""
2004
from bzrlib.fetch import GenericRepoFetcher
2005
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2006
self.source, self.source._format, self.target, self.target._format)
2007
f = GenericRepoFetcher(to_repository=self.target,
2008
from_repository=self.source,
2009
last_revision=revision_id,
2011
return f.count_copied, f.failed_revisions
2014
def missing_revision_ids(self, revision_id=None):
2015
"""See InterRepository.missing_revision_ids()."""
2016
# we want all revisions to satisfy revision_id in source.
2017
# but we don't want to stat every file here and there.
2018
# we want then, all revisions other needs to satisfy revision_id
2019
# checked, but not those that we have locally.
2020
# so the first thing is to get a subset of the revisions to
2021
# satisfy revision_id in source, and then eliminate those that
2022
# we do already have.
2023
# this is slow on high latency connection to self, but as as this
2024
# disk format scales terribly for push anyway due to rewriting
2025
# inventory.weave, this is considered acceptable.
2027
if revision_id is not None:
2028
source_ids = self.source.get_ancestry(revision_id)
2029
assert source_ids[0] is None
2032
source_ids = self.source._all_possible_ids()
2033
source_ids_set = set(source_ids)
2034
# source_ids is the worst possible case we may need to pull.
2035
# now we want to filter source_ids against what we actually
2036
# have in target, but don't try to check for existence where we know
2037
# we do not have a revision as that would be pointless.
2038
target_ids = set(self.target._all_possible_ids())
2039
possibly_present_revisions = target_ids.intersection(source_ids_set)
2040
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
2041
required_revisions = source_ids_set.difference(actually_present_revisions)
2042
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
2043
if revision_id is not None:
2044
# we used get_ancestry to determine source_ids then we are assured all
2045
# revisions referenced are present as they are installed in topological order.
2046
# and the tip revision was validated by get_ancestry.
2047
return required_topo_revisions
2049
# if we just grabbed the possibly available ids, then
2050
# we only have an estimate of whats available and need to validate
2051
# that against the revision records.
2052
return self.source._eliminate_revisions_not_present(required_topo_revisions)
2055
class InterKnitRepo(InterSameDataRepository):
2056
"""Optimised code paths between Knit based repositories."""
2058
_matching_repo_format = RepositoryFormatKnit1()
2059
"""Repository format for testing with."""
2062
def is_compatible(source, target):
2063
"""Be compatible with known Knit formats.
2065
We don't test for the stores being of specific types because that
2066
could lead to confusing results, and there is no need to be
2070
return (isinstance(source._format, (RepositoryFormatKnit1)) and
2071
isinstance(target._format, (RepositoryFormatKnit1)))
2072
except AttributeError:
2076
def fetch(self, revision_id=None, pb=None):
2077
"""See InterRepository.fetch()."""
2078
from bzrlib.fetch import KnitRepoFetcher
2079
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2080
self.source, self.source._format, self.target, self.target._format)
2081
f = KnitRepoFetcher(to_repository=self.target,
2082
from_repository=self.source,
2083
last_revision=revision_id,
2085
return f.count_copied, f.failed_revisions
2088
def missing_revision_ids(self, revision_id=None):
2089
"""See InterRepository.missing_revision_ids()."""
2090
if revision_id is not None:
2091
source_ids = self.source.get_ancestry(revision_id)
2092
assert source_ids[0] is None
2095
source_ids = self.source._all_possible_ids()
2096
source_ids_set = set(source_ids)
2097
# source_ids is the worst possible case we may need to pull.
2098
# now we want to filter source_ids against what we actually
2099
# have in target, but don't try to check for existence where we know
2100
# we do not have a revision as that would be pointless.
2101
target_ids = set(self.target._all_possible_ids())
2102
possibly_present_revisions = target_ids.intersection(source_ids_set)
2103
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
2104
required_revisions = source_ids_set.difference(actually_present_revisions)
2105
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
2106
if revision_id is not None:
2107
# we used get_ancestry to determine source_ids then we are assured all
2108
# revisions referenced are present as they are installed in topological order.
2109
# and the tip revision was validated by get_ancestry.
2110
return required_topo_revisions
2112
# if we just grabbed the possibly available ids, then
2113
# we only have an estimate of whats available and need to validate
2114
# that against the revision records.
2115
return self.source._eliminate_revisions_not_present(required_topo_revisions)
2118
class InterModel1and2(InterRepository):
2120
_matching_repo_format = None
2123
def is_compatible(source, target):
2124
if not isinstance(source, Repository):
2126
if not isinstance(target, Repository):
2128
if not source._format.rich_root_data and target._format.rich_root_data:
2134
def fetch(self, revision_id=None, pb=None):
2135
"""See InterRepository.fetch()."""
2136
from bzrlib.fetch import Model1toKnit2Fetcher
2137
f = Model1toKnit2Fetcher(to_repository=self.target,
2138
from_repository=self.source,
2139
last_revision=revision_id,
2141
return f.count_copied, f.failed_revisions
2144
def copy_content(self, revision_id=None, basis=None):
2145
"""Make a complete copy of the content in self into destination.
2147
This is a destructive operation! Do not use it on existing
2150
:param revision_id: Only copy the content needed to construct
2151
revision_id and its parents.
2152
:param basis: Copy the needed data preferentially from basis.
2155
self.target.set_make_working_trees(self.source.make_working_trees())
2156
except NotImplementedError:
2158
# grab the basis available data
2159
if basis is not None:
2160
self.target.fetch(basis, revision_id=revision_id)
2161
# but don't bother fetching if we have the needed data now.
2162
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
2163
self.target.has_revision(revision_id)):
2165
self.target.fetch(self.source, revision_id=revision_id)
2168
class InterKnit1and2(InterKnitRepo):
2170
_matching_repo_format = None
2173
def is_compatible(source, target):
2174
"""Be compatible with Knit1 source and Knit2 target"""
2176
return (isinstance(source._format, (RepositoryFormatKnit1)) and
2177
isinstance(target._format, (RepositoryFormatKnit2)))
2178
except AttributeError:
2182
def fetch(self, revision_id=None, pb=None):
2183
"""See InterRepository.fetch()."""
2184
from bzrlib.fetch import Knit1to2Fetcher
2185
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2186
self.source, self.source._format, self.target,
2187
self.target._format)
2188
f = Knit1to2Fetcher(to_repository=self.target,
2189
from_repository=self.source,
2190
last_revision=revision_id,
2192
return f.count_copied, f.failed_revisions
2195
InterRepository.register_optimiser(InterSameDataRepository)
2196
InterRepository.register_optimiser(InterWeaveRepo)
2197
InterRepository.register_optimiser(InterKnitRepo)
2198
InterRepository.register_optimiser(InterModel1and2)
2199
InterRepository.register_optimiser(InterKnit1and2)
2202
class RepositoryTestProviderAdapter(object):
2203
"""A tool to generate a suite testing multiple repository formats at once.
2205
This is done by copying the test once for each transport and injecting
2206
the transport_server, transport_readonly_server, and bzrdir_format and
2207
repository_format classes into each copy. Each copy is also given a new id()
2208
to make it easy to identify.
2211
def __init__(self, transport_server, transport_readonly_server, formats):
2212
self._transport_server = transport_server
2213
self._transport_readonly_server = transport_readonly_server
2214
self._formats = formats
2216
def adapt(self, test):
2217
result = unittest.TestSuite()
2218
for repository_format, bzrdir_format in self._formats:
2219
new_test = deepcopy(test)
2220
new_test.transport_server = self._transport_server
2221
new_test.transport_readonly_server = self._transport_readonly_server
2222
new_test.bzrdir_format = bzrdir_format
2223
new_test.repository_format = repository_format
2224
def make_new_test_id():
2225
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
2226
return lambda: new_id
2227
new_test.id = make_new_test_id()
2228
result.addTest(new_test)
2232
class InterRepositoryTestProviderAdapter(object):
2233
"""A tool to generate a suite testing multiple inter repository formats.
2235
This is done by copying the test once for each interrepo provider and injecting
2236
the transport_server, transport_readonly_server, repository_format and
2237
repository_to_format classes into each copy.
2238
Each copy is also given a new id() to make it easy to identify.
2241
def __init__(self, transport_server, transport_readonly_server, formats):
2242
self._transport_server = transport_server
2243
self._transport_readonly_server = transport_readonly_server
2244
self._formats = formats
2246
def adapt(self, test):
2247
result = unittest.TestSuite()
2248
for interrepo_class, repository_format, repository_format_to in self._formats:
2249
new_test = deepcopy(test)
2250
new_test.transport_server = self._transport_server
2251
new_test.transport_readonly_server = self._transport_readonly_server
2252
new_test.interrepo_class = interrepo_class
2253
new_test.repository_format = repository_format
2254
new_test.repository_format_to = repository_format_to
2255
def make_new_test_id():
2256
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
2257
return lambda: new_id
2258
new_test.id = make_new_test_id()
2259
result.addTest(new_test)
2263
def default_test_list():
2264
"""Generate the default list of interrepo permutations to test."""
2266
# test the default InterRepository between format 6 and the current
2268
# XXX: robertc 20060220 reinstate this when there are two supported
2269
# formats which do not have an optimal code path between them.
2270
#result.append((InterRepository,
2271
# RepositoryFormat6(),
2272
# RepositoryFormatKnit1()))
2273
for optimiser in InterRepository._optimisers:
2274
if optimiser._matching_repo_format is not None:
2275
result.append((optimiser,
2276
optimiser._matching_repo_format,
2277
optimiser._matching_repo_format
2279
# if there are specific combinations we want to use, we can add them
2281
result.append((InterModel1and2, RepositoryFormat5(),
2282
RepositoryFormatKnit2()))
2283
result.append((InterKnit1and2, RepositoryFormatKnit1(),
2284
RepositoryFormatKnit2()))
2288
class CopyConverter(object):
2289
"""A repository conversion tool which just performs a copy of the content.
2291
This is slow but quite reliable.
2294
def __init__(self, target_format):
2295
"""Create a CopyConverter.
2297
:param target_format: The format the resulting repository should be.
2299
self.target_format = target_format
2301
def convert(self, repo, pb):
2302
"""Perform the conversion of to_convert, giving feedback via pb.
2304
:param to_convert: The disk object to convert.
2305
:param pb: a progress bar to use for progress information.
2310
# this is only useful with metadir layouts - separated repo content.
2311
# trigger an assertion if not such
2312
repo._format.get_format_string()
2313
self.repo_dir = repo.bzrdir
2314
self.step('Moving repository to repository.backup')
2315
self.repo_dir.transport.move('repository', 'repository.backup')
2316
backup_transport = self.repo_dir.transport.clone('repository.backup')
2317
repo._format.check_conversion_target(self.target_format)
2318
self.source_repo = repo._format.open(self.repo_dir,
2320
_override_transport=backup_transport)
2321
self.step('Creating new repository')
2322
converted = self.target_format.initialize(self.repo_dir,
2323
self.source_repo.is_shared())
2324
converted.lock_write()
2326
self.step('Copying content into repository.')
2327
self.source_repo.copy_content_into(converted)
2330
self.step('Deleting old repository content.')
2331
self.repo_dir.transport.delete_tree('repository.backup')
2332
self.pb.note('repository converted')
2334
def step(self, message):
2335
"""Update the pb by a step."""
2337
self.pb.update(message, self.count, self.total)
2340
class CommitBuilder(object):
2341
"""Provides an interface to build up a commit.
2343
This allows describing a tree to be committed without needing to
2344
know the internals of the format of the repository.
2347
record_root_entry = False
2348
def __init__(self, repository, parents, config, timestamp=None,
2349
timezone=None, committer=None, revprops=None,
2351
"""Initiate a CommitBuilder.
2353
:param repository: Repository to commit to.
2354
:param parents: Revision ids of the parents of the new revision.
2355
:param config: Configuration to use.
2356
:param timestamp: Optional timestamp recorded for commit.
2357
:param timezone: Optional timezone for timestamp.
2358
:param committer: Optional committer to set for commit.
2359
:param revprops: Optional dictionary of revision properties.
2360
:param revision_id: Optional revision id.
2362
self._config = config
2364
if committer is None:
2365
self._committer = self._config.username()
2367
assert isinstance(committer, basestring), type(committer)
2368
self._committer = committer
2370
self.new_inventory = Inventory(None)
2371
self._new_revision_id = revision_id
2372
self.parents = parents
2373
self.repository = repository
2376
if revprops is not None:
2377
self._revprops.update(revprops)
2379
if timestamp is None:
2380
timestamp = time.time()
2381
# Restrict resolution to 1ms
2382
self._timestamp = round(timestamp, 3)
2384
if timezone is None:
2385
self._timezone = local_time_offset()
2387
self._timezone = int(timezone)
2389
self._generate_revision_if_needed()
2391
def commit(self, message):
2392
"""Make the actual commit.
2394
:return: The revision id of the recorded revision.
2396
rev = _mod_revision.Revision(
2397
timestamp=self._timestamp,
2398
timezone=self._timezone,
2399
committer=self._committer,
2401
inventory_sha1=self.inv_sha1,
2402
revision_id=self._new_revision_id,
2403
properties=self._revprops)
2404
rev.parent_ids = self.parents
2405
self.repository.add_revision(self._new_revision_id, rev,
2406
self.new_inventory, self._config)
2407
return self._new_revision_id
2409
def revision_tree(self):
2410
"""Return the tree that was just committed.
2412
After calling commit() this can be called to get a RevisionTree
2413
representing the newly committed tree. This is preferred to
2414
calling Repository.revision_tree() because that may require
2415
deserializing the inventory, while we already have a copy in
2418
return RevisionTree(self.repository, self.new_inventory,
2419
self._new_revision_id)
2421
def finish_inventory(self):
2422
"""Tell the builder that the inventory is finished."""
2423
if self.new_inventory.root is None:
2424
symbol_versioning.warn('Root entry should be supplied to'
2425
' record_entry_contents, as of bzr 0.10.',
2426
DeprecationWarning, stacklevel=2)
2427
self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
2428
self.new_inventory.revision_id = self._new_revision_id
2429
self.inv_sha1 = self.repository.add_inventory(
2430
self._new_revision_id,
2435
def _gen_revision_id(self):
2436
"""Return new revision-id."""
2437
return generate_ids.gen_revision_id(self._config.username(),
2440
def _generate_revision_if_needed(self):
2441
"""Create a revision id if None was supplied.
2443
If the repository can not support user-specified revision ids
2444
they should override this function and raise CannotSetRevisionId
2445
if _new_revision_id is not None.
2447
:raises: CannotSetRevisionId
2449
if self._new_revision_id is None:
2450
self._new_revision_id = self._gen_revision_id()
2452
def record_entry_contents(self, ie, parent_invs, path, tree):
2453
"""Record the content of ie from tree into the commit if needed.
2455
Side effect: sets ie.revision when unchanged
2457
:param ie: An inventory entry present in the commit.
2458
:param parent_invs: The inventories of the parent revisions of the
2460
:param path: The path the entry is at in the tree.
2461
:param tree: The tree which contains this entry and should be used to
2464
if self.new_inventory.root is None and ie.parent_id is not None:
2465
symbol_versioning.warn('Root entry should be supplied to'
2466
' record_entry_contents, as of bzr 0.10.',
2467
DeprecationWarning, stacklevel=2)
2468
self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
2470
self.new_inventory.add(ie)
2472
# ie.revision is always None if the InventoryEntry is considered
2473
# for committing. ie.snapshot will record the correct revision
2474
# which may be the sole parent if it is untouched.
2475
if ie.revision is not None:
2478
# In this revision format, root entries have no knit or weave
2479
if ie is self.new_inventory.root:
2480
# When serializing out to disk and back in
2481
# root.revision is always _new_revision_id
2482
ie.revision = self._new_revision_id
2484
previous_entries = ie.find_previous_heads(
2486
self.repository.weave_store,
2487
self.repository.get_transaction())
2488
# we are creating a new revision for ie in the history store
2490
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2492
def modified_directory(self, file_id, file_parents):
2493
"""Record the presence of a symbolic link.
2495
:param file_id: The file_id of the link to record.
2496
:param file_parents: The per-file parent revision ids.
2498
self._add_text_to_weave(file_id, [], file_parents.keys())
2500
def modified_file_text(self, file_id, file_parents,
2501
get_content_byte_lines, text_sha1=None,
2503
"""Record the text of file file_id
2505
:param file_id: The file_id of the file to record the text of.
2506
:param file_parents: The per-file parent revision ids.
2507
:param get_content_byte_lines: A callable which will return the byte
2509
:param text_sha1: Optional SHA1 of the file contents.
2510
:param text_size: Optional size of the file contents.
2512
# mutter('storing text of file {%s} in revision {%s} into %r',
2513
# file_id, self._new_revision_id, self.repository.weave_store)
2514
# special case to avoid diffing on renames or
2516
if (len(file_parents) == 1
2517
and text_sha1 == file_parents.values()[0].text_sha1
2518
and text_size == file_parents.values()[0].text_size):
2519
previous_ie = file_parents.values()[0]
2520
versionedfile = self.repository.weave_store.get_weave(file_id,
2521
self.repository.get_transaction())
2522
versionedfile.clone_text(self._new_revision_id,
2523
previous_ie.revision, file_parents.keys())
2524
return text_sha1, text_size
2526
new_lines = get_content_byte_lines()
2527
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2528
# should return the SHA1 and size
2529
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2530
return osutils.sha_strings(new_lines), \
2531
sum(map(len, new_lines))
2533
def modified_link(self, file_id, file_parents, link_target):
2534
"""Record the presence of a symbolic link.
2536
:param file_id: The file_id of the link to record.
2537
:param file_parents: The per-file parent revision ids.
2538
:param link_target: Target location of this link.
2540
self._add_text_to_weave(file_id, [], file_parents.keys())
2542
def _add_text_to_weave(self, file_id, new_lines, parents):
2543
versionedfile = self.repository.weave_store.get_weave_or_empty(
2544
file_id, self.repository.get_transaction())
2545
versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2546
versionedfile.clear_cache()
2549
class _CommitBuilder(CommitBuilder):
2550
"""Temporary class so old CommitBuilders are detected properly
2552
Note: CommitBuilder works whether or not root entry is recorded.
2555
record_root_entry = True
2558
class RootCommitBuilder(CommitBuilder):
2559
"""This commitbuilder actually records the root id"""
2561
record_root_entry = True
2563
def record_entry_contents(self, ie, parent_invs, path, tree):
2564
"""Record the content of ie from tree into the commit if needed.
2566
Side effect: sets ie.revision when unchanged
2568
:param ie: An inventory entry present in the commit.
2569
:param parent_invs: The inventories of the parent revisions of the
2571
:param path: The path the entry is at in the tree.
2572
:param tree: The tree which contains this entry and should be used to
2575
assert self.new_inventory.root is not None or ie.parent_id is None
2576
self.new_inventory.add(ie)
2578
# ie.revision is always None if the InventoryEntry is considered
2579
# for committing. ie.snapshot will record the correct revision
2580
# which may be the sole parent if it is untouched.
2581
if ie.revision is not None:
2584
previous_entries = ie.find_previous_heads(
2586
self.repository.weave_store,
2587
self.repository.get_transaction())
2588
# we are creating a new revision for ie in the history store
2590
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2602
def _unescaper(match, _map=_unescape_map):
2603
return _map[match.group(1)]
2609
def _unescape_xml(data):
2610
"""Unescape predefined XML entities in a string of data."""
2612
if _unescape_re is None:
2613
_unescape_re = re.compile('\&([^;]*);')
2614
return _unescape_re.sub(_unescaper, data)