1
# Copyright (C) 2005, 2006 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
from binascii import hexlify
18
from copy import deepcopy
19
from cStringIO import StringIO
22
from unittest import TestSuite
24
from bzrlib import bzrdir, check, delta, gpg, errors, xml5, ui, transactions, osutils
25
from bzrlib.decorators import needs_read_lock, needs_write_lock
26
from bzrlib.errors import InvalidRevisionId
27
from bzrlib.graph import Graph
28
from bzrlib.inter import InterObject
29
from bzrlib.inventory import Inventory
30
from bzrlib.knit import KnitVersionedFile, KnitPlainFactory
31
from bzrlib.lockable_files import LockableFiles, TransportLock
32
from bzrlib.lockdir import LockDir
33
from bzrlib.osutils import (safe_unicode, rand_bytes, compact_date,
35
from bzrlib.revision import NULL_REVISION, Revision
36
from bzrlib.revisiontree import RevisionTree
37
from bzrlib.store.versioned import VersionedFileStore, WeaveStore
38
from bzrlib.store.text import TextStore
39
from bzrlib.symbol_versioning import (deprecated_method,
42
from bzrlib.testament import Testament
43
from bzrlib.trace import mutter, note, warning
44
from bzrlib.tsort import topo_sort
45
from bzrlib.weave import WeaveFile
48
class Repository(object):
49
"""Repository holding history for one or more branches.
51
The repository holds and retrieves historical information including
52
revisions and file history. It's normally accessed only by the Branch,
53
which views a particular line of development through that history.
55
The Repository builds on top of Stores and a Transport, which respectively
56
describe the disk data format and the way of accessing the (possibly
61
def add_inventory(self, revid, inv, parents):
62
"""Add the inventory inv to the repository as revid.
64
:param parents: The revision ids of the parents that revid
65
is known to have and are in the repository already.
67
returns the sha1 of the serialized inventory.
69
assert inv.revision_id is None or inv.revision_id == revid, \
70
"Mismatch between inventory revision" \
71
" id and insertion revid (%r, %r)" % (inv.revision_id, revid)
72
inv_text = xml5.serializer_v5.write_inventory_to_string(inv)
73
inv_sha1 = osutils.sha_string(inv_text)
74
inv_vf = self.control_weaves.get_weave('inventory',
75
self.get_transaction())
76
self._inventory_add_lines(inv_vf, revid, parents, osutils.split_lines(inv_text))
79
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
81
for parent in parents:
83
final_parents.append(parent)
85
inv_vf.add_lines(revid, final_parents, lines)
88
def add_revision(self, rev_id, rev, inv=None, config=None):
89
"""Add rev to the revision store as rev_id.
91
:param rev_id: the revision id to use.
92
:param rev: The revision object.
93
:param inv: The inventory for the revision. if None, it will be looked
94
up in the inventory storer
95
:param config: If None no digital signature will be created.
96
If supplied its signature_needed method will be used
97
to determine if a signature should be made.
99
if config is not None and config.signature_needed():
101
inv = self.get_inventory(rev_id)
102
plaintext = Testament(rev, inv).as_short_text()
103
self.store_revision_signature(
104
gpg.GPGStrategy(config), plaintext, rev_id)
105
if not rev_id in self.get_inventory_weave():
107
raise errors.WeaveRevisionNotPresent(rev_id,
108
self.get_inventory_weave())
110
# yes, this is not suitable for adding with ghosts.
111
self.add_inventory(rev_id, inv, rev.parent_ids)
112
self._revision_store.add_revision(rev, self.get_transaction())
115
def _all_possible_ids(self):
116
"""Return all the possible revisions that we could find."""
117
return self.get_inventory_weave().versions()
119
def all_revision_ids(self):
120
"""Returns a list of all the revision ids in the repository.
122
This is deprecated because code should generally work on the graph
123
reachable from a particular revision, and ignore any other revisions
124
that might be present. There is no direct replacement method.
126
return self._all_revision_ids()
129
def _all_revision_ids(self):
130
"""Returns a list of all the revision ids in the repository.
132
These are in as much topological order as the underlying store can
133
present: for weaves ghosts may lead to a lack of correctness until
134
the reweave updates the parents list.
136
if self._revision_store.text_store.listable():
137
return self._revision_store.all_revision_ids(self.get_transaction())
138
result = self._all_possible_ids()
139
return self._eliminate_revisions_not_present(result)
141
def break_lock(self):
142
"""Break a lock if one is present from another instance.
144
Uses the ui factory to ask for confirmation if the lock may be from
147
self.control_files.break_lock()
150
def _eliminate_revisions_not_present(self, revision_ids):
151
"""Check every revision id in revision_ids to see if we have it.
153
Returns a set of the present revisions.
156
for id in revision_ids:
157
if self.has_revision(id):
162
def create(a_bzrdir):
163
"""Construct the current default format repository in a_bzrdir."""
164
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
166
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
167
"""instantiate a Repository.
169
:param _format: The format of the repository on disk.
170
:param a_bzrdir: The BzrDir of the repository.
172
In the future we will have a single api for all stores for
173
getting file texts, inventories and revisions, then
174
this construct will accept instances of those things.
176
super(Repository, self).__init__()
177
self._format = _format
178
# the following are part of the public API for Repository:
179
self.bzrdir = a_bzrdir
180
self.control_files = control_files
181
self._revision_store = _revision_store
182
self.text_store = text_store
183
# backwards compatibility
184
self.weave_store = text_store
185
# not right yet - should be more semantically clear ?
187
self.control_store = control_store
188
self.control_weaves = control_store
189
# TODO: make sure to construct the right store classes, etc, depending
190
# on whether escaping is required.
191
self._warn_if_deprecated()
194
return '%s(%r)' % (self.__class__.__name__,
195
self.bzrdir.transport.base)
198
return self.control_files.is_locked()
200
def lock_write(self):
201
self.control_files.lock_write()
204
self.control_files.lock_read()
206
def get_physical_lock_status(self):
207
return self.control_files.get_physical_lock_status()
210
def missing_revision_ids(self, other, revision_id=None):
211
"""Return the revision ids that other has that this does not.
213
These are returned in topological order.
215
revision_id: only return revision ids included by revision_id.
217
return InterRepository.get(other, self).missing_revision_ids(revision_id)
221
"""Open the repository rooted at base.
223
For instance, if the repository is at URL/.bzr/repository,
224
Repository.open(URL) -> a Repository instance.
226
control = bzrdir.BzrDir.open(base)
227
return control.open_repository()
229
def copy_content_into(self, destination, revision_id=None, basis=None):
230
"""Make a complete copy of the content in self into destination.
232
This is a destructive operation! Do not use it on existing
235
return InterRepository.get(self, destination).copy_content(revision_id, basis)
237
def fetch(self, source, revision_id=None, pb=None):
238
"""Fetch the content required to construct revision_id from source.
240
If revision_id is None all content is copied.
242
return InterRepository.get(source, self).fetch(revision_id=revision_id,
245
def get_commit_builder(self, branch, parents, config, timestamp=None,
246
timezone=None, committer=None, revprops=None,
248
"""Obtain a CommitBuilder for this repository.
250
:param branch: Branch to commit to.
251
:param parents: Revision ids of the parents of the new revision.
252
:param config: Configuration to use.
253
:param timestamp: Optional timestamp recorded for commit.
254
:param timezone: Optional timezone for timestamp.
255
:param committer: Optional committer to set for commit.
256
:param revprops: Optional dictionary of revision properties.
257
:param revision_id: Optional revision id.
259
return CommitBuilder(self, parents, config, timestamp, timezone,
260
committer, revprops, revision_id)
263
self.control_files.unlock()
266
def clone(self, a_bzrdir, revision_id=None, basis=None):
267
"""Clone this repository into a_bzrdir using the current format.
269
Currently no check is made that the format of this repository and
270
the bzrdir format are compatible. FIXME RBC 20060201.
272
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
273
# use target default format.
274
result = a_bzrdir.create_repository()
275
# FIXME RBC 20060209 split out the repository type to avoid this check ?
276
elif isinstance(a_bzrdir._format,
277
(bzrdir.BzrDirFormat4,
278
bzrdir.BzrDirFormat5,
279
bzrdir.BzrDirFormat6)):
280
result = a_bzrdir.open_repository()
282
result = self._format.initialize(a_bzrdir, shared=self.is_shared())
283
self.copy_content_into(result, revision_id, basis)
287
def has_revision(self, revision_id):
288
"""True if this repository has a copy of the revision."""
289
return self._revision_store.has_revision_id(revision_id,
290
self.get_transaction())
293
def get_revision_reconcile(self, revision_id):
294
"""'reconcile' helper routine that allows access to a revision always.
296
This variant of get_revision does not cross check the weave graph
297
against the revision one as get_revision does: but it should only
298
be used by reconcile, or reconcile-alike commands that are correcting
299
or testing the revision graph.
301
if not revision_id or not isinstance(revision_id, basestring):
302
raise InvalidRevisionId(revision_id=revision_id, branch=self)
303
return self._revision_store.get_revisions([revision_id],
304
self.get_transaction())[0]
306
def get_revisions(self, revision_ids):
307
return self._revision_store.get_revisions(revision_ids,
308
self.get_transaction())
311
def get_revision_xml(self, revision_id):
312
rev = self.get_revision(revision_id)
314
# the current serializer..
315
self._revision_store._serializer.write_revision(rev, rev_tmp)
317
return rev_tmp.getvalue()
320
def get_revision(self, revision_id):
321
"""Return the Revision object for a named revision"""
322
r = self.get_revision_reconcile(revision_id)
323
# weave corruption can lead to absent revision markers that should be
325
# the following test is reasonably cheap (it needs a single weave read)
326
# and the weave is cached in read transactions. In write transactions
327
# it is not cached but typically we only read a small number of
328
# revisions. For knits when they are introduced we will probably want
329
# to ensure that caching write transactions are in use.
330
inv = self.get_inventory_weave()
331
self._check_revision_parents(r, inv)
335
def get_deltas_for_revisions(self, revisions):
336
"""Produce a generator of revision deltas.
338
Note that the input is a sequence of REVISIONS, not revision_ids.
339
Trees will be held in memory until the generator exits.
340
Each delta is relative to the revision's lefthand predecessor.
342
required_trees = set()
343
for revision in revisions:
344
required_trees.add(revision.revision_id)
345
required_trees.update(revision.parent_ids[:1])
346
trees = dict((t.get_revision_id(), t) for
347
t in self.revision_trees(required_trees))
348
for revision in revisions:
349
if not revision.parent_ids:
350
old_tree = self.revision_tree(None)
352
old_tree = trees[revision.parent_ids[0]]
353
yield trees[revision.revision_id].changes_from(old_tree)
356
def get_revision_delta(self, revision_id):
357
"""Return the delta for one revision.
359
The delta is relative to the left-hand predecessor of the
362
r = self.get_revision(revision_id)
363
return list(self.get_deltas_for_revisions([r]))[0]
365
def _check_revision_parents(self, revision, inventory):
366
"""Private to Repository and Fetch.
368
This checks the parentage of revision in an inventory weave for
369
consistency and is only applicable to inventory-weave-for-ancestry
370
using repository formats & fetchers.
372
weave_parents = inventory.get_parents(revision.revision_id)
373
weave_names = inventory.versions()
374
for parent_id in revision.parent_ids:
375
if parent_id in weave_names:
376
# this parent must not be a ghost.
377
if not parent_id in weave_parents:
379
raise errors.CorruptRepository(self)
382
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
383
signature = gpg_strategy.sign(plaintext)
384
self._revision_store.add_revision_signature_text(revision_id,
386
self.get_transaction())
388
def fileids_altered_by_revision_ids(self, revision_ids):
389
"""Find the file ids and versions affected by revisions.
391
:param revisions: an iterable containing revision ids.
392
:return: a dictionary mapping altered file-ids to an iterable of
393
revision_ids. Each altered file-ids has the exact revision_ids that
394
altered it listed explicitly.
396
assert isinstance(self._format, (RepositoryFormat5,
399
RepositoryFormatKnit1)), \
400
("fileids_altered_by_revision_ids only supported for branches "
401
"which store inventory as unnested xml, not on %r" % self)
402
selected_revision_ids = set(revision_ids)
403
w = self.get_inventory_weave()
406
# this code needs to read every new line in every inventory for the
407
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
408
# not present in one of those inventories is unnecessary but not
409
# harmful because we are filtering by the revision id marker in the
410
# inventory lines : we only select file ids altered in one of those
411
# revisions. We don't need to see all lines in the inventory because
412
# only those added in an inventory in rev X can contain a revision=X
414
for line in w.iter_lines_added_or_present_in_versions(selected_revision_ids):
415
start = line.find('file_id="')+9
416
if start < 9: continue
417
end = line.find('"', start)
419
file_id = _unescape_xml(line[start:end])
421
start = line.find('revision="')+10
422
if start < 10: continue
423
end = line.find('"', start)
425
revision_id = _unescape_xml(line[start:end])
426
if revision_id in selected_revision_ids:
427
result.setdefault(file_id, set()).add(revision_id)
431
def get_inventory_weave(self):
432
return self.control_weaves.get_weave('inventory',
433
self.get_transaction())
436
def get_inventory(self, revision_id):
437
"""Get Inventory object by hash."""
438
return self.deserialise_inventory(
439
revision_id, self.get_inventory_xml(revision_id))
441
def deserialise_inventory(self, revision_id, xml):
442
"""Transform the xml into an inventory object.
444
:param revision_id: The expected revision id of the inventory.
445
:param xml: A serialised inventory.
447
return xml5.serializer_v5.read_inventory_from_string(xml)
450
def get_inventory_xml(self, revision_id):
451
"""Get inventory XML as a file object."""
453
assert isinstance(revision_id, basestring), type(revision_id)
454
iw = self.get_inventory_weave()
455
return iw.get_text(revision_id)
457
raise errors.HistoryMissing(self, 'inventory', revision_id)
460
def get_inventory_sha1(self, revision_id):
461
"""Return the sha1 hash of the inventory entry
463
return self.get_revision(revision_id).inventory_sha1
466
def get_revision_graph(self, revision_id=None):
467
"""Return a dictionary containing the revision graph.
469
:param revision_id: The revision_id to get a graph from. If None, then
470
the entire revision graph is returned. This is a deprecated mode of
471
operation and will be removed in the future.
472
:return: a dictionary of revision_id->revision_parents_list.
474
# special case NULL_REVISION
475
if revision_id == NULL_REVISION:
477
weave = self.get_inventory_weave()
478
all_revisions = self._eliminate_revisions_not_present(weave.versions())
479
entire_graph = dict([(node, weave.get_parents(node)) for
480
node in all_revisions])
481
if revision_id is None:
483
elif revision_id not in entire_graph:
484
raise errors.NoSuchRevision(self, revision_id)
486
# add what can be reached from revision_id
488
pending = set([revision_id])
489
while len(pending) > 0:
491
result[node] = entire_graph[node]
492
for revision_id in result[node]:
493
if revision_id not in result:
494
pending.add(revision_id)
498
def get_revision_graph_with_ghosts(self, revision_ids=None):
499
"""Return a graph of the revisions with ghosts marked as applicable.
501
:param revision_ids: an iterable of revisions to graph or None for all.
502
:return: a Graph object with the graph reachable from revision_ids.
506
pending = set(self.all_revision_ids())
509
pending = set(revision_ids)
510
# special case NULL_REVISION
511
if NULL_REVISION in pending:
512
pending.remove(NULL_REVISION)
513
required = set(pending)
516
revision_id = pending.pop()
518
rev = self.get_revision(revision_id)
519
except errors.NoSuchRevision:
520
if revision_id in required:
523
result.add_ghost(revision_id)
525
for parent_id in rev.parent_ids:
526
# is this queued or done ?
527
if (parent_id not in pending and
528
parent_id not in done):
530
pending.add(parent_id)
531
result.add_node(revision_id, rev.parent_ids)
532
done.add(revision_id)
536
def get_revision_inventory(self, revision_id):
537
"""Return inventory of a past revision."""
538
# TODO: Unify this with get_inventory()
539
# bzr 0.0.6 and later imposes the constraint that the inventory_id
540
# must be the same as its revision, so this is trivial.
541
if revision_id is None:
542
# This does not make sense: if there is no revision,
543
# then it is the current tree inventory surely ?!
544
# and thus get_root_id() is something that looks at the last
545
# commit on the branch, and the get_root_id is an inventory check.
546
raise NotImplementedError
547
# return Inventory(self.get_root_id())
549
return self.get_inventory(revision_id)
553
"""Return True if this repository is flagged as a shared repository."""
554
raise NotImplementedError(self.is_shared)
557
def reconcile(self, other=None, thorough=False):
558
"""Reconcile this repository."""
559
from bzrlib.reconcile import RepoReconciler
560
reconciler = RepoReconciler(self, thorough=thorough)
561
reconciler.reconcile()
565
def revision_tree(self, revision_id):
566
"""Return Tree for a revision on this branch.
568
`revision_id` may be None for the empty tree revision.
570
# TODO: refactor this to use an existing revision object
571
# so we don't need to read it in twice.
572
if revision_id is None or revision_id == NULL_REVISION:
573
return RevisionTree(self, Inventory(), NULL_REVISION)
575
inv = self.get_revision_inventory(revision_id)
576
return RevisionTree(self, inv, revision_id)
579
def revision_trees(self, revision_ids):
580
"""Return Tree for a revision on this branch.
582
`revision_id` may not be None or 'null:'"""
583
assert None not in revision_ids
584
assert NULL_REVISION not in revision_ids
585
texts = self.get_inventory_weave().get_texts(revision_ids)
586
for text, revision_id in zip(texts, revision_ids):
587
inv = self.deserialise_inventory(revision_id, text)
588
yield RevisionTree(self, inv, revision_id)
591
def get_ancestry(self, revision_id):
592
"""Return a list of revision-ids integrated by a revision.
594
The first element of the list is always None, indicating the origin
595
revision. This might change when we have history horizons, or
596
perhaps we should have a new API.
598
This is topologically sorted.
600
if revision_id is None:
602
if not self.has_revision(revision_id):
603
raise errors.NoSuchRevision(self, revision_id)
604
w = self.get_inventory_weave()
605
candidates = w.get_ancestry(revision_id)
606
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
609
def print_file(self, file, revision_id):
610
"""Print `file` to stdout.
612
FIXME RBC 20060125 as John Meinel points out this is a bad api
613
- it writes to stdout, it assumes that that is valid etc. Fix
614
by creating a new more flexible convenience function.
616
tree = self.revision_tree(revision_id)
617
# use inventory as it was in that revision
618
file_id = tree.inventory.path2id(file)
620
# TODO: jam 20060427 Write a test for this code path
621
# it had a bug in it, and was raising the wrong
623
raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
624
tree.print_file(file_id)
626
def get_transaction(self):
627
return self.control_files.get_transaction()
629
def revision_parents(self, revid):
630
return self.get_inventory_weave().parent_names(revid)
633
def set_make_working_trees(self, new_value):
634
"""Set the policy flag for making working trees when creating branches.
636
This only applies to branches that use this repository.
638
The default is 'True'.
639
:param new_value: True to restore the default, False to disable making
642
raise NotImplementedError(self.set_make_working_trees)
644
def make_working_trees(self):
645
"""Returns the policy for making working trees on new branches."""
646
raise NotImplementedError(self.make_working_trees)
649
def sign_revision(self, revision_id, gpg_strategy):
650
plaintext = Testament.from_revision(self, revision_id).as_short_text()
651
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
654
def has_signature_for_revision_id(self, revision_id):
655
"""Query for a revision signature for revision_id in the repository."""
656
return self._revision_store.has_signature(revision_id,
657
self.get_transaction())
660
def get_signature_text(self, revision_id):
661
"""Return the text for a signature."""
662
return self._revision_store.get_signature_text(revision_id,
663
self.get_transaction())
666
def check(self, revision_ids):
667
"""Check consistency of all history of given revision_ids.
669
Different repository implementations should override _check().
671
:param revision_ids: A non-empty list of revision_ids whose ancestry
672
will be checked. Typically the last revision_id of a branch.
675
raise ValueError("revision_ids must be non-empty in %s.check"
677
return self._check(revision_ids)
679
def _check(self, revision_ids):
680
result = check.Check(self)
684
def _warn_if_deprecated(self):
685
warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
686
% (self._format, self.bzrdir.transport.base))
689
class AllInOneRepository(Repository):
690
"""Legacy support - the repository behaviour for all-in-one branches."""
692
def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
693
# we reuse one control files instance.
694
dir_mode = a_bzrdir._control_files._dir_mode
695
file_mode = a_bzrdir._control_files._file_mode
697
def get_store(name, compressed=True, prefixed=False):
698
# FIXME: This approach of assuming stores are all entirely compressed
699
# or entirely uncompressed is tidy, but breaks upgrade from
700
# some existing branches where there's a mixture; we probably
701
# still want the option to look for both.
702
relpath = a_bzrdir._control_files._escape(name)
703
store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
704
prefixed=prefixed, compressed=compressed,
707
#if self._transport.should_cache():
708
# cache_path = os.path.join(self.cache_root, name)
709
# os.mkdir(cache_path)
710
# store = bzrlib.store.CachedStore(store, cache_path)
713
# not broken out yet because the controlweaves|inventory_store
714
# and text_store | weave_store bits are still different.
715
if isinstance(_format, RepositoryFormat4):
716
# cannot remove these - there is still no consistent api
717
# which allows access to this old info.
718
self.inventory_store = get_store('inventory-store')
719
text_store = get_store('text-store')
720
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
724
"""AllInOne repositories cannot be shared."""
728
def set_make_working_trees(self, new_value):
729
"""Set the policy flag for making working trees when creating branches.
731
This only applies to branches that use this repository.
733
The default is 'True'.
734
:param new_value: True to restore the default, False to disable making
737
raise NotImplementedError(self.set_make_working_trees)
739
def make_working_trees(self):
740
"""Returns the policy for making working trees on new branches."""
744
def install_revision(repository, rev, revision_tree):
745
"""Install all revision data into a repository."""
748
for p_id in rev.parent_ids:
749
if repository.has_revision(p_id):
750
present_parents.append(p_id)
751
parent_trees[p_id] = repository.revision_tree(p_id)
753
parent_trees[p_id] = repository.revision_tree(None)
755
inv = revision_tree.inventory
757
# backwards compatability hack: skip the root id.
758
entries = inv.iter_entries()
760
# Add the texts that are not already present
761
for path, ie in entries:
762
w = repository.weave_store.get_weave_or_empty(ie.file_id,
763
repository.get_transaction())
764
if ie.revision not in w:
766
# FIXME: TODO: The following loop *may* be overlapping/duplicate
767
# with InventoryEntry.find_previous_heads(). if it is, then there
768
# is a latent bug here where the parents may have ancestors of each
770
for revision, tree in parent_trees.iteritems():
771
if ie.file_id not in tree:
773
parent_id = tree.inventory[ie.file_id].revision
774
if parent_id in text_parents:
776
text_parents.append(parent_id)
778
vfile = repository.weave_store.get_weave_or_empty(ie.file_id,
779
repository.get_transaction())
780
lines = revision_tree.get_file(ie.file_id).readlines()
781
vfile.add_lines(rev.revision_id, text_parents, lines)
783
# install the inventory
784
repository.add_inventory(rev.revision_id, inv, present_parents)
785
except errors.RevisionAlreadyPresent:
787
repository.add_revision(rev.revision_id, rev, inv)
790
class MetaDirRepository(Repository):
791
"""Repositories in the new meta-dir layout."""
793
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
794
super(MetaDirRepository, self).__init__(_format,
800
dir_mode = self.control_files._dir_mode
801
file_mode = self.control_files._file_mode
805
"""Return True if this repository is flagged as a shared repository."""
806
return self.control_files._transport.has('shared-storage')
809
def set_make_working_trees(self, new_value):
810
"""Set the policy flag for making working trees when creating branches.
812
This only applies to branches that use this repository.
814
The default is 'True'.
815
:param new_value: True to restore the default, False to disable making
820
self.control_files._transport.delete('no-working-trees')
821
except errors.NoSuchFile:
824
self.control_files.put_utf8('no-working-trees', '')
826
def make_working_trees(self):
827
"""Returns the policy for making working trees on new branches."""
828
return not self.control_files._transport.has('no-working-trees')
831
class KnitRepository(MetaDirRepository):
832
"""Knit format repository."""
834
def _warn_if_deprecated(self):
835
# This class isn't deprecated
838
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
839
inv_vf.add_lines_with_ghosts(revid, parents, lines)
842
def _all_revision_ids(self):
843
"""See Repository.all_revision_ids()."""
844
# Knits get the revision graph from the index of the revision knit, so
845
# it's always possible even if they're on an unlistable transport.
846
return self._revision_store.all_revision_ids(self.get_transaction())
848
def fileid_involved_between_revs(self, from_revid, to_revid):
849
"""Find file_id(s) which are involved in the changes between revisions.
851
This determines the set of revisions which are involved, and then
852
finds all file ids affected by those revisions.
854
vf = self._get_revision_vf()
855
from_set = set(vf.get_ancestry(from_revid))
856
to_set = set(vf.get_ancestry(to_revid))
857
changed = to_set.difference(from_set)
858
return self._fileid_involved_by_set(changed)
860
def fileid_involved(self, last_revid=None):
861
"""Find all file_ids modified in the ancestry of last_revid.
863
:param last_revid: If None, last_revision() will be used.
866
changed = set(self.all_revision_ids())
868
changed = set(self.get_ancestry(last_revid))
871
return self._fileid_involved_by_set(changed)
874
def get_ancestry(self, revision_id):
875
"""Return a list of revision-ids integrated by a revision.
877
This is topologically sorted.
879
if revision_id is None:
881
vf = self._get_revision_vf()
883
return [None] + vf.get_ancestry(revision_id)
884
except errors.RevisionNotPresent:
885
raise errors.NoSuchRevision(self, revision_id)
888
def get_revision(self, revision_id):
889
"""Return the Revision object for a named revision"""
890
return self.get_revision_reconcile(revision_id)
893
def get_revision_graph(self, revision_id=None):
894
"""Return a dictionary containing the revision graph.
896
:param revision_id: The revision_id to get a graph from. If None, then
897
the entire revision graph is returned. This is a deprecated mode of
898
operation and will be removed in the future.
899
:return: a dictionary of revision_id->revision_parents_list.
901
# special case NULL_REVISION
902
if revision_id == NULL_REVISION:
904
weave = self._get_revision_vf()
905
entire_graph = weave.get_graph()
906
if revision_id is None:
907
return weave.get_graph()
908
elif revision_id not in weave:
909
raise errors.NoSuchRevision(self, revision_id)
911
# add what can be reached from revision_id
913
pending = set([revision_id])
914
while len(pending) > 0:
916
result[node] = weave.get_parents(node)
917
for revision_id in result[node]:
918
if revision_id not in result:
919
pending.add(revision_id)
923
def get_revision_graph_with_ghosts(self, revision_ids=None):
924
"""Return a graph of the revisions with ghosts marked as applicable.
926
:param revision_ids: an iterable of revisions to graph or None for all.
927
:return: a Graph object with the graph reachable from revision_ids.
930
vf = self._get_revision_vf()
931
versions = set(vf.versions())
933
pending = set(self.all_revision_ids())
936
pending = set(revision_ids)
937
# special case NULL_REVISION
938
if NULL_REVISION in pending:
939
pending.remove(NULL_REVISION)
940
required = set(pending)
943
revision_id = pending.pop()
944
if not revision_id in versions:
945
if revision_id in required:
946
raise errors.NoSuchRevision(self, revision_id)
948
result.add_ghost(revision_id)
949
# mark it as done so we don't try for it again.
950
done.add(revision_id)
952
parent_ids = vf.get_parents_with_ghosts(revision_id)
953
for parent_id in parent_ids:
954
# is this queued or done ?
955
if (parent_id not in pending and
956
parent_id not in done):
958
pending.add(parent_id)
959
result.add_node(revision_id, parent_ids)
960
done.add(revision_id)
963
def _get_revision_vf(self):
964
""":return: a versioned file containing the revisions."""
965
vf = self._revision_store.get_revision_file(self.get_transaction())
969
def reconcile(self, other=None, thorough=False):
970
"""Reconcile this repository."""
971
from bzrlib.reconcile import KnitReconciler
972
reconciler = KnitReconciler(self, thorough=thorough)
973
reconciler.reconcile()
976
def revision_parents(self, revision_id):
977
return self._get_revision_vf().get_parents(revision_id)
980
class RepositoryFormat(object):
981
"""A repository format.
983
Formats provide three things:
984
* An initialization routine to construct repository data on disk.
985
* a format string which is used when the BzrDir supports versioned
987
* an open routine which returns a Repository instance.
989
Formats are placed in an dict by their format string for reference
990
during opening. These should be subclasses of RepositoryFormat
993
Once a format is deprecated, just deprecate the initialize and open
994
methods on the format class. Do not deprecate the object, as the
995
object will be created every system load.
997
Common instance attributes:
998
_matchingbzrdir - the bzrdir format that the repository format was
999
originally written to work with. This can be used if manually
1000
constructing a bzrdir and repository, or more commonly for test suite
1004
_default_format = None
1005
"""The default format used for new repositories."""
1008
"""The known formats."""
1011
return "<%s>" % self.__class__.__name__
1014
def find_format(klass, a_bzrdir):
1015
"""Return the format for the repository object in a_bzrdir."""
1017
transport = a_bzrdir.get_repository_transport(None)
1018
format_string = transport.get("format").read()
1019
return klass._formats[format_string]
1020
except errors.NoSuchFile:
1021
raise errors.NoRepositoryPresent(a_bzrdir)
1023
raise errors.UnknownFormatError(format=format_string)
1025
def _get_control_store(self, repo_transport, control_files):
1026
"""Return the control store for this repository."""
1027
raise NotImplementedError(self._get_control_store)
1030
def get_default_format(klass):
1031
"""Return the current default format."""
1032
return klass._default_format
1034
def get_format_string(self):
1035
"""Return the ASCII format string that identifies this format.
1037
Note that in pre format ?? repositories the format string is
1038
not permitted nor written to disk.
1040
raise NotImplementedError(self.get_format_string)
1042
def get_format_description(self):
1043
"""Return the short description for this format."""
1044
raise NotImplementedError(self.get_format_description)
1046
def _get_revision_store(self, repo_transport, control_files):
1047
"""Return the revision store object for this a_bzrdir."""
1048
raise NotImplementedError(self._get_revision_store)
1050
def _get_text_rev_store(self,
1057
"""Common logic for getting a revision store for a repository.
1059
see self._get_revision_store for the subclass-overridable method to
1060
get the store for a repository.
1062
from bzrlib.store.revision.text import TextRevisionStore
1063
dir_mode = control_files._dir_mode
1064
file_mode = control_files._file_mode
1065
text_store =TextStore(transport.clone(name),
1067
compressed=compressed,
1069
file_mode=file_mode)
1070
_revision_store = TextRevisionStore(text_store, serializer)
1071
return _revision_store
1073
def _get_versioned_file_store(self,
1078
versionedfile_class=WeaveFile,
1080
weave_transport = control_files._transport.clone(name)
1081
dir_mode = control_files._dir_mode
1082
file_mode = control_files._file_mode
1083
return VersionedFileStore(weave_transport, prefixed=prefixed,
1085
file_mode=file_mode,
1086
versionedfile_class=versionedfile_class,
1089
def initialize(self, a_bzrdir, shared=False):
1090
"""Initialize a repository of this format in a_bzrdir.
1092
:param a_bzrdir: The bzrdir to put the new repository in it.
1093
:param shared: The repository should be initialized as a sharable one.
1095
This may raise UninitializableFormat if shared repository are not
1096
compatible the a_bzrdir.
1099
def is_supported(self):
1100
"""Is this format supported?
1102
Supported formats must be initializable and openable.
1103
Unsupported formats may not support initialization or committing or
1104
some other features depending on the reason for not being supported.
1108
def open(self, a_bzrdir, _found=False):
1109
"""Return an instance of this format for the bzrdir a_bzrdir.
1111
_found is a private parameter, do not use it.
1113
raise NotImplementedError(self.open)
1116
def register_format(klass, format):
1117
klass._formats[format.get_format_string()] = format
1120
def set_default_format(klass, format):
1121
klass._default_format = format
1124
def unregister_format(klass, format):
1125
assert klass._formats[format.get_format_string()] is format
1126
del klass._formats[format.get_format_string()]
1129
class PreSplitOutRepositoryFormat(RepositoryFormat):
1130
"""Base class for the pre split out repository formats."""
1132
def initialize(self, a_bzrdir, shared=False, _internal=False):
1133
"""Create a weave repository.
1135
TODO: when creating split out bzr branch formats, move this to a common
1136
base for Format5, Format6. or something like that.
1138
from bzrlib.weavefile import write_weave_v5
1139
from bzrlib.weave import Weave
1142
raise errors.IncompatibleFormat(self, a_bzrdir._format)
1145
# always initialized when the bzrdir is.
1146
return self.open(a_bzrdir, _found=True)
1148
# Create an empty weave
1150
write_weave_v5(Weave(), sio)
1151
empty_weave = sio.getvalue()
1153
mutter('creating repository in %s.', a_bzrdir.transport.base)
1154
dirs = ['revision-store', 'weaves']
1155
files = [('inventory.weave', StringIO(empty_weave)),
1158
# FIXME: RBC 20060125 don't peek under the covers
1159
# NB: no need to escape relative paths that are url safe.
1160
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock',
1162
control_files.create_lock()
1163
control_files.lock_write()
1164
control_files._transport.mkdir_multi(dirs,
1165
mode=control_files._dir_mode)
1167
for file, content in files:
1168
control_files.put(file, content)
1170
control_files.unlock()
1171
return self.open(a_bzrdir, _found=True)
1173
def _get_control_store(self, repo_transport, control_files):
1174
"""Return the control store for this repository."""
1175
return self._get_versioned_file_store('',
1180
def _get_text_store(self, transport, control_files):
1181
"""Get a store for file texts for this format."""
1182
raise NotImplementedError(self._get_text_store)
1184
def open(self, a_bzrdir, _found=False):
1185
"""See RepositoryFormat.open()."""
1187
# we are being called directly and must probe.
1188
raise NotImplementedError
1190
repo_transport = a_bzrdir.get_repository_transport(None)
1191
control_files = a_bzrdir._control_files
1192
text_store = self._get_text_store(repo_transport, control_files)
1193
control_store = self._get_control_store(repo_transport, control_files)
1194
_revision_store = self._get_revision_store(repo_transport, control_files)
1195
return AllInOneRepository(_format=self,
1197
_revision_store=_revision_store,
1198
control_store=control_store,
1199
text_store=text_store)
1202
class RepositoryFormat4(PreSplitOutRepositoryFormat):
1203
"""Bzr repository format 4.
1205
This repository format has:
1207
- TextStores for texts, inventories,revisions.
1209
This format is deprecated: it indexes texts using a text id which is
1210
removed in format 5; initialization and write support for this format
1215
super(RepositoryFormat4, self).__init__()
1216
self._matchingbzrdir = bzrdir.BzrDirFormat4()
1218
def get_format_description(self):
1219
"""See RepositoryFormat.get_format_description()."""
1220
return "Repository format 4"
1222
def initialize(self, url, shared=False, _internal=False):
1223
"""Format 4 branches cannot be created."""
1224
raise errors.UninitializableFormat(self)
1226
def is_supported(self):
1227
"""Format 4 is not supported.
1229
It is not supported because the model changed from 4 to 5 and the
1230
conversion logic is expensive - so doing it on the fly was not
1235
def _get_control_store(self, repo_transport, control_files):
1236
"""Format 4 repositories have no formal control store at this point.
1238
This will cause any control-file-needing apis to fail - this is desired.
1242
def _get_revision_store(self, repo_transport, control_files):
1243
"""See RepositoryFormat._get_revision_store()."""
1244
from bzrlib.xml4 import serializer_v4
1245
return self._get_text_rev_store(repo_transport,
1248
serializer=serializer_v4)
1250
def _get_text_store(self, transport, control_files):
1251
"""See RepositoryFormat._get_text_store()."""
1254
class RepositoryFormat5(PreSplitOutRepositoryFormat):
1255
"""Bzr control format 5.
1257
This repository format has:
1258
- weaves for file texts and inventory
1260
- TextStores for revisions and signatures.
1264
super(RepositoryFormat5, self).__init__()
1265
self._matchingbzrdir = bzrdir.BzrDirFormat5()
1267
def get_format_description(self):
1268
"""See RepositoryFormat.get_format_description()."""
1269
return "Weave repository format 5"
1271
def _get_revision_store(self, repo_transport, control_files):
1272
"""See RepositoryFormat._get_revision_store()."""
1273
"""Return the revision store object for this a_bzrdir."""
1274
return self._get_text_rev_store(repo_transport,
1279
def _get_text_store(self, transport, control_files):
1280
"""See RepositoryFormat._get_text_store()."""
1281
return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
1284
class RepositoryFormat6(PreSplitOutRepositoryFormat):
1285
"""Bzr control format 6.
1287
This repository format has:
1288
- weaves for file texts and inventory
1289
- hash subdirectory based stores.
1290
- TextStores for revisions and signatures.
1294
super(RepositoryFormat6, self).__init__()
1295
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1297
def get_format_description(self):
1298
"""See RepositoryFormat.get_format_description()."""
1299
return "Weave repository format 6"
1301
def _get_revision_store(self, repo_transport, control_files):
1302
"""See RepositoryFormat._get_revision_store()."""
1303
return self._get_text_rev_store(repo_transport,
1309
def _get_text_store(self, transport, control_files):
1310
"""See RepositoryFormat._get_text_store()."""
1311
return self._get_versioned_file_store('weaves', transport, control_files)
1314
class MetaDirRepositoryFormat(RepositoryFormat):
1315
"""Common base class for the new repositories using the metadir layout."""
1318
super(MetaDirRepositoryFormat, self).__init__()
1319
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1321
def _create_control_files(self, a_bzrdir):
1322
"""Create the required files and the initial control_files object."""
1323
# FIXME: RBC 20060125 don't peek under the covers
1324
# NB: no need to escape relative paths that are url safe.
1325
repository_transport = a_bzrdir.get_repository_transport(self)
1326
control_files = LockableFiles(repository_transport, 'lock', LockDir)
1327
control_files.create_lock()
1328
return control_files
1330
def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
1331
"""Upload the initial blank content."""
1332
control_files = self._create_control_files(a_bzrdir)
1333
control_files.lock_write()
1335
control_files._transport.mkdir_multi(dirs,
1336
mode=control_files._dir_mode)
1337
for file, content in files:
1338
control_files.put(file, content)
1339
for file, content in utf8_files:
1340
control_files.put_utf8(file, content)
1342
control_files.put_utf8('shared-storage', '')
1344
control_files.unlock()
1347
class RepositoryFormat7(MetaDirRepositoryFormat):
1348
"""Bzr repository 7.
1350
This repository format has:
1351
- weaves for file texts and inventory
1352
- hash subdirectory based stores.
1353
- TextStores for revisions and signatures.
1354
- a format marker of its own
1355
- an optional 'shared-storage' flag
1356
- an optional 'no-working-trees' flag
1359
def _get_control_store(self, repo_transport, control_files):
1360
"""Return the control store for this repository."""
1361
return self._get_versioned_file_store('',
1366
def get_format_string(self):
1367
"""See RepositoryFormat.get_format_string()."""
1368
return "Bazaar-NG Repository format 7"
1370
def get_format_description(self):
1371
"""See RepositoryFormat.get_format_description()."""
1372
return "Weave repository format 7"
1374
def _get_revision_store(self, repo_transport, control_files):
1375
"""See RepositoryFormat._get_revision_store()."""
1376
return self._get_text_rev_store(repo_transport,
1383
def _get_text_store(self, transport, control_files):
1384
"""See RepositoryFormat._get_text_store()."""
1385
return self._get_versioned_file_store('weaves',
1389
def initialize(self, a_bzrdir, shared=False):
1390
"""Create a weave repository.
1392
:param shared: If true the repository will be initialized as a shared
1395
from bzrlib.weavefile import write_weave_v5
1396
from bzrlib.weave import Weave
1398
# Create an empty weave
1400
write_weave_v5(Weave(), sio)
1401
empty_weave = sio.getvalue()
1403
mutter('creating repository in %s.', a_bzrdir.transport.base)
1404
dirs = ['revision-store', 'weaves']
1405
files = [('inventory.weave', StringIO(empty_weave)),
1407
utf8_files = [('format', self.get_format_string())]
1409
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1410
return self.open(a_bzrdir=a_bzrdir, _found=True)
1412
def open(self, a_bzrdir, _found=False, _override_transport=None):
1413
"""See RepositoryFormat.open().
1415
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1416
repository at a slightly different url
1417
than normal. I.e. during 'upgrade'.
1420
format = RepositoryFormat.find_format(a_bzrdir)
1421
assert format.__class__ == self.__class__
1422
if _override_transport is not None:
1423
repo_transport = _override_transport
1425
repo_transport = a_bzrdir.get_repository_transport(None)
1426
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1427
text_store = self._get_text_store(repo_transport, control_files)
1428
control_store = self._get_control_store(repo_transport, control_files)
1429
_revision_store = self._get_revision_store(repo_transport, control_files)
1430
return MetaDirRepository(_format=self,
1432
control_files=control_files,
1433
_revision_store=_revision_store,
1434
control_store=control_store,
1435
text_store=text_store)
1438
class RepositoryFormatKnit1(MetaDirRepositoryFormat):
1439
"""Bzr repository knit format 1.
1441
This repository format has:
1442
- knits for file texts and inventory
1443
- hash subdirectory based stores.
1444
- knits for revisions and signatures
1445
- TextStores for revisions and signatures.
1446
- a format marker of its own
1447
- an optional 'shared-storage' flag
1448
- an optional 'no-working-trees' flag
1451
This format was introduced in bzr 0.8.
1454
def _get_control_store(self, repo_transport, control_files):
1455
"""Return the control store for this repository."""
1456
return VersionedFileStore(
1459
file_mode=control_files._file_mode,
1460
versionedfile_class=KnitVersionedFile,
1461
versionedfile_kwargs={'factory':KnitPlainFactory()},
1464
def get_format_string(self):
1465
"""See RepositoryFormat.get_format_string()."""
1466
return "Bazaar-NG Knit Repository Format 1"
1468
def get_format_description(self):
1469
"""See RepositoryFormat.get_format_description()."""
1470
return "Knit repository format 1"
1472
def _get_revision_store(self, repo_transport, control_files):
1473
"""See RepositoryFormat._get_revision_store()."""
1474
from bzrlib.store.revision.knit import KnitRevisionStore
1475
versioned_file_store = VersionedFileStore(
1477
file_mode=control_files._file_mode,
1480
versionedfile_class=KnitVersionedFile,
1481
versionedfile_kwargs={'delta':False, 'factory':KnitPlainFactory()},
1484
return KnitRevisionStore(versioned_file_store)
1486
def _get_text_store(self, transport, control_files):
1487
"""See RepositoryFormat._get_text_store()."""
1488
return self._get_versioned_file_store('knits',
1491
versionedfile_class=KnitVersionedFile,
1494
def initialize(self, a_bzrdir, shared=False):
1495
"""Create a knit format 1 repository.
1497
:param a_bzrdir: bzrdir to contain the new repository; must already
1499
:param shared: If true the repository will be initialized as a shared
1502
mutter('creating repository in %s.', a_bzrdir.transport.base)
1503
dirs = ['revision-store', 'knits']
1505
utf8_files = [('format', self.get_format_string())]
1507
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1508
repo_transport = a_bzrdir.get_repository_transport(None)
1509
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1510
control_store = self._get_control_store(repo_transport, control_files)
1511
transaction = transactions.WriteTransaction()
1512
# trigger a write of the inventory store.
1513
control_store.get_weave_or_empty('inventory', transaction)
1514
_revision_store = self._get_revision_store(repo_transport, control_files)
1515
_revision_store.has_revision_id('A', transaction)
1516
_revision_store.get_signature_file(transaction)
1517
return self.open(a_bzrdir=a_bzrdir, _found=True)
1519
def open(self, a_bzrdir, _found=False, _override_transport=None):
1520
"""See RepositoryFormat.open().
1522
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1523
repository at a slightly different url
1524
than normal. I.e. during 'upgrade'.
1527
format = RepositoryFormat.find_format(a_bzrdir)
1528
assert format.__class__ == self.__class__
1529
if _override_transport is not None:
1530
repo_transport = _override_transport
1532
repo_transport = a_bzrdir.get_repository_transport(None)
1533
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1534
text_store = self._get_text_store(repo_transport, control_files)
1535
control_store = self._get_control_store(repo_transport, control_files)
1536
_revision_store = self._get_revision_store(repo_transport, control_files)
1537
return KnitRepository(_format=self,
1539
control_files=control_files,
1540
_revision_store=_revision_store,
1541
control_store=control_store,
1542
text_store=text_store)
1545
# formats which have no format string are not discoverable
1546
# and not independently creatable, so are not registered.
1547
RepositoryFormat.register_format(RepositoryFormat7())
1548
_default_format = RepositoryFormatKnit1()
1549
RepositoryFormat.register_format(_default_format)
1550
RepositoryFormat.set_default_format(_default_format)
1551
_legacy_formats = [RepositoryFormat4(),
1552
RepositoryFormat5(),
1553
RepositoryFormat6()]
1556
class InterRepository(InterObject):
1557
"""This class represents operations taking place between two repositories.
1559
Its instances have methods like copy_content and fetch, and contain
1560
references to the source and target repositories these operations can be
1563
Often we will provide convenience methods on 'repository' which carry out
1564
operations with another repository - they will always forward to
1565
InterRepository.get(other).method_name(parameters).
1569
"""The available optimised InterRepository types."""
1572
def copy_content(self, revision_id=None, basis=None):
1573
"""Make a complete copy of the content in self into destination.
1575
This is a destructive operation! Do not use it on existing
1578
:param revision_id: Only copy the content needed to construct
1579
revision_id and its parents.
1580
:param basis: Copy the needed data preferentially from basis.
1583
self.target.set_make_working_trees(self.source.make_working_trees())
1584
except NotImplementedError:
1586
# grab the basis available data
1587
if basis is not None:
1588
self.target.fetch(basis, revision_id=revision_id)
1589
# but don't bother fetching if we have the needed data now.
1590
if (revision_id not in (None, NULL_REVISION) and
1591
self.target.has_revision(revision_id)):
1593
self.target.fetch(self.source, revision_id=revision_id)
1596
def fetch(self, revision_id=None, pb=None):
1597
"""Fetch the content required to construct revision_id.
1599
The content is copied from source to target.
1601
:param revision_id: if None all content is copied, if NULL_REVISION no
1603
:param pb: optional progress bar to use for progress reports. If not
1604
provided a default one will be created.
1606
Returns the copied revision count and the failed revisions in a tuple:
1609
from bzrlib.fetch import GenericRepoFetcher
1610
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1611
self.source, self.source._format, self.target, self.target._format)
1612
f = GenericRepoFetcher(to_repository=self.target,
1613
from_repository=self.source,
1614
last_revision=revision_id,
1616
return f.count_copied, f.failed_revisions
1619
def missing_revision_ids(self, revision_id=None):
1620
"""Return the revision ids that source has that target does not.
1622
These are returned in topological order.
1624
:param revision_id: only return revision ids included by this
1627
# generic, possibly worst case, slow code path.
1628
target_ids = set(self.target.all_revision_ids())
1629
if revision_id is not None:
1630
source_ids = self.source.get_ancestry(revision_id)
1631
assert source_ids[0] == None
1634
source_ids = self.source.all_revision_ids()
1635
result_set = set(source_ids).difference(target_ids)
1636
# this may look like a no-op: its not. It preserves the ordering
1637
# other_ids had while only returning the members from other_ids
1638
# that we've decided we need.
1639
return [rev_id for rev_id in source_ids if rev_id in result_set]
1642
class InterWeaveRepo(InterRepository):
1643
"""Optimised code paths between Weave based repositories."""
1645
_matching_repo_format = RepositoryFormat7()
1646
"""Repository format for testing with."""
1649
def is_compatible(source, target):
1650
"""Be compatible with known Weave formats.
1652
We don't test for the stores being of specific types because that
1653
could lead to confusing results, and there is no need to be
1657
return (isinstance(source._format, (RepositoryFormat5,
1659
RepositoryFormat7)) and
1660
isinstance(target._format, (RepositoryFormat5,
1662
RepositoryFormat7)))
1663
except AttributeError:
1667
def copy_content(self, revision_id=None, basis=None):
1668
"""See InterRepository.copy_content()."""
1669
# weave specific optimised path:
1670
if basis is not None:
1671
# copy the basis in, then fetch remaining data.
1672
basis.copy_content_into(self.target, revision_id)
1673
# the basis copy_content_into could miss-set this.
1675
self.target.set_make_working_trees(self.source.make_working_trees())
1676
except NotImplementedError:
1678
self.target.fetch(self.source, revision_id=revision_id)
1681
self.target.set_make_working_trees(self.source.make_working_trees())
1682
except NotImplementedError:
1684
# FIXME do not peek!
1685
if self.source.control_files._transport.listable():
1686
pb = ui.ui_factory.nested_progress_bar()
1688
self.target.weave_store.copy_all_ids(
1689
self.source.weave_store,
1691
from_transaction=self.source.get_transaction(),
1692
to_transaction=self.target.get_transaction())
1693
pb.update('copying inventory', 0, 1)
1694
self.target.control_weaves.copy_multi(
1695
self.source.control_weaves, ['inventory'],
1696
from_transaction=self.source.get_transaction(),
1697
to_transaction=self.target.get_transaction())
1698
self.target._revision_store.text_store.copy_all_ids(
1699
self.source._revision_store.text_store,
1704
self.target.fetch(self.source, revision_id=revision_id)
1707
def fetch(self, revision_id=None, pb=None):
1708
"""See InterRepository.fetch()."""
1709
from bzrlib.fetch import GenericRepoFetcher
1710
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1711
self.source, self.source._format, self.target, self.target._format)
1712
f = GenericRepoFetcher(to_repository=self.target,
1713
from_repository=self.source,
1714
last_revision=revision_id,
1716
return f.count_copied, f.failed_revisions
1719
def missing_revision_ids(self, revision_id=None):
1720
"""See InterRepository.missing_revision_ids()."""
1721
# we want all revisions to satisfy revision_id in source.
1722
# but we don't want to stat every file here and there.
1723
# we want then, all revisions other needs to satisfy revision_id
1724
# checked, but not those that we have locally.
1725
# so the first thing is to get a subset of the revisions to
1726
# satisfy revision_id in source, and then eliminate those that
1727
# we do already have.
1728
# this is slow on high latency connection to self, but as as this
1729
# disk format scales terribly for push anyway due to rewriting
1730
# inventory.weave, this is considered acceptable.
1732
if revision_id is not None:
1733
source_ids = self.source.get_ancestry(revision_id)
1734
assert source_ids[0] == None
1737
source_ids = self.source._all_possible_ids()
1738
source_ids_set = set(source_ids)
1739
# source_ids is the worst possible case we may need to pull.
1740
# now we want to filter source_ids against what we actually
1741
# have in target, but don't try to check for existence where we know
1742
# we do not have a revision as that would be pointless.
1743
target_ids = set(self.target._all_possible_ids())
1744
possibly_present_revisions = target_ids.intersection(source_ids_set)
1745
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1746
required_revisions = source_ids_set.difference(actually_present_revisions)
1747
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1748
if revision_id is not None:
1749
# we used get_ancestry to determine source_ids then we are assured all
1750
# revisions referenced are present as they are installed in topological order.
1751
# and the tip revision was validated by get_ancestry.
1752
return required_topo_revisions
1754
# if we just grabbed the possibly available ids, then
1755
# we only have an estimate of whats available and need to validate
1756
# that against the revision records.
1757
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1760
class InterKnitRepo(InterRepository):
1761
"""Optimised code paths between Knit based repositories."""
1763
_matching_repo_format = RepositoryFormatKnit1()
1764
"""Repository format for testing with."""
1767
def is_compatible(source, target):
1768
"""Be compatible with known Knit formats.
1770
We don't test for the stores being of specific types because that
1771
could lead to confusing results, and there is no need to be
1775
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1776
isinstance(target._format, (RepositoryFormatKnit1)))
1777
except AttributeError:
1781
def fetch(self, revision_id=None, pb=None):
1782
"""See InterRepository.fetch()."""
1783
from bzrlib.fetch import KnitRepoFetcher
1784
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1785
self.source, self.source._format, self.target, self.target._format)
1786
f = KnitRepoFetcher(to_repository=self.target,
1787
from_repository=self.source,
1788
last_revision=revision_id,
1790
return f.count_copied, f.failed_revisions
1793
def missing_revision_ids(self, revision_id=None):
1794
"""See InterRepository.missing_revision_ids()."""
1795
if revision_id is not None:
1796
source_ids = self.source.get_ancestry(revision_id)
1797
assert source_ids[0] == None
1800
source_ids = self.source._all_possible_ids()
1801
source_ids_set = set(source_ids)
1802
# source_ids is the worst possible case we may need to pull.
1803
# now we want to filter source_ids against what we actually
1804
# have in target, but don't try to check for existence where we know
1805
# we do not have a revision as that would be pointless.
1806
target_ids = set(self.target._all_possible_ids())
1807
possibly_present_revisions = target_ids.intersection(source_ids_set)
1808
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1809
required_revisions = source_ids_set.difference(actually_present_revisions)
1810
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1811
if revision_id is not None:
1812
# we used get_ancestry to determine source_ids then we are assured all
1813
# revisions referenced are present as they are installed in topological order.
1814
# and the tip revision was validated by get_ancestry.
1815
return required_topo_revisions
1817
# if we just grabbed the possibly available ids, then
1818
# we only have an estimate of whats available and need to validate
1819
# that against the revision records.
1820
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1822
InterRepository.register_optimiser(InterWeaveRepo)
1823
InterRepository.register_optimiser(InterKnitRepo)
1826
class RepositoryTestProviderAdapter(object):
1827
"""A tool to generate a suite testing multiple repository formats at once.
1829
This is done by copying the test once for each transport and injecting
1830
the transport_server, transport_readonly_server, and bzrdir_format and
1831
repository_format classes into each copy. Each copy is also given a new id()
1832
to make it easy to identify.
1835
def __init__(self, transport_server, transport_readonly_server, formats):
1836
self._transport_server = transport_server
1837
self._transport_readonly_server = transport_readonly_server
1838
self._formats = formats
1840
def adapt(self, test):
1841
result = TestSuite()
1842
for repository_format, bzrdir_format in self._formats:
1843
new_test = deepcopy(test)
1844
new_test.transport_server = self._transport_server
1845
new_test.transport_readonly_server = self._transport_readonly_server
1846
new_test.bzrdir_format = bzrdir_format
1847
new_test.repository_format = repository_format
1848
def make_new_test_id():
1849
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
1850
return lambda: new_id
1851
new_test.id = make_new_test_id()
1852
result.addTest(new_test)
1856
class InterRepositoryTestProviderAdapter(object):
1857
"""A tool to generate a suite testing multiple inter repository formats.
1859
This is done by copying the test once for each interrepo provider and injecting
1860
the transport_server, transport_readonly_server, repository_format and
1861
repository_to_format classes into each copy.
1862
Each copy is also given a new id() to make it easy to identify.
1865
def __init__(self, transport_server, transport_readonly_server, formats):
1866
self._transport_server = transport_server
1867
self._transport_readonly_server = transport_readonly_server
1868
self._formats = formats
1870
def adapt(self, test):
1871
result = TestSuite()
1872
for interrepo_class, repository_format, repository_format_to in self._formats:
1873
new_test = deepcopy(test)
1874
new_test.transport_server = self._transport_server
1875
new_test.transport_readonly_server = self._transport_readonly_server
1876
new_test.interrepo_class = interrepo_class
1877
new_test.repository_format = repository_format
1878
new_test.repository_format_to = repository_format_to
1879
def make_new_test_id():
1880
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
1881
return lambda: new_id
1882
new_test.id = make_new_test_id()
1883
result.addTest(new_test)
1887
def default_test_list():
1888
"""Generate the default list of interrepo permutations to test."""
1890
# test the default InterRepository between format 6 and the current
1892
# XXX: robertc 20060220 reinstate this when there are two supported
1893
# formats which do not have an optimal code path between them.
1894
result.append((InterRepository,
1895
RepositoryFormat6(),
1896
RepositoryFormatKnit1()))
1897
for optimiser in InterRepository._optimisers:
1898
result.append((optimiser,
1899
optimiser._matching_repo_format,
1900
optimiser._matching_repo_format
1902
# if there are specific combinations we want to use, we can add them
1907
class CopyConverter(object):
1908
"""A repository conversion tool which just performs a copy of the content.
1910
This is slow but quite reliable.
1913
def __init__(self, target_format):
1914
"""Create a CopyConverter.
1916
:param target_format: The format the resulting repository should be.
1918
self.target_format = target_format
1920
def convert(self, repo, pb):
1921
"""Perform the conversion of to_convert, giving feedback via pb.
1923
:param to_convert: The disk object to convert.
1924
:param pb: a progress bar to use for progress information.
1929
# this is only useful with metadir layouts - separated repo content.
1930
# trigger an assertion if not such
1931
repo._format.get_format_string()
1932
self.repo_dir = repo.bzrdir
1933
self.step('Moving repository to repository.backup')
1934
self.repo_dir.transport.move('repository', 'repository.backup')
1935
backup_transport = self.repo_dir.transport.clone('repository.backup')
1936
self.source_repo = repo._format.open(self.repo_dir,
1938
_override_transport=backup_transport)
1939
self.step('Creating new repository')
1940
converted = self.target_format.initialize(self.repo_dir,
1941
self.source_repo.is_shared())
1942
converted.lock_write()
1944
self.step('Copying content into repository.')
1945
self.source_repo.copy_content_into(converted)
1948
self.step('Deleting old repository content.')
1949
self.repo_dir.transport.delete_tree('repository.backup')
1950
self.pb.note('repository converted')
1952
def step(self, message):
1953
"""Update the pb by a step."""
1955
self.pb.update(message, self.count, self.total)
1958
class CommitBuilder(object):
1959
"""Provides an interface to build up a commit.
1961
This allows describing a tree to be committed without needing to
1962
know the internals of the format of the repository.
1964
def __init__(self, repository, parents, config, timestamp=None,
1965
timezone=None, committer=None, revprops=None,
1967
"""Initiate a CommitBuilder.
1969
:param repository: Repository to commit to.
1970
:param parents: Revision ids of the parents of the new revision.
1971
:param config: Configuration to use.
1972
:param timestamp: Optional timestamp recorded for commit.
1973
:param timezone: Optional timezone for timestamp.
1974
:param committer: Optional committer to set for commit.
1975
:param revprops: Optional dictionary of revision properties.
1976
:param revision_id: Optional revision id.
1978
self._config = config
1980
if committer is None:
1981
self._committer = self._config.username()
1983
assert isinstance(committer, basestring), type(committer)
1984
self._committer = committer
1986
self.new_inventory = Inventory()
1987
self._new_revision_id = revision_id
1988
self.parents = parents
1989
self.repository = repository
1992
if revprops is not None:
1993
self._revprops.update(revprops)
1995
if timestamp is None:
1996
timestamp = time.time()
1997
# Restrict resolution to 1ms
1998
self._timestamp = round(timestamp, 3)
2000
if timezone is None:
2001
self._timezone = local_time_offset()
2003
self._timezone = int(timezone)
2005
self._generate_revision_if_needed()
2007
def commit(self, message):
2008
"""Make the actual commit.
2010
:return: The revision id of the recorded revision.
2012
rev = Revision(timestamp=self._timestamp,
2013
timezone=self._timezone,
2014
committer=self._committer,
2016
inventory_sha1=self.inv_sha1,
2017
revision_id=self._new_revision_id,
2018
properties=self._revprops)
2019
rev.parent_ids = self.parents
2020
self.repository.add_revision(self._new_revision_id, rev,
2021
self.new_inventory, self._config)
2022
return self._new_revision_id
2024
def finish_inventory(self):
2025
"""Tell the builder that the inventory is finished."""
2026
self.new_inventory.revision_id = self._new_revision_id
2027
self.inv_sha1 = self.repository.add_inventory(
2028
self._new_revision_id,
2033
def _gen_revision_id(self):
2034
"""Return new revision-id."""
2035
s = '%s-%s-' % (self._config.user_email(),
2036
compact_date(self._timestamp))
2037
s += hexlify(rand_bytes(8))
2040
def _generate_revision_if_needed(self):
2041
"""Create a revision id if None was supplied.
2043
If the repository can not support user-specified revision ids
2044
they should override this function and raise UnsupportedOperation
2045
if _new_revision_id is not None.
2047
:raises: UnsupportedOperation
2049
if self._new_revision_id is None:
2050
self._new_revision_id = self._gen_revision_id()
2052
def record_entry_contents(self, ie, parent_invs, path, tree):
2053
"""Record the content of ie from tree into the commit if needed.
2055
:param ie: An inventory entry present in the commit.
2056
:param parent_invs: The inventories of the parent revisions of the
2058
:param path: The path the entry is at in the tree.
2059
:param tree: The tree which contains this entry and should be used to
2062
self.new_inventory.add(ie)
2064
# ie.revision is always None if the InventoryEntry is considered
2065
# for committing. ie.snapshot will record the correct revision
2066
# which may be the sole parent if it is untouched.
2067
if ie.revision is not None:
2069
previous_entries = ie.find_previous_heads(
2071
self.repository.weave_store,
2072
self.repository.get_transaction())
2073
# we are creating a new revision for ie in the history store
2075
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2077
def modified_directory(self, file_id, file_parents):
2078
"""Record the presence of a symbolic link.
2080
:param file_id: The file_id of the link to record.
2081
:param file_parents: The per-file parent revision ids.
2083
self._add_text_to_weave(file_id, [], file_parents.keys())
2085
def modified_file_text(self, file_id, file_parents,
2086
get_content_byte_lines, text_sha1=None,
2088
"""Record the text of file file_id
2090
:param file_id: The file_id of the file to record the text of.
2091
:param file_parents: The per-file parent revision ids.
2092
:param get_content_byte_lines: A callable which will return the byte
2094
:param text_sha1: Optional SHA1 of the file contents.
2095
:param text_size: Optional size of the file contents.
2097
# mutter('storing text of file {%s} in revision {%s} into %r',
2098
# file_id, self._new_revision_id, self.repository.weave_store)
2099
# special case to avoid diffing on renames or
2101
if (len(file_parents) == 1
2102
and text_sha1 == file_parents.values()[0].text_sha1
2103
and text_size == file_parents.values()[0].text_size):
2104
previous_ie = file_parents.values()[0]
2105
versionedfile = self.repository.weave_store.get_weave(file_id,
2106
self.repository.get_transaction())
2107
versionedfile.clone_text(self._new_revision_id,
2108
previous_ie.revision, file_parents.keys())
2109
return text_sha1, text_size
2111
new_lines = get_content_byte_lines()
2112
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2113
# should return the SHA1 and size
2114
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2115
return osutils.sha_strings(new_lines), \
2116
sum(map(len, new_lines))
2118
def modified_link(self, file_id, file_parents, link_target):
2119
"""Record the presence of a symbolic link.
2121
:param file_id: The file_id of the link to record.
2122
:param file_parents: The per-file parent revision ids.
2123
:param link_target: Target location of this link.
2125
self._add_text_to_weave(file_id, [], file_parents.keys())
2127
def _add_text_to_weave(self, file_id, new_lines, parents):
2128
versionedfile = self.repository.weave_store.get_weave_or_empty(
2129
file_id, self.repository.get_transaction())
2130
versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2131
versionedfile.clear_cache()
2143
def _unescaper(match, _map=_unescape_map):
2144
return _map[match.group(1)]
2150
def _unescape_xml(data):
2151
"""Unescape predefined XML entities in a string of data."""
2153
if _unescape_re is None:
2154
_unescape_re = re.compile('\&([^;]*);')
2155
return _unescape_re.sub(_unescaper, data)