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 binascii import hexlify
18
from copy import deepcopy
19
from cStringIO import StringIO
22
from unittest import TestSuite
24
from bzrlib import bzrdir, check, delta, gpg, errors, xml5, ui, transactions, osutils
25
from bzrlib.decorators import needs_read_lock, needs_write_lock
26
from bzrlib.errors import InvalidRevisionId
27
from bzrlib.graph import Graph
28
from bzrlib.inter import InterObject
29
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
30
from bzrlib.knit import KnitVersionedFile, KnitPlainFactory
31
from bzrlib.lockable_files import LockableFiles, TransportLock
32
from bzrlib.lockdir import LockDir
33
from bzrlib.osutils import (safe_unicode, rand_bytes, compact_date,
35
from bzrlib.revision import NULL_REVISION, Revision
36
from bzrlib.revisiontree import RevisionTree
37
from bzrlib.store.versioned import VersionedFileStore, WeaveStore
38
from bzrlib.store.text import TextStore
39
from bzrlib import symbol_versioning
40
from bzrlib.symbol_versioning import (deprecated_method,
43
from bzrlib.testament import Testament
44
from bzrlib.trace import mutter, note, warning
45
from bzrlib.tsort import topo_sort
46
from bzrlib.weave import WeaveFile
49
# Old formats display a warning, but only once
50
_deprecation_warning_done = False
53
class Repository(object):
54
"""Repository holding history for one or more branches.
56
The repository holds and retrieves historical information including
57
revisions and file history. It's normally accessed only by the Branch,
58
which views a particular line of development through that history.
60
The Repository builds on top of Stores and a Transport, which respectively
61
describe the disk data format and the way of accessing the (possibly
66
def add_inventory(self, revid, inv, parents):
67
"""Add the inventory inv to the repository as revid.
69
:param parents: The revision ids of the parents that revid
70
is known to have and are in the repository already.
72
returns the sha1 of the serialized inventory.
74
assert inv.revision_id is None or inv.revision_id == revid, \
75
"Mismatch between inventory revision" \
76
" id and insertion revid (%r, %r)" % (inv.revision_id, revid)
77
assert inv.root is not None
78
inv_text = xml5.serializer_v5.write_inventory_to_string(inv)
79
inv_sha1 = osutils.sha_string(inv_text)
80
inv_vf = self.control_weaves.get_weave('inventory',
81
self.get_transaction())
82
self._inventory_add_lines(inv_vf, revid, parents, osutils.split_lines(inv_text))
85
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
87
for parent in parents:
89
final_parents.append(parent)
91
inv_vf.add_lines(revid, final_parents, lines)
94
def add_revision(self, rev_id, rev, inv=None, config=None):
95
"""Add rev to the revision store as rev_id.
97
:param rev_id: the revision id to use.
98
:param rev: The revision object.
99
:param inv: The inventory for the revision. if None, it will be looked
100
up in the inventory storer
101
:param config: If None no digital signature will be created.
102
If supplied its signature_needed method will be used
103
to determine if a signature should be made.
105
if config is not None and config.signature_needed():
107
inv = self.get_inventory(rev_id)
108
plaintext = Testament(rev, inv).as_short_text()
109
self.store_revision_signature(
110
gpg.GPGStrategy(config), plaintext, rev_id)
111
if not rev_id in self.get_inventory_weave():
113
raise errors.WeaveRevisionNotPresent(rev_id,
114
self.get_inventory_weave())
116
# yes, this is not suitable for adding with ghosts.
117
self.add_inventory(rev_id, inv, rev.parent_ids)
118
self._revision_store.add_revision(rev, self.get_transaction())
121
def _all_possible_ids(self):
122
"""Return all the possible revisions that we could find."""
123
return self.get_inventory_weave().versions()
125
def all_revision_ids(self):
126
"""Returns a list of all the revision ids in the repository.
128
This is deprecated because code should generally work on the graph
129
reachable from a particular revision, and ignore any other revisions
130
that might be present. There is no direct replacement method.
132
return self._all_revision_ids()
135
def _all_revision_ids(self):
136
"""Returns a list of all the revision ids in the repository.
138
These are in as much topological order as the underlying store can
139
present: for weaves ghosts may lead to a lack of correctness until
140
the reweave updates the parents list.
142
if self._revision_store.text_store.listable():
143
return self._revision_store.all_revision_ids(self.get_transaction())
144
result = self._all_possible_ids()
145
return self._eliminate_revisions_not_present(result)
147
def break_lock(self):
148
"""Break a lock if one is present from another instance.
150
Uses the ui factory to ask for confirmation if the lock may be from
153
self.control_files.break_lock()
156
def _eliminate_revisions_not_present(self, revision_ids):
157
"""Check every revision id in revision_ids to see if we have it.
159
Returns a set of the present revisions.
162
for id in revision_ids:
163
if self.has_revision(id):
168
def create(a_bzrdir):
169
"""Construct the current default format repository in a_bzrdir."""
170
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
172
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
173
"""instantiate a Repository.
175
:param _format: The format of the repository on disk.
176
:param a_bzrdir: The BzrDir of the repository.
178
In the future we will have a single api for all stores for
179
getting file texts, inventories and revisions, then
180
this construct will accept instances of those things.
182
super(Repository, self).__init__()
183
self._format = _format
184
# the following are part of the public API for Repository:
185
self.bzrdir = a_bzrdir
186
self.control_files = control_files
187
self._revision_store = _revision_store
188
self.text_store = text_store
189
# backwards compatibility
190
self.weave_store = text_store
191
# not right yet - should be more semantically clear ?
193
self.control_store = control_store
194
self.control_weaves = control_store
195
# TODO: make sure to construct the right store classes, etc, depending
196
# on whether escaping is required.
197
self._warn_if_deprecated()
200
return '%s(%r)' % (self.__class__.__name__,
201
self.bzrdir.transport.base)
204
return self.control_files.is_locked()
206
def lock_write(self):
207
self.control_files.lock_write()
210
self.control_files.lock_read()
212
def get_physical_lock_status(self):
213
return self.control_files.get_physical_lock_status()
216
def missing_revision_ids(self, other, revision_id=None):
217
"""Return the revision ids that other has that this does not.
219
These are returned in topological order.
221
revision_id: only return revision ids included by revision_id.
223
return InterRepository.get(other, self).missing_revision_ids(revision_id)
227
"""Open the repository rooted at base.
229
For instance, if the repository is at URL/.bzr/repository,
230
Repository.open(URL) -> a Repository instance.
232
control = bzrdir.BzrDir.open(base)
233
return control.open_repository()
235
def copy_content_into(self, destination, revision_id=None, basis=None):
236
"""Make a complete copy of the content in self into destination.
238
This is a destructive operation! Do not use it on existing
241
return InterRepository.get(self, destination).copy_content(revision_id, basis)
243
def fetch(self, source, revision_id=None, pb=None):
244
"""Fetch the content required to construct revision_id from source.
246
If revision_id is None all content is copied.
248
return InterRepository.get(source, self).fetch(revision_id=revision_id,
251
def get_commit_builder(self, branch, parents, config, timestamp=None,
252
timezone=None, committer=None, revprops=None,
254
"""Obtain a CommitBuilder for this repository.
256
:param branch: Branch to commit to.
257
:param parents: Revision ids of the parents of the new revision.
258
:param config: Configuration to use.
259
:param timestamp: Optional timestamp recorded for commit.
260
:param timezone: Optional timezone for timestamp.
261
:param committer: Optional committer to set for commit.
262
:param revprops: Optional dictionary of revision properties.
263
:param revision_id: Optional revision id.
265
return _CommitBuilder(self, parents, config, timestamp, timezone,
266
committer, revprops, revision_id)
269
self.control_files.unlock()
272
def clone(self, a_bzrdir, revision_id=None, basis=None):
273
"""Clone this repository into a_bzrdir using the current format.
275
Currently no check is made that the format of this repository and
276
the bzrdir format are compatible. FIXME RBC 20060201.
278
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
279
# use target default format.
280
result = a_bzrdir.create_repository()
281
# FIXME RBC 20060209 split out the repository type to avoid this check ?
282
elif isinstance(a_bzrdir._format,
283
(bzrdir.BzrDirFormat4,
284
bzrdir.BzrDirFormat5,
285
bzrdir.BzrDirFormat6)):
286
result = a_bzrdir.open_repository()
288
result = self._format.initialize(a_bzrdir, shared=self.is_shared())
289
self.copy_content_into(result, revision_id, basis)
293
def has_revision(self, revision_id):
294
"""True if this repository has a copy of the revision."""
295
return self._revision_store.has_revision_id(revision_id,
296
self.get_transaction())
299
def get_revision_reconcile(self, revision_id):
300
"""'reconcile' helper routine that allows access to a revision always.
302
This variant of get_revision does not cross check the weave graph
303
against the revision one as get_revision does: but it should only
304
be used by reconcile, or reconcile-alike commands that are correcting
305
or testing the revision graph.
307
if not revision_id or not isinstance(revision_id, basestring):
308
raise InvalidRevisionId(revision_id=revision_id, branch=self)
309
return self._revision_store.get_revisions([revision_id],
310
self.get_transaction())[0]
312
def get_revisions(self, revision_ids):
313
return self._revision_store.get_revisions(revision_ids,
314
self.get_transaction())
317
def get_revision_xml(self, revision_id):
318
rev = self.get_revision(revision_id)
320
# the current serializer..
321
self._revision_store._serializer.write_revision(rev, rev_tmp)
323
return rev_tmp.getvalue()
326
def get_revision(self, revision_id):
327
"""Return the Revision object for a named revision"""
328
r = self.get_revision_reconcile(revision_id)
329
# weave corruption can lead to absent revision markers that should be
331
# the following test is reasonably cheap (it needs a single weave read)
332
# and the weave is cached in read transactions. In write transactions
333
# it is not cached but typically we only read a small number of
334
# revisions. For knits when they are introduced we will probably want
335
# to ensure that caching write transactions are in use.
336
inv = self.get_inventory_weave()
337
self._check_revision_parents(r, inv)
341
def get_deltas_for_revisions(self, revisions):
342
"""Produce a generator of revision deltas.
344
Note that the input is a sequence of REVISIONS, not revision_ids.
345
Trees will be held in memory until the generator exits.
346
Each delta is relative to the revision's lefthand predecessor.
348
required_trees = set()
349
for revision in revisions:
350
required_trees.add(revision.revision_id)
351
required_trees.update(revision.parent_ids[:1])
352
trees = dict((t.get_revision_id(), t) for
353
t in self.revision_trees(required_trees))
354
for revision in revisions:
355
if not revision.parent_ids:
356
old_tree = self.revision_tree(None)
358
old_tree = trees[revision.parent_ids[0]]
359
yield trees[revision.revision_id].changes_from(old_tree)
362
def get_revision_delta(self, revision_id):
363
"""Return the delta for one revision.
365
The delta is relative to the left-hand predecessor of the
368
r = self.get_revision(revision_id)
369
return list(self.get_deltas_for_revisions([r]))[0]
371
def _check_revision_parents(self, revision, inventory):
372
"""Private to Repository and Fetch.
374
This checks the parentage of revision in an inventory weave for
375
consistency and is only applicable to inventory-weave-for-ancestry
376
using repository formats & fetchers.
378
weave_parents = inventory.get_parents(revision.revision_id)
379
weave_names = inventory.versions()
380
for parent_id in revision.parent_ids:
381
if parent_id in weave_names:
382
# this parent must not be a ghost.
383
if not parent_id in weave_parents:
385
raise errors.CorruptRepository(self)
388
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
389
signature = gpg_strategy.sign(plaintext)
390
self._revision_store.add_revision_signature_text(revision_id,
392
self.get_transaction())
394
def fileids_altered_by_revision_ids(self, revision_ids):
395
"""Find the file ids and versions affected by revisions.
397
:param revisions: an iterable containing revision ids.
398
:return: a dictionary mapping altered file-ids to an iterable of
399
revision_ids. Each altered file-ids has the exact revision_ids that
400
altered it listed explicitly.
402
assert self._format.has_unnested_inventory(), \
403
("fileids_altered_by_revision_ids only supported for branches "
404
"which store inventory as unnested xml, not on %r" % self)
405
selected_revision_ids = set(revision_ids)
406
w = self.get_inventory_weave()
409
# this code needs to read every new line in every inventory for the
410
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
411
# not present in one of those inventories is unnecessary but not
412
# harmful because we are filtering by the revision id marker in the
413
# inventory lines : we only select file ids altered in one of those
414
# revisions. We don't need to see all lines in the inventory because
415
# only those added in an inventory in rev X can contain a revision=X
417
for line in w.iter_lines_added_or_present_in_versions(selected_revision_ids):
418
start = line.find('file_id="')+9
419
if start < 9: continue
420
end = line.find('"', start)
422
file_id = _unescape_xml(line[start:end])
424
start = line.find('revision="')+10
425
if start < 10: continue
426
end = line.find('"', start)
428
revision_id = _unescape_xml(line[start:end])
429
if revision_id in selected_revision_ids:
430
result.setdefault(file_id, set()).add(revision_id)
434
def get_inventory_weave(self):
435
return self.control_weaves.get_weave('inventory',
436
self.get_transaction())
439
def get_inventory(self, revision_id):
440
"""Get Inventory object by hash."""
441
return self.deserialise_inventory(
442
revision_id, self.get_inventory_xml(revision_id))
444
def deserialise_inventory(self, revision_id, xml):
445
"""Transform the xml into an inventory object.
447
:param revision_id: The expected revision id of the inventory.
448
:param xml: A serialised inventory.
450
result = xml5.serializer_v5.read_inventory_from_string(xml)
451
result.root.revision = revision_id
455
def get_inventory_xml(self, revision_id):
456
"""Get inventory XML as a file object."""
458
assert isinstance(revision_id, basestring), type(revision_id)
459
iw = self.get_inventory_weave()
460
return iw.get_text(revision_id)
462
raise errors.HistoryMissing(self, 'inventory', revision_id)
465
def get_inventory_sha1(self, revision_id):
466
"""Return the sha1 hash of the inventory entry
468
return self.get_revision(revision_id).inventory_sha1
471
def get_revision_graph(self, revision_id=None):
472
"""Return a dictionary containing the revision graph.
474
:param revision_id: The revision_id to get a graph from. If None, then
475
the entire revision graph is returned. This is a deprecated mode of
476
operation and will be removed in the future.
477
:return: a dictionary of revision_id->revision_parents_list.
479
# special case NULL_REVISION
480
if revision_id == NULL_REVISION:
482
weave = self.get_inventory_weave()
483
all_revisions = self._eliminate_revisions_not_present(weave.versions())
484
entire_graph = dict([(node, weave.get_parents(node)) for
485
node in all_revisions])
486
if revision_id is None:
488
elif revision_id not in entire_graph:
489
raise errors.NoSuchRevision(self, revision_id)
491
# add what can be reached from revision_id
493
pending = set([revision_id])
494
while len(pending) > 0:
496
result[node] = entire_graph[node]
497
for revision_id in result[node]:
498
if revision_id not in result:
499
pending.add(revision_id)
503
def get_revision_graph_with_ghosts(self, revision_ids=None):
504
"""Return a graph of the revisions with ghosts marked as applicable.
506
:param revision_ids: an iterable of revisions to graph or None for all.
507
:return: a Graph object with the graph reachable from revision_ids.
511
pending = set(self.all_revision_ids())
514
pending = set(revision_ids)
515
# special case NULL_REVISION
516
if NULL_REVISION in pending:
517
pending.remove(NULL_REVISION)
518
required = set(pending)
521
revision_id = pending.pop()
523
rev = self.get_revision(revision_id)
524
except errors.NoSuchRevision:
525
if revision_id in required:
528
result.add_ghost(revision_id)
530
for parent_id in rev.parent_ids:
531
# is this queued or done ?
532
if (parent_id not in pending and
533
parent_id not in done):
535
pending.add(parent_id)
536
result.add_node(revision_id, rev.parent_ids)
537
done.add(revision_id)
541
def get_revision_inventory(self, revision_id):
542
"""Return inventory of a past revision."""
543
# TODO: Unify this with get_inventory()
544
# bzr 0.0.6 and later imposes the constraint that the inventory_id
545
# must be the same as its revision, so this is trivial.
546
if revision_id is None:
547
# This does not make sense: if there is no revision,
548
# then it is the current tree inventory surely ?!
549
# and thus get_root_id() is something that looks at the last
550
# commit on the branch, and the get_root_id is an inventory check.
551
raise NotImplementedError
552
# return Inventory(self.get_root_id())
554
return self.get_inventory(revision_id)
558
"""Return True if this repository is flagged as a shared repository."""
559
raise NotImplementedError(self.is_shared)
562
def reconcile(self, other=None, thorough=False):
563
"""Reconcile this repository."""
564
from bzrlib.reconcile import RepoReconciler
565
reconciler = RepoReconciler(self, thorough=thorough)
566
reconciler.reconcile()
570
def revision_tree(self, revision_id):
571
"""Return Tree for a revision on this branch.
573
`revision_id` may be None for the empty tree revision.
575
# TODO: refactor this to use an existing revision object
576
# so we don't need to read it in twice.
577
if revision_id is None or revision_id == NULL_REVISION:
578
return RevisionTree(self, Inventory(), NULL_REVISION)
580
inv = self.get_revision_inventory(revision_id)
581
return RevisionTree(self, inv, revision_id)
584
def revision_trees(self, revision_ids):
585
"""Return Tree for a revision on this branch.
587
`revision_id` may not be None or 'null:'"""
588
assert None not in revision_ids
589
assert NULL_REVISION not in revision_ids
590
texts = self.get_inventory_weave().get_texts(revision_ids)
591
for text, revision_id in zip(texts, revision_ids):
592
inv = self.deserialise_inventory(revision_id, text)
593
yield RevisionTree(self, inv, revision_id)
596
def get_ancestry(self, revision_id):
597
"""Return a list of revision-ids integrated by a revision.
599
The first element of the list is always None, indicating the origin
600
revision. This might change when we have history horizons, or
601
perhaps we should have a new API.
603
This is topologically sorted.
605
if revision_id is None:
607
if not self.has_revision(revision_id):
608
raise errors.NoSuchRevision(self, revision_id)
609
w = self.get_inventory_weave()
610
candidates = w.get_ancestry(revision_id)
611
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
614
def print_file(self, file, revision_id):
615
"""Print `file` to stdout.
617
FIXME RBC 20060125 as John Meinel points out this is a bad api
618
- it writes to stdout, it assumes that that is valid etc. Fix
619
by creating a new more flexible convenience function.
621
tree = self.revision_tree(revision_id)
622
# use inventory as it was in that revision
623
file_id = tree.inventory.path2id(file)
625
# TODO: jam 20060427 Write a test for this code path
626
# it had a bug in it, and was raising the wrong
628
raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
629
tree.print_file(file_id)
631
def get_transaction(self):
632
return self.control_files.get_transaction()
634
def revision_parents(self, revid):
635
return self.get_inventory_weave().parent_names(revid)
638
def set_make_working_trees(self, new_value):
639
"""Set the policy flag for making working trees when creating branches.
641
This only applies to branches that use this repository.
643
The default is 'True'.
644
:param new_value: True to restore the default, False to disable making
647
raise NotImplementedError(self.set_make_working_trees)
649
def make_working_trees(self):
650
"""Returns the policy for making working trees on new branches."""
651
raise NotImplementedError(self.make_working_trees)
654
def sign_revision(self, revision_id, gpg_strategy):
655
plaintext = Testament.from_revision(self, revision_id).as_short_text()
656
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
659
def has_signature_for_revision_id(self, revision_id):
660
"""Query for a revision signature for revision_id in the repository."""
661
return self._revision_store.has_signature(revision_id,
662
self.get_transaction())
665
def get_signature_text(self, revision_id):
666
"""Return the text for a signature."""
667
return self._revision_store.get_signature_text(revision_id,
668
self.get_transaction())
671
def check(self, revision_ids):
672
"""Check consistency of all history of given revision_ids.
674
Different repository implementations should override _check().
676
:param revision_ids: A non-empty list of revision_ids whose ancestry
677
will be checked. Typically the last revision_id of a branch.
680
raise ValueError("revision_ids must be non-empty in %s.check"
682
return self._check(revision_ids)
684
def _check(self, revision_ids):
685
result = check.Check(self)
689
def _warn_if_deprecated(self):
690
global _deprecation_warning_done
691
if _deprecation_warning_done:
693
_deprecation_warning_done = True
694
warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
695
% (self._format, self.bzrdir.transport.base))
698
class AllInOneRepository(Repository):
699
"""Legacy support - the repository behaviour for all-in-one branches."""
701
def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
702
# we reuse one control files instance.
703
dir_mode = a_bzrdir._control_files._dir_mode
704
file_mode = a_bzrdir._control_files._file_mode
706
def get_store(name, compressed=True, prefixed=False):
707
# FIXME: This approach of assuming stores are all entirely compressed
708
# or entirely uncompressed is tidy, but breaks upgrade from
709
# some existing branches where there's a mixture; we probably
710
# still want the option to look for both.
711
relpath = a_bzrdir._control_files._escape(name)
712
store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
713
prefixed=prefixed, compressed=compressed,
716
#if self._transport.should_cache():
717
# cache_path = os.path.join(self.cache_root, name)
718
# os.mkdir(cache_path)
719
# store = bzrlib.store.CachedStore(store, cache_path)
722
# not broken out yet because the controlweaves|inventory_store
723
# and text_store | weave_store bits are still different.
724
if isinstance(_format, RepositoryFormat4):
725
# cannot remove these - there is still no consistent api
726
# which allows access to this old info.
727
self.inventory_store = get_store('inventory-store')
728
text_store = get_store('text-store')
729
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
733
"""AllInOne repositories cannot be shared."""
737
def set_make_working_trees(self, new_value):
738
"""Set the policy flag for making working trees when creating branches.
740
This only applies to branches that use this repository.
742
The default is 'True'.
743
:param new_value: True to restore the default, False to disable making
746
raise NotImplementedError(self.set_make_working_trees)
748
def make_working_trees(self):
749
"""Returns the policy for making working trees on new branches."""
753
def install_revision(repository, rev, revision_tree):
754
"""Install all revision data into a repository."""
757
for p_id in rev.parent_ids:
758
if repository.has_revision(p_id):
759
present_parents.append(p_id)
760
parent_trees[p_id] = repository.revision_tree(p_id)
762
parent_trees[p_id] = repository.revision_tree(None)
764
inv = revision_tree.inventory
766
# backwards compatability hack: skip the root id.
767
entries = inv.iter_entries()
769
# Add the texts that are not already present
770
for path, ie in entries:
771
w = repository.weave_store.get_weave_or_empty(ie.file_id,
772
repository.get_transaction())
773
if ie.revision not in w:
775
# FIXME: TODO: The following loop *may* be overlapping/duplicate
776
# with InventoryEntry.find_previous_heads(). if it is, then there
777
# is a latent bug here where the parents may have ancestors of each
779
for revision, tree in parent_trees.iteritems():
780
if ie.file_id not in tree:
782
parent_id = tree.inventory[ie.file_id].revision
783
if parent_id in text_parents:
785
text_parents.append(parent_id)
787
vfile = repository.weave_store.get_weave_or_empty(ie.file_id,
788
repository.get_transaction())
789
lines = revision_tree.get_file(ie.file_id).readlines()
790
vfile.add_lines(rev.revision_id, text_parents, lines)
792
# install the inventory
793
repository.add_inventory(rev.revision_id, inv, present_parents)
794
except errors.RevisionAlreadyPresent:
796
repository.add_revision(rev.revision_id, rev, inv)
799
class MetaDirRepository(Repository):
800
"""Repositories in the new meta-dir layout."""
802
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
803
super(MetaDirRepository, self).__init__(_format,
809
dir_mode = self.control_files._dir_mode
810
file_mode = self.control_files._file_mode
814
"""Return True if this repository is flagged as a shared repository."""
815
return self.control_files._transport.has('shared-storage')
818
def set_make_working_trees(self, new_value):
819
"""Set the policy flag for making working trees when creating branches.
821
This only applies to branches that use this repository.
823
The default is 'True'.
824
:param new_value: True to restore the default, False to disable making
829
self.control_files._transport.delete('no-working-trees')
830
except errors.NoSuchFile:
833
self.control_files.put_utf8('no-working-trees', '')
835
def make_working_trees(self):
836
"""Returns the policy for making working trees on new branches."""
837
return not self.control_files._transport.has('no-working-trees')
840
class KnitRepository(MetaDirRepository):
841
"""Knit format repository."""
843
def _warn_if_deprecated(self):
844
# This class isn't deprecated
847
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
848
inv_vf.add_lines_with_ghosts(revid, parents, lines)
851
def _all_revision_ids(self):
852
"""See Repository.all_revision_ids()."""
853
# Knits get the revision graph from the index of the revision knit, so
854
# it's always possible even if they're on an unlistable transport.
855
return self._revision_store.all_revision_ids(self.get_transaction())
857
def fileid_involved_between_revs(self, from_revid, to_revid):
858
"""Find file_id(s) which are involved in the changes between revisions.
860
This determines the set of revisions which are involved, and then
861
finds all file ids affected by those revisions.
863
vf = self._get_revision_vf()
864
from_set = set(vf.get_ancestry(from_revid))
865
to_set = set(vf.get_ancestry(to_revid))
866
changed = to_set.difference(from_set)
867
return self._fileid_involved_by_set(changed)
869
def fileid_involved(self, last_revid=None):
870
"""Find all file_ids modified in the ancestry of last_revid.
872
:param last_revid: If None, last_revision() will be used.
875
changed = set(self.all_revision_ids())
877
changed = set(self.get_ancestry(last_revid))
880
return self._fileid_involved_by_set(changed)
883
def get_ancestry(self, revision_id):
884
"""Return a list of revision-ids integrated by a revision.
886
This is topologically sorted.
888
if revision_id is None:
890
vf = self._get_revision_vf()
892
return [None] + vf.get_ancestry(revision_id)
893
except errors.RevisionNotPresent:
894
raise errors.NoSuchRevision(self, revision_id)
897
def get_revision(self, revision_id):
898
"""Return the Revision object for a named revision"""
899
return self.get_revision_reconcile(revision_id)
902
def get_revision_graph(self, revision_id=None):
903
"""Return a dictionary containing the revision graph.
905
:param revision_id: The revision_id to get a graph from. If None, then
906
the entire revision graph is returned. This is a deprecated mode of
907
operation and will be removed in the future.
908
:return: a dictionary of revision_id->revision_parents_list.
910
# special case NULL_REVISION
911
if revision_id == NULL_REVISION:
913
weave = self._get_revision_vf()
914
entire_graph = weave.get_graph()
915
if revision_id is None:
916
return weave.get_graph()
917
elif revision_id not in weave:
918
raise errors.NoSuchRevision(self, revision_id)
920
# add what can be reached from revision_id
922
pending = set([revision_id])
923
while len(pending) > 0:
925
result[node] = weave.get_parents(node)
926
for revision_id in result[node]:
927
if revision_id not in result:
928
pending.add(revision_id)
932
def get_revision_graph_with_ghosts(self, revision_ids=None):
933
"""Return a graph of the revisions with ghosts marked as applicable.
935
:param revision_ids: an iterable of revisions to graph or None for all.
936
:return: a Graph object with the graph reachable from revision_ids.
939
vf = self._get_revision_vf()
940
versions = set(vf.versions())
942
pending = set(self.all_revision_ids())
945
pending = set(revision_ids)
946
# special case NULL_REVISION
947
if NULL_REVISION in pending:
948
pending.remove(NULL_REVISION)
949
required = set(pending)
952
revision_id = pending.pop()
953
if not revision_id in versions:
954
if revision_id in required:
955
raise errors.NoSuchRevision(self, revision_id)
957
result.add_ghost(revision_id)
958
# mark it as done so we don't try for it again.
959
done.add(revision_id)
961
parent_ids = vf.get_parents_with_ghosts(revision_id)
962
for parent_id in parent_ids:
963
# is this queued or done ?
964
if (parent_id not in pending and
965
parent_id not in done):
967
pending.add(parent_id)
968
result.add_node(revision_id, parent_ids)
969
done.add(revision_id)
972
def _get_revision_vf(self):
973
""":return: a versioned file containing the revisions."""
974
vf = self._revision_store.get_revision_file(self.get_transaction())
978
def reconcile(self, other=None, thorough=False):
979
"""Reconcile this repository."""
980
from bzrlib.reconcile import KnitReconciler
981
reconciler = KnitReconciler(self, thorough=thorough)
982
reconciler.reconcile()
985
def revision_parents(self, revision_id):
986
return self._get_revision_vf().get_parents(revision_id)
989
class RepositoryFormat(object):
990
"""A repository format.
992
Formats provide three things:
993
* An initialization routine to construct repository data on disk.
994
* a format string which is used when the BzrDir supports versioned
996
* an open routine which returns a Repository instance.
998
Formats are placed in an dict by their format string for reference
999
during opening. These should be subclasses of RepositoryFormat
1002
Once a format is deprecated, just deprecate the initialize and open
1003
methods on the format class. Do not deprecate the object, as the
1004
object will be created every system load.
1006
Common instance attributes:
1007
_matchingbzrdir - the bzrdir format that the repository format was
1008
originally written to work with. This can be used if manually
1009
constructing a bzrdir and repository, or more commonly for test suite
1013
_default_format = None
1014
"""The default format used for new repositories."""
1017
"""The known formats."""
1020
return "<%s>" % self.__class__.__name__
1023
def find_format(klass, a_bzrdir):
1024
"""Return the format for the repository object in a_bzrdir."""
1026
transport = a_bzrdir.get_repository_transport(None)
1027
format_string = transport.get("format").read()
1028
return klass._formats[format_string]
1029
except errors.NoSuchFile:
1030
raise errors.NoRepositoryPresent(a_bzrdir)
1032
raise errors.UnknownFormatError(format=format_string)
1034
def _get_control_store(self, repo_transport, control_files):
1035
"""Return the control store for this repository."""
1036
raise NotImplementedError(self._get_control_store)
1039
def get_default_format(klass):
1040
"""Return the current default format."""
1041
return klass._default_format
1043
def get_format_string(self):
1044
"""Return the ASCII format string that identifies this format.
1046
Note that in pre format ?? repositories the format string is
1047
not permitted nor written to disk.
1049
raise NotImplementedError(self.get_format_string)
1051
def get_format_description(self):
1052
"""Return the short description for this format."""
1053
raise NotImplementedError(self.get_format_description)
1055
def _get_revision_store(self, repo_transport, control_files):
1056
"""Return the revision store object for this a_bzrdir."""
1057
raise NotImplementedError(self._get_revision_store)
1059
def _get_text_rev_store(self,
1066
"""Common logic for getting a revision store for a repository.
1068
see self._get_revision_store for the subclass-overridable method to
1069
get the store for a repository.
1071
from bzrlib.store.revision.text import TextRevisionStore
1072
dir_mode = control_files._dir_mode
1073
file_mode = control_files._file_mode
1074
text_store =TextStore(transport.clone(name),
1076
compressed=compressed,
1078
file_mode=file_mode)
1079
_revision_store = TextRevisionStore(text_store, serializer)
1080
return _revision_store
1082
def _get_versioned_file_store(self,
1087
versionedfile_class=WeaveFile,
1089
weave_transport = control_files._transport.clone(name)
1090
dir_mode = control_files._dir_mode
1091
file_mode = control_files._file_mode
1092
return VersionedFileStore(weave_transport, prefixed=prefixed,
1094
file_mode=file_mode,
1095
versionedfile_class=versionedfile_class,
1098
def initialize(self, a_bzrdir, shared=False):
1099
"""Initialize a repository of this format in a_bzrdir.
1101
:param a_bzrdir: The bzrdir to put the new repository in it.
1102
:param shared: The repository should be initialized as a sharable one.
1104
This may raise UninitializableFormat if shared repository are not
1105
compatible the a_bzrdir.
1108
def is_supported(self):
1109
"""Is this format supported?
1111
Supported formats must be initializable and openable.
1112
Unsupported formats may not support initialization or committing or
1113
some other features depending on the reason for not being supported.
1117
def has_unnested_inventory(self):
1118
raise NotImplementedError(self.has_unnested_inventory)
1120
def check_conversion_target(self, target_format):
1121
raise NotImplementedError(self.check_conversion_target)
1123
def open(self, a_bzrdir, _found=False):
1124
"""Return an instance of this format for the bzrdir a_bzrdir.
1126
_found is a private parameter, do not use it.
1128
raise NotImplementedError(self.open)
1131
def register_format(klass, format):
1132
klass._formats[format.get_format_string()] = format
1135
def set_default_format(klass, format):
1136
klass._default_format = format
1139
def unregister_format(klass, format):
1140
assert klass._formats[format.get_format_string()] is format
1141
del klass._formats[format.get_format_string()]
1144
class PreSplitOutRepositoryFormat(RepositoryFormat):
1145
"""Base class for the pre split out repository formats."""
1147
def initialize(self, a_bzrdir, shared=False, _internal=False):
1148
"""Create a weave repository.
1150
TODO: when creating split out bzr branch formats, move this to a common
1151
base for Format5, Format6. or something like that.
1153
from bzrlib.weavefile import write_weave_v5
1154
from bzrlib.weave import Weave
1157
raise errors.IncompatibleFormat(self, a_bzrdir._format)
1160
# always initialized when the bzrdir is.
1161
return self.open(a_bzrdir, _found=True)
1163
# Create an empty weave
1165
write_weave_v5(Weave(), sio)
1166
empty_weave = sio.getvalue()
1168
mutter('creating repository in %s.', a_bzrdir.transport.base)
1169
dirs = ['revision-store', 'weaves']
1170
files = [('inventory.weave', StringIO(empty_weave)),
1173
# FIXME: RBC 20060125 don't peek under the covers
1174
# NB: no need to escape relative paths that are url safe.
1175
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock',
1177
control_files.create_lock()
1178
control_files.lock_write()
1179
control_files._transport.mkdir_multi(dirs,
1180
mode=control_files._dir_mode)
1182
for file, content in files:
1183
control_files.put(file, content)
1185
control_files.unlock()
1186
return self.open(a_bzrdir, _found=True)
1188
def _get_control_store(self, repo_transport, control_files):
1189
"""Return the control store for this repository."""
1190
return self._get_versioned_file_store('',
1195
def _get_text_store(self, transport, control_files):
1196
"""Get a store for file texts for this format."""
1197
raise NotImplementedError(self._get_text_store)
1199
def open(self, a_bzrdir, _found=False):
1200
"""See RepositoryFormat.open()."""
1202
# we are being called directly and must probe.
1203
raise NotImplementedError
1205
repo_transport = a_bzrdir.get_repository_transport(None)
1206
control_files = a_bzrdir._control_files
1207
text_store = self._get_text_store(repo_transport, control_files)
1208
control_store = self._get_control_store(repo_transport, control_files)
1209
_revision_store = self._get_revision_store(repo_transport, control_files)
1210
return AllInOneRepository(_format=self,
1212
_revision_store=_revision_store,
1213
control_store=control_store,
1214
text_store=text_store)
1216
def check_conversion_target(self, target_format):
1220
class RepositoryFormat4(PreSplitOutRepositoryFormat):
1221
"""Bzr repository format 4.
1223
This repository format has:
1225
- TextStores for texts, inventories,revisions.
1227
This format is deprecated: it indexes texts using a text id which is
1228
removed in format 5; initialization and write support for this format
1233
super(RepositoryFormat4, self).__init__()
1234
self._matchingbzrdir = bzrdir.BzrDirFormat4()
1236
def get_format_description(self):
1237
"""See RepositoryFormat.get_format_description()."""
1238
return "Repository format 4"
1240
def has_unnested_inventory(self):
1243
def initialize(self, url, shared=False, _internal=False):
1244
"""Format 4 branches cannot be created."""
1245
raise errors.UninitializableFormat(self)
1247
def is_supported(self):
1248
"""Format 4 is not supported.
1250
It is not supported because the model changed from 4 to 5 and the
1251
conversion logic is expensive - so doing it on the fly was not
1256
def _get_control_store(self, repo_transport, control_files):
1257
"""Format 4 repositories have no formal control store at this point.
1259
This will cause any control-file-needing apis to fail - this is desired.
1263
def _get_revision_store(self, repo_transport, control_files):
1264
"""See RepositoryFormat._get_revision_store()."""
1265
from bzrlib.xml4 import serializer_v4
1266
return self._get_text_rev_store(repo_transport,
1269
serializer=serializer_v4)
1271
def _get_text_store(self, transport, control_files):
1272
"""See RepositoryFormat._get_text_store()."""
1275
class RepositoryFormat5(PreSplitOutRepositoryFormat):
1276
"""Bzr control format 5.
1278
This repository format has:
1279
- weaves for file texts and inventory
1281
- TextStores for revisions and signatures.
1285
super(RepositoryFormat5, self).__init__()
1286
self._matchingbzrdir = bzrdir.BzrDirFormat5()
1288
def get_format_description(self):
1289
"""See RepositoryFormat.get_format_description()."""
1290
return "Weave repository format 5"
1292
def has_unnested_inventory(self):
1295
def _get_revision_store(self, repo_transport, control_files):
1296
"""See RepositoryFormat._get_revision_store()."""
1297
"""Return the revision store object for this a_bzrdir."""
1298
return self._get_text_rev_store(repo_transport,
1303
def _get_text_store(self, transport, control_files):
1304
"""See RepositoryFormat._get_text_store()."""
1305
return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
1308
class RepositoryFormat6(PreSplitOutRepositoryFormat):
1309
"""Bzr control format 6.
1311
This repository format has:
1312
- weaves for file texts and inventory
1313
- hash subdirectory based stores.
1314
- TextStores for revisions and signatures.
1318
super(RepositoryFormat6, self).__init__()
1319
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1321
def get_format_description(self):
1322
"""See RepositoryFormat.get_format_description()."""
1323
return "Weave repository format 6"
1325
def has_unnested_inventory(self):
1328
def _get_revision_store(self, repo_transport, control_files):
1329
"""See RepositoryFormat._get_revision_store()."""
1330
return self._get_text_rev_store(repo_transport,
1336
def _get_text_store(self, transport, control_files):
1337
"""See RepositoryFormat._get_text_store()."""
1338
return self._get_versioned_file_store('weaves', transport, control_files)
1341
class MetaDirRepositoryFormat(RepositoryFormat):
1342
"""Common base class for the new repositories using the metadir layout."""
1345
super(MetaDirRepositoryFormat, self).__init__()
1346
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1348
def _create_control_files(self, a_bzrdir):
1349
"""Create the required files and the initial control_files object."""
1350
# FIXME: RBC 20060125 don't peek under the covers
1351
# NB: no need to escape relative paths that are url safe.
1352
repository_transport = a_bzrdir.get_repository_transport(self)
1353
control_files = LockableFiles(repository_transport, 'lock', LockDir)
1354
control_files.create_lock()
1355
return control_files
1357
def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
1358
"""Upload the initial blank content."""
1359
control_files = self._create_control_files(a_bzrdir)
1360
control_files.lock_write()
1362
control_files._transport.mkdir_multi(dirs,
1363
mode=control_files._dir_mode)
1364
for file, content in files:
1365
control_files.put(file, content)
1366
for file, content in utf8_files:
1367
control_files.put_utf8(file, content)
1369
control_files.put_utf8('shared-storage', '')
1371
control_files.unlock()
1374
class RepositoryFormat7(MetaDirRepositoryFormat):
1375
"""Bzr repository 7.
1377
This repository format has:
1378
- weaves for file texts and inventory
1379
- hash subdirectory based stores.
1380
- TextStores for revisions and signatures.
1381
- a format marker of its own
1382
- an optional 'shared-storage' flag
1383
- an optional 'no-working-trees' flag
1386
def _get_control_store(self, repo_transport, control_files):
1387
"""Return the control store for this repository."""
1388
return self._get_versioned_file_store('',
1393
def get_format_string(self):
1394
"""See RepositoryFormat.get_format_string()."""
1395
return "Bazaar-NG Repository format 7"
1397
def get_format_description(self):
1398
"""See RepositoryFormat.get_format_description()."""
1399
return "Weave repository format 7"
1401
def has_unnested_inventory(self):
1404
def check_conversion_target(self, target_format):
1407
def _get_revision_store(self, repo_transport, control_files):
1408
"""See RepositoryFormat._get_revision_store()."""
1409
return self._get_text_rev_store(repo_transport,
1416
def _get_text_store(self, transport, control_files):
1417
"""See RepositoryFormat._get_text_store()."""
1418
return self._get_versioned_file_store('weaves',
1422
def initialize(self, a_bzrdir, shared=False):
1423
"""Create a weave repository.
1425
:param shared: If true the repository will be initialized as a shared
1428
from bzrlib.weavefile import write_weave_v5
1429
from bzrlib.weave import Weave
1431
# Create an empty weave
1433
write_weave_v5(Weave(), sio)
1434
empty_weave = sio.getvalue()
1436
mutter('creating repository in %s.', a_bzrdir.transport.base)
1437
dirs = ['revision-store', 'weaves']
1438
files = [('inventory.weave', StringIO(empty_weave)),
1440
utf8_files = [('format', self.get_format_string())]
1442
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1443
return self.open(a_bzrdir=a_bzrdir, _found=True)
1445
def open(self, a_bzrdir, _found=False, _override_transport=None):
1446
"""See RepositoryFormat.open().
1448
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1449
repository at a slightly different url
1450
than normal. I.e. during 'upgrade'.
1453
format = RepositoryFormat.find_format(a_bzrdir)
1454
assert format.__class__ == self.__class__
1455
if _override_transport is not None:
1456
repo_transport = _override_transport
1458
repo_transport = a_bzrdir.get_repository_transport(None)
1459
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1460
text_store = self._get_text_store(repo_transport, control_files)
1461
control_store = self._get_control_store(repo_transport, control_files)
1462
_revision_store = self._get_revision_store(repo_transport, control_files)
1463
return MetaDirRepository(_format=self,
1465
control_files=control_files,
1466
_revision_store=_revision_store,
1467
control_store=control_store,
1468
text_store=text_store)
1471
class RepositoryFormatKnit(MetaDirRepositoryFormat):
1472
"""Bzr repository knit format (generalized).
1474
This repository format has:
1475
- knits for file texts and inventory
1476
- hash subdirectory based stores.
1477
- knits for revisions and signatures
1478
- TextStores for revisions and signatures.
1479
- a format marker of its own
1480
- an optional 'shared-storage' flag
1481
- an optional 'no-working-trees' flag
1485
def _get_control_store(self, repo_transport, control_files):
1486
"""Return the control store for this repository."""
1487
return VersionedFileStore(
1490
file_mode=control_files._file_mode,
1491
versionedfile_class=KnitVersionedFile,
1492
versionedfile_kwargs={'factory':KnitPlainFactory()},
1495
def _get_revision_store(self, repo_transport, control_files):
1496
"""See RepositoryFormat._get_revision_store()."""
1497
from bzrlib.store.revision.knit import KnitRevisionStore
1498
versioned_file_store = VersionedFileStore(
1500
file_mode=control_files._file_mode,
1503
versionedfile_class=KnitVersionedFile,
1504
versionedfile_kwargs={'delta':False, 'factory':KnitPlainFactory()},
1507
return KnitRevisionStore(versioned_file_store)
1509
def _get_text_store(self, transport, control_files):
1510
"""See RepositoryFormat._get_text_store()."""
1511
return self._get_versioned_file_store('knits',
1514
versionedfile_class=KnitVersionedFile,
1517
def has_unnested_inventory(self):
1520
def initialize(self, a_bzrdir, shared=False):
1521
"""Create a knit format 1 repository.
1523
:param a_bzrdir: bzrdir to contain the new repository; must already
1525
:param shared: If true the repository will be initialized as a shared
1528
mutter('creating repository in %s.', a_bzrdir.transport.base)
1529
dirs = ['revision-store', 'knits']
1531
utf8_files = [('format', self.get_format_string())]
1533
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1534
repo_transport = a_bzrdir.get_repository_transport(None)
1535
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1536
control_store = self._get_control_store(repo_transport, control_files)
1537
transaction = transactions.WriteTransaction()
1538
# trigger a write of the inventory store.
1539
control_store.get_weave_or_empty('inventory', transaction)
1540
_revision_store = self._get_revision_store(repo_transport, control_files)
1541
_revision_store.has_revision_id('A', transaction)
1542
_revision_store.get_signature_file(transaction)
1543
return self.open(a_bzrdir=a_bzrdir, _found=True)
1545
def open(self, a_bzrdir, _found=False, _override_transport=None):
1546
"""See RepositoryFormat.open().
1548
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1549
repository at a slightly different url
1550
than normal. I.e. during 'upgrade'.
1553
format = RepositoryFormat.find_format(a_bzrdir)
1554
assert format.__class__ == self.__class__
1555
if _override_transport is not None:
1556
repo_transport = _override_transport
1558
repo_transport = a_bzrdir.get_repository_transport(None)
1559
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1560
text_store = self._get_text_store(repo_transport, control_files)
1561
control_store = self._get_control_store(repo_transport, control_files)
1562
_revision_store = self._get_revision_store(repo_transport, control_files)
1563
return KnitRepository(_format=self,
1565
control_files=control_files,
1566
_revision_store=_revision_store,
1567
control_store=control_store,
1568
text_store=text_store)
1571
class RepositoryFormatKnit1(RepositoryFormatKnit):
1572
"""Bzr repository knit format 1.
1574
This repository format has:
1575
- knits for file texts and inventory
1576
- hash subdirectory based stores.
1577
- knits for revisions and signatures
1578
- TextStores for revisions and signatures.
1579
- a format marker of its own
1580
- an optional 'shared-storage' flag
1581
- an optional 'no-working-trees' flag
1584
This format was introduced in bzr 0.8.
1586
def get_format_string(self):
1587
"""See RepositoryFormat.get_format_string()."""
1588
return "Bazaar-NG Knit Repository Format 1"
1590
def get_format_description(self):
1591
"""See RepositoryFormat.get_format_description()."""
1592
return "Knit repository format 1"
1594
def check_conversion_target(self, target_format):
1598
class RepositoryFormatKnit2(RepositoryFormatKnit):
1599
"""Bzr repository knit format 2.
1601
THIS FORMAT IS EXPERIMENTAL
1602
This repository format has:
1603
- knits for file texts and inventory
1604
- hash subdirectory based stores.
1605
- knits for revisions and signatures
1606
- TextStores for revisions and signatures.
1607
- a format marker of its own
1608
- an optional 'shared-storage' flag
1609
- an optional 'no-working-trees' flag
1611
- Support for recording full info about the tree root
1614
def get_format_string(self):
1615
"""See RepositoryFormat.get_format_string()."""
1616
return "Bazaar Knit Repository Format 2"
1618
def get_format_description(self):
1619
"""See RepositoryFormat.get_format_description()."""
1620
return "Knit repository format 2"
1622
def check_conversion_target(self, target_format):
1623
if not getattr(target_format, 'rich_root_data', False):
1624
raise errors.BadConversionTarget(
1625
'Does not support rich root data.', target_format)
1628
# formats which have no format string are not discoverable
1629
# and not independently creatable, so are not registered.
1630
RepositoryFormat.register_format(RepositoryFormat7())
1631
_default_format = RepositoryFormatKnit1()
1632
RepositoryFormat.register_format(_default_format)
1633
RepositoryFormat.register_format(RepositoryFormatKnit2())
1634
RepositoryFormat.set_default_format(_default_format)
1635
_legacy_formats = [RepositoryFormat4(),
1636
RepositoryFormat5(),
1637
RepositoryFormat6()]
1640
class InterRepository(InterObject):
1641
"""This class represents operations taking place between two repositories.
1643
Its instances have methods like copy_content and fetch, and contain
1644
references to the source and target repositories these operations can be
1647
Often we will provide convenience methods on 'repository' which carry out
1648
operations with another repository - they will always forward to
1649
InterRepository.get(other).method_name(parameters).
1653
"""The available optimised InterRepository types."""
1656
def copy_content(self, revision_id=None, basis=None):
1657
"""Make a complete copy of the content in self into destination.
1659
This is a destructive operation! Do not use it on existing
1662
:param revision_id: Only copy the content needed to construct
1663
revision_id and its parents.
1664
:param basis: Copy the needed data preferentially from basis.
1667
self.target.set_make_working_trees(self.source.make_working_trees())
1668
except NotImplementedError:
1670
# grab the basis available data
1671
if basis is not None:
1672
self.target.fetch(basis, revision_id=revision_id)
1673
# but don't bother fetching if we have the needed data now.
1674
if (revision_id not in (None, NULL_REVISION) and
1675
self.target.has_revision(revision_id)):
1677
self.target.fetch(self.source, revision_id=revision_id)
1680
def fetch(self, revision_id=None, pb=None):
1681
"""Fetch the content required to construct revision_id.
1683
The content is copied from source to target.
1685
:param revision_id: if None all content is copied, if NULL_REVISION no
1687
:param pb: optional progress bar to use for progress reports. If not
1688
provided a default one will be created.
1690
Returns the copied revision count and the failed revisions in a tuple:
1693
from bzrlib.fetch import GenericRepoFetcher
1694
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1695
self.source, self.source._format, self.target, self.target._format)
1696
f = GenericRepoFetcher(to_repository=self.target,
1697
from_repository=self.source,
1698
last_revision=revision_id,
1700
return f.count_copied, f.failed_revisions
1703
def missing_revision_ids(self, revision_id=None):
1704
"""Return the revision ids that source has that target does not.
1706
These are returned in topological order.
1708
:param revision_id: only return revision ids included by this
1711
# generic, possibly worst case, slow code path.
1712
target_ids = set(self.target.all_revision_ids())
1713
if revision_id is not None:
1714
source_ids = self.source.get_ancestry(revision_id)
1715
assert source_ids[0] == None
1718
source_ids = self.source.all_revision_ids()
1719
result_set = set(source_ids).difference(target_ids)
1720
# this may look like a no-op: its not. It preserves the ordering
1721
# other_ids had while only returning the members from other_ids
1722
# that we've decided we need.
1723
return [rev_id for rev_id in source_ids if rev_id in result_set]
1726
class InterWeaveRepo(InterRepository):
1727
"""Optimised code paths between Weave based repositories."""
1729
_matching_repo_format = RepositoryFormat7()
1730
"""Repository format for testing with."""
1733
def is_compatible(source, target):
1734
"""Be compatible with known Weave formats.
1736
We don't test for the stores being of specific types because that
1737
could lead to confusing results, and there is no need to be
1741
return (isinstance(source._format, (RepositoryFormat5,
1743
RepositoryFormat7)) and
1744
isinstance(target._format, (RepositoryFormat5,
1746
RepositoryFormat7)))
1747
except AttributeError:
1751
def copy_content(self, revision_id=None, basis=None):
1752
"""See InterRepository.copy_content()."""
1753
# weave specific optimised path:
1754
if basis is not None:
1755
# copy the basis in, then fetch remaining data.
1756
basis.copy_content_into(self.target, revision_id)
1757
# the basis copy_content_into could miss-set this.
1759
self.target.set_make_working_trees(self.source.make_working_trees())
1760
except NotImplementedError:
1762
self.target.fetch(self.source, revision_id=revision_id)
1765
self.target.set_make_working_trees(self.source.make_working_trees())
1766
except NotImplementedError:
1768
# FIXME do not peek!
1769
if self.source.control_files._transport.listable():
1770
pb = ui.ui_factory.nested_progress_bar()
1772
self.target.weave_store.copy_all_ids(
1773
self.source.weave_store,
1775
from_transaction=self.source.get_transaction(),
1776
to_transaction=self.target.get_transaction())
1777
pb.update('copying inventory', 0, 1)
1778
self.target.control_weaves.copy_multi(
1779
self.source.control_weaves, ['inventory'],
1780
from_transaction=self.source.get_transaction(),
1781
to_transaction=self.target.get_transaction())
1782
self.target._revision_store.text_store.copy_all_ids(
1783
self.source._revision_store.text_store,
1788
self.target.fetch(self.source, revision_id=revision_id)
1791
def fetch(self, revision_id=None, pb=None):
1792
"""See InterRepository.fetch()."""
1793
from bzrlib.fetch import GenericRepoFetcher
1794
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1795
self.source, self.source._format, self.target, self.target._format)
1796
f = GenericRepoFetcher(to_repository=self.target,
1797
from_repository=self.source,
1798
last_revision=revision_id,
1800
return f.count_copied, f.failed_revisions
1803
def missing_revision_ids(self, revision_id=None):
1804
"""See InterRepository.missing_revision_ids()."""
1805
# we want all revisions to satisfy revision_id in source.
1806
# but we don't want to stat every file here and there.
1807
# we want then, all revisions other needs to satisfy revision_id
1808
# checked, but not those that we have locally.
1809
# so the first thing is to get a subset of the revisions to
1810
# satisfy revision_id in source, and then eliminate those that
1811
# we do already have.
1812
# this is slow on high latency connection to self, but as as this
1813
# disk format scales terribly for push anyway due to rewriting
1814
# inventory.weave, this is considered acceptable.
1816
if revision_id is not None:
1817
source_ids = self.source.get_ancestry(revision_id)
1818
assert source_ids[0] == None
1821
source_ids = self.source._all_possible_ids()
1822
source_ids_set = set(source_ids)
1823
# source_ids is the worst possible case we may need to pull.
1824
# now we want to filter source_ids against what we actually
1825
# have in target, but don't try to check for existence where we know
1826
# we do not have a revision as that would be pointless.
1827
target_ids = set(self.target._all_possible_ids())
1828
possibly_present_revisions = target_ids.intersection(source_ids_set)
1829
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1830
required_revisions = source_ids_set.difference(actually_present_revisions)
1831
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1832
if revision_id is not None:
1833
# we used get_ancestry to determine source_ids then we are assured all
1834
# revisions referenced are present as they are installed in topological order.
1835
# and the tip revision was validated by get_ancestry.
1836
return required_topo_revisions
1838
# if we just grabbed the possibly available ids, then
1839
# we only have an estimate of whats available and need to validate
1840
# that against the revision records.
1841
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1844
class InterKnitRepo(InterRepository):
1845
"""Optimised code paths between Knit based repositories."""
1847
_matching_repo_format = RepositoryFormatKnit1()
1848
"""Repository format for testing with."""
1851
def is_compatible(source, target):
1852
"""Be compatible with known Knit formats.
1854
We don't test for the stores being of specific types because that
1855
could lead to confusing results, and there is no need to be
1859
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1860
isinstance(target._format, (RepositoryFormatKnit1)))
1861
except AttributeError:
1865
def fetch(self, revision_id=None, pb=None):
1866
"""See InterRepository.fetch()."""
1867
from bzrlib.fetch import KnitRepoFetcher
1868
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1869
self.source, self.source._format, self.target, self.target._format)
1870
f = KnitRepoFetcher(to_repository=self.target,
1871
from_repository=self.source,
1872
last_revision=revision_id,
1874
return f.count_copied, f.failed_revisions
1877
def missing_revision_ids(self, revision_id=None):
1878
"""See InterRepository.missing_revision_ids()."""
1879
if revision_id is not None:
1880
source_ids = self.source.get_ancestry(revision_id)
1881
assert source_ids[0] == None
1884
source_ids = self.source._all_possible_ids()
1885
source_ids_set = set(source_ids)
1886
# source_ids is the worst possible case we may need to pull.
1887
# now we want to filter source_ids against what we actually
1888
# have in target, but don't try to check for existence where we know
1889
# we do not have a revision as that would be pointless.
1890
target_ids = set(self.target._all_possible_ids())
1891
possibly_present_revisions = target_ids.intersection(source_ids_set)
1892
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1893
required_revisions = source_ids_set.difference(actually_present_revisions)
1894
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1895
if revision_id is not None:
1896
# we used get_ancestry to determine source_ids then we are assured all
1897
# revisions referenced are present as they are installed in topological order.
1898
# and the tip revision was validated by get_ancestry.
1899
return required_topo_revisions
1901
# if we just grabbed the possibly available ids, then
1902
# we only have an estimate of whats available and need to validate
1903
# that against the revision records.
1904
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1906
InterRepository.register_optimiser(InterWeaveRepo)
1907
InterRepository.register_optimiser(InterKnitRepo)
1910
class RepositoryTestProviderAdapter(object):
1911
"""A tool to generate a suite testing multiple repository formats at once.
1913
This is done by copying the test once for each transport and injecting
1914
the transport_server, transport_readonly_server, and bzrdir_format and
1915
repository_format classes into each copy. Each copy is also given a new id()
1916
to make it easy to identify.
1919
def __init__(self, transport_server, transport_readonly_server, formats):
1920
self._transport_server = transport_server
1921
self._transport_readonly_server = transport_readonly_server
1922
self._formats = formats
1924
def adapt(self, test):
1925
result = TestSuite()
1926
for repository_format, bzrdir_format in self._formats:
1927
new_test = deepcopy(test)
1928
new_test.transport_server = self._transport_server
1929
new_test.transport_readonly_server = self._transport_readonly_server
1930
new_test.bzrdir_format = bzrdir_format
1931
new_test.repository_format = repository_format
1932
def make_new_test_id():
1933
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
1934
return lambda: new_id
1935
new_test.id = make_new_test_id()
1936
result.addTest(new_test)
1940
class InterRepositoryTestProviderAdapter(object):
1941
"""A tool to generate a suite testing multiple inter repository formats.
1943
This is done by copying the test once for each interrepo provider and injecting
1944
the transport_server, transport_readonly_server, repository_format and
1945
repository_to_format classes into each copy.
1946
Each copy is also given a new id() to make it easy to identify.
1949
def __init__(self, transport_server, transport_readonly_server, formats):
1950
self._transport_server = transport_server
1951
self._transport_readonly_server = transport_readonly_server
1952
self._formats = formats
1954
def adapt(self, test):
1955
result = TestSuite()
1956
for interrepo_class, repository_format, repository_format_to in self._formats:
1957
new_test = deepcopy(test)
1958
new_test.transport_server = self._transport_server
1959
new_test.transport_readonly_server = self._transport_readonly_server
1960
new_test.interrepo_class = interrepo_class
1961
new_test.repository_format = repository_format
1962
new_test.repository_format_to = repository_format_to
1963
def make_new_test_id():
1964
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
1965
return lambda: new_id
1966
new_test.id = make_new_test_id()
1967
result.addTest(new_test)
1971
def default_test_list():
1972
"""Generate the default list of interrepo permutations to test."""
1974
# test the default InterRepository between format 6 and the current
1976
# XXX: robertc 20060220 reinstate this when there are two supported
1977
# formats which do not have an optimal code path between them.
1978
result.append((InterRepository,
1979
RepositoryFormat6(),
1980
RepositoryFormatKnit1()))
1981
for optimiser in InterRepository._optimisers:
1982
result.append((optimiser,
1983
optimiser._matching_repo_format,
1984
optimiser._matching_repo_format
1986
# if there are specific combinations we want to use, we can add them
1991
class CopyConverter(object):
1992
"""A repository conversion tool which just performs a copy of the content.
1994
This is slow but quite reliable.
1997
def __init__(self, target_format):
1998
"""Create a CopyConverter.
2000
:param target_format: The format the resulting repository should be.
2002
self.target_format = target_format
2004
def convert(self, repo, pb):
2005
"""Perform the conversion of to_convert, giving feedback via pb.
2007
:param to_convert: The disk object to convert.
2008
:param pb: a progress bar to use for progress information.
2013
# this is only useful with metadir layouts - separated repo content.
2014
# trigger an assertion if not such
2015
repo._format.get_format_string()
2016
self.repo_dir = repo.bzrdir
2017
self.step('Moving repository to repository.backup')
2018
self.repo_dir.transport.move('repository', 'repository.backup')
2019
backup_transport = self.repo_dir.transport.clone('repository.backup')
2020
repo._format.check_conversion_target(self.target_format)
2021
self.source_repo = repo._format.open(self.repo_dir,
2023
_override_transport=backup_transport)
2024
self.step('Creating new repository')
2025
converted = self.target_format.initialize(self.repo_dir,
2026
self.source_repo.is_shared())
2027
converted.lock_write()
2029
self.step('Copying content into repository.')
2030
self.source_repo.copy_content_into(converted)
2033
self.step('Deleting old repository content.')
2034
self.repo_dir.transport.delete_tree('repository.backup')
2035
self.pb.note('repository converted')
2037
def step(self, message):
2038
"""Update the pb by a step."""
2040
self.pb.update(message, self.count, self.total)
2043
class CommitBuilder(object):
2044
"""Provides an interface to build up a commit.
2046
This allows describing a tree to be committed without needing to
2047
know the internals of the format of the repository.
2050
record_root_entry = False
2051
def __init__(self, repository, parents, config, timestamp=None,
2052
timezone=None, committer=None, revprops=None,
2054
"""Initiate a CommitBuilder.
2056
:param repository: Repository to commit to.
2057
:param parents: Revision ids of the parents of the new revision.
2058
:param config: Configuration to use.
2059
:param timestamp: Optional timestamp recorded for commit.
2060
:param timezone: Optional timezone for timestamp.
2061
:param committer: Optional committer to set for commit.
2062
:param revprops: Optional dictionary of revision properties.
2063
:param revision_id: Optional revision id.
2065
self._config = config
2067
if committer is None:
2068
self._committer = self._config.username()
2070
assert isinstance(committer, basestring), type(committer)
2071
self._committer = committer
2073
self.new_inventory = Inventory(None)
2074
self._new_revision_id = revision_id
2075
self.parents = parents
2076
self.repository = repository
2079
if revprops is not None:
2080
self._revprops.update(revprops)
2082
if timestamp is None:
2083
timestamp = time.time()
2084
# Restrict resolution to 1ms
2085
self._timestamp = round(timestamp, 3)
2087
if timezone is None:
2088
self._timezone = local_time_offset()
2090
self._timezone = int(timezone)
2092
self._generate_revision_if_needed()
2094
def commit(self, message):
2095
"""Make the actual commit.
2097
:return: The revision id of the recorded revision.
2099
rev = Revision(timestamp=self._timestamp,
2100
timezone=self._timezone,
2101
committer=self._committer,
2103
inventory_sha1=self.inv_sha1,
2104
revision_id=self._new_revision_id,
2105
properties=self._revprops)
2106
rev.parent_ids = self.parents
2107
self.repository.add_revision(self._new_revision_id, rev,
2108
self.new_inventory, self._config)
2109
return self._new_revision_id
2111
def finish_inventory(self):
2112
"""Tell the builder that the inventory is finished."""
2113
if self.new_inventory.root is None:
2114
symbol_versioning.warn('Root entry should be supplied to'
2115
' record_entry_contents, as of bzr 0.10.',
2116
DeprecationWarning, stacklevel=2)
2117
self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
2118
self.new_inventory.revision_id = self._new_revision_id
2119
self.inv_sha1 = self.repository.add_inventory(
2120
self._new_revision_id,
2125
def _gen_revision_id(self):
2126
"""Return new revision-id."""
2127
s = '%s-%s-' % (self._config.user_email(),
2128
compact_date(self._timestamp))
2129
s += hexlify(rand_bytes(8))
2132
def _generate_revision_if_needed(self):
2133
"""Create a revision id if None was supplied.
2135
If the repository can not support user-specified revision ids
2136
they should override this function and raise UnsupportedOperation
2137
if _new_revision_id is not None.
2139
:raises: UnsupportedOperation
2141
if self._new_revision_id is None:
2142
self._new_revision_id = self._gen_revision_id()
2144
def record_entry_contents(self, ie, parent_invs, path, tree):
2145
"""Record the content of ie from tree into the commit if needed.
2147
Side effect: sets ie.revision when unchanged
2149
:param ie: An inventory entry present in the commit.
2150
:param parent_invs: The inventories of the parent revisions of the
2152
:param path: The path the entry is at in the tree.
2153
:param tree: The tree which contains this entry and should be used to
2156
if self.new_inventory.root is None and ie.parent_id is not None:
2157
symbol_versioning.warn('Root entry should be supplied to'
2158
' record_entry_contents, as of bzr 0.10.',
2159
DeprecationWarning, stacklevel=2)
2160
self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
2162
self.new_inventory.add(ie)
2164
# ie.revision is always None if the InventoryEntry is considered
2165
# for committing. ie.snapshot will record the correct revision
2166
# which may be the sole parent if it is untouched.
2167
if ie.revision is not None:
2170
# In this revision format, root entries have no knit or weave
2171
if ie is self.new_inventory.root:
2172
if len(parent_invs):
2173
ie.revision = parent_invs[0].root.revision
2177
previous_entries = ie.find_previous_heads(
2179
self.repository.weave_store,
2180
self.repository.get_transaction())
2181
# we are creating a new revision for ie in the history store
2183
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2185
def modified_directory(self, file_id, file_parents):
2186
"""Record the presence of a symbolic link.
2188
:param file_id: The file_id of the link to record.
2189
:param file_parents: The per-file parent revision ids.
2191
self._add_text_to_weave(file_id, [], file_parents.keys())
2193
def modified_file_text(self, file_id, file_parents,
2194
get_content_byte_lines, text_sha1=None,
2196
"""Record the text of file file_id
2198
:param file_id: The file_id of the file to record the text of.
2199
:param file_parents: The per-file parent revision ids.
2200
:param get_content_byte_lines: A callable which will return the byte
2202
:param text_sha1: Optional SHA1 of the file contents.
2203
:param text_size: Optional size of the file contents.
2205
# mutter('storing text of file {%s} in revision {%s} into %r',
2206
# file_id, self._new_revision_id, self.repository.weave_store)
2207
# special case to avoid diffing on renames or
2209
if (len(file_parents) == 1
2210
and text_sha1 == file_parents.values()[0].text_sha1
2211
and text_size == file_parents.values()[0].text_size):
2212
previous_ie = file_parents.values()[0]
2213
versionedfile = self.repository.weave_store.get_weave(file_id,
2214
self.repository.get_transaction())
2215
versionedfile.clone_text(self._new_revision_id,
2216
previous_ie.revision, file_parents.keys())
2217
return text_sha1, text_size
2219
new_lines = get_content_byte_lines()
2220
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2221
# should return the SHA1 and size
2222
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2223
return osutils.sha_strings(new_lines), \
2224
sum(map(len, new_lines))
2226
def modified_link(self, file_id, file_parents, link_target):
2227
"""Record the presence of a symbolic link.
2229
:param file_id: The file_id of the link to record.
2230
:param file_parents: The per-file parent revision ids.
2231
:param link_target: Target location of this link.
2233
self._add_text_to_weave(file_id, [], file_parents.keys())
2235
def _add_text_to_weave(self, file_id, new_lines, parents):
2236
versionedfile = self.repository.weave_store.get_weave_or_empty(
2237
file_id, self.repository.get_transaction())
2238
versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2239
versionedfile.clear_cache()
2242
class _CommitBuilder(CommitBuilder):
2243
"""Temporary class so old CommitBuilders are detected properly
2245
Note: CommitBuilder works whether or not root entry is recorded.
2248
record_root_entry = True
2260
def _unescaper(match, _map=_unescape_map):
2261
return _map[match.group(1)]
2267
def _unescape_xml(data):
2268
"""Unescape predefined XML entities in a string of data."""
2270
if _unescape_re is None:
2271
_unescape_re = re.compile('\&([^;]*);')
2272
return _unescape_re.sub(_unescaper, data)