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
import bzrlib.bzrdir as bzrdir
25
from bzrlib.decorators import needs_read_lock, needs_write_lock
26
import bzrlib.errors as errors
27
from bzrlib.errors import InvalidRevisionId
28
import bzrlib.gpg as gpg
29
from bzrlib.graph import Graph
30
from bzrlib.inter import InterObject
31
from bzrlib.inventory import Inventory
32
from bzrlib.knit import KnitVersionedFile, KnitPlainFactory
33
from bzrlib.lockable_files import LockableFiles, TransportLock
34
from bzrlib.lockdir import LockDir
35
from bzrlib.osutils import (safe_unicode, rand_bytes, compact_date,
37
from bzrlib.revision import NULL_REVISION, Revision
38
from bzrlib.store.versioned import VersionedFileStore, WeaveStore
39
from bzrlib.store.text import TextStore
40
from bzrlib.symbol_versioning import *
41
from bzrlib.trace import mutter, note
42
from bzrlib.tree import RevisionTree, EmptyTree
43
from bzrlib.tsort import topo_sort
44
from bzrlib.testament import Testament
45
from bzrlib.tree import EmptyTree
46
from bzrlib.delta import compare_trees
48
from bzrlib.weave import WeaveFile
52
class Repository(object):
53
"""Repository holding history for one or more branches.
55
The repository holds and retrieves historical information including
56
revisions and file history. It's normally accessed only by the Branch,
57
which views a particular line of development through that history.
59
The Repository builds on top of Stores and a Transport, which respectively
60
describe the disk data format and the way of accessing the (possibly
65
def add_inventory(self, revid, inv, parents):
66
"""Add the inventory inv to the repository as revid.
68
:param parents: The revision ids of the parents that revid
69
is known to have and are in the repository already.
71
returns the sha1 of the serialized inventory.
73
assert inv.revision_id is None or inv.revision_id == revid, \
74
"Mismatch between inventory revision" \
75
" id and insertion revid (%r, %r)" % (inv.revision_id, revid)
76
inv_text = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
77
inv_sha1 = bzrlib.osutils.sha_string(inv_text)
78
inv_vf = self.control_weaves.get_weave('inventory',
79
self.get_transaction())
80
self._inventory_add_lines(inv_vf, revid, parents, bzrlib.osutils.split_lines(inv_text))
83
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
85
for parent in parents:
87
final_parents.append(parent)
89
inv_vf.add_lines(revid, final_parents, lines)
92
def add_revision(self, rev_id, rev, inv=None, config=None):
93
"""Add rev to the revision store as rev_id.
95
:param rev_id: the revision id to use.
96
:param rev: The revision object.
97
:param inv: The inventory for the revision. if None, it will be looked
98
up in the inventory storer
99
:param config: If None no digital signature will be created.
100
If supplied its signature_needed method will be used
101
to determine if a signature should be made.
103
if config is not None and config.signature_needed():
105
inv = self.get_inventory(rev_id)
106
plaintext = Testament(rev, inv).as_short_text()
107
self.store_revision_signature(
108
gpg.GPGStrategy(config), plaintext, rev_id)
109
if not rev_id in self.get_inventory_weave():
111
raise errors.WeaveRevisionNotPresent(rev_id,
112
self.get_inventory_weave())
114
# yes, this is not suitable for adding with ghosts.
115
self.add_inventory(rev_id, inv, rev.parent_ids)
116
self._revision_store.add_revision(rev, self.get_transaction())
119
def _all_possible_ids(self):
120
"""Return all the possible revisions that we could find."""
121
return self.get_inventory_weave().versions()
123
@deprecated_method(zero_nine)
124
def all_revision_ids(self):
125
"""Returns a list of all the revision ids in the repository.
127
This is deprecated because code should generally work on the graph
128
reachable from a particular revision, and ignore any other revisions
129
that might be present. There is no direct replacement method.
131
return self._all_revision_ids()
134
def _all_revision_ids(self):
135
"""Returns a list of all the revision ids in the repository.
137
These are in as much topological order as the underlying store can
138
present: for weaves ghosts may lead to a lack of correctness until
139
the reweave updates the parents list.
141
if self._revision_store.text_store.listable():
142
return self._revision_store.all_revision_ids(self.get_transaction())
143
result = self._all_possible_ids()
144
return self._eliminate_revisions_not_present(result)
146
def break_lock(self):
147
"""Break a lock if one is present from another instance.
149
Uses the ui factory to ask for confirmation if the lock may be from
152
self.control_files.break_lock()
155
def _eliminate_revisions_not_present(self, revision_ids):
156
"""Check every revision id in revision_ids to see if we have it.
158
Returns a set of the present revisions.
161
for id in revision_ids:
162
if self.has_revision(id):
167
def create(a_bzrdir):
168
"""Construct the current default format repository in a_bzrdir."""
169
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
171
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
172
"""instantiate a Repository.
174
:param _format: The format of the repository on disk.
175
:param a_bzrdir: The BzrDir of the repository.
177
In the future we will have a single api for all stores for
178
getting file texts, inventories and revisions, then
179
this construct will accept instances of those things.
181
super(Repository, self).__init__()
182
self._format = _format
183
# the following are part of the public API for Repository:
184
self.bzrdir = a_bzrdir
185
self.control_files = control_files
186
self._revision_store = _revision_store
187
self.text_store = text_store
188
# backwards compatibility
189
self.weave_store = text_store
190
# not right yet - should be more semantically clear ?
192
self.control_store = control_store
193
self.control_weaves = control_store
194
# TODO: make sure to construct the right store classes, etc, depending
195
# on whether escaping is required.
198
return '%s(%r)' % (self.__class__.__name__,
199
self.bzrdir.transport.base)
202
return self.control_files.is_locked()
204
def lock_write(self):
205
self.control_files.lock_write()
208
self.control_files.lock_read()
210
def get_physical_lock_status(self):
211
return self.control_files.get_physical_lock_status()
214
def missing_revision_ids(self, other, revision_id=None):
215
"""Return the revision ids that other has that this does not.
217
These are returned in topological order.
219
revision_id: only return revision ids included by revision_id.
221
return InterRepository.get(other, self).missing_revision_ids(revision_id)
225
"""Open the repository rooted at base.
227
For instance, if the repository is at URL/.bzr/repository,
228
Repository.open(URL) -> a Repository instance.
230
control = bzrlib.bzrdir.BzrDir.open(base)
231
return control.open_repository()
233
def copy_content_into(self, destination, revision_id=None, basis=None):
234
"""Make a complete copy of the content in self into destination.
236
This is a destructive operation! Do not use it on existing
239
return InterRepository.get(self, destination).copy_content(revision_id, basis)
241
def fetch(self, source, revision_id=None, pb=None):
242
"""Fetch the content required to construct revision_id from source.
244
If revision_id is None all content is copied.
246
return InterRepository.get(source, self).fetch(revision_id=revision_id,
249
def get_commit_builder(self, branch, parents, config, timestamp=None,
250
timezone=None, committer=None, revprops=None,
252
"""Obtain a CommitBuilder for this repository.
254
:param branch: Branch to commit to.
255
:param parents: Revision ids of the parents of the new revision.
256
:param config: Configuration to use.
257
:param timestamp: Optional timestamp recorded for commit.
258
:param timezone: Optional timezone for timestamp.
259
:param committer: Optional committer to set for commit.
260
:param revprops: Optional dictionary of revision properties.
261
:param revision_id: Optional revision id.
263
return CommitBuilder(self, parents, config, timestamp, timezone,
264
committer, revprops, revision_id)
267
self.control_files.unlock()
270
def clone(self, a_bzrdir, revision_id=None, basis=None):
271
"""Clone this repository into a_bzrdir using the current format.
273
Currently no check is made that the format of this repository and
274
the bzrdir format are compatible. FIXME RBC 20060201.
276
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
277
# use target default format.
278
result = a_bzrdir.create_repository()
279
# FIXME RBC 20060209 split out the repository type to avoid this check ?
280
elif isinstance(a_bzrdir._format,
281
(bzrlib.bzrdir.BzrDirFormat4,
282
bzrlib.bzrdir.BzrDirFormat5,
283
bzrlib.bzrdir.BzrDirFormat6)):
284
result = a_bzrdir.open_repository()
286
result = self._format.initialize(a_bzrdir, shared=self.is_shared())
287
self.copy_content_into(result, revision_id, basis)
291
def has_revision(self, revision_id):
292
"""True if this repository has a copy of the revision."""
293
return self._revision_store.has_revision_id(revision_id,
294
self.get_transaction())
297
def get_revision_reconcile(self, revision_id):
298
"""'reconcile' helper routine that allows access to a revision always.
300
This variant of get_revision does not cross check the weave graph
301
against the revision one as get_revision does: but it should only
302
be used by reconcile, or reconcile-alike commands that are correcting
303
or testing the revision graph.
305
if not revision_id or not isinstance(revision_id, basestring):
306
raise InvalidRevisionId(revision_id=revision_id, branch=self)
307
return self._revision_store.get_revisions([revision_id],
308
self.get_transaction())[0]
310
def get_revisions(self, revision_ids):
311
return self._revision_store.get_revisions(revision_ids,
312
self.get_transaction())
315
def get_revision_xml(self, revision_id):
316
rev = self.get_revision(revision_id)
318
# the current serializer..
319
self._revision_store._serializer.write_revision(rev, rev_tmp)
321
return rev_tmp.getvalue()
324
def get_revision(self, revision_id):
325
"""Return the Revision object for a named revision"""
326
r = self.get_revision_reconcile(revision_id)
327
# weave corruption can lead to absent revision markers that should be
329
# the following test is reasonably cheap (it needs a single weave read)
330
# and the weave is cached in read transactions. In write transactions
331
# it is not cached but typically we only read a small number of
332
# revisions. For knits when they are introduced we will probably want
333
# to ensure that caching write transactions are in use.
334
inv = self.get_inventory_weave()
335
self._check_revision_parents(r, inv)
338
def get_revision_delta(self, revision_id):
339
"""Return the delta for one revision.
341
The delta is relative to the left-hand predecessor of the
344
revision = self.get_revision(revision_id)
345
new_tree = self.revision_tree(revision_id)
346
if not revision.parent_ids:
347
old_tree = EmptyTree()
349
old_tree = self.revision_tree(revision.parent_ids[0])
350
return compare_trees(old_tree, new_tree)
352
def _check_revision_parents(self, revision, inventory):
353
"""Private to Repository and Fetch.
355
This checks the parentage of revision in an inventory weave for
356
consistency and is only applicable to inventory-weave-for-ancestry
357
using repository formats & fetchers.
359
weave_parents = inventory.get_parents(revision.revision_id)
360
weave_names = inventory.versions()
361
for parent_id in revision.parent_ids:
362
if parent_id in weave_names:
363
# this parent must not be a ghost.
364
if not parent_id in weave_parents:
366
raise errors.CorruptRepository(self)
369
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
370
signature = gpg_strategy.sign(plaintext)
371
self._revision_store.add_revision_signature_text(revision_id,
373
self.get_transaction())
375
def fileids_altered_by_revision_ids(self, revision_ids):
376
"""Find the file ids and versions affected by revisions.
378
:param revisions: an iterable containing revision ids.
379
:return: a dictionary mapping altered file-ids to an iterable of
380
revision_ids. Each altered file-ids has the exact revision_ids that
381
altered it listed explicitly.
383
assert isinstance(self._format, (RepositoryFormat5,
386
RepositoryFormatKnit1)), \
387
("fileids_altered_by_revision_ids only supported for branches "
388
"which store inventory as unnested xml, not on %r" % self)
389
selected_revision_ids = set(revision_ids)
390
w = self.get_inventory_weave()
393
# this code needs to read every new line in every inventory for the
394
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
395
# not present in one of those inventories is unnecessary but not
396
# harmful because we are filtering by the revision id marker in the
397
# inventory lines : we only select file ids altered in one of those
398
# revisions. We don't need to see all lines in the inventory because
399
# only those added in an inventory in rev X can contain a revision=X
401
for line in w.iter_lines_added_or_present_in_versions(selected_revision_ids):
402
start = line.find('file_id="')+9
403
if start < 9: continue
404
end = line.find('"', start)
406
file_id = _unescape_xml(line[start:end])
408
start = line.find('revision="')+10
409
if start < 10: continue
410
end = line.find('"', start)
412
revision_id = _unescape_xml(line[start:end])
413
if revision_id in selected_revision_ids:
414
result.setdefault(file_id, set()).add(revision_id)
418
def get_inventory_weave(self):
419
return self.control_weaves.get_weave('inventory',
420
self.get_transaction())
423
def get_inventory(self, revision_id):
424
"""Get Inventory object by hash."""
425
return self.deserialise_inventory(
426
revision_id, self.get_inventory_xml(revision_id))
428
def deserialise_inventory(self, revision_id, xml):
429
"""Transform the xml into an inventory object.
431
:param revision_id: The expected revision id of the inventory.
432
:param xml: A serialised inventory.
434
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
437
def get_inventory_xml(self, revision_id):
438
"""Get inventory XML as a file object."""
440
assert isinstance(revision_id, basestring), type(revision_id)
441
iw = self.get_inventory_weave()
442
return iw.get_text(revision_id)
444
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
447
def get_inventory_sha1(self, revision_id):
448
"""Return the sha1 hash of the inventory entry
450
return self.get_revision(revision_id).inventory_sha1
453
def get_revision_graph(self, revision_id=None):
454
"""Return a dictionary containing the revision graph.
456
:return: a dictionary of revision_id->revision_parents_list.
458
weave = self.get_inventory_weave()
459
all_revisions = self._eliminate_revisions_not_present(weave.versions())
460
entire_graph = dict([(node, weave.get_parents(node)) for
461
node in all_revisions])
462
if revision_id is None:
464
elif revision_id not in entire_graph:
465
raise errors.NoSuchRevision(self, revision_id)
467
# add what can be reached from revision_id
469
pending = set([revision_id])
470
while len(pending) > 0:
472
result[node] = entire_graph[node]
473
for revision_id in result[node]:
474
if revision_id not in result:
475
pending.add(revision_id)
479
def get_revision_graph_with_ghosts(self, revision_ids=None):
480
"""Return a graph of the revisions with ghosts marked as applicable.
482
:param revision_ids: an iterable of revisions to graph or None for all.
483
:return: a Graph object with the graph reachable from revision_ids.
487
pending = set(self.all_revision_ids())
490
pending = set(revision_ids)
491
required = set(revision_ids)
494
revision_id = pending.pop()
496
rev = self.get_revision(revision_id)
497
except errors.NoSuchRevision:
498
if revision_id in required:
501
result.add_ghost(revision_id)
503
for parent_id in rev.parent_ids:
504
# is this queued or done ?
505
if (parent_id not in pending and
506
parent_id not in done):
508
pending.add(parent_id)
509
result.add_node(revision_id, rev.parent_ids)
510
done.add(revision_id)
514
def get_revision_inventory(self, revision_id):
515
"""Return inventory of a past revision."""
516
# TODO: Unify this with get_inventory()
517
# bzr 0.0.6 and later imposes the constraint that the inventory_id
518
# must be the same as its revision, so this is trivial.
519
if revision_id is None:
520
# This does not make sense: if there is no revision,
521
# then it is the current tree inventory surely ?!
522
# and thus get_root_id() is something that looks at the last
523
# commit on the branch, and the get_root_id is an inventory check.
524
raise NotImplementedError
525
# return Inventory(self.get_root_id())
527
return self.get_inventory(revision_id)
531
"""Return True if this repository is flagged as a shared repository."""
532
raise NotImplementedError(self.is_shared)
535
def reconcile(self, other=None, thorough=False):
536
"""Reconcile this repository."""
537
from bzrlib.reconcile import RepoReconciler
538
reconciler = RepoReconciler(self, thorough=thorough)
539
reconciler.reconcile()
543
def revision_tree(self, revision_id):
544
"""Return Tree for a revision on this branch.
546
`revision_id` may be None for the null revision, in which case
547
an `EmptyTree` is returned."""
548
# TODO: refactor this to use an existing revision object
549
# so we don't need to read it in twice.
550
if revision_id is None or revision_id == NULL_REVISION:
553
inv = self.get_revision_inventory(revision_id)
554
return RevisionTree(self, inv, revision_id)
557
def get_ancestry(self, revision_id):
558
"""Return a list of revision-ids integrated by a revision.
560
The first element of the list is always None, indicating the origin
561
revision. This might change when we have history horizons, or
562
perhaps we should have a new API.
564
This is topologically sorted.
566
if revision_id is None:
568
if not self.has_revision(revision_id):
569
raise errors.NoSuchRevision(self, revision_id)
570
w = self.get_inventory_weave()
571
candidates = w.get_ancestry(revision_id)
572
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
575
def print_file(self, file, revision_id):
576
"""Print `file` to stdout.
578
FIXME RBC 20060125 as John Meinel points out this is a bad api
579
- it writes to stdout, it assumes that that is valid etc. Fix
580
by creating a new more flexible convenience function.
582
tree = self.revision_tree(revision_id)
583
# use inventory as it was in that revision
584
file_id = tree.inventory.path2id(file)
586
# TODO: jam 20060427 Write a test for this code path
587
# it had a bug in it, and was raising the wrong
589
raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
590
tree.print_file(file_id)
592
def get_transaction(self):
593
return self.control_files.get_transaction()
595
def revision_parents(self, revid):
596
return self.get_inventory_weave().parent_names(revid)
599
def set_make_working_trees(self, new_value):
600
"""Set the policy flag for making working trees when creating branches.
602
This only applies to branches that use this repository.
604
The default is 'True'.
605
:param new_value: True to restore the default, False to disable making
608
raise NotImplementedError(self.set_make_working_trees)
610
def make_working_trees(self):
611
"""Returns the policy for making working trees on new branches."""
612
raise NotImplementedError(self.make_working_trees)
615
def sign_revision(self, revision_id, gpg_strategy):
616
plaintext = Testament.from_revision(self, revision_id).as_short_text()
617
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
620
def has_signature_for_revision_id(self, revision_id):
621
"""Query for a revision signature for revision_id in the repository."""
622
return self._revision_store.has_signature(revision_id,
623
self.get_transaction())
626
def get_signature_text(self, revision_id):
627
"""Return the text for a signature."""
628
return self._revision_store.get_signature_text(revision_id,
629
self.get_transaction())
632
def check(self, revision_ids):
633
"""Check consistency of all history of given revision_ids.
635
Different repository implementations should override _check().
637
:param revision_ids: A non-empty list of revision_ids whose ancestry
638
will be checked. Typically the last revision_id of a branch.
641
raise ValueError("revision_ids must be non-empty in %s.check"
643
return self._check(revision_ids)
645
def _check(self, revision_ids):
646
result = bzrlib.check.Check(self)
651
class AllInOneRepository(Repository):
652
"""Legacy support - the repository behaviour for all-in-one branches."""
654
def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
655
# we reuse one control files instance.
656
dir_mode = a_bzrdir._control_files._dir_mode
657
file_mode = a_bzrdir._control_files._file_mode
659
def get_store(name, compressed=True, prefixed=False):
660
# FIXME: This approach of assuming stores are all entirely compressed
661
# or entirely uncompressed is tidy, but breaks upgrade from
662
# some existing branches where there's a mixture; we probably
663
# still want the option to look for both.
664
relpath = a_bzrdir._control_files._escape(name)
665
store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
666
prefixed=prefixed, compressed=compressed,
669
#if self._transport.should_cache():
670
# cache_path = os.path.join(self.cache_root, name)
671
# os.mkdir(cache_path)
672
# store = bzrlib.store.CachedStore(store, cache_path)
675
# not broken out yet because the controlweaves|inventory_store
676
# and text_store | weave_store bits are still different.
677
if isinstance(_format, RepositoryFormat4):
678
# cannot remove these - there is still no consistent api
679
# which allows access to this old info.
680
self.inventory_store = get_store('inventory-store')
681
text_store = get_store('text-store')
682
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
686
"""AllInOne repositories cannot be shared."""
690
def set_make_working_trees(self, new_value):
691
"""Set the policy flag for making working trees when creating branches.
693
This only applies to branches that use this repository.
695
The default is 'True'.
696
:param new_value: True to restore the default, False to disable making
699
raise NotImplementedError(self.set_make_working_trees)
701
def make_working_trees(self):
702
"""Returns the policy for making working trees on new branches."""
706
def install_revision(repository, rev, revision_tree):
707
"""Install all revision data into a repository."""
710
for p_id in rev.parent_ids:
711
if repository.has_revision(p_id):
712
present_parents.append(p_id)
713
parent_trees[p_id] = repository.revision_tree(p_id)
715
parent_trees[p_id] = EmptyTree()
717
inv = revision_tree.inventory
719
# Add the texts that are not already present
720
for path, ie in inv.iter_entries():
721
w = repository.weave_store.get_weave_or_empty(ie.file_id,
722
repository.get_transaction())
723
if ie.revision not in w:
725
# FIXME: TODO: The following loop *may* be overlapping/duplicate
726
# with InventoryEntry.find_previous_heads(). if it is, then there
727
# is a latent bug here where the parents may have ancestors of each
729
for revision, tree in parent_trees.iteritems():
730
if ie.file_id not in tree:
732
parent_id = tree.inventory[ie.file_id].revision
733
if parent_id in text_parents:
735
text_parents.append(parent_id)
737
vfile = repository.weave_store.get_weave_or_empty(ie.file_id,
738
repository.get_transaction())
739
lines = revision_tree.get_file(ie.file_id).readlines()
740
vfile.add_lines(rev.revision_id, text_parents, lines)
742
# install the inventory
743
repository.add_inventory(rev.revision_id, inv, present_parents)
744
except errors.RevisionAlreadyPresent:
746
repository.add_revision(rev.revision_id, rev, inv)
749
class MetaDirRepository(Repository):
750
"""Repositories in the new meta-dir layout."""
752
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
753
super(MetaDirRepository, self).__init__(_format,
760
dir_mode = self.control_files._dir_mode
761
file_mode = self.control_files._file_mode
765
"""Return True if this repository is flagged as a shared repository."""
766
return self.control_files._transport.has('shared-storage')
769
def set_make_working_trees(self, new_value):
770
"""Set the policy flag for making working trees when creating branches.
772
This only applies to branches that use this repository.
774
The default is 'True'.
775
:param new_value: True to restore the default, False to disable making
780
self.control_files._transport.delete('no-working-trees')
781
except errors.NoSuchFile:
784
self.control_files.put_utf8('no-working-trees', '')
786
def make_working_trees(self):
787
"""Returns the policy for making working trees on new branches."""
788
return not self.control_files._transport.has('no-working-trees')
791
class KnitRepository(MetaDirRepository):
792
"""Knit format repository."""
794
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
795
inv_vf.add_lines_with_ghosts(revid, parents, lines)
798
def _all_revision_ids(self):
799
"""See Repository.all_revision_ids()."""
800
# Knits get the revision graph from the index of the revision knit, so
801
# it's always possible even if they're on an unlistable transport.
802
return self._revision_store.all_revision_ids(self.get_transaction())
804
def fileid_involved_between_revs(self, from_revid, to_revid):
805
"""Find file_id(s) which are involved in the changes between revisions.
807
This determines the set of revisions which are involved, and then
808
finds all file ids affected by those revisions.
810
vf = self._get_revision_vf()
811
from_set = set(vf.get_ancestry(from_revid))
812
to_set = set(vf.get_ancestry(to_revid))
813
changed = to_set.difference(from_set)
814
return self._fileid_involved_by_set(changed)
816
def fileid_involved(self, last_revid=None):
817
"""Find all file_ids modified in the ancestry of last_revid.
819
:param last_revid: If None, last_revision() will be used.
822
changed = set(self.all_revision_ids())
824
changed = set(self.get_ancestry(last_revid))
827
return self._fileid_involved_by_set(changed)
830
def get_ancestry(self, revision_id):
831
"""Return a list of revision-ids integrated by a revision.
833
This is topologically sorted.
835
if revision_id is None:
837
vf = self._get_revision_vf()
839
return [None] + vf.get_ancestry(revision_id)
840
except errors.RevisionNotPresent:
841
raise errors.NoSuchRevision(self, revision_id)
844
def get_revision(self, revision_id):
845
"""Return the Revision object for a named revision"""
846
return self.get_revision_reconcile(revision_id)
849
def get_revision_graph(self, revision_id=None):
850
"""Return a dictionary containing the revision graph.
852
:return: a dictionary of revision_id->revision_parents_list.
854
weave = self._get_revision_vf()
855
entire_graph = weave.get_graph()
856
if revision_id is None:
857
return weave.get_graph()
858
elif revision_id not in weave:
859
raise errors.NoSuchRevision(self, revision_id)
861
# add what can be reached from revision_id
863
pending = set([revision_id])
864
while len(pending) > 0:
866
result[node] = weave.get_parents(node)
867
for revision_id in result[node]:
868
if revision_id not in result:
869
pending.add(revision_id)
873
def get_revision_graph_with_ghosts(self, revision_ids=None):
874
"""Return a graph of the revisions with ghosts marked as applicable.
876
:param revision_ids: an iterable of revisions to graph or None for all.
877
:return: a Graph object with the graph reachable from revision_ids.
880
vf = self._get_revision_vf()
881
versions = set(vf.versions())
883
pending = set(self.all_revision_ids())
886
pending = set(revision_ids)
887
required = set(revision_ids)
890
revision_id = pending.pop()
891
if not revision_id in versions:
892
if revision_id in required:
893
raise errors.NoSuchRevision(self, revision_id)
895
result.add_ghost(revision_id)
896
# mark it as done so we don't try for it again.
897
done.add(revision_id)
899
parent_ids = vf.get_parents_with_ghosts(revision_id)
900
for parent_id in parent_ids:
901
# is this queued or done ?
902
if (parent_id not in pending and
903
parent_id not in done):
905
pending.add(parent_id)
906
result.add_node(revision_id, parent_ids)
907
done.add(revision_id)
910
def _get_revision_vf(self):
911
""":return: a versioned file containing the revisions."""
912
vf = self._revision_store.get_revision_file(self.get_transaction())
916
def reconcile(self, other=None, thorough=False):
917
"""Reconcile this repository."""
918
from bzrlib.reconcile import KnitReconciler
919
reconciler = KnitReconciler(self, thorough=thorough)
920
reconciler.reconcile()
923
def revision_parents(self, revid):
924
return self._get_revision_vf().get_parents(rev_id)
926
class RepositoryFormat(object):
927
"""A repository format.
929
Formats provide three things:
930
* An initialization routine to construct repository data on disk.
931
* a format string which is used when the BzrDir supports versioned
933
* an open routine which returns a Repository instance.
935
Formats are placed in an dict by their format string for reference
936
during opening. These should be subclasses of RepositoryFormat
939
Once a format is deprecated, just deprecate the initialize and open
940
methods on the format class. Do not deprecate the object, as the
941
object will be created every system load.
943
Common instance attributes:
944
_matchingbzrdir - the bzrdir format that the repository format was
945
originally written to work with. This can be used if manually
946
constructing a bzrdir and repository, or more commonly for test suite
950
_default_format = None
951
"""The default format used for new repositories."""
954
"""The known formats."""
957
def find_format(klass, a_bzrdir):
958
"""Return the format for the repository object in a_bzrdir."""
960
transport = a_bzrdir.get_repository_transport(None)
961
format_string = transport.get("format").read()
962
return klass._formats[format_string]
963
except errors.NoSuchFile:
964
raise errors.NoRepositoryPresent(a_bzrdir)
966
raise errors.UnknownFormatError(format_string)
968
def _get_control_store(self, repo_transport, control_files):
969
"""Return the control store for this repository."""
970
raise NotImplementedError(self._get_control_store)
973
def get_default_format(klass):
974
"""Return the current default format."""
975
return klass._default_format
977
def get_format_string(self):
978
"""Return the ASCII format string that identifies this format.
980
Note that in pre format ?? repositories the format string is
981
not permitted nor written to disk.
983
raise NotImplementedError(self.get_format_string)
985
def get_format_description(self):
986
"""Return the short description for this format."""
987
raise NotImplementedError(self.get_format_description)
989
def _get_revision_store(self, repo_transport, control_files):
990
"""Return the revision store object for this a_bzrdir."""
991
raise NotImplementedError(self._get_revision_store)
993
def _get_text_rev_store(self,
1000
"""Common logic for getting a revision store for a repository.
1002
see self._get_revision_store for the subclass-overridable method to
1003
get the store for a repository.
1005
from bzrlib.store.revision.text import TextRevisionStore
1006
dir_mode = control_files._dir_mode
1007
file_mode = control_files._file_mode
1008
text_store =TextStore(transport.clone(name),
1010
compressed=compressed,
1012
file_mode=file_mode)
1013
_revision_store = TextRevisionStore(text_store, serializer)
1014
return _revision_store
1016
def _get_versioned_file_store(self,
1021
versionedfile_class=WeaveFile,
1023
weave_transport = control_files._transport.clone(name)
1024
dir_mode = control_files._dir_mode
1025
file_mode = control_files._file_mode
1026
return VersionedFileStore(weave_transport, prefixed=prefixed,
1028
file_mode=file_mode,
1029
versionedfile_class=versionedfile_class,
1032
def initialize(self, a_bzrdir, shared=False):
1033
"""Initialize a repository of this format in a_bzrdir.
1035
:param a_bzrdir: The bzrdir to put the new repository in it.
1036
:param shared: The repository should be initialized as a sharable one.
1038
This may raise UninitializableFormat if shared repository are not
1039
compatible the a_bzrdir.
1042
def is_supported(self):
1043
"""Is this format supported?
1045
Supported formats must be initializable and openable.
1046
Unsupported formats may not support initialization or committing or
1047
some other features depending on the reason for not being supported.
1051
def open(self, a_bzrdir, _found=False):
1052
"""Return an instance of this format for the bzrdir a_bzrdir.
1054
_found is a private parameter, do not use it.
1056
raise NotImplementedError(self.open)
1059
def register_format(klass, format):
1060
klass._formats[format.get_format_string()] = format
1063
def set_default_format(klass, format):
1064
klass._default_format = format
1067
def unregister_format(klass, format):
1068
assert klass._formats[format.get_format_string()] is format
1069
del klass._formats[format.get_format_string()]
1072
class PreSplitOutRepositoryFormat(RepositoryFormat):
1073
"""Base class for the pre split out repository formats."""
1075
def initialize(self, a_bzrdir, shared=False, _internal=False):
1076
"""Create a weave repository.
1078
TODO: when creating split out bzr branch formats, move this to a common
1079
base for Format5, Format6. or something like that.
1081
from bzrlib.weavefile import write_weave_v5
1082
from bzrlib.weave import Weave
1085
raise errors.IncompatibleFormat(self, a_bzrdir._format)
1088
# always initialized when the bzrdir is.
1089
return self.open(a_bzrdir, _found=True)
1091
# Create an empty weave
1093
bzrlib.weavefile.write_weave_v5(Weave(), sio)
1094
empty_weave = sio.getvalue()
1096
mutter('creating repository in %s.', a_bzrdir.transport.base)
1097
dirs = ['revision-store', 'weaves']
1098
files = [('inventory.weave', StringIO(empty_weave)),
1101
# FIXME: RBC 20060125 don't peek under the covers
1102
# NB: no need to escape relative paths that are url safe.
1103
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock',
1105
control_files.create_lock()
1106
control_files.lock_write()
1107
control_files._transport.mkdir_multi(dirs,
1108
mode=control_files._dir_mode)
1110
for file, content in files:
1111
control_files.put(file, content)
1113
control_files.unlock()
1114
return self.open(a_bzrdir, _found=True)
1116
def _get_control_store(self, repo_transport, control_files):
1117
"""Return the control store for this repository."""
1118
return self._get_versioned_file_store('',
1123
def _get_text_store(self, transport, control_files):
1124
"""Get a store for file texts for this format."""
1125
raise NotImplementedError(self._get_text_store)
1127
def open(self, a_bzrdir, _found=False):
1128
"""See RepositoryFormat.open()."""
1130
# we are being called directly and must probe.
1131
raise NotImplementedError
1133
repo_transport = a_bzrdir.get_repository_transport(None)
1134
control_files = a_bzrdir._control_files
1135
text_store = self._get_text_store(repo_transport, control_files)
1136
control_store = self._get_control_store(repo_transport, control_files)
1137
_revision_store = self._get_revision_store(repo_transport, control_files)
1138
return AllInOneRepository(_format=self,
1140
_revision_store=_revision_store,
1141
control_store=control_store,
1142
text_store=text_store)
1145
class RepositoryFormat4(PreSplitOutRepositoryFormat):
1146
"""Bzr repository format 4.
1148
This repository format has:
1150
- TextStores for texts, inventories,revisions.
1152
This format is deprecated: it indexes texts using a text id which is
1153
removed in format 5; initialization and write support for this format
1158
super(RepositoryFormat4, self).__init__()
1159
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat4()
1161
def get_format_description(self):
1162
"""See RepositoryFormat.get_format_description()."""
1163
return "Repository format 4"
1165
def initialize(self, url, shared=False, _internal=False):
1166
"""Format 4 branches cannot be created."""
1167
raise errors.UninitializableFormat(self)
1169
def is_supported(self):
1170
"""Format 4 is not supported.
1172
It is not supported because the model changed from 4 to 5 and the
1173
conversion logic is expensive - so doing it on the fly was not
1178
def _get_control_store(self, repo_transport, control_files):
1179
"""Format 4 repositories have no formal control store at this point.
1181
This will cause any control-file-needing apis to fail - this is desired.
1185
def _get_revision_store(self, repo_transport, control_files):
1186
"""See RepositoryFormat._get_revision_store()."""
1187
from bzrlib.xml4 import serializer_v4
1188
return self._get_text_rev_store(repo_transport,
1191
serializer=serializer_v4)
1193
def _get_text_store(self, transport, control_files):
1194
"""See RepositoryFormat._get_text_store()."""
1197
class RepositoryFormat5(PreSplitOutRepositoryFormat):
1198
"""Bzr control format 5.
1200
This repository format has:
1201
- weaves for file texts and inventory
1203
- TextStores for revisions and signatures.
1207
super(RepositoryFormat5, self).__init__()
1208
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat5()
1210
def get_format_description(self):
1211
"""See RepositoryFormat.get_format_description()."""
1212
return "Weave repository format 5"
1214
def _get_revision_store(self, repo_transport, control_files):
1215
"""See RepositoryFormat._get_revision_store()."""
1216
"""Return the revision store object for this a_bzrdir."""
1217
return self._get_text_rev_store(repo_transport,
1222
def _get_text_store(self, transport, control_files):
1223
"""See RepositoryFormat._get_text_store()."""
1224
return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
1227
class RepositoryFormat6(PreSplitOutRepositoryFormat):
1228
"""Bzr control format 6.
1230
This repository format has:
1231
- weaves for file texts and inventory
1232
- hash subdirectory based stores.
1233
- TextStores for revisions and signatures.
1237
super(RepositoryFormat6, self).__init__()
1238
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat6()
1240
def get_format_description(self):
1241
"""See RepositoryFormat.get_format_description()."""
1242
return "Weave repository format 6"
1244
def _get_revision_store(self, repo_transport, control_files):
1245
"""See RepositoryFormat._get_revision_store()."""
1246
return self._get_text_rev_store(repo_transport,
1252
def _get_text_store(self, transport, control_files):
1253
"""See RepositoryFormat._get_text_store()."""
1254
return self._get_versioned_file_store('weaves', transport, control_files)
1257
class MetaDirRepositoryFormat(RepositoryFormat):
1258
"""Common base class for the new repositories using the metadir layout."""
1261
super(MetaDirRepositoryFormat, self).__init__()
1262
self._matchingbzrdir = bzrlib.bzrdir.BzrDirMetaFormat1()
1264
def _create_control_files(self, a_bzrdir):
1265
"""Create the required files and the initial control_files object."""
1266
# FIXME: RBC 20060125 don't peek under the covers
1267
# NB: no need to escape relative paths that are url safe.
1268
repository_transport = a_bzrdir.get_repository_transport(self)
1269
control_files = LockableFiles(repository_transport, 'lock', LockDir)
1270
control_files.create_lock()
1271
return control_files
1273
def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
1274
"""Upload the initial blank content."""
1275
control_files = self._create_control_files(a_bzrdir)
1276
control_files.lock_write()
1278
control_files._transport.mkdir_multi(dirs,
1279
mode=control_files._dir_mode)
1280
for file, content in files:
1281
control_files.put(file, content)
1282
for file, content in utf8_files:
1283
control_files.put_utf8(file, content)
1285
control_files.put_utf8('shared-storage', '')
1287
control_files.unlock()
1290
class RepositoryFormat7(MetaDirRepositoryFormat):
1291
"""Bzr repository 7.
1293
This repository format has:
1294
- weaves for file texts and inventory
1295
- hash subdirectory based stores.
1296
- TextStores for revisions and signatures.
1297
- a format marker of its own
1298
- an optional 'shared-storage' flag
1299
- an optional 'no-working-trees' flag
1302
def _get_control_store(self, repo_transport, control_files):
1303
"""Return the control store for this repository."""
1304
return self._get_versioned_file_store('',
1309
def get_format_string(self):
1310
"""See RepositoryFormat.get_format_string()."""
1311
return "Bazaar-NG Repository format 7"
1313
def get_format_description(self):
1314
"""See RepositoryFormat.get_format_description()."""
1315
return "Weave repository format 7"
1317
def _get_revision_store(self, repo_transport, control_files):
1318
"""See RepositoryFormat._get_revision_store()."""
1319
return self._get_text_rev_store(repo_transport,
1326
def _get_text_store(self, transport, control_files):
1327
"""See RepositoryFormat._get_text_store()."""
1328
return self._get_versioned_file_store('weaves',
1332
def initialize(self, a_bzrdir, shared=False):
1333
"""Create a weave repository.
1335
:param shared: If true the repository will be initialized as a shared
1338
from bzrlib.weavefile import write_weave_v5
1339
from bzrlib.weave import Weave
1341
# Create an empty weave
1343
bzrlib.weavefile.write_weave_v5(Weave(), sio)
1344
empty_weave = sio.getvalue()
1346
mutter('creating repository in %s.', a_bzrdir.transport.base)
1347
dirs = ['revision-store', 'weaves']
1348
files = [('inventory.weave', StringIO(empty_weave)),
1350
utf8_files = [('format', self.get_format_string())]
1352
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1353
return self.open(a_bzrdir=a_bzrdir, _found=True)
1355
def open(self, a_bzrdir, _found=False, _override_transport=None):
1356
"""See RepositoryFormat.open().
1358
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1359
repository at a slightly different url
1360
than normal. I.e. during 'upgrade'.
1363
format = RepositoryFormat.find_format(a_bzrdir)
1364
assert format.__class__ == self.__class__
1365
if _override_transport is not None:
1366
repo_transport = _override_transport
1368
repo_transport = a_bzrdir.get_repository_transport(None)
1369
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1370
text_store = self._get_text_store(repo_transport, control_files)
1371
control_store = self._get_control_store(repo_transport, control_files)
1372
_revision_store = self._get_revision_store(repo_transport, control_files)
1373
return MetaDirRepository(_format=self,
1375
control_files=control_files,
1376
_revision_store=_revision_store,
1377
control_store=control_store,
1378
text_store=text_store)
1381
class RepositoryFormatKnit1(MetaDirRepositoryFormat):
1382
"""Bzr repository knit format 1.
1384
This repository format has:
1385
- knits for file texts and inventory
1386
- hash subdirectory based stores.
1387
- knits for revisions and signatures
1388
- TextStores for revisions and signatures.
1389
- a format marker of its own
1390
- an optional 'shared-storage' flag
1391
- an optional 'no-working-trees' flag
1394
This format was introduced in bzr 0.8.
1397
def _get_control_store(self, repo_transport, control_files):
1398
"""Return the control store for this repository."""
1399
return VersionedFileStore(
1402
file_mode=control_files._file_mode,
1403
versionedfile_class=KnitVersionedFile,
1404
versionedfile_kwargs={'factory':KnitPlainFactory()},
1407
def get_format_string(self):
1408
"""See RepositoryFormat.get_format_string()."""
1409
return "Bazaar-NG Knit Repository Format 1"
1411
def get_format_description(self):
1412
"""See RepositoryFormat.get_format_description()."""
1413
return "Knit repository format 1"
1415
def _get_revision_store(self, repo_transport, control_files):
1416
"""See RepositoryFormat._get_revision_store()."""
1417
from bzrlib.store.revision.knit import KnitRevisionStore
1418
versioned_file_store = VersionedFileStore(
1420
file_mode=control_files._file_mode,
1423
versionedfile_class=KnitVersionedFile,
1424
versionedfile_kwargs={'delta':False, 'factory':KnitPlainFactory()},
1427
return KnitRevisionStore(versioned_file_store)
1429
def _get_text_store(self, transport, control_files):
1430
"""See RepositoryFormat._get_text_store()."""
1431
return self._get_versioned_file_store('knits',
1434
versionedfile_class=KnitVersionedFile,
1437
def initialize(self, a_bzrdir, shared=False):
1438
"""Create a knit format 1 repository.
1440
:param a_bzrdir: bzrdir to contain the new repository; must already
1442
:param shared: If true the repository will be initialized as a shared
1445
mutter('creating repository in %s.', a_bzrdir.transport.base)
1446
dirs = ['revision-store', 'knits']
1448
utf8_files = [('format', self.get_format_string())]
1450
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1451
repo_transport = a_bzrdir.get_repository_transport(None)
1452
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1453
control_store = self._get_control_store(repo_transport, control_files)
1454
transaction = bzrlib.transactions.WriteTransaction()
1455
# trigger a write of the inventory store.
1456
control_store.get_weave_or_empty('inventory', transaction)
1457
_revision_store = self._get_revision_store(repo_transport, control_files)
1458
_revision_store.has_revision_id('A', transaction)
1459
_revision_store.get_signature_file(transaction)
1460
return self.open(a_bzrdir=a_bzrdir, _found=True)
1462
def open(self, a_bzrdir, _found=False, _override_transport=None):
1463
"""See RepositoryFormat.open().
1465
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1466
repository at a slightly different url
1467
than normal. I.e. during 'upgrade'.
1470
format = RepositoryFormat.find_format(a_bzrdir)
1471
assert format.__class__ == self.__class__
1472
if _override_transport is not None:
1473
repo_transport = _override_transport
1475
repo_transport = a_bzrdir.get_repository_transport(None)
1476
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1477
text_store = self._get_text_store(repo_transport, control_files)
1478
control_store = self._get_control_store(repo_transport, control_files)
1479
_revision_store = self._get_revision_store(repo_transport, control_files)
1480
return KnitRepository(_format=self,
1482
control_files=control_files,
1483
_revision_store=_revision_store,
1484
control_store=control_store,
1485
text_store=text_store)
1488
# formats which have no format string are not discoverable
1489
# and not independently creatable, so are not registered.
1490
RepositoryFormat.register_format(RepositoryFormat7())
1491
_default_format = RepositoryFormatKnit1()
1492
RepositoryFormat.register_format(_default_format)
1493
RepositoryFormat.set_default_format(_default_format)
1494
_legacy_formats = [RepositoryFormat4(),
1495
RepositoryFormat5(),
1496
RepositoryFormat6()]
1499
class InterRepository(InterObject):
1500
"""This class represents operations taking place between two repositories.
1502
Its instances have methods like copy_content and fetch, and contain
1503
references to the source and target repositories these operations can be
1506
Often we will provide convenience methods on 'repository' which carry out
1507
operations with another repository - they will always forward to
1508
InterRepository.get(other).method_name(parameters).
1512
"""The available optimised InterRepository types."""
1515
def copy_content(self, revision_id=None, basis=None):
1516
"""Make a complete copy of the content in self into destination.
1518
This is a destructive operation! Do not use it on existing
1521
:param revision_id: Only copy the content needed to construct
1522
revision_id and its parents.
1523
:param basis: Copy the needed data preferentially from basis.
1526
self.target.set_make_working_trees(self.source.make_working_trees())
1527
except NotImplementedError:
1529
# grab the basis available data
1530
if basis is not None:
1531
self.target.fetch(basis, revision_id=revision_id)
1532
# but don't bother fetching if we have the needed data now.
1533
if (revision_id not in (None, NULL_REVISION) and
1534
self.target.has_revision(revision_id)):
1536
self.target.fetch(self.source, revision_id=revision_id)
1538
def _double_lock(self, lock_source, lock_target):
1539
"""Take out too locks, rolling back the first if the second throws."""
1544
# we want to ensure that we don't leave source locked by mistake.
1545
# and any error on target should not confuse source.
1546
self.source.unlock()
1550
def fetch(self, revision_id=None, pb=None):
1551
"""Fetch the content required to construct revision_id.
1553
The content is copied from source to target.
1555
:param revision_id: if None all content is copied, if NULL_REVISION no
1557
:param pb: optional progress bar to use for progress reports. If not
1558
provided a default one will be created.
1560
Returns the copied revision count and the failed revisions in a tuple:
1563
from bzrlib.fetch import GenericRepoFetcher
1564
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1565
self.source, self.source._format, self.target, self.target._format)
1566
f = GenericRepoFetcher(to_repository=self.target,
1567
from_repository=self.source,
1568
last_revision=revision_id,
1570
return f.count_copied, f.failed_revisions
1572
def lock_read(self):
1573
"""Take out a logical read lock.
1575
This will lock the source branch and the target branch. The source gets
1576
a read lock and the target a read lock.
1578
self._double_lock(self.source.lock_read, self.target.lock_read)
1580
def lock_write(self):
1581
"""Take out a logical write lock.
1583
This will lock the source branch and the target branch. The source gets
1584
a read lock and the target a write lock.
1586
self._double_lock(self.source.lock_read, self.target.lock_write)
1589
def missing_revision_ids(self, revision_id=None):
1590
"""Return the revision ids that source has that target does not.
1592
These are returned in topological order.
1594
:param revision_id: only return revision ids included by this
1597
# generic, possibly worst case, slow code path.
1598
target_ids = set(self.target.all_revision_ids())
1599
if revision_id is not None:
1600
source_ids = self.source.get_ancestry(revision_id)
1601
assert source_ids[0] == None
1604
source_ids = self.source.all_revision_ids()
1605
result_set = set(source_ids).difference(target_ids)
1606
# this may look like a no-op: its not. It preserves the ordering
1607
# other_ids had while only returning the members from other_ids
1608
# that we've decided we need.
1609
return [rev_id for rev_id in source_ids if rev_id in result_set]
1612
"""Release the locks on source and target."""
1614
self.target.unlock()
1616
self.source.unlock()
1619
class InterWeaveRepo(InterRepository):
1620
"""Optimised code paths between Weave based repositories."""
1622
_matching_repo_format = RepositoryFormat7()
1623
"""Repository format for testing with."""
1626
def is_compatible(source, target):
1627
"""Be compatible with known Weave formats.
1629
We don't test for the stores being of specific types because that
1630
could lead to confusing results, and there is no need to be
1634
return (isinstance(source._format, (RepositoryFormat5,
1636
RepositoryFormat7)) and
1637
isinstance(target._format, (RepositoryFormat5,
1639
RepositoryFormat7)))
1640
except AttributeError:
1644
def copy_content(self, revision_id=None, basis=None):
1645
"""See InterRepository.copy_content()."""
1646
# weave specific optimised path:
1647
if basis is not None:
1648
# copy the basis in, then fetch remaining data.
1649
basis.copy_content_into(self.target, revision_id)
1650
# the basis copy_content_into could miss-set this.
1652
self.target.set_make_working_trees(self.source.make_working_trees())
1653
except NotImplementedError:
1655
self.target.fetch(self.source, revision_id=revision_id)
1658
self.target.set_make_working_trees(self.source.make_working_trees())
1659
except NotImplementedError:
1661
# FIXME do not peek!
1662
if self.source.control_files._transport.listable():
1663
pb = bzrlib.ui.ui_factory.nested_progress_bar()
1665
self.target.weave_store.copy_all_ids(
1666
self.source.weave_store,
1668
from_transaction=self.source.get_transaction(),
1669
to_transaction=self.target.get_transaction())
1670
pb.update('copying inventory', 0, 1)
1671
self.target.control_weaves.copy_multi(
1672
self.source.control_weaves, ['inventory'],
1673
from_transaction=self.source.get_transaction(),
1674
to_transaction=self.target.get_transaction())
1675
self.target._revision_store.text_store.copy_all_ids(
1676
self.source._revision_store.text_store,
1681
self.target.fetch(self.source, revision_id=revision_id)
1684
def fetch(self, revision_id=None, pb=None):
1685
"""See InterRepository.fetch()."""
1686
from bzrlib.fetch import GenericRepoFetcher
1687
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1688
self.source, self.source._format, self.target, self.target._format)
1689
f = GenericRepoFetcher(to_repository=self.target,
1690
from_repository=self.source,
1691
last_revision=revision_id,
1693
return f.count_copied, f.failed_revisions
1696
def missing_revision_ids(self, revision_id=None):
1697
"""See InterRepository.missing_revision_ids()."""
1698
# we want all revisions to satisfy revision_id in source.
1699
# but we don't want to stat every file here and there.
1700
# we want then, all revisions other needs to satisfy revision_id
1701
# checked, but not those that we have locally.
1702
# so the first thing is to get a subset of the revisions to
1703
# satisfy revision_id in source, and then eliminate those that
1704
# we do already have.
1705
# this is slow on high latency connection to self, but as as this
1706
# disk format scales terribly for push anyway due to rewriting
1707
# inventory.weave, this is considered acceptable.
1709
if revision_id is not None:
1710
source_ids = self.source.get_ancestry(revision_id)
1711
assert source_ids[0] == None
1714
source_ids = self.source._all_possible_ids()
1715
source_ids_set = set(source_ids)
1716
# source_ids is the worst possible case we may need to pull.
1717
# now we want to filter source_ids against what we actually
1718
# have in target, but don't try to check for existence where we know
1719
# we do not have a revision as that would be pointless.
1720
target_ids = set(self.target._all_possible_ids())
1721
possibly_present_revisions = target_ids.intersection(source_ids_set)
1722
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1723
required_revisions = source_ids_set.difference(actually_present_revisions)
1724
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1725
if revision_id is not None:
1726
# we used get_ancestry to determine source_ids then we are assured all
1727
# revisions referenced are present as they are installed in topological order.
1728
# and the tip revision was validated by get_ancestry.
1729
return required_topo_revisions
1731
# if we just grabbed the possibly available ids, then
1732
# we only have an estimate of whats available and need to validate
1733
# that against the revision records.
1734
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1737
class InterKnitRepo(InterRepository):
1738
"""Optimised code paths between Knit based repositories."""
1740
_matching_repo_format = RepositoryFormatKnit1()
1741
"""Repository format for testing with."""
1744
def is_compatible(source, target):
1745
"""Be compatible with known Knit formats.
1747
We don't test for the stores being of specific types because that
1748
could lead to confusing results, and there is no need to be
1752
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1753
isinstance(target._format, (RepositoryFormatKnit1)))
1754
except AttributeError:
1758
def fetch(self, revision_id=None, pb=None):
1759
"""See InterRepository.fetch()."""
1760
from bzrlib.fetch import KnitRepoFetcher
1761
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1762
self.source, self.source._format, self.target, self.target._format)
1763
f = KnitRepoFetcher(to_repository=self.target,
1764
from_repository=self.source,
1765
last_revision=revision_id,
1767
return f.count_copied, f.failed_revisions
1770
def missing_revision_ids(self, revision_id=None):
1771
"""See InterRepository.missing_revision_ids()."""
1772
if revision_id is not None:
1773
source_ids = self.source.get_ancestry(revision_id)
1774
assert source_ids[0] == None
1777
source_ids = self.source._all_possible_ids()
1778
source_ids_set = set(source_ids)
1779
# source_ids is the worst possible case we may need to pull.
1780
# now we want to filter source_ids against what we actually
1781
# have in target, but don't try to check for existence where we know
1782
# we do not have a revision as that would be pointless.
1783
target_ids = set(self.target._all_possible_ids())
1784
possibly_present_revisions = target_ids.intersection(source_ids_set)
1785
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1786
required_revisions = source_ids_set.difference(actually_present_revisions)
1787
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1788
if revision_id is not None:
1789
# we used get_ancestry to determine source_ids then we are assured all
1790
# revisions referenced are present as they are installed in topological order.
1791
# and the tip revision was validated by get_ancestry.
1792
return required_topo_revisions
1794
# if we just grabbed the possibly available ids, then
1795
# we only have an estimate of whats available and need to validate
1796
# that against the revision records.
1797
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1799
InterRepository.register_optimiser(InterWeaveRepo)
1800
InterRepository.register_optimiser(InterKnitRepo)
1803
class RepositoryTestProviderAdapter(object):
1804
"""A tool to generate a suite testing multiple repository formats at once.
1806
This is done by copying the test once for each transport and injecting
1807
the transport_server, transport_readonly_server, and bzrdir_format and
1808
repository_format classes into each copy. Each copy is also given a new id()
1809
to make it easy to identify.
1812
def __init__(self, transport_server, transport_readonly_server, formats):
1813
self._transport_server = transport_server
1814
self._transport_readonly_server = transport_readonly_server
1815
self._formats = formats
1817
def adapt(self, test):
1818
result = TestSuite()
1819
for repository_format, bzrdir_format in self._formats:
1820
new_test = deepcopy(test)
1821
new_test.transport_server = self._transport_server
1822
new_test.transport_readonly_server = self._transport_readonly_server
1823
new_test.bzrdir_format = bzrdir_format
1824
new_test.repository_format = repository_format
1825
def make_new_test_id():
1826
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
1827
return lambda: new_id
1828
new_test.id = make_new_test_id()
1829
result.addTest(new_test)
1833
class InterRepositoryTestProviderAdapter(object):
1834
"""A tool to generate a suite testing multiple inter repository formats.
1836
This is done by copying the test once for each interrepo provider and injecting
1837
the transport_server, transport_readonly_server, repository_format and
1838
repository_to_format classes into each copy.
1839
Each copy is also given a new id() to make it easy to identify.
1842
def __init__(self, transport_server, transport_readonly_server, formats):
1843
self._transport_server = transport_server
1844
self._transport_readonly_server = transport_readonly_server
1845
self._formats = formats
1847
def adapt(self, test):
1848
result = TestSuite()
1849
for interrepo_class, repository_format, repository_format_to in self._formats:
1850
new_test = deepcopy(test)
1851
new_test.transport_server = self._transport_server
1852
new_test.transport_readonly_server = self._transport_readonly_server
1853
new_test.interrepo_class = interrepo_class
1854
new_test.repository_format = repository_format
1855
new_test.repository_format_to = repository_format_to
1856
def make_new_test_id():
1857
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
1858
return lambda: new_id
1859
new_test.id = make_new_test_id()
1860
result.addTest(new_test)
1864
def default_test_list():
1865
"""Generate the default list of interrepo permutations to test."""
1867
# test the default InterRepository between format 6 and the current
1869
# XXX: robertc 20060220 reinstate this when there are two supported
1870
# formats which do not have an optimal code path between them.
1871
result.append((InterRepository,
1872
RepositoryFormat6(),
1873
RepositoryFormatKnit1()))
1874
for optimiser in InterRepository._optimisers:
1875
result.append((optimiser,
1876
optimiser._matching_repo_format,
1877
optimiser._matching_repo_format
1879
# if there are specific combinations we want to use, we can add them
1884
class CopyConverter(object):
1885
"""A repository conversion tool which just performs a copy of the content.
1887
This is slow but quite reliable.
1890
def __init__(self, target_format):
1891
"""Create a CopyConverter.
1893
:param target_format: The format the resulting repository should be.
1895
self.target_format = target_format
1897
def convert(self, repo, pb):
1898
"""Perform the conversion of to_convert, giving feedback via pb.
1900
:param to_convert: The disk object to convert.
1901
:param pb: a progress bar to use for progress information.
1906
# this is only useful with metadir layouts - separated repo content.
1907
# trigger an assertion if not such
1908
repo._format.get_format_string()
1909
self.repo_dir = repo.bzrdir
1910
self.step('Moving repository to repository.backup')
1911
self.repo_dir.transport.move('repository', 'repository.backup')
1912
backup_transport = self.repo_dir.transport.clone('repository.backup')
1913
self.source_repo = repo._format.open(self.repo_dir,
1915
_override_transport=backup_transport)
1916
self.step('Creating new repository')
1917
converted = self.target_format.initialize(self.repo_dir,
1918
self.source_repo.is_shared())
1919
converted.lock_write()
1921
self.step('Copying content into repository.')
1922
self.source_repo.copy_content_into(converted)
1925
self.step('Deleting old repository content.')
1926
self.repo_dir.transport.delete_tree('repository.backup')
1927
self.pb.note('repository converted')
1929
def step(self, message):
1930
"""Update the pb by a step."""
1932
self.pb.update(message, self.count, self.total)
1935
class CommitBuilder(object):
1936
"""Provides an interface to build up a commit.
1938
This allows describing a tree to be committed without needing to
1939
know the internals of the format of the repository.
1941
def __init__(self, repository, parents, config, timestamp=None,
1942
timezone=None, committer=None, revprops=None,
1944
"""Initiate a CommitBuilder.
1946
:param repository: Repository to commit to.
1947
:param parents: Revision ids of the parents of the new revision.
1948
:param config: Configuration to use.
1949
:param timestamp: Optional timestamp recorded for commit.
1950
:param timezone: Optional timezone for timestamp.
1951
:param committer: Optional committer to set for commit.
1952
:param revprops: Optional dictionary of revision properties.
1953
:param revision_id: Optional revision id.
1955
self._config = config
1957
if committer is None:
1958
self._committer = self._config.username()
1960
assert isinstance(committer, basestring), type(committer)
1961
self._committer = committer
1963
self.new_inventory = Inventory()
1964
self._new_revision_id = revision_id
1965
self.parents = parents
1966
self.repository = repository
1969
if revprops is not None:
1970
self._revprops.update(revprops)
1972
if timestamp is None:
1973
self._timestamp = time.time()
1975
self._timestamp = long(timestamp)
1977
if timezone is None:
1978
self._timezone = local_time_offset()
1980
self._timezone = int(timezone)
1982
self._generate_revision_if_needed()
1984
def commit(self, message):
1985
"""Make the actual commit.
1987
:return: The revision id of the recorded revision.
1989
rev = Revision(timestamp=self._timestamp,
1990
timezone=self._timezone,
1991
committer=self._committer,
1993
inventory_sha1=self.inv_sha1,
1994
revision_id=self._new_revision_id,
1995
properties=self._revprops)
1996
rev.parent_ids = self.parents
1997
self.repository.add_revision(self._new_revision_id, rev,
1998
self.new_inventory, self._config)
1999
return self._new_revision_id
2001
def finish_inventory(self):
2002
"""Tell the builder that the inventory is finished."""
2003
self.new_inventory.revision_id = self._new_revision_id
2004
self.inv_sha1 = self.repository.add_inventory(
2005
self._new_revision_id,
2010
def _gen_revision_id(self):
2011
"""Return new revision-id."""
2012
s = '%s-%s-' % (self._config.user_email(),
2013
compact_date(self._timestamp))
2014
s += hexlify(rand_bytes(8))
2017
def _generate_revision_if_needed(self):
2018
"""Create a revision id if None was supplied.
2020
If the repository can not support user-specified revision ids
2021
they should override this function and raise UnsupportedOperation
2022
if _new_revision_id is not None.
2024
:raises: UnsupportedOperation
2026
if self._new_revision_id is None:
2027
self._new_revision_id = self._gen_revision_id()
2029
def record_entry_contents(self, ie, parent_invs, path, tree):
2030
"""Record the content of ie from tree into the commit if needed.
2032
:param ie: An inventory entry present in the commit.
2033
:param parent_invs: The inventories of the parent revisions of the
2035
:param path: The path the entry is at in the tree.
2036
:param tree: The tree which contains this entry and should be used to
2039
self.new_inventory.add(ie)
2041
# ie.revision is always None if the InventoryEntry is considered
2042
# for committing. ie.snapshot will record the correct revision
2043
# which may be the sole parent if it is untouched.
2044
if ie.revision is not None:
2046
previous_entries = ie.find_previous_heads(
2048
self.repository.weave_store,
2049
self.repository.get_transaction())
2050
# we are creating a new revision for ie in the history store
2052
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2054
def modified_directory(self, file_id, file_parents):
2055
"""Record the presence of a symbolic link.
2057
:param file_id: The file_id of the link to record.
2058
:param file_parents: The per-file parent revision ids.
2060
self._add_text_to_weave(file_id, [], file_parents.keys())
2062
def modified_file_text(self, file_id, file_parents,
2063
get_content_byte_lines, text_sha1=None,
2065
"""Record the text of file file_id
2067
:param file_id: The file_id of the file to record the text of.
2068
:param file_parents: The per-file parent revision ids.
2069
:param get_content_byte_lines: A callable which will return the byte
2071
:param text_sha1: Optional SHA1 of the file contents.
2072
:param text_size: Optional size of the file contents.
2074
mutter('storing text of file {%s} in revision {%s} into %r',
2075
file_id, self._new_revision_id, self.repository.weave_store)
2076
# special case to avoid diffing on renames or
2078
if (len(file_parents) == 1
2079
and text_sha1 == file_parents.values()[0].text_sha1
2080
and text_size == file_parents.values()[0].text_size):
2081
previous_ie = file_parents.values()[0]
2082
versionedfile = self.repository.weave_store.get_weave(file_id,
2083
self.repository.get_transaction())
2084
versionedfile.clone_text(self._new_revision_id,
2085
previous_ie.revision, file_parents.keys())
2086
return text_sha1, text_size
2088
new_lines = get_content_byte_lines()
2089
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2090
# should return the SHA1 and size
2091
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2092
return bzrlib.osutils.sha_strings(new_lines), \
2093
sum(map(len, new_lines))
2095
def modified_link(self, file_id, file_parents, link_target):
2096
"""Record the presence of a symbolic link.
2098
:param file_id: The file_id of the link to record.
2099
:param file_parents: The per-file parent revision ids.
2100
:param link_target: Target location of this link.
2102
self._add_text_to_weave(file_id, [], file_parents.keys())
2104
def _add_text_to_weave(self, file_id, new_lines, parents):
2105
versionedfile = self.repository.weave_store.get_weave_or_empty(
2106
file_id, self.repository.get_transaction())
2107
versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2108
versionedfile.clear_cache()
2111
# Copied from xml.sax.saxutils
2112
def _unescape_xml(data):
2113
"""Unescape &, <, and > in a string of data.
2115
data = data.replace("<", "<")
2116
data = data.replace(">", ">")
2117
# must do ampersand last
2118
return data.replace("&", "&")