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
assert inv.revision_id is None or inv.revision_id == revid, \
101
"Mismatch between inventory revision" \
102
" id and insertion revid (%r, %r)" % (inv.revision_id, revid)
103
assert inv.root is not None
104
inv_text = self.serialise_inventory(inv)
105
inv_sha1 = osutils.sha_string(inv_text)
106
inv_vf = self.control_weaves.get_weave('inventory',
107
self.get_transaction())
108
self._inventory_add_lines(inv_vf, revid, parents, osutils.split_lines(inv_text))
111
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
113
for parent in parents:
115
final_parents.append(parent)
117
inv_vf.add_lines(revid, final_parents, lines)
120
def add_revision(self, rev_id, rev, inv=None, config=None):
121
"""Add rev to the revision store as rev_id.
123
:param rev_id: the revision id to use.
124
:param rev: The revision object.
125
:param inv: The inventory for the revision. if None, it will be looked
126
up in the inventory storer
127
:param config: If None no digital signature will be created.
128
If supplied its signature_needed method will be used
129
to determine if a signature should be made.
131
if config is not None and config.signature_needed():
133
inv = self.get_inventory(rev_id)
134
plaintext = Testament(rev, inv).as_short_text()
135
self.store_revision_signature(
136
gpg.GPGStrategy(config), plaintext, rev_id)
137
if not rev_id in self.get_inventory_weave():
139
raise errors.WeaveRevisionNotPresent(rev_id,
140
self.get_inventory_weave())
142
# yes, this is not suitable for adding with ghosts.
143
self.add_inventory(rev_id, inv, rev.parent_ids)
144
self._revision_store.add_revision(rev, self.get_transaction())
147
def _all_possible_ids(self):
148
"""Return all the possible revisions that we could find."""
149
return self.get_inventory_weave().versions()
151
def all_revision_ids(self):
152
"""Returns a list of all the revision ids in the repository.
154
This is deprecated because code should generally work on the graph
155
reachable from a particular revision, and ignore any other revisions
156
that might be present. There is no direct replacement method.
158
return self._all_revision_ids()
161
def _all_revision_ids(self):
162
"""Returns a list of all the revision ids in the repository.
164
These are in as much topological order as the underlying store can
165
present: for weaves ghosts may lead to a lack of correctness until
166
the reweave updates the parents list.
168
if self._revision_store.text_store.listable():
169
return self._revision_store.all_revision_ids(self.get_transaction())
170
result = self._all_possible_ids()
171
return self._eliminate_revisions_not_present(result)
173
def break_lock(self):
174
"""Break a lock if one is present from another instance.
176
Uses the ui factory to ask for confirmation if the lock may be from
179
self.control_files.break_lock()
182
def _eliminate_revisions_not_present(self, revision_ids):
183
"""Check every revision id in revision_ids to see if we have it.
185
Returns a set of the present revisions.
188
for id in revision_ids:
189
if self.has_revision(id):
194
def create(a_bzrdir):
195
"""Construct the current default format repository in a_bzrdir."""
196
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
198
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
199
"""instantiate a Repository.
201
:param _format: The format of the repository on disk.
202
:param a_bzrdir: The BzrDir of the repository.
204
In the future we will have a single api for all stores for
205
getting file texts, inventories and revisions, then
206
this construct will accept instances of those things.
208
super(Repository, self).__init__()
209
self._format = _format
210
# the following are part of the public API for Repository:
211
self.bzrdir = a_bzrdir
212
self.control_files = control_files
213
self._revision_store = _revision_store
214
self.text_store = text_store
215
# backwards compatibility
216
self.weave_store = text_store
217
# not right yet - should be more semantically clear ?
219
self.control_store = control_store
220
self.control_weaves = control_store
221
# TODO: make sure to construct the right store classes, etc, depending
222
# on whether escaping is required.
223
self._warn_if_deprecated()
224
self._serializer = xml5.serializer_v5
227
return '%s(%r)' % (self.__class__.__name__,
228
self.bzrdir.transport.base)
231
return self.control_files.is_locked()
233
def lock_write(self):
234
self.control_files.lock_write()
237
self.control_files.lock_read()
239
def get_physical_lock_status(self):
240
return self.control_files.get_physical_lock_status()
243
def missing_revision_ids(self, other, revision_id=None):
244
"""Return the revision ids that other has that this does not.
246
These are returned in topological order.
248
revision_id: only return revision ids included by revision_id.
250
return InterRepository.get(other, self).missing_revision_ids(revision_id)
254
"""Open the repository rooted at base.
256
For instance, if the repository is at URL/.bzr/repository,
257
Repository.open(URL) -> a Repository instance.
259
control = bzrdir.BzrDir.open(base)
260
return control.open_repository()
262
def copy_content_into(self, destination, revision_id=None, basis=None):
263
"""Make a complete copy of the content in self into destination.
265
This is a destructive operation! Do not use it on existing
268
return InterRepository.get(self, destination).copy_content(revision_id, basis)
270
def fetch(self, source, revision_id=None, pb=None):
271
"""Fetch the content required to construct revision_id from source.
273
If revision_id is None all content is copied.
275
return InterRepository.get(source, self).fetch(revision_id=revision_id,
278
def get_commit_builder(self, branch, parents, config, timestamp=None,
279
timezone=None, committer=None, revprops=None,
281
"""Obtain a CommitBuilder for this repository.
283
:param branch: Branch to commit to.
284
:param parents: Revision ids of the parents of the new revision.
285
:param config: Configuration to use.
286
:param timestamp: Optional timestamp recorded for commit.
287
:param timezone: Optional timezone for timestamp.
288
:param committer: Optional committer to set for commit.
289
:param revprops: Optional dictionary of revision properties.
290
:param revision_id: Optional revision id.
292
return _CommitBuilder(self, parents, config, timestamp, timezone,
293
committer, revprops, revision_id)
296
self.control_files.unlock()
299
def clone(self, a_bzrdir, revision_id=None, basis=None):
300
"""Clone this repository into a_bzrdir using the current format.
302
Currently no check is made that the format of this repository and
303
the bzrdir format are compatible. FIXME RBC 20060201.
305
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
306
# use target default format.
307
result = a_bzrdir.create_repository()
308
# FIXME RBC 20060209 split out the repository type to avoid this check ?
309
elif isinstance(a_bzrdir._format,
310
(bzrdir.BzrDirFormat4,
311
bzrdir.BzrDirFormat5,
312
bzrdir.BzrDirFormat6)):
313
result = a_bzrdir.open_repository()
315
result = self._format.initialize(a_bzrdir, shared=self.is_shared())
316
self.copy_content_into(result, revision_id, basis)
320
def has_revision(self, revision_id):
321
"""True if this repository has a copy of the revision."""
322
return self._revision_store.has_revision_id(revision_id,
323
self.get_transaction())
326
def get_revision_reconcile(self, revision_id):
327
"""'reconcile' helper routine that allows access to a revision always.
329
This variant of get_revision does not cross check the weave graph
330
against the revision one as get_revision does: but it should only
331
be used by reconcile, or reconcile-alike commands that are correcting
332
or testing the revision graph.
334
if not revision_id or not isinstance(revision_id, basestring):
335
raise errors.InvalidRevisionId(revision_id=revision_id,
337
return self._revision_store.get_revisions([revision_id],
338
self.get_transaction())[0]
340
def get_revisions(self, revision_ids):
341
return self._revision_store.get_revisions(revision_ids,
342
self.get_transaction())
345
def get_revision_xml(self, revision_id):
346
rev = self.get_revision(revision_id)
348
# the current serializer..
349
self._revision_store._serializer.write_revision(rev, rev_tmp)
351
return rev_tmp.getvalue()
354
def get_revision(self, revision_id):
355
"""Return the Revision object for a named revision"""
356
r = self.get_revision_reconcile(revision_id)
357
# weave corruption can lead to absent revision markers that should be
359
# the following test is reasonably cheap (it needs a single weave read)
360
# and the weave is cached in read transactions. In write transactions
361
# it is not cached but typically we only read a small number of
362
# revisions. For knits when they are introduced we will probably want
363
# to ensure that caching write transactions are in use.
364
inv = self.get_inventory_weave()
365
self._check_revision_parents(r, inv)
369
def get_deltas_for_revisions(self, revisions):
370
"""Produce a generator of revision deltas.
372
Note that the input is a sequence of REVISIONS, not revision_ids.
373
Trees will be held in memory until the generator exits.
374
Each delta is relative to the revision's lefthand predecessor.
376
required_trees = set()
377
for revision in revisions:
378
required_trees.add(revision.revision_id)
379
required_trees.update(revision.parent_ids[:1])
380
trees = dict((t.get_revision_id(), t) for
381
t in self.revision_trees(required_trees))
382
for revision in revisions:
383
if not revision.parent_ids:
384
old_tree = self.revision_tree(None)
386
old_tree = trees[revision.parent_ids[0]]
387
yield trees[revision.revision_id].changes_from(old_tree)
390
def get_revision_delta(self, revision_id):
391
"""Return the delta for one revision.
393
The delta is relative to the left-hand predecessor of the
396
r = self.get_revision(revision_id)
397
return list(self.get_deltas_for_revisions([r]))[0]
399
def _check_revision_parents(self, revision, inventory):
400
"""Private to Repository and Fetch.
402
This checks the parentage of revision in an inventory weave for
403
consistency and is only applicable to inventory-weave-for-ancestry
404
using repository formats & fetchers.
406
weave_parents = inventory.get_parents(revision.revision_id)
407
weave_names = inventory.versions()
408
for parent_id in revision.parent_ids:
409
if parent_id in weave_names:
410
# this parent must not be a ghost.
411
if not parent_id in weave_parents:
413
raise errors.CorruptRepository(self)
416
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
417
signature = gpg_strategy.sign(plaintext)
418
self._revision_store.add_revision_signature_text(revision_id,
420
self.get_transaction())
422
def fileids_altered_by_revision_ids(self, revision_ids):
423
"""Find the file ids and versions affected by revisions.
425
:param revisions: an iterable containing revision ids.
426
:return: a dictionary mapping altered file-ids to an iterable of
427
revision_ids. Each altered file-ids has the exact revision_ids that
428
altered it listed explicitly.
430
assert self._serializer.support_altered_by_hack, \
431
("fileids_altered_by_revision_ids only supported for branches "
432
"which store inventory as unnested xml, not on %r" % self)
433
selected_revision_ids = set(revision_ids)
434
w = self.get_inventory_weave()
437
# this code needs to read every new line in every inventory for the
438
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
439
# not present in one of those inventories is unnecessary but not
440
# harmful because we are filtering by the revision id marker in the
441
# inventory lines : we only select file ids altered in one of those
442
unescape_revid_cache = {}
443
unescape_fileid_cache = {}
445
# revisions. We don't need to see all lines in the inventory because
446
# only those added in an inventory in rev X can contain a revision=X
448
pb = ui.ui_factory.nested_progress_bar()
450
for line in w.iter_lines_added_or_present_in_versions(
451
selected_revision_ids, pb=pb):
452
match = self._file_ids_altered_regex.search(line)
455
file_id, revision_id = match.group('file_id', 'revision_id')
456
revision_id = _unescape_xml_cached(revision_id,
457
unescape_revid_cache)
458
if revision_id in selected_revision_ids:
459
file_id = _unescape_xml_cached(file_id,
460
unescape_fileid_cache)
461
result.setdefault(file_id, set()).add(revision_id)
467
def get_inventory_weave(self):
468
return self.control_weaves.get_weave('inventory',
469
self.get_transaction())
472
def get_inventory(self, revision_id):
473
"""Get Inventory object by hash."""
474
return self.deserialise_inventory(
475
revision_id, self.get_inventory_xml(revision_id))
477
def deserialise_inventory(self, revision_id, xml):
478
"""Transform the xml into an inventory object.
480
:param revision_id: The expected revision id of the inventory.
481
:param xml: A serialised inventory.
483
result = self._serializer.read_inventory_from_string(xml)
484
result.root.revision = revision_id
487
def serialise_inventory(self, inv):
488
return self._serializer.write_inventory_to_string(inv)
491
def get_inventory_xml(self, revision_id):
492
"""Get inventory XML as a file object."""
494
assert isinstance(revision_id, basestring), type(revision_id)
495
iw = self.get_inventory_weave()
496
return iw.get_text(revision_id)
498
raise errors.HistoryMissing(self, 'inventory', revision_id)
501
def get_inventory_sha1(self, revision_id):
502
"""Return the sha1 hash of the inventory entry
504
return self.get_revision(revision_id).inventory_sha1
507
def get_revision_graph(self, revision_id=None):
508
"""Return a dictionary containing the revision graph.
510
:param revision_id: The revision_id to get a graph from. If None, then
511
the entire revision graph is returned. This is a deprecated mode of
512
operation and will be removed in the future.
513
:return: a dictionary of revision_id->revision_parents_list.
515
# special case NULL_REVISION
516
if revision_id == _mod_revision.NULL_REVISION:
518
a_weave = self.get_inventory_weave()
519
all_revisions = self._eliminate_revisions_not_present(
521
entire_graph = dict([(node, a_weave.get_parents(node)) for
522
node in all_revisions])
523
if revision_id is None:
525
elif revision_id not in entire_graph:
526
raise errors.NoSuchRevision(self, revision_id)
528
# add what can be reached from revision_id
530
pending = set([revision_id])
531
while len(pending) > 0:
533
result[node] = entire_graph[node]
534
for revision_id in result[node]:
535
if revision_id not in result:
536
pending.add(revision_id)
540
def get_revision_graph_with_ghosts(self, revision_ids=None):
541
"""Return a graph of the revisions with ghosts marked as applicable.
543
:param revision_ids: an iterable of revisions to graph or None for all.
544
:return: a Graph object with the graph reachable from revision_ids.
546
result = graph.Graph()
548
pending = set(self.all_revision_ids())
551
pending = set(revision_ids)
552
# special case NULL_REVISION
553
if _mod_revision.NULL_REVISION in pending:
554
pending.remove(_mod_revision.NULL_REVISION)
555
required = set(pending)
558
revision_id = pending.pop()
560
rev = self.get_revision(revision_id)
561
except errors.NoSuchRevision:
562
if revision_id in required:
565
result.add_ghost(revision_id)
567
for parent_id in rev.parent_ids:
568
# is this queued or done ?
569
if (parent_id not in pending and
570
parent_id not in done):
572
pending.add(parent_id)
573
result.add_node(revision_id, rev.parent_ids)
574
done.add(revision_id)
578
def get_revision_inventory(self, revision_id):
579
"""Return inventory of a past revision."""
580
# TODO: Unify this with get_inventory()
581
# bzr 0.0.6 and later imposes the constraint that the inventory_id
582
# must be the same as its revision, so this is trivial.
583
if revision_id is None:
584
# This does not make sense: if there is no revision,
585
# then it is the current tree inventory surely ?!
586
# and thus get_root_id() is something that looks at the last
587
# commit on the branch, and the get_root_id is an inventory check.
588
raise NotImplementedError
589
# return Inventory(self.get_root_id())
591
return self.get_inventory(revision_id)
595
"""Return True if this repository is flagged as a shared repository."""
596
raise NotImplementedError(self.is_shared)
599
def reconcile(self, other=None, thorough=False):
600
"""Reconcile this repository."""
601
from bzrlib.reconcile import RepoReconciler
602
reconciler = RepoReconciler(self, thorough=thorough)
603
reconciler.reconcile()
607
def revision_tree(self, revision_id):
608
"""Return Tree for a revision on this branch.
610
`revision_id` may be None for the empty tree revision.
612
# TODO: refactor this to use an existing revision object
613
# so we don't need to read it in twice.
614
if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
615
return RevisionTree(self, Inventory(root_id=None),
616
_mod_revision.NULL_REVISION)
618
inv = self.get_revision_inventory(revision_id)
619
return RevisionTree(self, inv, revision_id)
622
def revision_trees(self, revision_ids):
623
"""Return Tree for a revision on this branch.
625
`revision_id` may not be None or 'null:'"""
626
assert None not in revision_ids
627
assert _mod_revision.NULL_REVISION not in revision_ids
628
texts = self.get_inventory_weave().get_texts(revision_ids)
629
for text, revision_id in zip(texts, revision_ids):
630
inv = self.deserialise_inventory(revision_id, text)
631
yield RevisionTree(self, inv, revision_id)
634
def get_ancestry(self, revision_id):
635
"""Return a list of revision-ids integrated by a revision.
637
The first element of the list is always None, indicating the origin
638
revision. This might change when we have history horizons, or
639
perhaps we should have a new API.
641
This is topologically sorted.
643
if revision_id is None:
645
if not self.has_revision(revision_id):
646
raise errors.NoSuchRevision(self, revision_id)
647
w = self.get_inventory_weave()
648
candidates = w.get_ancestry(revision_id)
649
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
652
def print_file(self, file, revision_id):
653
"""Print `file` to stdout.
655
FIXME RBC 20060125 as John Meinel points out this is a bad api
656
- it writes to stdout, it assumes that that is valid etc. Fix
657
by creating a new more flexible convenience function.
659
tree = self.revision_tree(revision_id)
660
# use inventory as it was in that revision
661
file_id = tree.inventory.path2id(file)
663
# TODO: jam 20060427 Write a test for this code path
664
# it had a bug in it, and was raising the wrong
666
raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
667
tree.print_file(file_id)
669
def get_transaction(self):
670
return self.control_files.get_transaction()
672
def revision_parents(self, revid):
673
return self.get_inventory_weave().parent_names(revid)
676
def set_make_working_trees(self, new_value):
677
"""Set the policy flag for making working trees when creating branches.
679
This only applies to branches that use this repository.
681
The default is 'True'.
682
:param new_value: True to restore the default, False to disable making
685
raise NotImplementedError(self.set_make_working_trees)
687
def make_working_trees(self):
688
"""Returns the policy for making working trees on new branches."""
689
raise NotImplementedError(self.make_working_trees)
692
def sign_revision(self, revision_id, gpg_strategy):
693
plaintext = Testament.from_revision(self, revision_id).as_short_text()
694
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
697
def has_signature_for_revision_id(self, revision_id):
698
"""Query for a revision signature for revision_id in the repository."""
699
return self._revision_store.has_signature(revision_id,
700
self.get_transaction())
703
def get_signature_text(self, revision_id):
704
"""Return the text for a signature."""
705
return self._revision_store.get_signature_text(revision_id,
706
self.get_transaction())
709
def check(self, revision_ids):
710
"""Check consistency of all history of given revision_ids.
712
Different repository implementations should override _check().
714
:param revision_ids: A non-empty list of revision_ids whose ancestry
715
will be checked. Typically the last revision_id of a branch.
718
raise ValueError("revision_ids must be non-empty in %s.check"
720
return self._check(revision_ids)
722
def _check(self, revision_ids):
723
result = check.Check(self)
727
def _warn_if_deprecated(self):
728
global _deprecation_warning_done
729
if _deprecation_warning_done:
731
_deprecation_warning_done = True
732
warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
733
% (self._format, self.bzrdir.transport.base))
735
def supports_rich_root(self):
736
return self._format.rich_root_data
738
def _check_ascii_revisionid(self, revision_id, method):
739
"""Private helper for ascii-only repositories."""
740
# weave repositories refuse to store revisionids that are non-ascii.
741
if revision_id is not None:
742
# weaves require ascii revision ids.
743
if isinstance(revision_id, unicode):
745
revision_id.encode('ascii')
746
except UnicodeEncodeError:
747
raise errors.NonAsciiRevisionId(method, self)
750
class AllInOneRepository(Repository):
751
"""Legacy support - the repository behaviour for all-in-one branches."""
753
def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
754
# we reuse one control files instance.
755
dir_mode = a_bzrdir._control_files._dir_mode
756
file_mode = a_bzrdir._control_files._file_mode
758
def get_store(name, compressed=True, prefixed=False):
759
# FIXME: This approach of assuming stores are all entirely compressed
760
# or entirely uncompressed is tidy, but breaks upgrade from
761
# some existing branches where there's a mixture; we probably
762
# still want the option to look for both.
763
relpath = a_bzrdir._control_files._escape(name)
764
store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
765
prefixed=prefixed, compressed=compressed,
768
#if self._transport.should_cache():
769
# cache_path = os.path.join(self.cache_root, name)
770
# os.mkdir(cache_path)
771
# store = bzrlib.store.CachedStore(store, cache_path)
774
# not broken out yet because the controlweaves|inventory_store
775
# and text_store | weave_store bits are still different.
776
if isinstance(_format, RepositoryFormat4):
777
# cannot remove these - there is still no consistent api
778
# which allows access to this old info.
779
self.inventory_store = get_store('inventory-store')
780
text_store = get_store('text-store')
781
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
783
def get_commit_builder(self, branch, parents, config, timestamp=None,
784
timezone=None, committer=None, revprops=None,
786
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
787
return Repository.get_commit_builder(self, branch, parents, config,
788
timestamp, timezone, committer, revprops, revision_id)
792
"""AllInOne repositories cannot be shared."""
796
def set_make_working_trees(self, new_value):
797
"""Set the policy flag for making working trees when creating branches.
799
This only applies to branches that use this repository.
801
The default is 'True'.
802
:param new_value: True to restore the default, False to disable making
805
raise NotImplementedError(self.set_make_working_trees)
807
def make_working_trees(self):
808
"""Returns the policy for making working trees on new branches."""
812
def install_revision(repository, rev, revision_tree):
813
"""Install all revision data into a repository."""
816
for p_id in rev.parent_ids:
817
if repository.has_revision(p_id):
818
present_parents.append(p_id)
819
parent_trees[p_id] = repository.revision_tree(p_id)
821
parent_trees[p_id] = repository.revision_tree(None)
823
inv = revision_tree.inventory
824
entries = inv.iter_entries()
825
# backwards compatability hack: skip the root id.
826
if not repository.supports_rich_root():
827
path, root = entries.next()
828
if root.revision != rev.revision_id:
829
raise errors.IncompatibleRevision(repr(repository))
830
# Add the texts that are not already present
831
for path, ie in entries:
832
w = repository.weave_store.get_weave_or_empty(ie.file_id,
833
repository.get_transaction())
834
if ie.revision not in w:
836
# FIXME: TODO: The following loop *may* be overlapping/duplicate
837
# with InventoryEntry.find_previous_heads(). if it is, then there
838
# is a latent bug here where the parents may have ancestors of each
840
for revision, tree in parent_trees.iteritems():
841
if ie.file_id not in tree:
843
parent_id = tree.inventory[ie.file_id].revision
844
if parent_id in text_parents:
846
text_parents.append(parent_id)
848
vfile = repository.weave_store.get_weave_or_empty(ie.file_id,
849
repository.get_transaction())
850
lines = revision_tree.get_file(ie.file_id).readlines()
851
vfile.add_lines(rev.revision_id, text_parents, lines)
853
# install the inventory
854
repository.add_inventory(rev.revision_id, inv, present_parents)
855
except errors.RevisionAlreadyPresent:
857
repository.add_revision(rev.revision_id, rev, inv)
860
class MetaDirRepository(Repository):
861
"""Repositories in the new meta-dir layout."""
863
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
864
super(MetaDirRepository, self).__init__(_format,
870
dir_mode = self.control_files._dir_mode
871
file_mode = self.control_files._file_mode
875
"""Return True if this repository is flagged as a shared repository."""
876
return self.control_files._transport.has('shared-storage')
879
def set_make_working_trees(self, new_value):
880
"""Set the policy flag for making working trees when creating branches.
882
This only applies to branches that use this repository.
884
The default is 'True'.
885
:param new_value: True to restore the default, False to disable making
890
self.control_files._transport.delete('no-working-trees')
891
except errors.NoSuchFile:
894
self.control_files.put_utf8('no-working-trees', '')
896
def make_working_trees(self):
897
"""Returns the policy for making working trees on new branches."""
898
return not self.control_files._transport.has('no-working-trees')
901
class WeaveMetaDirRepository(MetaDirRepository):
902
"""A subclass of MetaDirRepository to set weave specific policy."""
904
def get_commit_builder(self, branch, parents, config, timestamp=None,
905
timezone=None, committer=None, revprops=None,
907
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
908
return MetaDirRepository.get_commit_builder(self, branch, parents,
909
config, timestamp, timezone, committer, revprops, revision_id)
912
class KnitRepository(MetaDirRepository):
913
"""Knit format repository."""
915
def _warn_if_deprecated(self):
916
# This class isn't deprecated
919
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
920
inv_vf.add_lines_with_ghosts(revid, parents, lines)
923
def _all_revision_ids(self):
924
"""See Repository.all_revision_ids()."""
925
# Knits get the revision graph from the index of the revision knit, so
926
# it's always possible even if they're on an unlistable transport.
927
return self._revision_store.all_revision_ids(self.get_transaction())
929
def fileid_involved_between_revs(self, from_revid, to_revid):
930
"""Find file_id(s) which are involved in the changes between revisions.
932
This determines the set of revisions which are involved, and then
933
finds all file ids affected by those revisions.
935
vf = self._get_revision_vf()
936
from_set = set(vf.get_ancestry(from_revid))
937
to_set = set(vf.get_ancestry(to_revid))
938
changed = to_set.difference(from_set)
939
return self._fileid_involved_by_set(changed)
941
def fileid_involved(self, last_revid=None):
942
"""Find all file_ids modified in the ancestry of last_revid.
944
:param last_revid: If None, last_revision() will be used.
947
changed = set(self.all_revision_ids())
949
changed = set(self.get_ancestry(last_revid))
952
return self._fileid_involved_by_set(changed)
955
def get_ancestry(self, revision_id):
956
"""Return a list of revision-ids integrated by a revision.
958
This is topologically sorted.
960
if revision_id is None:
962
vf = self._get_revision_vf()
964
return [None] + vf.get_ancestry(revision_id)
965
except errors.RevisionNotPresent:
966
raise errors.NoSuchRevision(self, revision_id)
969
def get_revision(self, revision_id):
970
"""Return the Revision object for a named revision"""
971
return self.get_revision_reconcile(revision_id)
974
def get_revision_graph(self, revision_id=None):
975
"""Return a dictionary containing the revision graph.
977
:param revision_id: The revision_id to get a graph from. If None, then
978
the entire revision graph is returned. This is a deprecated mode of
979
operation and will be removed in the future.
980
:return: a dictionary of revision_id->revision_parents_list.
982
# special case NULL_REVISION
983
if revision_id == _mod_revision.NULL_REVISION:
985
a_weave = self._get_revision_vf()
986
entire_graph = a_weave.get_graph()
987
if revision_id is None:
988
return a_weave.get_graph()
989
elif revision_id not in a_weave:
990
raise errors.NoSuchRevision(self, revision_id)
992
# add what can be reached from revision_id
994
pending = set([revision_id])
995
while len(pending) > 0:
997
result[node] = a_weave.get_parents(node)
998
for revision_id in result[node]:
999
if revision_id not in result:
1000
pending.add(revision_id)
1004
def get_revision_graph_with_ghosts(self, revision_ids=None):
1005
"""Return a graph of the revisions with ghosts marked as applicable.
1007
:param revision_ids: an iterable of revisions to graph or None for all.
1008
:return: a Graph object with the graph reachable from revision_ids.
1010
result = graph.Graph()
1011
vf = self._get_revision_vf()
1012
versions = set(vf.versions())
1013
if not revision_ids:
1014
pending = set(self.all_revision_ids())
1017
pending = set(revision_ids)
1018
# special case NULL_REVISION
1019
if _mod_revision.NULL_REVISION in pending:
1020
pending.remove(_mod_revision.NULL_REVISION)
1021
required = set(pending)
1024
revision_id = pending.pop()
1025
if not revision_id in versions:
1026
if revision_id in required:
1027
raise errors.NoSuchRevision(self, revision_id)
1029
result.add_ghost(revision_id)
1030
# mark it as done so we don't try for it again.
1031
done.add(revision_id)
1033
parent_ids = vf.get_parents_with_ghosts(revision_id)
1034
for parent_id in parent_ids:
1035
# is this queued or done ?
1036
if (parent_id not in pending and
1037
parent_id not in done):
1039
pending.add(parent_id)
1040
result.add_node(revision_id, parent_ids)
1041
done.add(revision_id)
1044
def _get_revision_vf(self):
1045
""":return: a versioned file containing the revisions."""
1046
vf = self._revision_store.get_revision_file(self.get_transaction())
1050
def reconcile(self, other=None, thorough=False):
1051
"""Reconcile this repository."""
1052
from bzrlib.reconcile import KnitReconciler
1053
reconciler = KnitReconciler(self, thorough=thorough)
1054
reconciler.reconcile()
1057
def revision_parents(self, revision_id):
1058
return self._get_revision_vf().get_parents(revision_id)
1061
class KnitRepository2(KnitRepository):
1063
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
1064
control_store, text_store):
1065
KnitRepository.__init__(self, _format, a_bzrdir, control_files,
1066
_revision_store, control_store, text_store)
1067
self._serializer = xml6.serializer_v6
1069
def deserialise_inventory(self, revision_id, xml):
1070
"""Transform the xml into an inventory object.
1072
:param revision_id: The expected revision id of the inventory.
1073
:param xml: A serialised inventory.
1075
result = self._serializer.read_inventory_from_string(xml)
1076
assert result.root.revision is not None
1079
def serialise_inventory(self, inv):
1080
"""Transform the inventory object into XML text.
1082
:param revision_id: The expected revision id of the inventory.
1083
:param xml: A serialised inventory.
1085
assert inv.revision_id is not None
1086
assert inv.root.revision is not None
1087
return KnitRepository.serialise_inventory(self, inv)
1089
def get_commit_builder(self, branch, parents, config, timestamp=None,
1090
timezone=None, committer=None, revprops=None,
1092
"""Obtain a CommitBuilder for this repository.
1094
:param branch: Branch to commit to.
1095
:param parents: Revision ids of the parents of the new revision.
1096
:param config: Configuration to use.
1097
:param timestamp: Optional timestamp recorded for commit.
1098
:param timezone: Optional timezone for timestamp.
1099
:param committer: Optional committer to set for commit.
1100
:param revprops: Optional dictionary of revision properties.
1101
:param revision_id: Optional revision id.
1103
return RootCommitBuilder(self, parents, config, timestamp, timezone,
1104
committer, revprops, revision_id)
1107
class RepositoryFormat(object):
1108
"""A repository format.
1110
Formats provide three things:
1111
* An initialization routine to construct repository data on disk.
1112
* a format string which is used when the BzrDir supports versioned
1114
* an open routine which returns a Repository instance.
1116
Formats are placed in an dict by their format string for reference
1117
during opening. These should be subclasses of RepositoryFormat
1120
Once a format is deprecated, just deprecate the initialize and open
1121
methods on the format class. Do not deprecate the object, as the
1122
object will be created every system load.
1124
Common instance attributes:
1125
_matchingbzrdir - the bzrdir format that the repository format was
1126
originally written to work with. This can be used if manually
1127
constructing a bzrdir and repository, or more commonly for test suite
1131
_default_format = None
1132
"""The default format used for new repositories."""
1135
"""The known formats."""
1138
return "<%s>" % self.__class__.__name__
1141
def find_format(klass, a_bzrdir):
1142
"""Return the format for the repository object in a_bzrdir."""
1144
transport = a_bzrdir.get_repository_transport(None)
1145
format_string = transport.get("format").read()
1146
return klass._formats[format_string]
1147
except errors.NoSuchFile:
1148
raise errors.NoRepositoryPresent(a_bzrdir)
1150
raise errors.UnknownFormatError(format=format_string)
1152
def _get_control_store(self, repo_transport, control_files):
1153
"""Return the control store for this repository."""
1154
raise NotImplementedError(self._get_control_store)
1157
def get_default_format(klass):
1158
"""Return the current default format."""
1159
return klass._default_format
1161
def get_format_string(self):
1162
"""Return the ASCII format string that identifies this format.
1164
Note that in pre format ?? repositories the format string is
1165
not permitted nor written to disk.
1167
raise NotImplementedError(self.get_format_string)
1169
def get_format_description(self):
1170
"""Return the short description for this format."""
1171
raise NotImplementedError(self.get_format_description)
1173
def _get_revision_store(self, repo_transport, control_files):
1174
"""Return the revision store object for this a_bzrdir."""
1175
raise NotImplementedError(self._get_revision_store)
1177
def _get_text_rev_store(self,
1184
"""Common logic for getting a revision store for a repository.
1186
see self._get_revision_store for the subclass-overridable method to
1187
get the store for a repository.
1189
from bzrlib.store.revision.text import TextRevisionStore
1190
dir_mode = control_files._dir_mode
1191
file_mode = control_files._file_mode
1192
text_store =TextStore(transport.clone(name),
1194
compressed=compressed,
1196
file_mode=file_mode)
1197
_revision_store = TextRevisionStore(text_store, serializer)
1198
return _revision_store
1200
def _get_versioned_file_store(self,
1205
versionedfile_class=weave.WeaveFile,
1206
versionedfile_kwargs={},
1208
weave_transport = control_files._transport.clone(name)
1209
dir_mode = control_files._dir_mode
1210
file_mode = control_files._file_mode
1211
return VersionedFileStore(weave_transport, prefixed=prefixed,
1213
file_mode=file_mode,
1214
versionedfile_class=versionedfile_class,
1215
versionedfile_kwargs=versionedfile_kwargs,
1218
def initialize(self, a_bzrdir, shared=False):
1219
"""Initialize a repository of this format in a_bzrdir.
1221
:param a_bzrdir: The bzrdir to put the new repository in it.
1222
:param shared: The repository should be initialized as a sharable one.
1224
This may raise UninitializableFormat if shared repository are not
1225
compatible the a_bzrdir.
1228
def is_supported(self):
1229
"""Is this format supported?
1231
Supported formats must be initializable and openable.
1232
Unsupported formats may not support initialization or committing or
1233
some other features depending on the reason for not being supported.
1237
def check_conversion_target(self, target_format):
1238
raise NotImplementedError(self.check_conversion_target)
1240
def open(self, a_bzrdir, _found=False):
1241
"""Return an instance of this format for the bzrdir a_bzrdir.
1243
_found is a private parameter, do not use it.
1245
raise NotImplementedError(self.open)
1248
def register_format(klass, format):
1249
klass._formats[format.get_format_string()] = format
1252
def set_default_format(klass, format):
1253
klass._default_format = format
1256
def unregister_format(klass, format):
1257
assert klass._formats[format.get_format_string()] is format
1258
del klass._formats[format.get_format_string()]
1261
class PreSplitOutRepositoryFormat(RepositoryFormat):
1262
"""Base class for the pre split out repository formats."""
1264
rich_root_data = False
1266
def initialize(self, a_bzrdir, shared=False, _internal=False):
1267
"""Create a weave repository.
1269
TODO: when creating split out bzr branch formats, move this to a common
1270
base for Format5, Format6. or something like that.
1273
raise errors.IncompatibleFormat(self, a_bzrdir._format)
1276
# always initialized when the bzrdir is.
1277
return self.open(a_bzrdir, _found=True)
1279
# Create an empty weave
1281
weavefile.write_weave_v5(weave.Weave(), sio)
1282
empty_weave = sio.getvalue()
1284
mutter('creating repository in %s.', a_bzrdir.transport.base)
1285
dirs = ['revision-store', 'weaves']
1286
files = [('inventory.weave', StringIO(empty_weave)),
1289
# FIXME: RBC 20060125 don't peek under the covers
1290
# NB: no need to escape relative paths that are url safe.
1291
control_files = lockable_files.LockableFiles(a_bzrdir.transport,
1292
'branch-lock', lockable_files.TransportLock)
1293
control_files.create_lock()
1294
control_files.lock_write()
1295
control_files._transport.mkdir_multi(dirs,
1296
mode=control_files._dir_mode)
1298
for file, content in files:
1299
control_files.put(file, content)
1301
control_files.unlock()
1302
return self.open(a_bzrdir, _found=True)
1304
def _get_control_store(self, repo_transport, control_files):
1305
"""Return the control store for this repository."""
1306
return self._get_versioned_file_store('',
1311
def _get_text_store(self, transport, control_files):
1312
"""Get a store for file texts for this format."""
1313
raise NotImplementedError(self._get_text_store)
1315
def open(self, a_bzrdir, _found=False):
1316
"""See RepositoryFormat.open()."""
1318
# we are being called directly and must probe.
1319
raise NotImplementedError
1321
repo_transport = a_bzrdir.get_repository_transport(None)
1322
control_files = a_bzrdir._control_files
1323
text_store = self._get_text_store(repo_transport, control_files)
1324
control_store = self._get_control_store(repo_transport, control_files)
1325
_revision_store = self._get_revision_store(repo_transport, control_files)
1326
return AllInOneRepository(_format=self,
1328
_revision_store=_revision_store,
1329
control_store=control_store,
1330
text_store=text_store)
1332
def check_conversion_target(self, target_format):
1336
class RepositoryFormat4(PreSplitOutRepositoryFormat):
1337
"""Bzr repository format 4.
1339
This repository format has:
1341
- TextStores for texts, inventories,revisions.
1343
This format is deprecated: it indexes texts using a text id which is
1344
removed in format 5; initialization and write support for this format
1349
super(RepositoryFormat4, self).__init__()
1350
self._matchingbzrdir = bzrdir.BzrDirFormat4()
1352
def get_format_description(self):
1353
"""See RepositoryFormat.get_format_description()."""
1354
return "Repository format 4"
1356
def initialize(self, url, shared=False, _internal=False):
1357
"""Format 4 branches cannot be created."""
1358
raise errors.UninitializableFormat(self)
1360
def is_supported(self):
1361
"""Format 4 is not supported.
1363
It is not supported because the model changed from 4 to 5 and the
1364
conversion logic is expensive - so doing it on the fly was not
1369
def _get_control_store(self, repo_transport, control_files):
1370
"""Format 4 repositories have no formal control store at this point.
1372
This will cause any control-file-needing apis to fail - this is desired.
1376
def _get_revision_store(self, repo_transport, control_files):
1377
"""See RepositoryFormat._get_revision_store()."""
1378
from bzrlib.xml4 import serializer_v4
1379
return self._get_text_rev_store(repo_transport,
1382
serializer=serializer_v4)
1384
def _get_text_store(self, transport, control_files):
1385
"""See RepositoryFormat._get_text_store()."""
1388
class RepositoryFormat5(PreSplitOutRepositoryFormat):
1389
"""Bzr control format 5.
1391
This repository format has:
1392
- weaves for file texts and inventory
1394
- TextStores for revisions and signatures.
1398
super(RepositoryFormat5, self).__init__()
1399
self._matchingbzrdir = bzrdir.BzrDirFormat5()
1401
def get_format_description(self):
1402
"""See RepositoryFormat.get_format_description()."""
1403
return "Weave repository format 5"
1405
def _get_revision_store(self, repo_transport, control_files):
1406
"""See RepositoryFormat._get_revision_store()."""
1407
"""Return the revision store object for this a_bzrdir."""
1408
return self._get_text_rev_store(repo_transport,
1413
def _get_text_store(self, transport, control_files):
1414
"""See RepositoryFormat._get_text_store()."""
1415
return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
1418
class RepositoryFormat6(PreSplitOutRepositoryFormat):
1419
"""Bzr control format 6.
1421
This repository format has:
1422
- weaves for file texts and inventory
1423
- hash subdirectory based stores.
1424
- TextStores for revisions and signatures.
1428
super(RepositoryFormat6, self).__init__()
1429
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1431
def get_format_description(self):
1432
"""See RepositoryFormat.get_format_description()."""
1433
return "Weave repository format 6"
1435
def _get_revision_store(self, repo_transport, control_files):
1436
"""See RepositoryFormat._get_revision_store()."""
1437
return self._get_text_rev_store(repo_transport,
1443
def _get_text_store(self, transport, control_files):
1444
"""See RepositoryFormat._get_text_store()."""
1445
return self._get_versioned_file_store('weaves', transport, control_files)
1448
class MetaDirRepositoryFormat(RepositoryFormat):
1449
"""Common base class for the new repositories using the metadir layout."""
1451
rich_root_data = False
1454
super(MetaDirRepositoryFormat, self).__init__()
1455
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1457
def _create_control_files(self, a_bzrdir):
1458
"""Create the required files and the initial control_files object."""
1459
# FIXME: RBC 20060125 don't peek under the covers
1460
# NB: no need to escape relative paths that are url safe.
1461
repository_transport = a_bzrdir.get_repository_transport(self)
1462
control_files = lockable_files.LockableFiles(repository_transport,
1463
'lock', lockdir.LockDir)
1464
control_files.create_lock()
1465
return control_files
1467
def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
1468
"""Upload the initial blank content."""
1469
control_files = self._create_control_files(a_bzrdir)
1470
control_files.lock_write()
1472
control_files._transport.mkdir_multi(dirs,
1473
mode=control_files._dir_mode)
1474
for file, content in files:
1475
control_files.put(file, content)
1476
for file, content in utf8_files:
1477
control_files.put_utf8(file, content)
1479
control_files.put_utf8('shared-storage', '')
1481
control_files.unlock()
1484
class RepositoryFormat7(MetaDirRepositoryFormat):
1485
"""Bzr repository 7.
1487
This repository format has:
1488
- weaves for file texts and inventory
1489
- hash subdirectory based stores.
1490
- TextStores for revisions and signatures.
1491
- a format marker of its own
1492
- an optional 'shared-storage' flag
1493
- an optional 'no-working-trees' flag
1496
def _get_control_store(self, repo_transport, control_files):
1497
"""Return the control store for this repository."""
1498
return self._get_versioned_file_store('',
1503
def get_format_string(self):
1504
"""See RepositoryFormat.get_format_string()."""
1505
return "Bazaar-NG Repository format 7"
1507
def get_format_description(self):
1508
"""See RepositoryFormat.get_format_description()."""
1509
return "Weave repository format 7"
1511
def check_conversion_target(self, target_format):
1514
def _get_revision_store(self, repo_transport, control_files):
1515
"""See RepositoryFormat._get_revision_store()."""
1516
return self._get_text_rev_store(repo_transport,
1523
def _get_text_store(self, transport, control_files):
1524
"""See RepositoryFormat._get_text_store()."""
1525
return self._get_versioned_file_store('weaves',
1529
def initialize(self, a_bzrdir, shared=False):
1530
"""Create a weave repository.
1532
:param shared: If true the repository will be initialized as a shared
1535
# Create an empty weave
1537
weavefile.write_weave_v5(weave.Weave(), sio)
1538
empty_weave = sio.getvalue()
1540
mutter('creating repository in %s.', a_bzrdir.transport.base)
1541
dirs = ['revision-store', 'weaves']
1542
files = [('inventory.weave', StringIO(empty_weave)),
1544
utf8_files = [('format', self.get_format_string())]
1546
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1547
return self.open(a_bzrdir=a_bzrdir, _found=True)
1549
def open(self, a_bzrdir, _found=False, _override_transport=None):
1550
"""See RepositoryFormat.open().
1552
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1553
repository at a slightly different url
1554
than normal. I.e. during 'upgrade'.
1557
format = RepositoryFormat.find_format(a_bzrdir)
1558
assert format.__class__ == self.__class__
1559
if _override_transport is not None:
1560
repo_transport = _override_transport
1562
repo_transport = a_bzrdir.get_repository_transport(None)
1563
control_files = lockable_files.LockableFiles(repo_transport,
1564
'lock', lockdir.LockDir)
1565
text_store = self._get_text_store(repo_transport, control_files)
1566
control_store = self._get_control_store(repo_transport, control_files)
1567
_revision_store = self._get_revision_store(repo_transport, control_files)
1568
return WeaveMetaDirRepository(_format=self,
1570
control_files=control_files,
1571
_revision_store=_revision_store,
1572
control_store=control_store,
1573
text_store=text_store)
1576
class RepositoryFormatKnit(MetaDirRepositoryFormat):
1577
"""Bzr repository knit format (generalized).
1579
This repository format has:
1580
- knits for file texts and inventory
1581
- hash subdirectory based stores.
1582
- knits for revisions and signatures
1583
- TextStores for revisions and signatures.
1584
- a format marker of its own
1585
- an optional 'shared-storage' flag
1586
- an optional 'no-working-trees' flag
1590
def _get_control_store(self, repo_transport, control_files):
1591
"""Return the control store for this repository."""
1592
return VersionedFileStore(
1595
file_mode=control_files._file_mode,
1596
versionedfile_class=knit.KnitVersionedFile,
1597
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
1600
def _get_revision_store(self, repo_transport, control_files):
1601
"""See RepositoryFormat._get_revision_store()."""
1602
from bzrlib.store.revision.knit import KnitRevisionStore
1603
versioned_file_store = VersionedFileStore(
1605
file_mode=control_files._file_mode,
1608
versionedfile_class=knit.KnitVersionedFile,
1609
versionedfile_kwargs={'delta':False,
1610
'factory':knit.KnitPlainFactory(),
1614
return KnitRevisionStore(versioned_file_store)
1616
def _get_text_store(self, transport, control_files):
1617
"""See RepositoryFormat._get_text_store()."""
1618
return self._get_versioned_file_store('knits',
1621
versionedfile_class=knit.KnitVersionedFile,
1622
versionedfile_kwargs={
1623
'create_parent_dir':True,
1624
'delay_create':True,
1625
'dir_mode':control_files._dir_mode,
1629
def initialize(self, a_bzrdir, shared=False):
1630
"""Create a knit format 1 repository.
1632
:param a_bzrdir: bzrdir to contain the new repository; must already
1634
:param shared: If true the repository will be initialized as a shared
1637
mutter('creating repository in %s.', a_bzrdir.transport.base)
1638
dirs = ['revision-store', 'knits']
1640
utf8_files = [('format', self.get_format_string())]
1642
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1643
repo_transport = a_bzrdir.get_repository_transport(None)
1644
control_files = lockable_files.LockableFiles(repo_transport,
1645
'lock', lockdir.LockDir)
1646
control_store = self._get_control_store(repo_transport, control_files)
1647
transaction = transactions.WriteTransaction()
1648
# trigger a write of the inventory store.
1649
control_store.get_weave_or_empty('inventory', transaction)
1650
_revision_store = self._get_revision_store(repo_transport, control_files)
1651
# the revision id here is irrelevant: it will not be stored, and cannot
1653
_revision_store.has_revision_id('A', transaction)
1654
_revision_store.get_signature_file(transaction)
1655
return self.open(a_bzrdir=a_bzrdir, _found=True)
1657
def open(self, a_bzrdir, _found=False, _override_transport=None):
1658
"""See RepositoryFormat.open().
1660
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1661
repository at a slightly different url
1662
than normal. I.e. during 'upgrade'.
1665
format = RepositoryFormat.find_format(a_bzrdir)
1666
assert format.__class__ == self.__class__
1667
if _override_transport is not None:
1668
repo_transport = _override_transport
1670
repo_transport = a_bzrdir.get_repository_transport(None)
1671
control_files = lockable_files.LockableFiles(repo_transport,
1672
'lock', lockdir.LockDir)
1673
text_store = self._get_text_store(repo_transport, control_files)
1674
control_store = self._get_control_store(repo_transport, control_files)
1675
_revision_store = self._get_revision_store(repo_transport, control_files)
1676
return KnitRepository(_format=self,
1678
control_files=control_files,
1679
_revision_store=_revision_store,
1680
control_store=control_store,
1681
text_store=text_store)
1684
class RepositoryFormatKnit1(RepositoryFormatKnit):
1685
"""Bzr repository knit format 1.
1687
This repository format has:
1688
- knits for file texts and inventory
1689
- hash subdirectory based stores.
1690
- knits for revisions and signatures
1691
- TextStores for revisions and signatures.
1692
- a format marker of its own
1693
- an optional 'shared-storage' flag
1694
- an optional 'no-working-trees' flag
1697
This format was introduced in bzr 0.8.
1699
def get_format_string(self):
1700
"""See RepositoryFormat.get_format_string()."""
1701
return "Bazaar-NG Knit Repository Format 1"
1703
def get_format_description(self):
1704
"""See RepositoryFormat.get_format_description()."""
1705
return "Knit repository format 1"
1707
def check_conversion_target(self, target_format):
1711
class RepositoryFormatKnit2(RepositoryFormatKnit):
1712
"""Bzr repository knit format 2.
1714
THIS FORMAT IS EXPERIMENTAL
1715
This repository format has:
1716
- knits for file texts and inventory
1717
- hash subdirectory based stores.
1718
- knits for revisions and signatures
1719
- TextStores for revisions and signatures.
1720
- a format marker of its own
1721
- an optional 'shared-storage' flag
1722
- an optional 'no-working-trees' flag
1724
- Support for recording full info about the tree root
1728
rich_root_data = True
1730
def get_format_string(self):
1731
"""See RepositoryFormat.get_format_string()."""
1732
return "Bazaar Knit Repository Format 2\n"
1734
def get_format_description(self):
1735
"""See RepositoryFormat.get_format_description()."""
1736
return "Knit repository format 2"
1738
def check_conversion_target(self, target_format):
1739
if not target_format.rich_root_data:
1740
raise errors.BadConversionTarget(
1741
'Does not support rich root data.', target_format)
1743
def open(self, a_bzrdir, _found=False, _override_transport=None):
1744
"""See RepositoryFormat.open().
1746
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1747
repository at a slightly different url
1748
than normal. I.e. during 'upgrade'.
1751
format = RepositoryFormat.find_format(a_bzrdir)
1752
assert format.__class__ == self.__class__
1753
if _override_transport is not None:
1754
repo_transport = _override_transport
1756
repo_transport = a_bzrdir.get_repository_transport(None)
1757
control_files = lockable_files.LockableFiles(repo_transport, 'lock',
1759
text_store = self._get_text_store(repo_transport, control_files)
1760
control_store = self._get_control_store(repo_transport, control_files)
1761
_revision_store = self._get_revision_store(repo_transport, control_files)
1762
return KnitRepository2(_format=self,
1764
control_files=control_files,
1765
_revision_store=_revision_store,
1766
control_store=control_store,
1767
text_store=text_store)
1771
# formats which have no format string are not discoverable
1772
# and not independently creatable, so are not registered.
1773
RepositoryFormat.register_format(RepositoryFormat7())
1774
_default_format = RepositoryFormatKnit1()
1775
RepositoryFormat.register_format(_default_format)
1776
RepositoryFormat.register_format(RepositoryFormatKnit2())
1777
RepositoryFormat.set_default_format(_default_format)
1778
_legacy_formats = [RepositoryFormat4(),
1779
RepositoryFormat5(),
1780
RepositoryFormat6()]
1783
class InterRepository(InterObject):
1784
"""This class represents operations taking place between two repositories.
1786
Its instances have methods like copy_content and fetch, and contain
1787
references to the source and target repositories these operations can be
1790
Often we will provide convenience methods on 'repository' which carry out
1791
operations with another repository - they will always forward to
1792
InterRepository.get(other).method_name(parameters).
1796
"""The available optimised InterRepository types."""
1798
def copy_content(self, revision_id=None, basis=None):
1799
raise NotImplementedError(self.copy_content)
1801
def fetch(self, revision_id=None, pb=None):
1802
"""Fetch the content required to construct revision_id.
1804
The content is copied from self.source to self.target.
1806
:param revision_id: if None all content is copied, if NULL_REVISION no
1808
:param pb: optional progress bar to use for progress reports. If not
1809
provided a default one will be created.
1811
Returns the copied revision count and the failed revisions in a tuple:
1814
raise NotImplementedError(self.fetch)
1817
def missing_revision_ids(self, revision_id=None):
1818
"""Return the revision ids that source has that target does not.
1820
These are returned in topological order.
1822
:param revision_id: only return revision ids included by this
1825
# generic, possibly worst case, slow code path.
1826
target_ids = set(self.target.all_revision_ids())
1827
if revision_id is not None:
1828
source_ids = self.source.get_ancestry(revision_id)
1829
assert source_ids[0] is None
1832
source_ids = self.source.all_revision_ids()
1833
result_set = set(source_ids).difference(target_ids)
1834
# this may look like a no-op: its not. It preserves the ordering
1835
# other_ids had while only returning the members from other_ids
1836
# that we've decided we need.
1837
return [rev_id for rev_id in source_ids if rev_id in result_set]
1840
class InterSameDataRepository(InterRepository):
1841
"""Code for converting between repositories that represent the same data.
1843
Data format and model must match for this to work.
1846
_matching_repo_format = RepositoryFormat4()
1847
"""Repository format for testing with."""
1850
def is_compatible(source, target):
1851
if not isinstance(source, Repository):
1853
if not isinstance(target, Repository):
1855
if source._format.rich_root_data == target._format.rich_root_data:
1861
def copy_content(self, revision_id=None, basis=None):
1862
"""Make a complete copy of the content in self into destination.
1864
This is a destructive operation! Do not use it on existing
1867
:param revision_id: Only copy the content needed to construct
1868
revision_id and its parents.
1869
:param basis: Copy the needed data preferentially from basis.
1872
self.target.set_make_working_trees(self.source.make_working_trees())
1873
except NotImplementedError:
1875
# grab the basis available data
1876
if basis is not None:
1877
self.target.fetch(basis, revision_id=revision_id)
1878
# but don't bother fetching if we have the needed data now.
1879
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1880
self.target.has_revision(revision_id)):
1882
self.target.fetch(self.source, revision_id=revision_id)
1885
def fetch(self, revision_id=None, pb=None):
1886
"""See InterRepository.fetch()."""
1887
from bzrlib.fetch import GenericRepoFetcher
1888
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1889
self.source, self.source._format, self.target,
1890
self.target._format)
1891
f = GenericRepoFetcher(to_repository=self.target,
1892
from_repository=self.source,
1893
last_revision=revision_id,
1895
return f.count_copied, f.failed_revisions
1898
class InterWeaveRepo(InterSameDataRepository):
1899
"""Optimised code paths between Weave based repositories."""
1901
_matching_repo_format = RepositoryFormat7()
1902
"""Repository format for testing with."""
1905
def is_compatible(source, target):
1906
"""Be compatible with known Weave formats.
1908
We don't test for the stores being of specific types because that
1909
could lead to confusing results, and there is no need to be
1913
return (isinstance(source._format, (RepositoryFormat5,
1915
RepositoryFormat7)) and
1916
isinstance(target._format, (RepositoryFormat5,
1918
RepositoryFormat7)))
1919
except AttributeError:
1923
def copy_content(self, revision_id=None, basis=None):
1924
"""See InterRepository.copy_content()."""
1925
# weave specific optimised path:
1926
if basis is not None:
1927
# copy the basis in, then fetch remaining data.
1928
basis.copy_content_into(self.target, revision_id)
1929
# the basis copy_content_into could miss-set this.
1931
self.target.set_make_working_trees(self.source.make_working_trees())
1932
except NotImplementedError:
1934
self.target.fetch(self.source, revision_id=revision_id)
1937
self.target.set_make_working_trees(self.source.make_working_trees())
1938
except NotImplementedError:
1940
# FIXME do not peek!
1941
if self.source.control_files._transport.listable():
1942
pb = ui.ui_factory.nested_progress_bar()
1944
self.target.weave_store.copy_all_ids(
1945
self.source.weave_store,
1947
from_transaction=self.source.get_transaction(),
1948
to_transaction=self.target.get_transaction())
1949
pb.update('copying inventory', 0, 1)
1950
self.target.control_weaves.copy_multi(
1951
self.source.control_weaves, ['inventory'],
1952
from_transaction=self.source.get_transaction(),
1953
to_transaction=self.target.get_transaction())
1954
self.target._revision_store.text_store.copy_all_ids(
1955
self.source._revision_store.text_store,
1960
self.target.fetch(self.source, revision_id=revision_id)
1963
def fetch(self, revision_id=None, pb=None):
1964
"""See InterRepository.fetch()."""
1965
from bzrlib.fetch import GenericRepoFetcher
1966
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1967
self.source, self.source._format, self.target, self.target._format)
1968
f = GenericRepoFetcher(to_repository=self.target,
1969
from_repository=self.source,
1970
last_revision=revision_id,
1972
return f.count_copied, f.failed_revisions
1975
def missing_revision_ids(self, revision_id=None):
1976
"""See InterRepository.missing_revision_ids()."""
1977
# we want all revisions to satisfy revision_id in source.
1978
# but we don't want to stat every file here and there.
1979
# we want then, all revisions other needs to satisfy revision_id
1980
# checked, but not those that we have locally.
1981
# so the first thing is to get a subset of the revisions to
1982
# satisfy revision_id in source, and then eliminate those that
1983
# we do already have.
1984
# this is slow on high latency connection to self, but as as this
1985
# disk format scales terribly for push anyway due to rewriting
1986
# inventory.weave, this is considered acceptable.
1988
if revision_id is not None:
1989
source_ids = self.source.get_ancestry(revision_id)
1990
assert source_ids[0] is None
1993
source_ids = self.source._all_possible_ids()
1994
source_ids_set = set(source_ids)
1995
# source_ids is the worst possible case we may need to pull.
1996
# now we want to filter source_ids against what we actually
1997
# have in target, but don't try to check for existence where we know
1998
# we do not have a revision as that would be pointless.
1999
target_ids = set(self.target._all_possible_ids())
2000
possibly_present_revisions = target_ids.intersection(source_ids_set)
2001
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
2002
required_revisions = source_ids_set.difference(actually_present_revisions)
2003
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
2004
if revision_id is not None:
2005
# we used get_ancestry to determine source_ids then we are assured all
2006
# revisions referenced are present as they are installed in topological order.
2007
# and the tip revision was validated by get_ancestry.
2008
return required_topo_revisions
2010
# if we just grabbed the possibly available ids, then
2011
# we only have an estimate of whats available and need to validate
2012
# that against the revision records.
2013
return self.source._eliminate_revisions_not_present(required_topo_revisions)
2016
class InterKnitRepo(InterSameDataRepository):
2017
"""Optimised code paths between Knit based repositories."""
2019
_matching_repo_format = RepositoryFormatKnit1()
2020
"""Repository format for testing with."""
2023
def is_compatible(source, target):
2024
"""Be compatible with known Knit formats.
2026
We don't test for the stores being of specific types because that
2027
could lead to confusing results, and there is no need to be
2031
return (isinstance(source._format, (RepositoryFormatKnit1)) and
2032
isinstance(target._format, (RepositoryFormatKnit1)))
2033
except AttributeError:
2037
def fetch(self, revision_id=None, pb=None):
2038
"""See InterRepository.fetch()."""
2039
from bzrlib.fetch import KnitRepoFetcher
2040
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2041
self.source, self.source._format, self.target, self.target._format)
2042
f = KnitRepoFetcher(to_repository=self.target,
2043
from_repository=self.source,
2044
last_revision=revision_id,
2046
return f.count_copied, f.failed_revisions
2049
def missing_revision_ids(self, revision_id=None):
2050
"""See InterRepository.missing_revision_ids()."""
2051
if revision_id is not None:
2052
source_ids = self.source.get_ancestry(revision_id)
2053
assert source_ids[0] is None
2056
source_ids = self.source._all_possible_ids()
2057
source_ids_set = set(source_ids)
2058
# source_ids is the worst possible case we may need to pull.
2059
# now we want to filter source_ids against what we actually
2060
# have in target, but don't try to check for existence where we know
2061
# we do not have a revision as that would be pointless.
2062
target_ids = set(self.target._all_possible_ids())
2063
possibly_present_revisions = target_ids.intersection(source_ids_set)
2064
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
2065
required_revisions = source_ids_set.difference(actually_present_revisions)
2066
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
2067
if revision_id is not None:
2068
# we used get_ancestry to determine source_ids then we are assured all
2069
# revisions referenced are present as they are installed in topological order.
2070
# and the tip revision was validated by get_ancestry.
2071
return required_topo_revisions
2073
# if we just grabbed the possibly available ids, then
2074
# we only have an estimate of whats available and need to validate
2075
# that against the revision records.
2076
return self.source._eliminate_revisions_not_present(required_topo_revisions)
2079
class InterModel1and2(InterRepository):
2081
_matching_repo_format = None
2084
def is_compatible(source, target):
2085
if not isinstance(source, Repository):
2087
if not isinstance(target, Repository):
2089
if not source._format.rich_root_data and target._format.rich_root_data:
2095
def fetch(self, revision_id=None, pb=None):
2096
"""See InterRepository.fetch()."""
2097
from bzrlib.fetch import Model1toKnit2Fetcher
2098
f = Model1toKnit2Fetcher(to_repository=self.target,
2099
from_repository=self.source,
2100
last_revision=revision_id,
2102
return f.count_copied, f.failed_revisions
2105
def copy_content(self, revision_id=None, basis=None):
2106
"""Make a complete copy of the content in self into destination.
2108
This is a destructive operation! Do not use it on existing
2111
:param revision_id: Only copy the content needed to construct
2112
revision_id and its parents.
2113
:param basis: Copy the needed data preferentially from basis.
2116
self.target.set_make_working_trees(self.source.make_working_trees())
2117
except NotImplementedError:
2119
# grab the basis available data
2120
if basis is not None:
2121
self.target.fetch(basis, revision_id=revision_id)
2122
# but don't bother fetching if we have the needed data now.
2123
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
2124
self.target.has_revision(revision_id)):
2126
self.target.fetch(self.source, revision_id=revision_id)
2129
class InterKnit1and2(InterKnitRepo):
2131
_matching_repo_format = None
2134
def is_compatible(source, target):
2135
"""Be compatible with Knit1 source and Knit2 target"""
2137
return (isinstance(source._format, (RepositoryFormatKnit1)) and
2138
isinstance(target._format, (RepositoryFormatKnit2)))
2139
except AttributeError:
2143
def fetch(self, revision_id=None, pb=None):
2144
"""See InterRepository.fetch()."""
2145
from bzrlib.fetch import Knit1to2Fetcher
2146
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2147
self.source, self.source._format, self.target,
2148
self.target._format)
2149
f = Knit1to2Fetcher(to_repository=self.target,
2150
from_repository=self.source,
2151
last_revision=revision_id,
2153
return f.count_copied, f.failed_revisions
2156
InterRepository.register_optimiser(InterSameDataRepository)
2157
InterRepository.register_optimiser(InterWeaveRepo)
2158
InterRepository.register_optimiser(InterKnitRepo)
2159
InterRepository.register_optimiser(InterModel1and2)
2160
InterRepository.register_optimiser(InterKnit1and2)
2163
class RepositoryTestProviderAdapter(object):
2164
"""A tool to generate a suite testing multiple repository formats at once.
2166
This is done by copying the test once for each transport and injecting
2167
the transport_server, transport_readonly_server, and bzrdir_format and
2168
repository_format classes into each copy. Each copy is also given a new id()
2169
to make it easy to identify.
2172
def __init__(self, transport_server, transport_readonly_server, formats):
2173
self._transport_server = transport_server
2174
self._transport_readonly_server = transport_readonly_server
2175
self._formats = formats
2177
def adapt(self, test):
2178
result = unittest.TestSuite()
2179
for repository_format, bzrdir_format in self._formats:
2180
new_test = deepcopy(test)
2181
new_test.transport_server = self._transport_server
2182
new_test.transport_readonly_server = self._transport_readonly_server
2183
new_test.bzrdir_format = bzrdir_format
2184
new_test.repository_format = repository_format
2185
def make_new_test_id():
2186
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
2187
return lambda: new_id
2188
new_test.id = make_new_test_id()
2189
result.addTest(new_test)
2193
class InterRepositoryTestProviderAdapter(object):
2194
"""A tool to generate a suite testing multiple inter repository formats.
2196
This is done by copying the test once for each interrepo provider and injecting
2197
the transport_server, transport_readonly_server, repository_format and
2198
repository_to_format classes into each copy.
2199
Each copy is also given a new id() to make it easy to identify.
2202
def __init__(self, transport_server, transport_readonly_server, formats):
2203
self._transport_server = transport_server
2204
self._transport_readonly_server = transport_readonly_server
2205
self._formats = formats
2207
def adapt(self, test):
2208
result = unittest.TestSuite()
2209
for interrepo_class, repository_format, repository_format_to in self._formats:
2210
new_test = deepcopy(test)
2211
new_test.transport_server = self._transport_server
2212
new_test.transport_readonly_server = self._transport_readonly_server
2213
new_test.interrepo_class = interrepo_class
2214
new_test.repository_format = repository_format
2215
new_test.repository_format_to = repository_format_to
2216
def make_new_test_id():
2217
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
2218
return lambda: new_id
2219
new_test.id = make_new_test_id()
2220
result.addTest(new_test)
2224
def default_test_list():
2225
"""Generate the default list of interrepo permutations to test."""
2227
# test the default InterRepository between format 6 and the current
2229
# XXX: robertc 20060220 reinstate this when there are two supported
2230
# formats which do not have an optimal code path between them.
2231
#result.append((InterRepository,
2232
# RepositoryFormat6(),
2233
# RepositoryFormatKnit1()))
2234
for optimiser in InterRepository._optimisers:
2235
if optimiser._matching_repo_format is not None:
2236
result.append((optimiser,
2237
optimiser._matching_repo_format,
2238
optimiser._matching_repo_format
2240
# if there are specific combinations we want to use, we can add them
2242
result.append((InterModel1and2, RepositoryFormat5(),
2243
RepositoryFormatKnit2()))
2244
result.append((InterKnit1and2, RepositoryFormatKnit1(),
2245
RepositoryFormatKnit2()))
2249
class CopyConverter(object):
2250
"""A repository conversion tool which just performs a copy of the content.
2252
This is slow but quite reliable.
2255
def __init__(self, target_format):
2256
"""Create a CopyConverter.
2258
:param target_format: The format the resulting repository should be.
2260
self.target_format = target_format
2262
def convert(self, repo, pb):
2263
"""Perform the conversion of to_convert, giving feedback via pb.
2265
:param to_convert: The disk object to convert.
2266
:param pb: a progress bar to use for progress information.
2271
# this is only useful with metadir layouts - separated repo content.
2272
# trigger an assertion if not such
2273
repo._format.get_format_string()
2274
self.repo_dir = repo.bzrdir
2275
self.step('Moving repository to repository.backup')
2276
self.repo_dir.transport.move('repository', 'repository.backup')
2277
backup_transport = self.repo_dir.transport.clone('repository.backup')
2278
repo._format.check_conversion_target(self.target_format)
2279
self.source_repo = repo._format.open(self.repo_dir,
2281
_override_transport=backup_transport)
2282
self.step('Creating new repository')
2283
converted = self.target_format.initialize(self.repo_dir,
2284
self.source_repo.is_shared())
2285
converted.lock_write()
2287
self.step('Copying content into repository.')
2288
self.source_repo.copy_content_into(converted)
2291
self.step('Deleting old repository content.')
2292
self.repo_dir.transport.delete_tree('repository.backup')
2293
self.pb.note('repository converted')
2295
def step(self, message):
2296
"""Update the pb by a step."""
2298
self.pb.update(message, self.count, self.total)
2301
class CommitBuilder(object):
2302
"""Provides an interface to build up a commit.
2304
This allows describing a tree to be committed without needing to
2305
know the internals of the format of the repository.
2308
record_root_entry = False
2309
def __init__(self, repository, parents, config, timestamp=None,
2310
timezone=None, committer=None, revprops=None,
2312
"""Initiate a CommitBuilder.
2314
:param repository: Repository to commit to.
2315
:param parents: Revision ids of the parents of the new revision.
2316
:param config: Configuration to use.
2317
:param timestamp: Optional timestamp recorded for commit.
2318
:param timezone: Optional timezone for timestamp.
2319
:param committer: Optional committer to set for commit.
2320
:param revprops: Optional dictionary of revision properties.
2321
:param revision_id: Optional revision id.
2323
self._config = config
2325
if committer is None:
2326
self._committer = self._config.username()
2328
assert isinstance(committer, basestring), type(committer)
2329
self._committer = committer
2331
self.new_inventory = Inventory(None)
2332
self._new_revision_id = revision_id
2333
self.parents = parents
2334
self.repository = repository
2337
if revprops is not None:
2338
self._revprops.update(revprops)
2340
if timestamp is None:
2341
timestamp = time.time()
2342
# Restrict resolution to 1ms
2343
self._timestamp = round(timestamp, 3)
2345
if timezone is None:
2346
self._timezone = local_time_offset()
2348
self._timezone = int(timezone)
2350
self._generate_revision_if_needed()
2352
def commit(self, message):
2353
"""Make the actual commit.
2355
:return: The revision id of the recorded revision.
2357
rev = _mod_revision.Revision(
2358
timestamp=self._timestamp,
2359
timezone=self._timezone,
2360
committer=self._committer,
2362
inventory_sha1=self.inv_sha1,
2363
revision_id=self._new_revision_id,
2364
properties=self._revprops)
2365
rev.parent_ids = self.parents
2366
self.repository.add_revision(self._new_revision_id, rev,
2367
self.new_inventory, self._config)
2368
return self._new_revision_id
2370
def revision_tree(self):
2371
"""Return the tree that was just committed.
2373
After calling commit() this can be called to get a RevisionTree
2374
representing the newly committed tree. This is preferred to
2375
calling Repository.revision_tree() because that may require
2376
deserializing the inventory, while we already have a copy in
2379
return RevisionTree(self.repository, self.new_inventory,
2380
self._new_revision_id)
2382
def finish_inventory(self):
2383
"""Tell the builder that the inventory is finished."""
2384
if self.new_inventory.root is None:
2385
symbol_versioning.warn('Root entry should be supplied to'
2386
' record_entry_contents, as of bzr 0.10.',
2387
DeprecationWarning, stacklevel=2)
2388
self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
2389
self.new_inventory.revision_id = self._new_revision_id
2390
self.inv_sha1 = self.repository.add_inventory(
2391
self._new_revision_id,
2396
def _gen_revision_id(self):
2397
"""Return new revision-id."""
2398
return generate_ids.gen_revision_id(self._config.username(),
2401
def _generate_revision_if_needed(self):
2402
"""Create a revision id if None was supplied.
2404
If the repository can not support user-specified revision ids
2405
they should override this function and raise CannotSetRevisionId
2406
if _new_revision_id is not None.
2408
:raises: CannotSetRevisionId
2410
if self._new_revision_id is None:
2411
self._new_revision_id = self._gen_revision_id()
2413
def record_entry_contents(self, ie, parent_invs, path, tree):
2414
"""Record the content of ie from tree into the commit if needed.
2416
Side effect: sets ie.revision when unchanged
2418
:param ie: An inventory entry present in the commit.
2419
:param parent_invs: The inventories of the parent revisions of the
2421
:param path: The path the entry is at in the tree.
2422
:param tree: The tree which contains this entry and should be used to
2425
if self.new_inventory.root is None and ie.parent_id is not None:
2426
symbol_versioning.warn('Root entry should be supplied to'
2427
' record_entry_contents, as of bzr 0.10.',
2428
DeprecationWarning, stacklevel=2)
2429
self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
2431
self.new_inventory.add(ie)
2433
# ie.revision is always None if the InventoryEntry is considered
2434
# for committing. ie.snapshot will record the correct revision
2435
# which may be the sole parent if it is untouched.
2436
if ie.revision is not None:
2439
# In this revision format, root entries have no knit or weave
2440
if ie is self.new_inventory.root:
2441
# When serializing out to disk and back in
2442
# root.revision is always _new_revision_id
2443
ie.revision = self._new_revision_id
2445
previous_entries = ie.find_previous_heads(
2447
self.repository.weave_store,
2448
self.repository.get_transaction())
2449
# we are creating a new revision for ie in the history store
2451
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2453
def modified_directory(self, file_id, file_parents):
2454
"""Record the presence of a symbolic link.
2456
:param file_id: The file_id of the link to record.
2457
:param file_parents: The per-file parent revision ids.
2459
self._add_text_to_weave(file_id, [], file_parents.keys())
2461
def modified_file_text(self, file_id, file_parents,
2462
get_content_byte_lines, text_sha1=None,
2464
"""Record the text of file file_id
2466
:param file_id: The file_id of the file to record the text of.
2467
:param file_parents: The per-file parent revision ids.
2468
:param get_content_byte_lines: A callable which will return the byte
2470
:param text_sha1: Optional SHA1 of the file contents.
2471
:param text_size: Optional size of the file contents.
2473
# mutter('storing text of file {%s} in revision {%s} into %r',
2474
# file_id, self._new_revision_id, self.repository.weave_store)
2475
# special case to avoid diffing on renames or
2477
if (len(file_parents) == 1
2478
and text_sha1 == file_parents.values()[0].text_sha1
2479
and text_size == file_parents.values()[0].text_size):
2480
previous_ie = file_parents.values()[0]
2481
versionedfile = self.repository.weave_store.get_weave(file_id,
2482
self.repository.get_transaction())
2483
versionedfile.clone_text(self._new_revision_id,
2484
previous_ie.revision, file_parents.keys())
2485
return text_sha1, text_size
2487
new_lines = get_content_byte_lines()
2488
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2489
# should return the SHA1 and size
2490
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2491
return osutils.sha_strings(new_lines), \
2492
sum(map(len, new_lines))
2494
def modified_link(self, file_id, file_parents, link_target):
2495
"""Record the presence of a symbolic link.
2497
:param file_id: The file_id of the link to record.
2498
:param file_parents: The per-file parent revision ids.
2499
:param link_target: Target location of this link.
2501
self._add_text_to_weave(file_id, [], file_parents.keys())
2503
def _add_text_to_weave(self, file_id, new_lines, parents):
2504
versionedfile = self.repository.weave_store.get_weave_or_empty(
2505
file_id, self.repository.get_transaction())
2506
versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2507
versionedfile.clear_cache()
2510
class _CommitBuilder(CommitBuilder):
2511
"""Temporary class so old CommitBuilders are detected properly
2513
Note: CommitBuilder works whether or not root entry is recorded.
2516
record_root_entry = True
2519
class RootCommitBuilder(CommitBuilder):
2520
"""This commitbuilder actually records the root id"""
2522
record_root_entry = True
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
assert self.new_inventory.root is not None or ie.parent_id is None
2537
self.new_inventory.add(ie)
2539
# ie.revision is always None if the InventoryEntry is considered
2540
# for committing. ie.snapshot will record the correct revision
2541
# which may be the sole parent if it is untouched.
2542
if ie.revision is not None:
2545
previous_entries = ie.find_previous_heads(
2547
self.repository.weave_store,
2548
self.repository.get_transaction())
2549
# we are creating a new revision for ie in the history store
2551
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2563
def _unescaper(match, _map=_unescape_map):
2564
return _map[match.group(1)]
2570
def _unescape_xml(data):
2571
"""Unescape predefined XML entities in a string of data."""
2573
if _unescape_re is None:
2574
_unescape_re = re.compile('\&([^;]*);')
2575
return _unescape_re.sub(_unescaper, data)
2578
def _unescape_xml_cached(data, cache):
2582
unescaped = _unescape_xml(data)
2583
cache[data] = unescaped