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, 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
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.store.versioned import VersionedFileStore, WeaveStore
37
from bzrlib.store.text import TextStore
38
from bzrlib.symbol_versioning import (deprecated_method,
41
from bzrlib.trace import mutter, note
42
from bzrlib.tree import RevisionTree
43
from bzrlib.tsort import topo_sort
44
from bzrlib.testament import Testament
45
from bzrlib.tree import EmptyTree
46
from bzrlib.weave import WeaveFile
49
class Repository(object):
50
"""Repository holding history for one or more branches.
52
The repository holds and retrieves historical information including
53
revisions and file history. It's normally accessed only by the Branch,
54
which views a particular line of development through that history.
56
The Repository builds on top of Stores and a Transport, which respectively
57
describe the disk data format and the way of accessing the (possibly
62
def add_inventory(self, revid, inv, parents):
63
"""Add the inventory inv to the repository as revid.
65
:param parents: The revision ids of the parents that revid
66
is known to have and are in the repository already.
68
returns the sha1 of the serialized inventory.
70
assert inv.revision_id is None or inv.revision_id == revid, \
71
"Mismatch between inventory revision" \
72
" id and insertion revid (%r, %r)" % (inv.revision_id, revid)
73
inv_text = xml5.serializer_v5.write_inventory_to_string(inv)
74
inv_sha1 = osutils.sha_string(inv_text)
75
inv_vf = self.control_weaves.get_weave('inventory',
76
self.get_transaction())
77
self._inventory_add_lines(inv_vf, revid, parents, osutils.split_lines(inv_text))
80
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
82
for parent in parents:
84
final_parents.append(parent)
86
inv_vf.add_lines(revid, final_parents, lines)
89
def add_revision(self, rev_id, rev, inv=None, config=None):
90
"""Add rev to the revision store as rev_id.
92
:param rev_id: the revision id to use.
93
:param rev: The revision object.
94
:param inv: The inventory for the revision. if None, it will be looked
95
up in the inventory storer
96
:param config: If None no digital signature will be created.
97
If supplied its signature_needed method will be used
98
to determine if a signature should be made.
100
if config is not None and config.signature_needed():
102
inv = self.get_inventory(rev_id)
103
plaintext = Testament(rev, inv).as_short_text()
104
self.store_revision_signature(
105
gpg.GPGStrategy(config), plaintext, rev_id)
106
if not rev_id in self.get_inventory_weave():
108
raise errors.WeaveRevisionNotPresent(rev_id,
109
self.get_inventory_weave())
111
# yes, this is not suitable for adding with ghosts.
112
self.add_inventory(rev_id, inv, rev.parent_ids)
113
self._revision_store.add_revision(rev, self.get_transaction())
116
def _all_possible_ids(self):
117
"""Return all the possible revisions that we could find."""
118
return self.get_inventory_weave().versions()
120
@deprecated_method(zero_nine)
121
def all_revision_ids(self):
122
"""Returns a list of all the revision ids in the repository.
124
This is deprecated because code should generally work on the graph
125
reachable from a particular revision, and ignore any other revisions
126
that might be present. There is no direct replacement method.
128
return self._all_revision_ids()
131
def _all_revision_ids(self):
132
"""Returns a list of all the revision ids in the repository.
134
These are in as much topological order as the underlying store can
135
present: for weaves ghosts may lead to a lack of correctness until
136
the reweave updates the parents list.
138
if self._revision_store.text_store.listable():
139
return self._revision_store.all_revision_ids(self.get_transaction())
140
result = self._all_possible_ids()
141
return self._eliminate_revisions_not_present(result)
143
def break_lock(self):
144
"""Break a lock if one is present from another instance.
146
Uses the ui factory to ask for confirmation if the lock may be from
149
self.control_files.break_lock()
152
def _eliminate_revisions_not_present(self, revision_ids):
153
"""Check every revision id in revision_ids to see if we have it.
155
Returns a set of the present revisions.
158
for id in revision_ids:
159
if self.has_revision(id):
164
def create(a_bzrdir):
165
"""Construct the current default format repository in a_bzrdir."""
166
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
168
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
169
"""instantiate a Repository.
171
:param _format: The format of the repository on disk.
172
:param a_bzrdir: The BzrDir of the repository.
174
In the future we will have a single api for all stores for
175
getting file texts, inventories and revisions, then
176
this construct will accept instances of those things.
178
super(Repository, self).__init__()
179
self._format = _format
180
# the following are part of the public API for Repository:
181
self.bzrdir = a_bzrdir
182
self.control_files = control_files
183
self._revision_store = _revision_store
184
self.text_store = text_store
185
# backwards compatibility
186
self.weave_store = text_store
187
# not right yet - should be more semantically clear ?
189
self.control_store = control_store
190
self.control_weaves = control_store
191
# TODO: make sure to construct the right store classes, etc, depending
192
# on whether escaping is required.
195
return '%s(%r)' % (self.__class__.__name__,
196
self.bzrdir.transport.base)
199
return self.control_files.is_locked()
201
def lock_write(self):
202
self.control_files.lock_write()
205
self.control_files.lock_read()
207
def get_physical_lock_status(self):
208
return self.control_files.get_physical_lock_status()
211
def missing_revision_ids(self, other, revision_id=None):
212
"""Return the revision ids that other has that this does not.
214
These are returned in topological order.
216
revision_id: only return revision ids included by revision_id.
218
return InterRepository.get(other, self).missing_revision_ids(revision_id)
222
"""Open the repository rooted at base.
224
For instance, if the repository is at URL/.bzr/repository,
225
Repository.open(URL) -> a Repository instance.
227
control = bzrdir.BzrDir.open(base)
228
return control.open_repository()
230
def copy_content_into(self, destination, revision_id=None, basis=None):
231
"""Make a complete copy of the content in self into destination.
233
This is a destructive operation! Do not use it on existing
236
return InterRepository.get(self, destination).copy_content(revision_id, basis)
238
def fetch(self, source, revision_id=None, pb=None):
239
"""Fetch the content required to construct revision_id from source.
241
If revision_id is None all content is copied.
243
return InterRepository.get(source, self).fetch(revision_id=revision_id,
246
def get_commit_builder(self, branch, parents, config, timestamp=None,
247
timezone=None, committer=None, revprops=None,
249
"""Obtain a CommitBuilder for this repository.
251
:param branch: Branch to commit to.
252
:param parents: Revision ids of the parents of the new revision.
253
:param config: Configuration to use.
254
:param timestamp: Optional timestamp recorded for commit.
255
:param timezone: Optional timezone for timestamp.
256
:param committer: Optional committer to set for commit.
257
:param revprops: Optional dictionary of revision properties.
258
:param revision_id: Optional revision id.
260
return CommitBuilder(self, parents, config, timestamp, timezone,
261
committer, revprops, revision_id)
264
self.control_files.unlock()
267
def clone(self, a_bzrdir, revision_id=None, basis=None):
268
"""Clone this repository into a_bzrdir using the current format.
270
Currently no check is made that the format of this repository and
271
the bzrdir format are compatible. FIXME RBC 20060201.
273
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
274
# use target default format.
275
result = a_bzrdir.create_repository()
276
# FIXME RBC 20060209 split out the repository type to avoid this check ?
277
elif isinstance(a_bzrdir._format,
278
(bzrdir.BzrDirFormat4,
279
bzrdir.BzrDirFormat5,
280
bzrdir.BzrDirFormat6)):
281
result = a_bzrdir.open_repository()
283
result = self._format.initialize(a_bzrdir, shared=self.is_shared())
284
self.copy_content_into(result, revision_id, basis)
288
def has_revision(self, revision_id):
289
"""True if this repository has a copy of the revision."""
290
return self._revision_store.has_revision_id(revision_id,
291
self.get_transaction())
294
def get_revision_reconcile(self, revision_id):
295
"""'reconcile' helper routine that allows access to a revision always.
297
This variant of get_revision does not cross check the weave graph
298
against the revision one as get_revision does: but it should only
299
be used by reconcile, or reconcile-alike commands that are correcting
300
or testing the revision graph.
302
if not revision_id or not isinstance(revision_id, basestring):
303
raise InvalidRevisionId(revision_id=revision_id, branch=self)
304
return self._revision_store.get_revision(revision_id,
305
self.get_transaction())
308
def get_revision_xml(self, revision_id):
309
rev = self.get_revision(revision_id)
311
# the current serializer..
312
self._revision_store._serializer.write_revision(rev, rev_tmp)
314
return rev_tmp.getvalue()
317
def get_revision(self, revision_id):
318
"""Return the Revision object for a named revision"""
319
r = self.get_revision_reconcile(revision_id)
320
# weave corruption can lead to absent revision markers that should be
322
# the following test is reasonably cheap (it needs a single weave read)
323
# and the weave is cached in read transactions. In write transactions
324
# it is not cached but typically we only read a small number of
325
# revisions. For knits when they are introduced we will probably want
326
# to ensure that caching write transactions are in use.
327
inv = self.get_inventory_weave()
328
self._check_revision_parents(r, inv)
331
def _check_revision_parents(self, revision, inventory):
332
"""Private to Repository and Fetch.
334
This checks the parentage of revision in an inventory weave for
335
consistency and is only applicable to inventory-weave-for-ancestry
336
using repository formats & fetchers.
338
weave_parents = inventory.get_parents(revision.revision_id)
339
weave_names = inventory.versions()
340
for parent_id in revision.parent_ids:
341
if parent_id in weave_names:
342
# this parent must not be a ghost.
343
if not parent_id in weave_parents:
345
raise errors.CorruptRepository(self)
348
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
349
signature = gpg_strategy.sign(plaintext)
350
self._revision_store.add_revision_signature_text(revision_id,
352
self.get_transaction())
354
def fileids_altered_by_revision_ids(self, revision_ids):
355
"""Find the file ids and versions affected by revisions.
357
:param revisions: an iterable containing revision ids.
358
:return: a dictionary mapping altered file-ids to an iterable of
359
revision_ids. Each altered file-ids has the exact revision_ids that
360
altered it listed explicitly.
362
assert isinstance(self._format, (RepositoryFormat5,
365
RepositoryFormatKnit1)), \
366
("fileids_altered_by_revision_ids only supported for branches "
367
"which store inventory as unnested xml, not on %r" % self)
368
selected_revision_ids = set(revision_ids)
369
w = self.get_inventory_weave()
372
# this code needs to read every new line in every inventory for the
373
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
374
# not present in one of those inventories is unnecessary but not
375
# harmful because we are filtering by the revision id marker in the
376
# inventory lines : we only select file ids altered in one of those
377
# revisions. We don't need to see all lines in the inventory because
378
# only those added in an inventory in rev X can contain a revision=X
380
for line in w.iter_lines_added_or_present_in_versions(selected_revision_ids):
381
start = line.find('file_id="')+9
382
if start < 9: continue
383
end = line.find('"', start)
385
file_id = _unescape_xml(line[start:end])
387
start = line.find('revision="')+10
388
if start < 10: continue
389
end = line.find('"', start)
391
revision_id = _unescape_xml(line[start:end])
392
if revision_id in selected_revision_ids:
393
result.setdefault(file_id, set()).add(revision_id)
397
def get_inventory_weave(self):
398
return self.control_weaves.get_weave('inventory',
399
self.get_transaction())
402
def get_inventory(self, revision_id):
403
"""Get Inventory object by hash."""
404
return self.deserialise_inventory(
405
revision_id, self.get_inventory_xml(revision_id))
407
def deserialise_inventory(self, revision_id, xml):
408
"""Transform the xml into an inventory object.
410
:param revision_id: The expected revision id of the inventory.
411
:param xml: A serialised inventory.
413
return xml5.serializer_v5.read_inventory_from_string(xml)
416
def get_inventory_xml(self, revision_id):
417
"""Get inventory XML as a file object."""
419
assert isinstance(revision_id, basestring), type(revision_id)
420
iw = self.get_inventory_weave()
421
return iw.get_text(revision_id)
423
raise errors.HistoryMissing(self, 'inventory', revision_id)
426
def get_inventory_sha1(self, revision_id):
427
"""Return the sha1 hash of the inventory entry
429
return self.get_revision(revision_id).inventory_sha1
432
def get_revision_graph(self, revision_id=None):
433
"""Return a dictionary containing the revision graph.
435
:return: a dictionary of revision_id->revision_parents_list.
437
weave = self.get_inventory_weave()
438
all_revisions = self._eliminate_revisions_not_present(weave.versions())
439
entire_graph = dict([(node, weave.get_parents(node)) for
440
node in all_revisions])
441
if revision_id is None:
443
elif revision_id not in entire_graph:
444
raise errors.NoSuchRevision(self, revision_id)
446
# add what can be reached from revision_id
448
pending = set([revision_id])
449
while len(pending) > 0:
451
result[node] = entire_graph[node]
452
for revision_id in result[node]:
453
if revision_id not in result:
454
pending.add(revision_id)
458
def get_revision_graph_with_ghosts(self, revision_ids=None):
459
"""Return a graph of the revisions with ghosts marked as applicable.
461
:param revision_ids: an iterable of revisions to graph or None for all.
462
:return: a Graph object with the graph reachable from revision_ids.
466
pending = set(self._all_revision_ids())
469
pending = set(revision_ids)
470
required = set(revision_ids)
473
revision_id = pending.pop()
475
rev = self.get_revision(revision_id)
476
except errors.NoSuchRevision:
477
if revision_id in required:
480
result.add_ghost(revision_id)
482
for parent_id in rev.parent_ids:
483
# is this queued or done ?
484
if (parent_id not in pending and
485
parent_id not in done):
487
pending.add(parent_id)
488
result.add_node(revision_id, rev.parent_ids)
489
done.add(revision_id)
493
def get_revision_inventory(self, revision_id):
494
"""Return inventory of a past revision."""
495
# TODO: Unify this with get_inventory()
496
# bzr 0.0.6 and later imposes the constraint that the inventory_id
497
# must be the same as its revision, so this is trivial.
498
if revision_id is None:
499
# This does not make sense: if there is no revision,
500
# then it is the current tree inventory surely ?!
501
# and thus get_root_id() is something that looks at the last
502
# commit on the branch, and the get_root_id is an inventory check.
503
raise NotImplementedError
504
# return Inventory(self.get_root_id())
506
return self.get_inventory(revision_id)
510
"""Return True if this repository is flagged as a shared repository."""
511
raise NotImplementedError(self.is_shared)
514
def reconcile(self, other=None, thorough=False):
515
"""Reconcile this repository."""
516
from bzrlib.reconcile import RepoReconciler
517
reconciler = RepoReconciler(self, thorough=thorough)
518
reconciler.reconcile()
522
def revision_tree(self, revision_id):
523
"""Return Tree for a revision on this branch.
525
`revision_id` may be None for the null revision, in which case
526
an `EmptyTree` is returned."""
527
# TODO: refactor this to use an existing revision object
528
# so we don't need to read it in twice.
529
if revision_id is None or revision_id == NULL_REVISION:
532
inv = self.get_revision_inventory(revision_id)
533
return RevisionTree(self, inv, revision_id)
536
def get_ancestry(self, revision_id):
537
"""Return a list of revision-ids integrated by a revision.
539
The first element of the list is always None, indicating the origin
540
revision. This might change when we have history horizons, or
541
perhaps we should have a new API.
543
This is topologically sorted.
545
if revision_id is None:
547
if not self.has_revision(revision_id):
548
raise errors.NoSuchRevision(self, revision_id)
549
w = self.get_inventory_weave()
550
candidates = w.get_ancestry(revision_id)
551
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
554
def print_file(self, file, revision_id):
555
"""Print `file` to stdout.
557
FIXME RBC 20060125 as John Meinel points out this is a bad api
558
- it writes to stdout, it assumes that that is valid etc. Fix
559
by creating a new more flexible convenience function.
561
tree = self.revision_tree(revision_id)
562
# use inventory as it was in that revision
563
file_id = tree.inventory.path2id(file)
565
# TODO: jam 20060427 Write a test for this code path
566
# it had a bug in it, and was raising the wrong
568
raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
569
tree.print_file(file_id)
571
def get_transaction(self):
572
return self.control_files.get_transaction()
574
def revision_parents(self, revid):
575
return self.get_inventory_weave().parent_names(revid)
578
def set_make_working_trees(self, new_value):
579
"""Set the policy flag for making working trees when creating branches.
581
This only applies to branches that use this repository.
583
The default is 'True'.
584
:param new_value: True to restore the default, False to disable making
587
raise NotImplementedError(self.set_make_working_trees)
589
def make_working_trees(self):
590
"""Returns the policy for making working trees on new branches."""
591
raise NotImplementedError(self.make_working_trees)
594
def sign_revision(self, revision_id, gpg_strategy):
595
plaintext = Testament.from_revision(self, revision_id).as_short_text()
596
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
599
def has_signature_for_revision_id(self, revision_id):
600
"""Query for a revision signature for revision_id in the repository."""
601
return self._revision_store.has_signature(revision_id,
602
self.get_transaction())
605
def get_signature_text(self, revision_id):
606
"""Return the text for a signature."""
607
return self._revision_store.get_signature_text(revision_id,
608
self.get_transaction())
611
def check(self, revision_ids):
612
"""Check consistency of all history of given revision_ids.
614
Different repository implementations should override _check().
616
:param revision_ids: A non-empty list of revision_ids whose ancestry
617
will be checked. Typically the last revision_id of a branch.
620
raise ValueError("revision_ids must be non-empty in %s.check"
622
return self._check(revision_ids)
624
def _check(self, revision_ids):
625
result = check.Check(self)
630
class AllInOneRepository(Repository):
631
"""Legacy support - the repository behaviour for all-in-one branches."""
633
def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
634
# we reuse one control files instance.
635
dir_mode = a_bzrdir._control_files._dir_mode
636
file_mode = a_bzrdir._control_files._file_mode
638
def get_store(name, compressed=True, prefixed=False):
639
# FIXME: This approach of assuming stores are all entirely compressed
640
# or entirely uncompressed is tidy, but breaks upgrade from
641
# some existing branches where there's a mixture; we probably
642
# still want the option to look for both.
643
relpath = a_bzrdir._control_files._escape(name)
644
store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
645
prefixed=prefixed, compressed=compressed,
648
#if self._transport.should_cache():
649
# cache_path = os.path.join(self.cache_root, name)
650
# os.mkdir(cache_path)
651
# store = bzrlib.store.CachedStore(store, cache_path)
654
# not broken out yet because the controlweaves|inventory_store
655
# and text_store | weave_store bits are still different.
656
if isinstance(_format, RepositoryFormat4):
657
# cannot remove these - there is still no consistent api
658
# which allows access to this old info.
659
self.inventory_store = get_store('inventory-store')
660
text_store = get_store('text-store')
661
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
665
"""AllInOne repositories cannot be shared."""
669
def set_make_working_trees(self, new_value):
670
"""Set the policy flag for making working trees when creating branches.
672
This only applies to branches that use this repository.
674
The default is 'True'.
675
:param new_value: True to restore the default, False to disable making
678
raise NotImplementedError(self.set_make_working_trees)
680
def make_working_trees(self):
681
"""Returns the policy for making working trees on new branches."""
685
def install_revision(repository, rev, revision_tree):
686
"""Install all revision data into a repository."""
689
for p_id in rev.parent_ids:
690
if repository.has_revision(p_id):
691
present_parents.append(p_id)
692
parent_trees[p_id] = repository.revision_tree(p_id)
694
parent_trees[p_id] = EmptyTree()
696
inv = revision_tree.inventory
698
# Add the texts that are not already present
699
for path, ie in inv.iter_entries():
700
w = repository.weave_store.get_weave_or_empty(ie.file_id,
701
repository.get_transaction())
702
if ie.revision not in w:
704
# FIXME: TODO: The following loop *may* be overlapping/duplicate
705
# with InventoryEntry.find_previous_heads(). if it is, then there
706
# is a latent bug here where the parents may have ancestors of each
708
for revision, tree in parent_trees.iteritems():
709
if ie.file_id not in tree:
711
parent_id = tree.inventory[ie.file_id].revision
712
if parent_id in text_parents:
714
text_parents.append(parent_id)
716
vfile = repository.weave_store.get_weave_or_empty(ie.file_id,
717
repository.get_transaction())
718
lines = revision_tree.get_file(ie.file_id).readlines()
719
vfile.add_lines(rev.revision_id, text_parents, lines)
721
# install the inventory
722
repository.add_inventory(rev.revision_id, inv, present_parents)
723
except errors.RevisionAlreadyPresent:
725
repository.add_revision(rev.revision_id, rev, inv)
728
class MetaDirRepository(Repository):
729
"""Repositories in the new meta-dir layout."""
731
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
732
super(MetaDirRepository, self).__init__(_format,
739
dir_mode = self.control_files._dir_mode
740
file_mode = self.control_files._file_mode
744
"""Return True if this repository is flagged as a shared repository."""
745
return self.control_files._transport.has('shared-storage')
748
def set_make_working_trees(self, new_value):
749
"""Set the policy flag for making working trees when creating branches.
751
This only applies to branches that use this repository.
753
The default is 'True'.
754
:param new_value: True to restore the default, False to disable making
759
self.control_files._transport.delete('no-working-trees')
760
except errors.NoSuchFile:
763
self.control_files.put_utf8('no-working-trees', '')
765
def make_working_trees(self):
766
"""Returns the policy for making working trees on new branches."""
767
return not self.control_files._transport.has('no-working-trees')
770
class KnitRepository(MetaDirRepository):
771
"""Knit format repository."""
773
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
774
inv_vf.add_lines_with_ghosts(revid, parents, lines)
777
def _all_revision_ids(self):
778
"""See Repository.all_revision_ids()."""
779
# Knits get the revision graph from the index of the revision knit, so
780
# it's always possible even if they're on an unlistable transport.
781
return self._revision_store.all_revision_ids(self.get_transaction())
783
def fileid_involved_between_revs(self, from_revid, to_revid):
784
"""Find file_id(s) which are involved in the changes between revisions.
786
This determines the set of revisions which are involved, and then
787
finds all file ids affected by those revisions.
789
vf = self._get_revision_vf()
790
from_set = set(vf.get_ancestry(from_revid))
791
to_set = set(vf.get_ancestry(to_revid))
792
changed = to_set.difference(from_set)
793
return self._fileid_involved_by_set(changed)
795
def fileid_involved(self, last_revid=None):
796
"""Find all file_ids modified in the ancestry of last_revid.
798
:param last_revid: If None, last_revision() will be used.
801
changed = set(self.all_revision_ids())
803
changed = set(self.get_ancestry(last_revid))
806
return self._fileid_involved_by_set(changed)
809
def get_ancestry(self, revision_id):
810
"""Return a list of revision-ids integrated by a revision.
812
This is topologically sorted.
814
if revision_id is None:
816
vf = self._get_revision_vf()
818
return [None] + vf.get_ancestry(revision_id)
819
except errors.RevisionNotPresent:
820
raise errors.NoSuchRevision(self, revision_id)
823
def get_revision(self, revision_id):
824
"""Return the Revision object for a named revision"""
825
return self.get_revision_reconcile(revision_id)
828
def get_revision_graph(self, revision_id=None):
829
"""Return a dictionary containing the revision graph.
831
:return: a dictionary of revision_id->revision_parents_list.
833
weave = self._get_revision_vf()
834
entire_graph = weave.get_graph()
835
if revision_id is None:
836
return weave.get_graph()
837
elif revision_id not in weave:
838
raise errors.NoSuchRevision(self, revision_id)
840
# add what can be reached from revision_id
842
pending = set([revision_id])
843
while len(pending) > 0:
845
result[node] = weave.get_parents(node)
846
for revision_id in result[node]:
847
if revision_id not in result:
848
pending.add(revision_id)
852
def get_revision_graph_with_ghosts(self, revision_ids=None):
853
"""Return a graph of the revisions with ghosts marked as applicable.
855
:param revision_ids: an iterable of revisions to graph or None for all.
856
:return: a Graph object with the graph reachable from revision_ids.
859
vf = self._get_revision_vf()
860
versions = set(vf.versions())
862
pending = set(self._all_revision_ids())
865
pending = set(revision_ids)
866
required = set(revision_ids)
869
revision_id = pending.pop()
870
if not revision_id in versions:
871
if revision_id in required:
872
raise errors.NoSuchRevision(self, revision_id)
874
result.add_ghost(revision_id)
875
# mark it as done so we don't try for it again.
876
done.add(revision_id)
878
parent_ids = vf.get_parents_with_ghosts(revision_id)
879
for parent_id in parent_ids:
880
# is this queued or done ?
881
if (parent_id not in pending and
882
parent_id not in done):
884
pending.add(parent_id)
885
result.add_node(revision_id, parent_ids)
886
done.add(revision_id)
889
def _get_revision_vf(self):
890
""":return: a versioned file containing the revisions."""
891
vf = self._revision_store.get_revision_file(self.get_transaction())
895
def reconcile(self, other=None, thorough=False):
896
"""Reconcile this repository."""
897
from bzrlib.reconcile import KnitReconciler
898
reconciler = KnitReconciler(self, thorough=thorough)
899
reconciler.reconcile()
902
def revision_parents(self, revision_id):
903
return self._get_revision_vf().get_parents(revision_id)
906
class RepositoryFormat(object):
907
"""A repository format.
909
Formats provide three things:
910
* An initialization routine to construct repository data on disk.
911
* a format string which is used when the BzrDir supports versioned
913
* an open routine which returns a Repository instance.
915
Formats are placed in an dict by their format string for reference
916
during opening. These should be subclasses of RepositoryFormat
919
Once a format is deprecated, just deprecate the initialize and open
920
methods on the format class. Do not deprecate the object, as the
921
object will be created every system load.
923
Common instance attributes:
924
_matchingbzrdir - the bzrdir format that the repository format was
925
originally written to work with. This can be used if manually
926
constructing a bzrdir and repository, or more commonly for test suite
930
_default_format = None
931
"""The default format used for new repositories."""
934
"""The known formats."""
937
def find_format(klass, a_bzrdir):
938
"""Return the format for the repository object in a_bzrdir."""
940
transport = a_bzrdir.get_repository_transport(None)
941
format_string = transport.get("format").read()
942
return klass._formats[format_string]
943
except errors.NoSuchFile:
944
raise errors.NoRepositoryPresent(a_bzrdir)
946
raise errors.UnknownFormatError(format_string)
948
def _get_control_store(self, repo_transport, control_files):
949
"""Return the control store for this repository."""
950
raise NotImplementedError(self._get_control_store)
953
def get_default_format(klass):
954
"""Return the current default format."""
955
return klass._default_format
957
def get_format_string(self):
958
"""Return the ASCII format string that identifies this format.
960
Note that in pre format ?? repositories the format string is
961
not permitted nor written to disk.
963
raise NotImplementedError(self.get_format_string)
965
def get_format_description(self):
966
"""Return the short description for this format."""
967
raise NotImplementedError(self.get_format_description)
969
def _get_revision_store(self, repo_transport, control_files):
970
"""Return the revision store object for this a_bzrdir."""
971
raise NotImplementedError(self._get_revision_store)
973
def _get_text_rev_store(self,
980
"""Common logic for getting a revision store for a repository.
982
see self._get_revision_store for the subclass-overridable method to
983
get the store for a repository.
985
from bzrlib.store.revision.text import TextRevisionStore
986
dir_mode = control_files._dir_mode
987
file_mode = control_files._file_mode
988
text_store =TextStore(transport.clone(name),
990
compressed=compressed,
993
_revision_store = TextRevisionStore(text_store, serializer)
994
return _revision_store
996
def _get_versioned_file_store(self,
1001
versionedfile_class=WeaveFile,
1003
weave_transport = control_files._transport.clone(name)
1004
dir_mode = control_files._dir_mode
1005
file_mode = control_files._file_mode
1006
return VersionedFileStore(weave_transport, prefixed=prefixed,
1008
file_mode=file_mode,
1009
versionedfile_class=versionedfile_class,
1012
def initialize(self, a_bzrdir, shared=False):
1013
"""Initialize a repository of this format in a_bzrdir.
1015
:param a_bzrdir: The bzrdir to put the new repository in it.
1016
:param shared: The repository should be initialized as a sharable one.
1018
This may raise UninitializableFormat if shared repository are not
1019
compatible the a_bzrdir.
1022
def is_supported(self):
1023
"""Is this format supported?
1025
Supported formats must be initializable and openable.
1026
Unsupported formats may not support initialization or committing or
1027
some other features depending on the reason for not being supported.
1031
def open(self, a_bzrdir, _found=False):
1032
"""Return an instance of this format for the bzrdir a_bzrdir.
1034
_found is a private parameter, do not use it.
1036
raise NotImplementedError(self.open)
1039
def register_format(klass, format):
1040
klass._formats[format.get_format_string()] = format
1043
def set_default_format(klass, format):
1044
klass._default_format = format
1047
def unregister_format(klass, format):
1048
assert klass._formats[format.get_format_string()] is format
1049
del klass._formats[format.get_format_string()]
1052
class PreSplitOutRepositoryFormat(RepositoryFormat):
1053
"""Base class for the pre split out repository formats."""
1055
def initialize(self, a_bzrdir, shared=False, _internal=False):
1056
"""Create a weave repository.
1058
TODO: when creating split out bzr branch formats, move this to a common
1059
base for Format5, Format6. or something like that.
1061
from bzrlib.weavefile import write_weave_v5
1062
from bzrlib.weave import Weave
1065
raise errors.IncompatibleFormat(self, a_bzrdir._format)
1068
# always initialized when the bzrdir is.
1069
return self.open(a_bzrdir, _found=True)
1071
# Create an empty weave
1073
write_weave_v5(Weave(), sio)
1074
empty_weave = sio.getvalue()
1076
mutter('creating repository in %s.', a_bzrdir.transport.base)
1077
dirs = ['revision-store', 'weaves']
1078
files = [('inventory.weave', StringIO(empty_weave)),
1081
# FIXME: RBC 20060125 don't peek under the covers
1082
# NB: no need to escape relative paths that are url safe.
1083
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock',
1085
control_files.create_lock()
1086
control_files.lock_write()
1087
control_files._transport.mkdir_multi(dirs,
1088
mode=control_files._dir_mode)
1090
for file, content in files:
1091
control_files.put(file, content)
1093
control_files.unlock()
1094
return self.open(a_bzrdir, _found=True)
1096
def _get_control_store(self, repo_transport, control_files):
1097
"""Return the control store for this repository."""
1098
return self._get_versioned_file_store('',
1103
def _get_text_store(self, transport, control_files):
1104
"""Get a store for file texts for this format."""
1105
raise NotImplementedError(self._get_text_store)
1107
def open(self, a_bzrdir, _found=False):
1108
"""See RepositoryFormat.open()."""
1110
# we are being called directly and must probe.
1111
raise NotImplementedError
1113
repo_transport = a_bzrdir.get_repository_transport(None)
1114
control_files = a_bzrdir._control_files
1115
text_store = self._get_text_store(repo_transport, control_files)
1116
control_store = self._get_control_store(repo_transport, control_files)
1117
_revision_store = self._get_revision_store(repo_transport, control_files)
1118
return AllInOneRepository(_format=self,
1120
_revision_store=_revision_store,
1121
control_store=control_store,
1122
text_store=text_store)
1125
class RepositoryFormat4(PreSplitOutRepositoryFormat):
1126
"""Bzr repository format 4.
1128
This repository format has:
1130
- TextStores for texts, inventories,revisions.
1132
This format is deprecated: it indexes texts using a text id which is
1133
removed in format 5; initialization and write support for this format
1138
super(RepositoryFormat4, self).__init__()
1139
self._matchingbzrdir = bzrdir.BzrDirFormat4()
1141
def get_format_description(self):
1142
"""See RepositoryFormat.get_format_description()."""
1143
return "Repository format 4"
1145
def initialize(self, url, shared=False, _internal=False):
1146
"""Format 4 branches cannot be created."""
1147
raise errors.UninitializableFormat(self)
1149
def is_supported(self):
1150
"""Format 4 is not supported.
1152
It is not supported because the model changed from 4 to 5 and the
1153
conversion logic is expensive - so doing it on the fly was not
1158
def _get_control_store(self, repo_transport, control_files):
1159
"""Format 4 repositories have no formal control store at this point.
1161
This will cause any control-file-needing apis to fail - this is desired.
1165
def _get_revision_store(self, repo_transport, control_files):
1166
"""See RepositoryFormat._get_revision_store()."""
1167
from bzrlib.xml4 import serializer_v4
1168
return self._get_text_rev_store(repo_transport,
1171
serializer=serializer_v4)
1173
def _get_text_store(self, transport, control_files):
1174
"""See RepositoryFormat._get_text_store()."""
1177
class RepositoryFormat5(PreSplitOutRepositoryFormat):
1178
"""Bzr control format 5.
1180
This repository format has:
1181
- weaves for file texts and inventory
1183
- TextStores for revisions and signatures.
1187
super(RepositoryFormat5, self).__init__()
1188
self._matchingbzrdir = bzrdir.BzrDirFormat5()
1190
def get_format_description(self):
1191
"""See RepositoryFormat.get_format_description()."""
1192
return "Weave repository format 5"
1194
def _get_revision_store(self, repo_transport, control_files):
1195
"""See RepositoryFormat._get_revision_store()."""
1196
"""Return the revision store object for this a_bzrdir."""
1197
return self._get_text_rev_store(repo_transport,
1202
def _get_text_store(self, transport, control_files):
1203
"""See RepositoryFormat._get_text_store()."""
1204
return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
1207
class RepositoryFormat6(PreSplitOutRepositoryFormat):
1208
"""Bzr control format 6.
1210
This repository format has:
1211
- weaves for file texts and inventory
1212
- hash subdirectory based stores.
1213
- TextStores for revisions and signatures.
1217
super(RepositoryFormat6, self).__init__()
1218
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1220
def get_format_description(self):
1221
"""See RepositoryFormat.get_format_description()."""
1222
return "Weave repository format 6"
1224
def _get_revision_store(self, repo_transport, control_files):
1225
"""See RepositoryFormat._get_revision_store()."""
1226
return self._get_text_rev_store(repo_transport,
1232
def _get_text_store(self, transport, control_files):
1233
"""See RepositoryFormat._get_text_store()."""
1234
return self._get_versioned_file_store('weaves', transport, control_files)
1237
class MetaDirRepositoryFormat(RepositoryFormat):
1238
"""Common base class for the new repositories using the metadir layout."""
1241
super(MetaDirRepositoryFormat, self).__init__()
1242
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1244
def _create_control_files(self, a_bzrdir):
1245
"""Create the required files and the initial control_files object."""
1246
# FIXME: RBC 20060125 don't peek under the covers
1247
# NB: no need to escape relative paths that are url safe.
1248
repository_transport = a_bzrdir.get_repository_transport(self)
1249
control_files = LockableFiles(repository_transport, 'lock', LockDir)
1250
control_files.create_lock()
1251
return control_files
1253
def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
1254
"""Upload the initial blank content."""
1255
control_files = self._create_control_files(a_bzrdir)
1256
control_files.lock_write()
1258
control_files._transport.mkdir_multi(dirs,
1259
mode=control_files._dir_mode)
1260
for file, content in files:
1261
control_files.put(file, content)
1262
for file, content in utf8_files:
1263
control_files.put_utf8(file, content)
1265
control_files.put_utf8('shared-storage', '')
1267
control_files.unlock()
1270
class RepositoryFormat7(MetaDirRepositoryFormat):
1271
"""Bzr repository 7.
1273
This repository format has:
1274
- weaves for file texts and inventory
1275
- hash subdirectory based stores.
1276
- TextStores for revisions and signatures.
1277
- a format marker of its own
1278
- an optional 'shared-storage' flag
1279
- an optional 'no-working-trees' flag
1282
def _get_control_store(self, repo_transport, control_files):
1283
"""Return the control store for this repository."""
1284
return self._get_versioned_file_store('',
1289
def get_format_string(self):
1290
"""See RepositoryFormat.get_format_string()."""
1291
return "Bazaar-NG Repository format 7"
1293
def get_format_description(self):
1294
"""See RepositoryFormat.get_format_description()."""
1295
return "Weave repository format 7"
1297
def _get_revision_store(self, repo_transport, control_files):
1298
"""See RepositoryFormat._get_revision_store()."""
1299
return self._get_text_rev_store(repo_transport,
1306
def _get_text_store(self, transport, control_files):
1307
"""See RepositoryFormat._get_text_store()."""
1308
return self._get_versioned_file_store('weaves',
1312
def initialize(self, a_bzrdir, shared=False):
1313
"""Create a weave repository.
1315
:param shared: If true the repository will be initialized as a shared
1318
from bzrlib.weavefile import write_weave_v5
1319
from bzrlib.weave import Weave
1321
# Create an empty weave
1323
write_weave_v5(Weave(), sio)
1324
empty_weave = sio.getvalue()
1326
mutter('creating repository in %s.', a_bzrdir.transport.base)
1327
dirs = ['revision-store', 'weaves']
1328
files = [('inventory.weave', StringIO(empty_weave)),
1330
utf8_files = [('format', self.get_format_string())]
1332
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1333
return self.open(a_bzrdir=a_bzrdir, _found=True)
1335
def open(self, a_bzrdir, _found=False, _override_transport=None):
1336
"""See RepositoryFormat.open().
1338
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1339
repository at a slightly different url
1340
than normal. I.e. during 'upgrade'.
1343
format = RepositoryFormat.find_format(a_bzrdir)
1344
assert format.__class__ == self.__class__
1345
if _override_transport is not None:
1346
repo_transport = _override_transport
1348
repo_transport = a_bzrdir.get_repository_transport(None)
1349
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1350
text_store = self._get_text_store(repo_transport, control_files)
1351
control_store = self._get_control_store(repo_transport, control_files)
1352
_revision_store = self._get_revision_store(repo_transport, control_files)
1353
return MetaDirRepository(_format=self,
1355
control_files=control_files,
1356
_revision_store=_revision_store,
1357
control_store=control_store,
1358
text_store=text_store)
1361
class RepositoryFormatKnit1(MetaDirRepositoryFormat):
1362
"""Bzr repository knit format 1.
1364
This repository format has:
1365
- knits for file texts and inventory
1366
- hash subdirectory based stores.
1367
- knits for revisions and signatures
1368
- TextStores for revisions and signatures.
1369
- a format marker of its own
1370
- an optional 'shared-storage' flag
1371
- an optional 'no-working-trees' flag
1374
This format was introduced in bzr 0.8.
1377
def _get_control_store(self, repo_transport, control_files):
1378
"""Return the control store for this repository."""
1379
return VersionedFileStore(
1382
file_mode=control_files._file_mode,
1383
versionedfile_class=KnitVersionedFile,
1384
versionedfile_kwargs={'factory':KnitPlainFactory()},
1387
def get_format_string(self):
1388
"""See RepositoryFormat.get_format_string()."""
1389
return "Bazaar-NG Knit Repository Format 1"
1391
def get_format_description(self):
1392
"""See RepositoryFormat.get_format_description()."""
1393
return "Knit repository format 1"
1395
def _get_revision_store(self, repo_transport, control_files):
1396
"""See RepositoryFormat._get_revision_store()."""
1397
from bzrlib.store.revision.knit import KnitRevisionStore
1398
versioned_file_store = VersionedFileStore(
1400
file_mode=control_files._file_mode,
1403
versionedfile_class=KnitVersionedFile,
1404
versionedfile_kwargs={'delta':False, 'factory':KnitPlainFactory()},
1407
return KnitRevisionStore(versioned_file_store)
1409
def _get_text_store(self, transport, control_files):
1410
"""See RepositoryFormat._get_text_store()."""
1411
return self._get_versioned_file_store('knits',
1414
versionedfile_class=KnitVersionedFile,
1417
def initialize(self, a_bzrdir, shared=False):
1418
"""Create a knit format 1 repository.
1420
:param a_bzrdir: bzrdir to contain the new repository; must already
1422
:param shared: If true the repository will be initialized as a shared
1425
mutter('creating repository in %s.', a_bzrdir.transport.base)
1426
dirs = ['revision-store', 'knits']
1428
utf8_files = [('format', self.get_format_string())]
1430
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1431
repo_transport = a_bzrdir.get_repository_transport(None)
1432
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1433
control_store = self._get_control_store(repo_transport, control_files)
1434
transaction = transactions.WriteTransaction()
1435
# trigger a write of the inventory store.
1436
control_store.get_weave_or_empty('inventory', transaction)
1437
_revision_store = self._get_revision_store(repo_transport, control_files)
1438
_revision_store.has_revision_id('A', transaction)
1439
_revision_store.get_signature_file(transaction)
1440
return self.open(a_bzrdir=a_bzrdir, _found=True)
1442
def open(self, a_bzrdir, _found=False, _override_transport=None):
1443
"""See RepositoryFormat.open().
1445
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1446
repository at a slightly different url
1447
than normal. I.e. during 'upgrade'.
1450
format = RepositoryFormat.find_format(a_bzrdir)
1451
assert format.__class__ == self.__class__
1452
if _override_transport is not None:
1453
repo_transport = _override_transport
1455
repo_transport = a_bzrdir.get_repository_transport(None)
1456
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1457
text_store = self._get_text_store(repo_transport, control_files)
1458
control_store = self._get_control_store(repo_transport, control_files)
1459
_revision_store = self._get_revision_store(repo_transport, control_files)
1460
return KnitRepository(_format=self,
1462
control_files=control_files,
1463
_revision_store=_revision_store,
1464
control_store=control_store,
1465
text_store=text_store)
1468
# formats which have no format string are not discoverable
1469
# and not independently creatable, so are not registered.
1470
RepositoryFormat.register_format(RepositoryFormat7())
1471
_default_format = RepositoryFormatKnit1()
1472
RepositoryFormat.register_format(_default_format)
1473
RepositoryFormat.set_default_format(_default_format)
1474
_legacy_formats = [RepositoryFormat4(),
1475
RepositoryFormat5(),
1476
RepositoryFormat6()]
1479
class InterRepository(InterObject):
1480
"""This class represents operations taking place between two repositories.
1482
Its instances have methods like copy_content and fetch, and contain
1483
references to the source and target repositories these operations can be
1486
Often we will provide convenience methods on 'repository' which carry out
1487
operations with another repository - they will always forward to
1488
InterRepository.get(other).method_name(parameters).
1492
"""The available optimised InterRepository types."""
1495
def copy_content(self, revision_id=None, basis=None):
1496
"""Make a complete copy of the content in self into destination.
1498
This is a destructive operation! Do not use it on existing
1501
:param revision_id: Only copy the content needed to construct
1502
revision_id and its parents.
1503
:param basis: Copy the needed data preferentially from basis.
1506
self.target.set_make_working_trees(self.source.make_working_trees())
1507
except NotImplementedError:
1509
# grab the basis available data
1510
if basis is not None:
1511
self.target.fetch(basis, revision_id=revision_id)
1512
# but don't bother fetching if we have the needed data now.
1513
if (revision_id not in (None, NULL_REVISION) and
1514
self.target.has_revision(revision_id)):
1516
self.target.fetch(self.source, revision_id=revision_id)
1518
def _double_lock(self, lock_source, lock_target):
1519
"""Take out too locks, rolling back the first if the second throws."""
1524
# we want to ensure that we don't leave source locked by mistake.
1525
# and any error on target should not confuse source.
1526
self.source.unlock()
1530
def fetch(self, revision_id=None, pb=None):
1531
"""Fetch the content required to construct revision_id.
1533
The content is copied from source to target.
1535
:param revision_id: if None all content is copied, if NULL_REVISION no
1537
:param pb: optional progress bar to use for progress reports. If not
1538
provided a default one will be created.
1540
Returns the copied revision count and the failed revisions in a tuple:
1543
from bzrlib.fetch import GenericRepoFetcher
1544
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1545
self.source, self.source._format, self.target, self.target._format)
1546
f = GenericRepoFetcher(to_repository=self.target,
1547
from_repository=self.source,
1548
last_revision=revision_id,
1550
return f.count_copied, f.failed_revisions
1552
def lock_read(self):
1553
"""Take out a logical read lock.
1555
This will lock the source branch and the target branch. The source gets
1556
a read lock and the target a read lock.
1558
self._double_lock(self.source.lock_read, self.target.lock_read)
1560
def lock_write(self):
1561
"""Take out a logical write lock.
1563
This will lock the source branch and the target branch. The source gets
1564
a read lock and the target a write lock.
1566
self._double_lock(self.source.lock_read, self.target.lock_write)
1569
def missing_revision_ids(self, revision_id=None):
1570
"""Return the revision ids that source has that target does not.
1572
These are returned in topological order.
1574
:param revision_id: only return revision ids included by this
1577
# generic, possibly worst case, slow code path.
1578
target_ids = set(self.target.all_revision_ids())
1579
if revision_id is not None:
1580
source_ids = self.source.get_ancestry(revision_id)
1581
assert source_ids[0] == None
1584
source_ids = self.source.all_revision_ids()
1585
result_set = set(source_ids).difference(target_ids)
1586
# this may look like a no-op: its not. It preserves the ordering
1587
# other_ids had while only returning the members from other_ids
1588
# that we've decided we need.
1589
return [rev_id for rev_id in source_ids if rev_id in result_set]
1592
"""Release the locks on source and target."""
1594
self.target.unlock()
1596
self.source.unlock()
1599
class InterWeaveRepo(InterRepository):
1600
"""Optimised code paths between Weave based repositories."""
1602
_matching_repo_format = RepositoryFormat7()
1603
"""Repository format for testing with."""
1606
def is_compatible(source, target):
1607
"""Be compatible with known Weave formats.
1609
We don't test for the stores being of specific types because that
1610
could lead to confusing results, and there is no need to be
1614
return (isinstance(source._format, (RepositoryFormat5,
1616
RepositoryFormat7)) and
1617
isinstance(target._format, (RepositoryFormat5,
1619
RepositoryFormat7)))
1620
except AttributeError:
1624
def copy_content(self, revision_id=None, basis=None):
1625
"""See InterRepository.copy_content()."""
1626
# weave specific optimised path:
1627
if basis is not None:
1628
# copy the basis in, then fetch remaining data.
1629
basis.copy_content_into(self.target, revision_id)
1630
# the basis copy_content_into could miss-set this.
1632
self.target.set_make_working_trees(self.source.make_working_trees())
1633
except NotImplementedError:
1635
self.target.fetch(self.source, revision_id=revision_id)
1638
self.target.set_make_working_trees(self.source.make_working_trees())
1639
except NotImplementedError:
1641
# FIXME do not peek!
1642
if self.source.control_files._transport.listable():
1643
pb = ui.ui_factory.nested_progress_bar()
1645
self.target.weave_store.copy_all_ids(
1646
self.source.weave_store,
1648
from_transaction=self.source.get_transaction(),
1649
to_transaction=self.target.get_transaction())
1650
pb.update('copying inventory', 0, 1)
1651
self.target.control_weaves.copy_multi(
1652
self.source.control_weaves, ['inventory'],
1653
from_transaction=self.source.get_transaction(),
1654
to_transaction=self.target.get_transaction())
1655
self.target._revision_store.text_store.copy_all_ids(
1656
self.source._revision_store.text_store,
1661
self.target.fetch(self.source, revision_id=revision_id)
1664
def fetch(self, revision_id=None, pb=None):
1665
"""See InterRepository.fetch()."""
1666
from bzrlib.fetch import GenericRepoFetcher
1667
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1668
self.source, self.source._format, self.target, self.target._format)
1669
f = GenericRepoFetcher(to_repository=self.target,
1670
from_repository=self.source,
1671
last_revision=revision_id,
1673
return f.count_copied, f.failed_revisions
1676
def missing_revision_ids(self, revision_id=None):
1677
"""See InterRepository.missing_revision_ids()."""
1678
# we want all revisions to satisfy revision_id in source.
1679
# but we don't want to stat every file here and there.
1680
# we want then, all revisions other needs to satisfy revision_id
1681
# checked, but not those that we have locally.
1682
# so the first thing is to get a subset of the revisions to
1683
# satisfy revision_id in source, and then eliminate those that
1684
# we do already have.
1685
# this is slow on high latency connection to self, but as as this
1686
# disk format scales terribly for push anyway due to rewriting
1687
# inventory.weave, this is considered acceptable.
1689
if revision_id is not None:
1690
source_ids = self.source.get_ancestry(revision_id)
1691
assert source_ids[0] == None
1694
source_ids = self.source._all_possible_ids()
1695
source_ids_set = set(source_ids)
1696
# source_ids is the worst possible case we may need to pull.
1697
# now we want to filter source_ids against what we actually
1698
# have in target, but don't try to check for existence where we know
1699
# we do not have a revision as that would be pointless.
1700
target_ids = set(self.target._all_possible_ids())
1701
possibly_present_revisions = target_ids.intersection(source_ids_set)
1702
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1703
required_revisions = source_ids_set.difference(actually_present_revisions)
1704
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1705
if revision_id is not None:
1706
# we used get_ancestry to determine source_ids then we are assured all
1707
# revisions referenced are present as they are installed in topological order.
1708
# and the tip revision was validated by get_ancestry.
1709
return required_topo_revisions
1711
# if we just grabbed the possibly available ids, then
1712
# we only have an estimate of whats available and need to validate
1713
# that against the revision records.
1714
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1717
class InterKnitRepo(InterRepository):
1718
"""Optimised code paths between Knit based repositories."""
1720
_matching_repo_format = RepositoryFormatKnit1()
1721
"""Repository format for testing with."""
1724
def is_compatible(source, target):
1725
"""Be compatible with known Knit formats.
1727
We don't test for the stores being of specific types because that
1728
could lead to confusing results, and there is no need to be
1732
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1733
isinstance(target._format, (RepositoryFormatKnit1)))
1734
except AttributeError:
1738
def fetch(self, revision_id=None, pb=None):
1739
"""See InterRepository.fetch()."""
1740
from bzrlib.fetch import KnitRepoFetcher
1741
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1742
self.source, self.source._format, self.target, self.target._format)
1743
f = KnitRepoFetcher(to_repository=self.target,
1744
from_repository=self.source,
1745
last_revision=revision_id,
1747
return f.count_copied, f.failed_revisions
1750
def missing_revision_ids(self, revision_id=None):
1751
"""See InterRepository.missing_revision_ids()."""
1752
if revision_id is not None:
1753
source_ids = self.source.get_ancestry(revision_id)
1754
assert source_ids[0] == None
1757
source_ids = self.source._all_possible_ids()
1758
source_ids_set = set(source_ids)
1759
# source_ids is the worst possible case we may need to pull.
1760
# now we want to filter source_ids against what we actually
1761
# have in target, but don't try to check for existence where we know
1762
# we do not have a revision as that would be pointless.
1763
target_ids = set(self.target._all_possible_ids())
1764
possibly_present_revisions = target_ids.intersection(source_ids_set)
1765
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1766
required_revisions = source_ids_set.difference(actually_present_revisions)
1767
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1768
if revision_id is not None:
1769
# we used get_ancestry to determine source_ids then we are assured all
1770
# revisions referenced are present as they are installed in topological order.
1771
# and the tip revision was validated by get_ancestry.
1772
return required_topo_revisions
1774
# if we just grabbed the possibly available ids, then
1775
# we only have an estimate of whats available and need to validate
1776
# that against the revision records.
1777
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1779
InterRepository.register_optimiser(InterWeaveRepo)
1780
InterRepository.register_optimiser(InterKnitRepo)
1783
class RepositoryTestProviderAdapter(object):
1784
"""A tool to generate a suite testing multiple repository formats at once.
1786
This is done by copying the test once for each transport and injecting
1787
the transport_server, transport_readonly_server, and bzrdir_format and
1788
repository_format classes into each copy. Each copy is also given a new id()
1789
to make it easy to identify.
1792
def __init__(self, transport_server, transport_readonly_server, formats):
1793
self._transport_server = transport_server
1794
self._transport_readonly_server = transport_readonly_server
1795
self._formats = formats
1797
def adapt(self, test):
1798
result = TestSuite()
1799
for repository_format, bzrdir_format in self._formats:
1800
new_test = deepcopy(test)
1801
new_test.transport_server = self._transport_server
1802
new_test.transport_readonly_server = self._transport_readonly_server
1803
new_test.bzrdir_format = bzrdir_format
1804
new_test.repository_format = repository_format
1805
def make_new_test_id():
1806
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
1807
return lambda: new_id
1808
new_test.id = make_new_test_id()
1809
result.addTest(new_test)
1813
class InterRepositoryTestProviderAdapter(object):
1814
"""A tool to generate a suite testing multiple inter repository formats.
1816
This is done by copying the test once for each interrepo provider and injecting
1817
the transport_server, transport_readonly_server, repository_format and
1818
repository_to_format classes into each copy.
1819
Each copy is also given a new id() to make it easy to identify.
1822
def __init__(self, transport_server, transport_readonly_server, formats):
1823
self._transport_server = transport_server
1824
self._transport_readonly_server = transport_readonly_server
1825
self._formats = formats
1827
def adapt(self, test):
1828
result = TestSuite()
1829
for interrepo_class, repository_format, repository_format_to in self._formats:
1830
new_test = deepcopy(test)
1831
new_test.transport_server = self._transport_server
1832
new_test.transport_readonly_server = self._transport_readonly_server
1833
new_test.interrepo_class = interrepo_class
1834
new_test.repository_format = repository_format
1835
new_test.repository_format_to = repository_format_to
1836
def make_new_test_id():
1837
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
1838
return lambda: new_id
1839
new_test.id = make_new_test_id()
1840
result.addTest(new_test)
1844
def default_test_list():
1845
"""Generate the default list of interrepo permutations to test."""
1847
# test the default InterRepository between format 6 and the current
1849
# XXX: robertc 20060220 reinstate this when there are two supported
1850
# formats which do not have an optimal code path between them.
1851
result.append((InterRepository,
1852
RepositoryFormat6(),
1853
RepositoryFormatKnit1()))
1854
for optimiser in InterRepository._optimisers:
1855
result.append((optimiser,
1856
optimiser._matching_repo_format,
1857
optimiser._matching_repo_format
1859
# if there are specific combinations we want to use, we can add them
1864
class CopyConverter(object):
1865
"""A repository conversion tool which just performs a copy of the content.
1867
This is slow but quite reliable.
1870
def __init__(self, target_format):
1871
"""Create a CopyConverter.
1873
:param target_format: The format the resulting repository should be.
1875
self.target_format = target_format
1877
def convert(self, repo, pb):
1878
"""Perform the conversion of to_convert, giving feedback via pb.
1880
:param to_convert: The disk object to convert.
1881
:param pb: a progress bar to use for progress information.
1886
# this is only useful with metadir layouts - separated repo content.
1887
# trigger an assertion if not such
1888
repo._format.get_format_string()
1889
self.repo_dir = repo.bzrdir
1890
self.step('Moving repository to repository.backup')
1891
self.repo_dir.transport.move('repository', 'repository.backup')
1892
backup_transport = self.repo_dir.transport.clone('repository.backup')
1893
self.source_repo = repo._format.open(self.repo_dir,
1895
_override_transport=backup_transport)
1896
self.step('Creating new repository')
1897
converted = self.target_format.initialize(self.repo_dir,
1898
self.source_repo.is_shared())
1899
converted.lock_write()
1901
self.step('Copying content into repository.')
1902
self.source_repo.copy_content_into(converted)
1905
self.step('Deleting old repository content.')
1906
self.repo_dir.transport.delete_tree('repository.backup')
1907
self.pb.note('repository converted')
1909
def step(self, message):
1910
"""Update the pb by a step."""
1912
self.pb.update(message, self.count, self.total)
1915
class CommitBuilder(object):
1916
"""Provides an interface to build up a commit.
1918
This allows describing a tree to be committed without needing to
1919
know the internals of the format of the repository.
1921
def __init__(self, repository, parents, config, timestamp=None,
1922
timezone=None, committer=None, revprops=None,
1924
"""Initiate a CommitBuilder.
1926
:param repository: Repository to commit to.
1927
:param parents: Revision ids of the parents of the new revision.
1928
:param config: Configuration to use.
1929
:param timestamp: Optional timestamp recorded for commit.
1930
:param timezone: Optional timezone for timestamp.
1931
:param committer: Optional committer to set for commit.
1932
:param revprops: Optional dictionary of revision properties.
1933
:param revision_id: Optional revision id.
1935
self._config = config
1937
if committer is None:
1938
self._committer = self._config.username()
1940
assert isinstance(committer, basestring), type(committer)
1941
self._committer = committer
1943
self.new_inventory = Inventory()
1944
self._new_revision_id = revision_id
1945
self.parents = parents
1946
self.repository = repository
1949
if revprops is not None:
1950
self._revprops.update(revprops)
1952
if timestamp is None:
1953
self._timestamp = time.time()
1955
self._timestamp = long(timestamp)
1957
if timezone is None:
1958
self._timezone = local_time_offset()
1960
self._timezone = int(timezone)
1962
self._generate_revision_if_needed()
1964
def commit(self, message):
1965
"""Make the actual commit.
1967
:return: The revision id of the recorded revision.
1969
rev = Revision(timestamp=self._timestamp,
1970
timezone=self._timezone,
1971
committer=self._committer,
1973
inventory_sha1=self.inv_sha1,
1974
revision_id=self._new_revision_id,
1975
properties=self._revprops)
1976
rev.parent_ids = self.parents
1977
self.repository.add_revision(self._new_revision_id, rev,
1978
self.new_inventory, self._config)
1979
return self._new_revision_id
1981
def finish_inventory(self):
1982
"""Tell the builder that the inventory is finished."""
1983
self.new_inventory.revision_id = self._new_revision_id
1984
self.inv_sha1 = self.repository.add_inventory(
1985
self._new_revision_id,
1990
def _gen_revision_id(self):
1991
"""Return new revision-id."""
1992
s = '%s-%s-' % (self._config.user_email(),
1993
compact_date(self._timestamp))
1994
s += hexlify(rand_bytes(8))
1997
def _generate_revision_if_needed(self):
1998
"""Create a revision id if None was supplied.
2000
If the repository can not support user-specified revision ids
2001
they should override this function and raise UnsupportedOperation
2002
if _new_revision_id is not None.
2004
:raises: UnsupportedOperation
2006
if self._new_revision_id is None:
2007
self._new_revision_id = self._gen_revision_id()
2009
def record_entry_contents(self, ie, parent_invs, path, tree):
2010
"""Record the content of ie from tree into the commit if needed.
2012
:param ie: An inventory entry present in the commit.
2013
:param parent_invs: The inventories of the parent revisions of the
2015
:param path: The path the entry is at in the tree.
2016
:param tree: The tree which contains this entry and should be used to
2019
self.new_inventory.add(ie)
2021
# ie.revision is always None if the InventoryEntry is considered
2022
# for committing. ie.snapshot will record the correct revision
2023
# which may be the sole parent if it is untouched.
2024
if ie.revision is not None:
2026
previous_entries = ie.find_previous_heads(
2028
self.repository.weave_store,
2029
self.repository.get_transaction())
2030
# we are creating a new revision for ie in the history store
2032
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2034
def modified_directory(self, file_id, file_parents):
2035
"""Record the presence of a symbolic link.
2037
:param file_id: The file_id of the link to record.
2038
:param file_parents: The per-file parent revision ids.
2040
self._add_text_to_weave(file_id, [], file_parents.keys())
2042
def modified_file_text(self, file_id, file_parents,
2043
get_content_byte_lines, text_sha1=None,
2045
"""Record the text of file file_id
2047
:param file_id: The file_id of the file to record the text of.
2048
:param file_parents: The per-file parent revision ids.
2049
:param get_content_byte_lines: A callable which will return the byte
2051
:param text_sha1: Optional SHA1 of the file contents.
2052
:param text_size: Optional size of the file contents.
2054
mutter('storing text of file {%s} in revision {%s} into %r',
2055
file_id, self._new_revision_id, self.repository.weave_store)
2056
# special case to avoid diffing on renames or
2058
if (len(file_parents) == 1
2059
and text_sha1 == file_parents.values()[0].text_sha1
2060
and text_size == file_parents.values()[0].text_size):
2061
previous_ie = file_parents.values()[0]
2062
versionedfile = self.repository.weave_store.get_weave(file_id,
2063
self.repository.get_transaction())
2064
versionedfile.clone_text(self._new_revision_id,
2065
previous_ie.revision, file_parents.keys())
2066
return text_sha1, text_size
2068
new_lines = get_content_byte_lines()
2069
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2070
# should return the SHA1 and size
2071
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2072
return osutils.sha_strings(new_lines), \
2073
sum(map(len, new_lines))
2075
def modified_link(self, file_id, file_parents, link_target):
2076
"""Record the presence of a symbolic link.
2078
:param file_id: The file_id of the link to record.
2079
:param file_parents: The per-file parent revision ids.
2080
:param link_target: Target location of this link.
2082
self._add_text_to_weave(file_id, [], file_parents.keys())
2084
def _add_text_to_weave(self, file_id, new_lines, parents):
2085
versionedfile = self.repository.weave_store.get_weave_or_empty(
2086
file_id, self.repository.get_transaction())
2087
versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2088
versionedfile.clear_cache()
2091
# Copied from xml.sax.saxutils
2092
def _unescape_xml(data):
2093
"""Unescape &, <, and > in a string of data.
2095
data = data.replace("<", "<")
2096
data = data.replace(">", ">")
2097
# must do ampersand last
2098
return data.replace("&", "&")